Skip to content

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.


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 / notify

Because 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:

ActionWhat the agent does
keep_awake_startHolds the Mac awake (caffeinate) for a duration.
keep_awake_stopReleases an active keep-awake window.
lock_sleepLocks the screen and sleeps the Mac (optionally after a delay).
send_notificationShows a macOS banner.
cancel_pending_lockDrops a delayed lock_sleep the agent is holding.

You never name these actions directly — the subcommands compose them for you.


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:

Terminal window
commandlatch status

It 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).


The CLI stores its credentials in ~/.commandlatch/config. You normally don’t need to touch this file — the standard flow is:

  1. Run the desktop agent.
  2. Pair it from the web dashboard’s Pair a device screen.
  3. The agent writes ~/.commandlatch/config automatically — 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.


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 usage

Run commandlatch, commandlatch help, or commandlatch -h to print the built-in usage with examples.

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.

Anywhere a duration is accepted (--for, --ttl, --lock-sleep-after):

  • Format: a positive integer with an optional s / m / h suffix.
  • 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.

Terminal window
commandlatch status

Prints 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/config
Device: MacBook Pro (abc123…)
Paired at: 2026-05-30T18:04:11.000Z

Terminal window
commandlatch 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 bare commandlatch awake is useful but conservative.
  • The agent clamps the upper bound to 24h server-side.
Terminal window
commandlatch awake --for 90m # stay awake an hour and a half
commandlatch awake # 5-minute default

Terminal window
commandlatch done [--notify] [--lock-sleep-after <duration>]
commandlatch done --cancel

Locks and sleeps the paired Mac. Three flags compose into five shapes:

CommandBehaviour
commandlatch doneLock + sleep immediately.
commandlatch done --notifyBanner first, then lock immediately.
commandlatch done --lock-sleep-after 5mAgent waits 5m, then locks.
commandlatch done --notify --lock-sleep-after 5mBanner now, then lock in 5m.
commandlatch done --cancelDrop whatever delayed lock the agent is holding.
  • --notify — fires a send_notification banner 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 one lock_sleep carrying delay_secs and 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 — enqueues cancel_pending_lock, dropping the delayed lock the agent is currently holding (a no-op if there’s none). Exclusive: it cannot be combined with --notify or --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.

Terminal window
commandlatch done # lock right now
commandlatch done --notify # warn, then lock now
commandlatch done --lock-sleep-after 10m # lock in 10 minutes
commandlatch done --notify --lock-sleep-after 5m # warn now, lock in 5m
commandlatch done --cancel # cancel any pending lock

Terminal window
commandlatch run [--ttl <duration>] [--lock-sleep-after <duration>] -- <cmd...>

The primary developer wrapper. It bundles three things around a single long-running command:

  1. Before — if --ttl is given, enqueue keep_awake_start so the Mac doesn’t sleep mid-job.
  2. During — run the wrapped command with inherited stdio, propagating its exit code so run drops in front of any pnpm test / make / cargo build line without changing the script’s semantics.
  3. After — if --lock-sleep-after is given and the command succeeded (wasn’t interrupted), enqueue lock_sleep with delay_secs and exit.

Rules and behaviour:

  • The command to wrap comes after --. Flags are parsed only on the left of --, so a --ttl inside the wrapped command’s own args is never hijacked.
  • You must supply at least one of --ttl or --lock-sleep-after — otherwise run is just running the command for you.
  • Exit code is forwarded from the child. A signal-killed child maps to 128 + signum (POSIX convention), so set -e shells see non-zero for a ^C.
  • Ctrl-C / interruptrun forwards 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 ^C hard-exits.
Terminal window
# 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:/backup

Advanced 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.


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.

Keep the Mac awake while the job runs, then lock it shortly after it finishes — but only if the job succeeded:

Terminal window
commandlatch run --ttl 2h --lock-sleep-after 5m -- pnpm test:e2e

Because run forwards the child’s exit code, you can still chain it:

Terminal window
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”
Terminal window
commandlatch awake --for 30m
# ...start a big download in another tab...
# when it's done:
commandlatch done --notify

Or do it in one wrapped command:

Terminal window
commandlatch run --ttl 45m --lock-sleep-after 2m -- ./scripts/upload-artifacts.sh

4. 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:

Terminal window
alias eod='commandlatch done --notify --lock-sleep-after 2m'

Changed your mind? commandlatch done --cancel.

The simplest possible use — you’re walking away and want the paired Mac locked and asleep immediately:

Terminal window
commandlatch done

Before running done from an unfamiliar shell, check the target:

Terminal window
commandlatch status

7. 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:

Terminal window
id=$(commandlatch done --lock-sleep-after 10m | awk '{print $NF}')
echo "scheduled lock: $id"

  • 0 — success (for everything except run, where the exit code is the wrapped command’s).
  • run forwards the wrapped command’s exit code; a signal-killed child maps to 128 + 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 prefixed commandlatch:.

  • 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 use commandlatch pair for a headless setup.
  • commandlatch: command not found in a hook log. Hooks run in your login shell, but environments differ — use the absolute path /usr/local/bin/commandlatch in settings.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 status and confirm the agent is running and the device isn’t stale.
  • No banner. send_notification goes 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.