Skip to content

Latest commit

 

History

History
910 lines (616 loc) · 20.4 KB

File metadata and controls

910 lines (616 loc) · 20.4 KB

kcd CLI Reference

kcd is both the daemon and the command-line client. Sub-commands that interact with a running daemon communicate over a Unix socket (/run/user/$UID/kcd/kcd.sock). The daemon itself is started with kcd daemon.


Global Flags

These flags apply to every command:

Flag Default Description
--config <path> ~/.config/kcd/kcd.toml Path to config file
--log-level <level> info Log verbosity: debug, info, warn, error, quiet
--help, -h Show help
--version, -v Show version, commit, and build date

--config and --log-level can also be set via environment variables KCD_CONFIG and KCD_LOG_LEVEL.


daemon

Start the kcd background daemon. This is the only command that does not connect to a running daemon — it is the daemon.

kcd daemon [--config <path>] [--log-level <level>]

The daemon:

  • Listens for device announcements via UDP and mDNS (always on)
  • Broadcasts its own identity only during kcd pair (listen mode)
  • Accepts inbound TCP connections on port 1716
  • Runs all enabled plugins
  • Opens the IPC Unix socket for CLI clients
  • Sends SIGINT or SIGTERM to stop cleanly

Running as a systemd user service (recommended)

cp packaging/kcd-user.service ~/.config/systemd/user/kcd.service
systemctl --user daemon-reload
systemctl --user enable --now kcd

Check status:

systemctl --user status kcd
journalctl --user -u kcd -f

doctor

Check runtime dependencies and print a coloured pass/fail table. Exits with code 1 if any check fails. Works even when the daemon is not running (the daemon-running check will show as failed).

kcd doctor

Checks performed:

  • Daemon reachable via IPC socket
  • notify-send installed (desktop notifications)
  • wl-copy or xclip installed (clipboard sync)
  • sshfs installed (SFTP mount)
  • ydotool or xdotool installed (mousepad)
  • Port 1716/UDP available
  • Port 1716/TCP available
  • Config file readable
  • TLS cert file exists

status

Show daemon runtime information.

kcd status [--json]

Flags

Flag Description
--json Output raw JSON

Example output

kcd v1.0.5 — up 3h 12m
Socket:    /run/user/1000/kcd/kcd.sock
Config:    /home/user/.config/kcd/kcd.toml
Devices:   2 known, 1 connected
Plugins:   Battery, Clipboard, Notification, Share, ...

devices

List all known devices — both currently connected and remembered from previous sessions.

kcd devices [--json]

Flags

Flag Description
--json Output as a JSON array
--watch, -w Stream device changes live (clears screen on each change)

Example output

DEVICE ID                            NAME              TYPE       STATE      CONNECTED
---------------------------------------------------------------------------------------------------
a1b2c3d4_e5f6_7890_abcd_ef1234567890 Pixel 8 Pro       phone      Paired     true
b9e1f234_0000_1111_2222_333344445555 Galaxy Tab S9     tablet     Unpaired   false

JSON output

kcd devices --json | jq '.[0].ID'
[
  {
    "ID": "a1b2c3d4_e5f6_7890_abcd_ef1234567890",
    "Name": "Pixel 8 Pro",
    "Type": "phone",
    "State": "Paired",
    "Connected": true
  }
]

Example — live mode

kcd devices --watch

connect

Manually connect to a device by IP address. Use this when UDP broadcast and mDNS are blocked (corporate Wi-Fi, Docker, university networks).

kcd connect <ip-address>

Example

kcd connect 192.168.1.50

On the Android side, open KDE Connect → ⋮ → "Add device by IP address" and enter your PC's IP if the phone isn't finding the PC either.

Auto-reconnect: paired devices reconnect automatically after a connection drop. The daemon dials the last known IP with exponential backoff up to 5 minutes. To stop reconnection, unpair the device.


pair

Pair with a device. Two modes depending on whether you provide a device ID.

Pair to a specific device

kcd pair <device-id>

If the device has already sent a pair request to kcd (state PairRequestedByPeer), this accepts it. Otherwise, it sends a new pair request — accept on your phone.

Listen mode (headless / server)

kcd pair

No arguments = listen mode. Broadcast is started automatically so the phone can discover the PC. The CLI waits for any incoming pair request and accepts it immediately, printing the verification code:

Listening for pair requests… (Ctrl+C to cancel)
Paired with Pixel 8 Pro (a1b2c3d4...)
Verification code: 3a8f

Broadcast stops when pairing completes or you press Ctrl+C.


unpair

Revoke trust and remove a device from the paired list.

kcd unpair <device-id>

This sends a rejection packet to the device and removes it from the local device registry.


ping

Send a ping notification to a device. The phone displays a "Ping!" notification.

kcd ping <device-id>

battery

Fetch the current battery level and charging state of a device.

kcd battery <device-id>

Example output

Battery: 74% (charging)
Battery: 31% (discharging)

For continuous monitoring, use kcd watch --events=battery.update instead.


clipboard

Push the local clipboard content to a device.

kcd clipboard [device-id]

If device-id is omitted, kcd automatically targets the first connected device.

Clipboard backend detection

  • Wayland ($WAYLAND_DISPLAY set): reads via wl-paste, writes via wl-copy
  • X11: reads/writes via xclip

Example — Sway keybinding

bindsym Super+c exec kcd clipboard

mpris

Control media playback on remote devices (phones) and show their now-playing state.

All action subcommands (play, pause, toggle, next, prev, stop, volume, seek) support auto-device and auto-player — omit --device and --player and they automatically target the first connected phone with cached MPRIS state.

mpris list

List active media players on remote devices. Refreshes state by sending requestPlayerList to all connected phones.

kcd mpris list [--json]

Flags

Flag Description
--json Output as a JSON array

Examples

# Human-readable
kcd mpris list

# JSON for scripting
kcd mpris list --json | jq -r '.[] | "\(.player): \(.title)"'

mpris status

Show currently playing media on remote devices, with position, volume, and artist info.

kcd mpris status [--device <id>] [--json]

Flags

Flag Description
--device Target device ID (omit for auto-detect)
--json Output as a JSON array

Examples

kcd mpris status
kcd mpris status --json | jq -r '.[].title'

mpris play / pause / toggle

Control playback on the phone.

kcd mpris play    [--device <id>] [--player <name>]
kcd mpris pause   [--device <id>] [--player <name>]
kcd mpris toggle  [--device <id>] [--player <name>]

Flags

Flag Description
--device Target device ID (omit for auto-detect)
--player, -p Player name (omit for auto-fill from cached state)

mpris next / prev

Skip to the next or previous track. After skipping, an automatic Play action is sent to handle phone-side MPRIS implementations that stop after a track change.

kcd mpris next       [--device <id>] [--player <name>]
kcd mpris previous   [--device <id>] [--player <name>]
kcd mpris prev       [--device <id>] [--player <name>]  (alias)

mpris stop

Stop playback on the phone.

kcd mpris stop [--device <id>] [--player <name>]

mpris volume

Set the phone's media volume (0–100).

kcd mpris volume [--device <id>] [--player <name>] <0-100>

Example

kcd mpris volume 50

mpris seek

Seek to a position or by an offset on the phone.

kcd mpris seek [--device <id>] [--player <name>] <offset>

Offset formats:

  • +30s — forward 30 seconds
  • -10s — back 10 seconds
  • 1m30s — absolute 1 minute 30 seconds
  • 45 — absolute 45 seconds (bare number)

Examples

kcd mpris seek +30s
kcd mpris seek -10s
kcd mpris seek 1m30s

mpris raw

Dump raw MPRIS debug state for all local media players as JSON. Shows player names, their running status, and device-to-player mappings. Useful for diagnosing "no player found" issues.

kcd mpris raw

Returns the same data as the mpris_status IPC command.

{
  "watcherRunning": true,
  "deviceCount": 2,
  "players": ["spotify", "firefox"],
  "playerMappings": {
    "a1b2c3d4e5f6_...": "spotify"
  }
}

Scripting examples

# Get current track title (exit 0 on success)
kcd mpris status --json | jq -r '.[].title'

# Toggle playback and check result
kcd mpris toggle
kcd mpris status --json | jq -r '.[] | "playing=\(.isPlaying)"'

# Next track, wait for state update, print new title
kcd mpris next && sleep 2 && kcd mpris list --json | jq -r '.[] | "\(.title) - \(.artist)"'

# Watch live track changes
kcd watch --events=mpris.update

# Sway keybinding: next track on the phone
# bindsym XF86AudioNext exec kcd mpris next

Note: The mpris.update event fires whenever the phone sends a now-playing state change (track change, play/pause toggle). Subscribe with kcd watch --events=mpris.update.


share

Send a local file to a device.

kcd share <device-id> <file-path>

The file is transferred over a dedicated TCP side-channel (ports 1739–1764) and saved to the phone's Downloads folder.

Example

kcd share a1b2c3d4_e5f6_7890_abcd_ef1234567890 ~/Documents/report.pdf

Watch transfer progress:

kcd watch --events=share.progress,share.complete

reply

Send a text reply to a notification that supports it (e.g. a messaging app notification).

kcd reply <device-id> <reply-id> <message>

The reply-id is included in the notification event payload. Capture it with kcd watch --json:

kcd watch --json | jq 'select(.type=="notification") | {id: .payload.id, app: .payload.appName}'

Example

kcd reply a1b2... abc-123 "On my way!"

call

Manage phone calls.

call mute

Silence the ringtone for an incoming call without answering.

kcd call mute <device-id>

findmyphone

Make the phone play a loud ringtone to help locate it.

kcd findmyphone <device-id>

lock / unlock

Lock or unlock the current desktop session.

kcd lock   <device-id>
kcd unlock <device-id>

Uses loginctl lock-session / loginctl unlock-session under the hood.


sftp

Manage SFTP access to the phone's filesystem.

The phone's SFTP server may expose multiple storage volumes (e.g. "Internal shared storage" and "SD card"). These are reported via multiPaths and pathNames in the KDE Connect protocol.

sftp request

Ask the device for SFTP credentials (host, port, user, password). The result is emitted as an sftp.mount event.

kcd sftp request <device-id>

Capture the credentials:

kcd watch --json --events=sftp.mount | jq -r 'select(.type=="sftp.mount") | .payload.uri'

sftp info

Show cached SFTP connection details for a paired device, including available storage volumes:

kcd sftp info <device-id>

Example output

Device: a1b2c3d4_e5f6_7890_abcd_ef1234567890 (Pixel 8 Pro)
IP:     192.168.1.50
Port:   8022
User:   sftp-user
Password: ********

Volumes:
  1. Internal shared storage  →  /storage/emulated/0
  2. SD card                  →  /storage/ABCD-1234

If the phone returned an error (e.g. storage permission not granted), the errorMessage field is shown instead.

sftp volumes

List available storage volumes without the full info output:

kcd sftp volumes <device-id>

Example output

Internal shared storage  →  /storage/emulated/0
SD card                  →  /storage/ABCD-1234

The phone must have an active SFTP session (run kcd sftp request first if the volumes list is empty).

sftp mount

Request credentials and immediately mount the phone's filesystem using sshfs.

kcd sftp mount <device-id>

The mount point is printed to stdout. Unmount with fusermount -u <mountpoint>.

sftp unmount

Cleanly unmount a previously mounted phone filesystem.

kcd sftp unmount <device-id>

Calls fusermount3 (or fusermount on older systems) and removes the temporary mount point directory. Returns an error if the device was never mounted in this daemon session.

sftp browse

Request fresh SFTP credentials and list available storage volumes or mount a specific one immediately.

kcd sftp browse <device-id> [volume-index|volume-name|volume-path]

Without a volume argument — requests fresh credentials and lists volumes:

$ kcd sftp browse a1b2c3d4
Requesting SFTP credentials from phone (waiting up to 20s)…
Available volumes:
  0. Internal shared storage         /storage/emulated/0
  1. SD card                         /storage/ABCD-1234

Mount a volume: kcd sftp browse <device-id> <index|name|path>

With a volume argument — requests fresh credentials and mounts the specified volume via sshfs, opening it in the default file manager:

$ kcd sftp browse a1b2c3d4 "SD card"
Requesting SFTP credentials from phone (waiting up to 20s)…
Mounted at: /home/user/Downloads/kcd/mnt/kcd-sftp-a1b2c3d4

The volume argument is resolved in this order:

  1. Index (0-based) — 0, 1, etc.
  2. Name (case-insensitive) — "SD card", "internal shared storage"
  3. Path (exact then case-insensitive) — /storage/ABCD-1234

This is the recommended way to mount a specific phone volume without having to request + mount separately. It always fetches fresh credentials, so the credentials are guaranteed valid.


run

Execute and manage remote commands configured on the phone's KDE Connect app, or expose local shell commands to the phone via the [commands] config table.

run list

Request the list of commands available on the remote device.

kcd run list <device-id>

Results arrive as a runcommand.list event; watch for them:

kcd watch --json | jq 'select(.type=="runcommand.list")'

run exec

Execute one of the local commands registered in [commands] config, triggered from the phone, or send a command execution request to the phone.

kcd run exec <device-id> <command-key>

Example config (kcd.toml)

[commands]
uptime  = "uptime"
lock    = "loginctl lock-session"
suspend  = "systemctl suspend"
kcd run exec a1b2... uptime

sms

Send and receive SMS via a connected phone.

sms send

Send an SMS message.

kcd sms send <device-id> <phone-number> <message>

Example

kcd sms send a1b2... +1555000111 "Heading home in 10"

sms conversations

Request a list of SMS conversation threads. Results arrive as sms.incoming events.

kcd sms conversations <device-id>

sms conversation

Request messages from a specific conversation thread.

kcd sms conversation <device-id> <thread-id>

sms attachment

Request an MMS attachment file from a device. The file is saved locally and an sms.attachment event is emitted with the path.

kcd sms attachment <device-id> <part-id> <unique-identifier>

volume

Control the remote device's audio volume (requires remotesystemvolume plugin).

volume list

List audio sinks on a remote device and their current volume/mute state.

kcd volume list <device-id>

Example output

media  75%
alarm  100%

volume set

Set a sink's volume (0–100) on a remote device.

kcd volume set <device-id> <sink-name> <0-100>

Examples

kcd volume set a1b2... media 50
kcd volume set a1b2... alarm 80

volume mute

Mute or unmute a sink on a remote device.

kcd volume mute <device-id> <sink-name> <true|false>

Examples

kcd volume mute a1b2... media true    # Mute
kcd volume mute a1b2... media false   # Unmute

Sink names and current volume can be discovered with kcd volume list. Volume changes made on the phone are also published as volume.update events.


watch

Monitor real-time events from the daemon as an NDJSON stream. This is the primary way to observe what is happening across all devices.

For the full list of event types and their payload fields, see README.md § Events.

kcd watch [--events <type,...>] [--json]

Flags

Flag Description
--events, -e Comma-separated list of event types to filter (default: all)
--json Output raw NDJSON instead of human-readable text

kcd watch reconnects automatically if the daemon restarts, with exponential backoff up to 30 seconds.

Available event types

Event type Description
device.added A new device was seen for the first time
device.removed A device was unpaired and removed
device.connected A device established a TCP connection
device.disconnected A device's connection dropped
pair.requested The phone sent a pairing request
pair.accepted Pairing was accepted on both sides
pair.rejected Pairing was denied or cancelled
battery.update New battery reading: {charge, charging}
notification Notification from the phone: {appName, title, text, id, ...}
notification.canceled The phone dismissed a notification: {id}
share.progress File transfer progress: {file, current, total}
share.complete File transfer finished: {file, success, error?}
share.text Plain text received: {text}
share.url URL received: {url}
ping.received A ping arrived
telephony.ringing Incoming call: {contactName, phoneNumber}
telephony.missed Missed call: {contactName, phoneNumber}
telephony.canceled Call ended
connectivity.update Signal strength: {signal, networkType}
volume.update Device volume changed: {name, volume, muted}
mpris.update Now playing: {player, title, artist, album, isPlaying, pos, length, volume}
sftp.mount SFTP credentials: {uri, ip, port, user, password, path, multiPaths, pathNames, errorMessage}
battery.threshold Battery low/full alert: {charge, charging, event}
telephony.talking Call in progress: {contactName, phoneNumber}
sms.incoming SMS/MMS received: {body, sender, date, thread_id, read}
sms.attachment MMS attachment downloaded: {filename, path, thread_id}
ring.received Phone wants this PC to ring

Examples

Watch everything, human-readable

kcd watch
[a1b2...] battery: 62% (charging: false)
[a1b2...] notification: WhatsApp - Alice: "Hey, are you free?"
[a1b2...] telephony.ringing — Bob (+15550001234)

Filter to battery and calls only

kcd watch --events=battery.update,telephony.ringing

Raw NDJSON for scripting

kcd watch --json
{"type":"battery.update","timestamp":"2025-04-21T10:22:01Z","deviceId":"a1b2...","payload":{"charge":62,"charging":false}}
{"type":"notification","timestamp":"2025-04-21T10:22:45Z","deviceId":"a1b2...","payload":{"appName":"WhatsApp","title":"Alice","text":"Hey, are you free?","id":"notif-abc"}}

Waybar phone battery integration

kcd watch --json | jq -r '
  select(.type=="battery.update") |
  "\(.payload.charge)%" + (if .payload.charging then " " else "" end)
'

Desktop notification on incoming call

kcd watch --json --events=telephony.ringing | \
  jq -r '.payload | "Call from \(.contactName // .phoneNumber)"' | \
  xargs -I{} notify-send "📞 Incoming Call" "{}"

Auto-mute calls when screen is locked

kcd watch --json --events=telephony.ringing | while read -r ev; do
    id=$(echo "$ev" | jq -r '.deviceId')
    if loginctl show-session self -p LockedHint | grep -q yes; then
        kcd call mute "$id"
    fi
done

Tips

Get the first connected device ID

kcd devices --json | jq -r '[.[] | select(.Connected)] | .[0].ID'

Send a file from a Nautilus script

Install packaging/nautilus-kcd.py for right-click → "Send to phone" in GNOME Files.

Debug connection issues

kcd daemon --log-level=debug 2>&1 | grep -E "discovery|transport|pair"

Broadcast is off by default (only active during kcd pair listen mode). The daemon sits at 0.0% idle CPU — no config change needed.