Skip to content

CLI Reference

pcmd is the only executable PocketCmd Server ships. With no arguments it runs the first-run flow or the server itself; subcommands manage the service, devices, team members, vaults, metric requests, configuration and backups. Commands that read or change state work directly on ~/.pocketcmd/state.db, so run them as the user that owns the server.

pcmd [flags]
pcmd <command> [arguments] [flags]

Every command answers --help with a description and examples, and pcmd help <command> does the same. Commands that list things accept --json for scripts. Commands that change or remove something ask for confirmation and accept -y / --yes to skip it; with --non-interactive, or when stdin is not a terminal, they refuse unless --yes is given.

These flags are accepted by every command.

Flag Default Description
-p, --port <n> 8767 Server port. The value chosen on the first run is saved in config.json and used by every later command unless you pass the flag again.
--host <addr> the interface picked on the first run Bind address: an IP address of this machine, or 0.0.0.0 (:: to include IPv6) for every interface; hostnames are refused. The server never listens on every interface unless you pass it. Saved on the first run like --port.
--no-qr false Never draw QR codes; print the URL only.
--qr-invert false Swap the dark and light modules of the QR code, for a dark terminal without color support.
-v, --version Print pcmd <version> (commit <hash>, built <date>).
-h, --help Print help for the command.

--foreground, --pair-timeout, --advertise-ip, --debug-log, --non-interactive, --json and --yes belong to the commands that use them and are listed with each command.

Code Meaning
0 Success.
1 Error, printed on stderr as Error: ....
2 pcmd status: the service is installed but not running.
3 pcmd status: no service is installed.
130 Interrupted with Ctrl+C while waiting (first run, sync, logs -f, audit -f).

On the first run (no ~/.pocketcmd/config.json), creates the server identity, prints a pairing QR code, waits for the first device, then starts the server the way you chose: as a background service, or in the foreground with --foreground. A new code replaces the old one every 60 seconds while it waits. If no device pairs, nothing is saved and the next pcmd starts the first run again. Server Installation describes each step.

This is the only way to pair the first device. Further devices and people join with pcmd invite or from the app.

On later runs in background mode, pcmd installs the service if it is missing, starts it if it is stopped, and prints its status:

╭────────────╮
│ >_ │
╰────────────╯
POCKETCMD 1.0.0
Status: running (pid 48213)
Port: 8767
Devices: 2 paired
pcmd invite Invite a device or team member
pcmd status Detailed status
pcmd uninstall Stop and remove service

If the service cannot be installed or started, pcmd says why and runs the server in the foreground instead. In foreground mode (chosen on the first run, or --foreground), it runs the server until you press Ctrl+C. The installed service starts the binary this way.

On systemd and macOS, pcmd refuses to run as root or under sudo, because the service and ~/.pocketcmd belong to the user who installs them.

Flag Default Description
--foreground false Run the server in the foreground instead of installing a service.
--pair-timeout <duration> 5m How long the first run waits for a device. 0 waits forever.
--advertise-ip <addr> the bind address IP address or hostname to put in the QR code and URL instead, for example a public address or a dynamic-DNS name behind NAT. Saved for later commands.
--debug-log <path> none Also write the structured JSON log to this file, rotated at 100 MB.
--non-interactive false Never prompt: listen on the first detected address and wait for a device.
Terminal window
pcmd
pcmd --port 9000 --host 10.8.0.1
pcmd --non-interactive --no-qr --pair-timeout 0

Prints the service status: running (with its PID) or stopped, the port, the number of active paired devices, the service mode, on Linux whether lingering is enabled, and the paths of config.json, the data directory and, on macOS, server.log. With no service installed it prints Status: not installed. The exit code says the same for scripts: 0 running, 2 stopped, 3 not installed.

Flag Default Description
--json false Print one JSON object with version, installed, running, pid, port, paired_devices, service_mode, config_path, data_dir, stdout_log, linger and service_state.

Writes the service definition for the detected init system and starts it. The first run calls this for you; run it by hand after pcmd uninstall or on a restored data directory. See Background service for the files it writes. With OpenRC it needs sudo; on systemd and macOS it refuses to run as root or under sudo.

On a host where no device has paired yet it refuses with pair a device first. Run pcmd first.

Flag Default Description
--allow-unpaired false Install even though no device has paired yet.

Stops the service and removes its definition. Asks for confirmation. The binary and ~/.pocketcmd are left in place. In background mode, the next pcmd installs the service again.

Flag Default Description
-y, --yes false Skip the confirmation.
--non-interactive false Never prompt; refuse unless --yes is given.

Starts the installed service and waits for it to answer /api/health. Fails if no service is installed.

Stops the service and keeps it installed. pcmd start, or the next plain pcmd, brings it back. Stop the service before pcmd restore.

Stops and starts the service. The service definition is rewritten only when it no longer matches this binary, port and bind address, for example after the binary moved or --port or --host changed.

Prints the server’s structured log, the same entries the owner sees in the app under Server logs. The log is kept in state.db (roughly the last 10,000 entries) and printed oldest first as time LEVEL message key=value ....

The process’s own output (crash messages, startup errors) is not part of it. On systemd it goes to the journal (journalctl --user -u pocketcmd), on macOS to ~/.pocketcmd/server.log.

Flag Default Description
-f, --follow false Keep printing new entries until Ctrl+C.
-n, --lines <n> 50 Number of recent entries to show.
--since <duration> none Only entries newer than this, for example 30m or 2h.
--level <level> none Only entries at this level or above: debug, info, warn or error.
--json false Print the raw JSON entries, one per line.
--file <path> none Read a --debug-log file instead of the store; -f follows it across rotation.
Terminal window
pcmd logs -f --level warn
pcmd logs --since 2h --json

Lists the active devices under a header row: the start of the public-key fingerprint, the owning user, the device name, its status and when it paired. Prints No active devices. when there are none.

FINGERPRINT USER DEVICE STATUS PAIRED REMOVED
a1b2c3d4e5f6... alice iPhone 15 active 2026-03-15T10:30:00Z -
f6e5d4c3b2a1... bob iPad Pro left 2026-03-20T14:45:00Z 2026-04-02T09:12:00Z
Flag Default Description
--all false Also list revoked and left devices, with the time they were removed.
--json false Print a JSON array with the full fingerprint, user, names, status and timestamps.

Status is active, left or revoked. See Device states.

Sets a device to revoked. The argument matches the start of the public-key fingerprint or the device ID and must be at least 8 characters. Without an argument, a numbered picker lists the devices; with --non-interactive the argument is required. Asks for confirmation.

Revocation takes effect on the device’s next request, and the key can never pair again: the app has to be reinstalled before the device can join with a new invite. Revoking a left device makes its removal permanent the same way, which is what you want for a lost or stolen device. The command reminds you that removing a device in the app also publishes a signed revocation that stops your other devices from sharing vault keys with it, which the server cannot do on its own.

Flag Default Description
-y, --yes false Skip the confirmation.
--non-interactive false Never prompt; requires the argument and --yes.

Changes a device’s name, label or both. The prefix is resolved like revoke-device. Each value is at most 128 characters; longer values are refused. The app shows the new name on its next device listing.

Flag Default Description
--name <text> none New device name. Must not be empty.
--label <text> none New device label. An empty value clears it.
Terminal window
pcmd rename-device a1b2c3d4 --name "Alice's iPhone"

Replaces the server’s identity certificate and gets every device onto the new one. Asks for confirmation, then:

  1. Deletes tls/server.crt and tls/server.key, generates a new certificate and prints the start of its fingerprint.
  2. Restarts the background service so it serves the new certificate. Without a service, it tells you to stop your foreground server with Ctrl+C and start it again with pcmd.
  3. Prints a re-pair QR code and pocketcmd:// URL with the new fingerprint: Scan this code in the app on every paired device to re-pair it:.

Every device pinned the old fingerprint and cannot connect until it scans the code. The code does not expire and can be scanned by every device. It only works for devices that are registered and active: the server re-issues their certificate after checking that the request is signed with the device’s registered key, and they keep their user and role. It pairs no new devices. See QR Code Pairing.

Flag Default Description
-y, --yes false Skip the confirmation.
--non-interactive false Never prompt; refuse unless --yes is given.
--advertise-ip <addr> saved address IP address or hostname for the re-pair URL.

One-shot export of the SSH hosts in this user’s ~/.ssh/config, with the key files they reference, to one device. The command starts a temporary server on a random free port (or --port if given), prints a QR code, serves the configuration once to the device that scans it and exits. The hosts are read with ssh_config rules; see SSH Config Import.

The app accepts this QR code on the free plan; pairing a server does not apply here.

Flag Default Description
--timeout <duration> 60s How long to wait for a device to fetch.
--advertise-ip <addr> saved address IP address or hostname for the sync URL.
--debug-log <path> none Also write the temporary server’s structured log to this file.

The first device to pair is the team owner. The owner alone can transfer ownership, create admin invites and read the audit and server logs in the app. Approving custom metric commands needs metrics.commands.create: the owner holds it, and admins and members hold it only when the owner grants it. The CLI acts with operator authority: whoever can run it on the host already controls state.db.

Creates an invite and prints its code, expiry, a QR code and a pocketcmd://invite?... URL. The invitee joins in the app under Teams › Join a team or pastes the URL under Paste URL. Refused until the owner’s device has paired.

Flag Default Description
--role admin|member member Role of the new user. Ignored with --for.
--for <user> none Add a device for an existing user (user ID or display name). The device inherits that user’s role.
--expires <duration> 24h Validity, for example 48h. Clamped to 30 days, with a note when it was reduced.
--pin-device <fingerprint> none Accept the invite only from the device with this public-key fingerprint. The app shows a device’s fingerprint on the Join a team screen.
--label <text> none Note for your own records; not sent to the invitee.
--advertise-ip <addr> saved address IP address or hostname for the invite URL.
Terminal window
pcmd invite --expires 48h --label "Alice's phone"
pcmd invite --role admin --label "Backup admin"
pcmd invite --for alice --label "Alice's tablet"

Invite codes are 16 lowercase base32 characters (80 bits) and can be redeemed once.

Lists outstanding invites with their code, role, expiry, target (the user ID for a second-device invite, otherwise (new user)) and label. invites is an alias. Accepts --json.

Cancels an unredeemed invite. A prefix must match exactly one invite. Asks for confirmation; accepts --yes and --non-interactive.

Lists users with their ID, display name, role (owner, admin or member), status and number of active devices. Accepts --json, which also lists each user’s permissions.

Revokes a user, every active device of that user, the invites addressed to them and the invites they created. <user> is a user ID or a display name. The owner cannot be revoked, and neither can the last active admin. Asks for confirmation; accepts --yes and --non-interactive.

--purge-data also deletes the sync records the user’s devices wrote: they become tombstones immediately, so your remaining devices sync the deletion, and the tombstones are removed by the regular cleanup after 365 days. Records in team vaults and permanent device-revocation records are kept.

Makes a member an admin. Admins hold every permission except metrics.commands.create, which they keep only if it was granted; the member’s other grants no longer apply.

Makes an admin a member. Refused for the owner and for the last active admin. Asks for confirmation; accepts --yes and --non-interactive.

Makes another active member the owner. The new owner becomes an admin with the owner role, and the previous owner stays an admin. The change is atomic and takes effect at once. This is the same operation as Transfer ownership on a member’s page in the app. Asks for confirmation; accepts --yes and --non-interactive.

pcmd rename-user <user> <new-display-name>

Section titled “pcmd rename-user <user> <new-display-name>”

Changes a member’s display name as shown in the app and the CLI.

Grants one permission. The owner holds every permission and admins hold every one except metrics.commands.create, so the other keys can be granted to members only; anything granted to the owner is refused. metrics.commands.create can be granted to admins and members. The keys correspond to the switches on a member’s page in the app:

Key In the app
metrics.read View metrics
metrics.commands.read View command definitions
metrics.commands.create Add monitoring commands. Approve and decline metric-command requests, and see every member’s requests. Only the owner grants it in the app, to admins or members. Granting it lets that person run code on the host.
metrics.commands.update Edit monitoring commands
metrics.commands.delete Remove monitoring commands
alerts.read Receive alerts. Limit it to some metrics with --metrics.
alerts.create, alerts.update, alerts.delete Add alert rules, Edit alert rules, Remove alert rules
sshkeys.sync Sync SSH keys
users.manage Manage members
invites.manage Manage invites
invites.device Additional devices

New members start with metrics.read, metrics.commands.read, alerts.read (all metrics), sshkeys.sync and invites.device.

Flag Default Description
--metrics <ids> all metrics For alerts.read only: comma-separated metric IDs the member receives alerts for.
Terminal window
pcmd grant bob invites.manage
pcmd grant bob alerts.read --metrics custom:req-1a2b3c4d5e6f7a8b

pcmd revoke-permission <user> <permission>

Section titled “pcmd revoke-permission <user> <permission>”

Takes one permission away from a member, or metrics.commands.create from an admin. The keys are the same as for grant.

Lists the shared vaults the server knows from their access lists, with the number of members who hold access. The contents are ciphertext the server cannot read. Accepts --json.

Lists who has access to a vault and at which level: view, edit or manage. Accepts --json.

A metric request proposes a shell command for the server to run on a schedule. Members file requests from the app; nothing runs until a request is approved. See Monitoring.

Lists the requests with their ID, status (pending, approved or declined), label, requester, creation time and command. Accepts --json.

Approves a pending request. The command is added to the monitoring configuration and the running server starts collecting it. In the app, approving needs metrics.commands.create; the CLI acts as the operator. The decision is written to the audit log.

Declines a pending request. The requester sees the decision in the app, and nothing runs.

Prints the saved configuration: service_mode, port, host and advertise_ip, with defaults for unset values. Accepts --json. See Configuration file.

Prints the newest entries of ~/.pocketcmd/audit.log: pairings, invites, role and permission changes, ownership transfers, device removals and renames, metric-command decisions and SSH config exports. Prints No audit log yet. when nothing has been recorded.

Flag Default Description
-f, --follow false Keep printing new entries until Ctrl+C.
-n, --lines <n> 50 Number of recent entries to show.
--since <duration> none Only entries newer than this.
--json false Print the raw JSON entries, one per line.

Writes a gzip-compressed tar archive of ~/.pocketcmd: configuration, server certificate and key, both databases and the audit log. The databases are snapshotted consistently, so the server may keep running. The default file name is pocketcmd-backup-<date>.tar.gz in the current directory.

The archive contains the server’s private key and the device registry, so it is written with mode 0600. Store it accordingly.

Restores ~/.pocketcmd from a backup. In this order, it:

  1. Validates the archive: a gzip tarball whose entries all sit under .pocketcmd/ (no ../, no symlinks) and that contains state.db and tls/server.key.
  2. Refuses while the background service runs (pcmd stop first) or while any server answers /api/health on the configured port.
  3. Asks before overwriting an existing data directory.

Every restored file gets mode 0600.

Flag Default Description
-y, --yes false Overwrite an existing ~/.pocketcmd without asking.
--force false Same as --yes.
--non-interactive false Never prompt; refuse to overwrite unless --yes is given.
Terminal window
pcmd stop
pcmd restore ~/pocketcmd-backup.tar.gz --yes
pcmd start

pcmd completion bash|zsh|fish|powershell prints a shell completion script.

pocketcmd://<host>:<port>/<pairing_token>#<server_fingerprint>
  • host: the advertised address. IPv6 addresses are bracketed, for example [fd00::1].
  • pairing_token: 32 hex characters, single use, valid for 60 seconds, held only in memory.
  • server_fingerprint: SHA-256 of the server certificate. The app pins it at pairing time and rejects any other certificate afterwards.

Invites use pocketcmd://invite?host=<host>&port=<port>&code=<code>#<server_fingerprint>, and pcmd sync uses pocketcmd://ssh-sync?host=<host>&port=<port>&token=<token>#<server_fingerprint>.

~/.pocketcmd/config.json (mode 0600) is written when the first device pairs:

{
"config_version": 1,
"service_mode": "background",
"port": 8767,
"host": "192.168.1.5",
"advertise_ip": "myhome.example.net"
}
Field Description
config_version Managed by the server. A server refuses a config.json written by a newer release.
service_mode background (pcmd keeps a service installed and running) or foreground (pcmd runs the server in the foreground).
port, host The port and bind address from the first run, reused by later commands unless you pass --port or --host. host is the interface picked on the first run, or 0.0.0.0 / :: when you chose every interface with --host.
advertise_ip The address put into pairing, invite, sync and re-pair codes, set with --advertise-ip on the first run. Empty means the bind address.