The commandlatch CLI
Queue safe remote actions on your paired Mac straight from the terminal.
commandlatch lets you control your Mac from the terminal using the same
actions available in the web dashboard. Use it to keep your laptop awake
during a long job and lock or sleep it when the work is done — from a
shell script, a build tool wrapper, or an AI-coding hook.
- For the auto-lock workflow (lock and sleep when Claude Code finishes), see Auto-lock with Claude Code.
How it works
Section titled “How it works”The CLI never talks to your Mac directly. It enqueues an action through the CommandLatch backend; the desktop agent running on the paired Mac polls for queued commands and executes them locally.
commandlatch <cmd> backend queue desktop agent ─────────────────► enqueue-command (Edge Fn) ─────────────────► (your terminal) + devices/commands (your Mac) tables caffeinate / lock / sleep / notifyBecause the action is queued rather than streamed, the CLI returns immediately after enqueuing — it does not wait for the Mac to act. This is deliberate: it means a Stop hook never blocks your next prompt, and a queued lock survives the terminal closing.
The five actions the CLI can enqueue:
| Action | What the agent does |
|---|---|
keep_awake_start | Holds the Mac awake (caffeinate) for a duration. |
keep_awake_stop | Releases an active keep-awake window. |
lock_sleep | Locks the screen and sleeps the Mac (optionally after a delay). |
send_notification | Shows a macOS banner. |
cancel_pending_lock | Drops a delayed lock_sleep the agent is holding. |
You never name these actions directly — the subcommands compose them for you.
Installation
Section titled “Installation”CommandLatch installs commandlatch as a self-contained binary with no other
dependencies required.
In the menu-bar app, open Settings → Command-line tool → Install
commandlatch on PATH and approve the one-time admin prompt. This
installs commandlatch into /usr/local/bin.
Verify the installation:
commandlatch statusIt should print your paired device’s name and pairing date. If it
prints No config at ~/.commandlatch/config, launch the menu-bar app
and complete pairing first — the app writes the CLI’s config automatically
when pairing succeeds (see Pairing & config).
Pairing & config
Section titled “Pairing & config”The CLI stores its credentials in ~/.commandlatch/config. You normally
don’t need to touch this file — the standard flow is:
- Run the desktop agent.
- Pair it from the web dashboard’s Pair a device screen.
- The agent writes
~/.commandlatch/configautomatically — the CLI now works on that Mac with zero setup.
commandlatch pair exists for advanced setups without the menu-bar app — for
example, pairing directly from a terminal on a Mac that runs headlessly.
It refuses to run if the CLI is already paired, so you can’t accidentally
create a duplicate entry. See the pair reference below.
Most users never need this.
Command reference
Section titled “Command reference”commandlatch <command> [options]
Commands: awake [--for <duration>] Keep the paired desktop awake (default 5m) done [opts] Lock + sleep the paired desktop run [opts] -- <cmd...> Wrap a long task: keep awake, then lock-sleep status Show the paired device record pair Pair without the desktop agent (advanced) help Print usageRun commandlatch, commandlatch help, or commandlatch -h to print
the built-in usage with examples.
Output convention
Section titled “Output convention”The queued command ID is printed to stdout so you can capture it in a
script. Progress and status messages go to stderr, so wrapping a command
with run never interferes with that command’s own output.
Durations
Section titled “Durations”Anywhere a duration is accepted (--for, --ttl, --lock-sleep-after):
- Format: a positive integer with an optional
s/m/hsuffix. - No suffix means seconds (matching
sleep,timeout). - Examples:
30s,90,5m,1h. - Rejected: negatives, zero, decimals, bare units, mixed units — the CLI fails fast rather than silently rounding to zero.
status
Section titled “status”commandlatch statusPrints the config path, the device name and id the CLI is talking to,
and the pair timestamp. Use it to confirm pairing succeeded and to
double-check which device you’re about to act on before running
done.
Config: /Users/you/.commandlatch/configDevice: MacBook Pro (abc123…)Paired at: 2026-05-30T18:04:11.000Zcommandlatch awake [--for <duration>]Queues keep_awake_start, holding the Mac awake for a window.
--for,-f<duration>— how long to stay awake. Defaults to 5 minutes, so a barecommandlatch awakeis useful but conservative.- The agent clamps the upper bound to 24h server-side.
commandlatch awake --for 90m # stay awake an hour and a halfcommandlatch awake # 5-minute defaultcommandlatch done [--notify] [--lock-sleep-after <duration>]commandlatch done --cancelLocks and sleeps the paired Mac. Three flags compose into five shapes:
| Command | Behaviour |
|---|---|
commandlatch done | Lock + sleep immediately. |
commandlatch done --notify | Banner first, then lock immediately. |
commandlatch done --lock-sleep-after 5m | Agent waits 5m, then locks. |
commandlatch done --notify --lock-sleep-after 5m | Banner now, then lock in 5m. |
commandlatch done --cancel | Drop whatever delayed lock the agent is holding. |
--notify— fires asend_notificationbanner up front, before any countdown starts, so the user sees the warning the instant the command runs. The banner reads “Locking now” or “Locking in<dur>”.--lock-sleep-after <duration>— instead of locking now, the CLI sends onelock_sleepcarryingdelay_secsand exits; the agent parks it and fires after the delay. This grace window lives on the agent (not the CLI process), which is why it survives the terminal closing and never blocks a hook.--cancel— enqueuescancel_pending_lock, dropping the delayed lock the agent is currently holding (a no-op if there’s none). Exclusive: it cannot be combined with--notifyor--lock-sleep-after.
A delayed lock can be cancelled from anywhere: commandlatch done --cancel, the menu bar’s Cancel Pending Action item, or the dashboard’s
Cancel button — they all flip the same held command to cancelled.
commandlatch done # lock right nowcommandlatch done --notify # warn, then lock nowcommandlatch done --lock-sleep-after 10m # lock in 10 minutescommandlatch done --notify --lock-sleep-after 5m # warn now, lock in 5mcommandlatch done --cancel # cancel any pending lockcommandlatch run [--ttl <duration>] [--lock-sleep-after <duration>] -- <cmd...>The primary developer wrapper. It bundles three things around a single long-running command:
- Before — if
--ttlis given, enqueuekeep_awake_startso the Mac doesn’t sleep mid-job. - During — run the wrapped command with inherited stdio,
propagating its exit code so
rundrops in front of anypnpm test/make/cargo buildline without changing the script’s semantics. - After — if
--lock-sleep-afteris given and the command succeeded (wasn’t interrupted), enqueuelock_sleepwithdelay_secsand exit.
Rules and behaviour:
- The command to wrap comes after
--. Flags are parsed only on the left of--, so a--ttlinside the wrapped command’s own args is never hijacked. - You must supply at least one of
--ttlor--lock-sleep-after— otherwiserunis just running the command for you. - Exit code is forwarded from the child. A signal-killed child maps
to
128 + signum(POSIX convention), soset -eshells see non-zero for a^C. - Ctrl-C / interrupt —
runforwards the signal to the child, skips the post-task lock, and best-effort releases keep-awake (keep_awake_stop) so a cancelled run doesn’t pin the Mac awake after you walk away. A second^Chard-exits.
# Keep awake while tests run; do nothing after.commandlatch run --ttl 90m -- pnpm test
# Keep awake during a build, then lock 5 minutes after it finishes.commandlatch run --ttl 2h --lock-sleep-after 5m -- make release
# No keep-awake, just lock when a long sync finishes.commandlatch run --lock-sleep-after 1m -- rsync -a ./big remote:/backupAdvanced only. If you have the menu-bar app, you don’t need this — the app writes the CLI’s config automatically when you pair.
Runs a terminal-driven pairing flow for setups without the menu-bar app. It prints a pairing code — enter it in the web dashboard’s “Pair a device” screen. The CLI then polls and writes its config when pairing succeeds.
Options:
--force— pair even if the CLI is already configured (creates a new device entry). Without--force, the command refuses if a config already exists.COMMANDLATCH_DEVICE_NAME— set an environment variable to override the device name (defaults to<hostname> (CLI)).
To re-pair from scratch, delete ~/.commandlatch/config first.
Use-case examples
Section titled “Use-case examples”1. Auto-lock when Claude Code finishes
Section titled “1. Auto-lock when Claude Code finishes”Add a Stop hook to ~/.claude/settings.json so your Mac warns and then
locks itself a few minutes after Claude stops responding — start a long
task, walk away, and the laptop secures itself:
{ "hooks": { "Stop": [ { "hooks": [ { "type": "command", "command": "commandlatch done --notify --lock-sleep-after 5m" } ] } ] }}Cancel any pending lock with commandlatch done --cancel. Full setup,
pitfalls, and troubleshooting live in
Auto-lock with Claude Code.
2. Wrap a long test or build run
Section titled “2. Wrap a long test or build run”Keep the Mac awake while the job runs, then lock it shortly after it finishes — but only if the job succeeded:
commandlatch run --ttl 2h --lock-sleep-after 5m -- pnpm test:e2eBecause run forwards the child’s exit code, you can still chain it:
commandlatch run --ttl 1h -- make build && echo "build ok"3. Keep awake for a download/upload, lock when done
Section titled “3. Keep awake for a download/upload, lock when done”commandlatch awake --for 30m# ...start a big download in another tab...# when it's done:commandlatch done --notifyOr do it in one wrapped command:
commandlatch run --ttl 45m --lock-sleep-after 2m -- ./scripts/upload-artifacts.sh4. End-of-day “lock my desk machine” from a script
Section titled “4. End-of-day “lock my desk machine” from a script”A keyboard-friendly one-liner you can bind to a shell alias or a cron-ish trigger — warns first, then locks two minutes later so you can abort:
alias eod='commandlatch done --notify --lock-sleep-after 2m'Changed your mind? commandlatch done --cancel.
5. Lock now, from any terminal
Section titled “5. Lock now, from any terminal”The simplest possible use — you’re walking away and want the paired Mac locked and asleep immediately:
commandlatch done6. Confirm which machine you’re driving
Section titled “6. Confirm which machine you’re driving”Before running done from an unfamiliar shell, check the target:
commandlatch status7. Capture the queued command id in a script
Section titled “7. Capture the queued command id in a script”The queued id is printed on stdout, so you can grab it for logging while status lines stay on stderr:
id=$(commandlatch done --lock-sleep-after 10m | awk '{print $NF}')echo "scheduled lock: $id"Exit codes
Section titled “Exit codes”0— success (for everything exceptrun, where the exit code is the wrapped command’s).runforwards the wrapped command’s exit code; a signal-killed child maps to128 + signum.2— unknown subcommand (usage is printed to stderr).1— any thrown error (bad duration, missing config, HTTP/transport failure, unknown flags). The message is prefixedcommandlatch:.
Troubleshooting
Section titled “Troubleshooting”No config at ~/.commandlatch/config. The CLI isn’t paired. Start the desktop agent and pair it via the web dashboard (it writes the config), or usecommandlatch pairfor a headless setup.commandlatch: command not foundin a hook log. Hooks run in your login shell, but environments differ — use the absolute path/usr/local/bin/commandlatchinsettings.json.- Lock fires immediately instead of waiting. A glued-together flag
like
--lock-sleep-after5m(no space) is parsed as an unknown argument. Use a space or the=form:--lock-sleep-after=5m. - Nothing happens on the Mac. The CLI only queues the action; the
desktop agent must be online to run it. Run
commandlatch statusand confirm the agent is running and the device isn’t stale. - No banner.
send_notificationgoes through macOS Notifications, so a Do-Not-Disturb / Focus mode silences it. Banners also require that you allowed notifications for CommandLatch: the menu-bar app shows the standard macOS “Allow notifications?” prompt on first launch, and the Settings → Security checks → Notifications row shows the current state with a “Turn on notifications” / “Open Notification settings…” action if they’re off.
See also
Section titled “See also”- Auto-lock with Claude Code — the Claude Code Stop-hook workflow in depth.
- Webhooks — triggering actions over HTTP.