You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Canonical operational reference — daemon flags, deckctl commands, environment variables, authentication, diagnostic workflows, and project status. This is the single source of truth; other documentation links here rather than duplicating command tables.
Disable the focus watcher; serve only the default deck.
--allow-while-locked
off
Keep presses / key / type / trackpad / raise working while the desktop session is locked (issue #160). Default: a locked session refuses those surfaces — the client shows the lock takeover, shell:+ friends get a screen_locked error, and the refusal is recorded as a lock_dropped outcome.
--client-dist PATH
none
Serve a built client at /.
--password-file PATH
$XDG_CONFIG_HOME/deckd/password
Shared password file. Generated on first start if absent.
--no-auth
off
Disable password auth entirely.
--scroll-momentum-friction F
0.90
Momentum decay per 60 Hz frame.
--scroll-momentum-cutoff C
20
Min absolute velocity before momentum stops.
--log-format {text,json}
text
json emits structured JSON-per-record.
--log-file PATH
stderr only
Append structured logs to file (also writes stderr).
Restarts the daemon in-process when any daemon/**/*.py file changes. Deck YAML hot-reload is built into the daemon itself; this supervisor is only needed when editing Python.
Daemon address. Set to a LAN IP or Tailscale name for remote daemons.
--port PORT
8765
Daemon port.
--password PASSWORD
$DECKD_PASSWORD
Shared password (required when auth is on; not needed with --no-auth).
Commands
Command
Auth?
Description
deckctl status
no
Hit /health — sessions, app, bind surface, pairing URL.
deckctl diag
no
Hit /diag — full diagnostic snapshot (focus, input, decks, sessions, MPRIS).
deckctl metrics
no
Hit /metrics — Prometheus text-format counters.
deckctl decks
no
Hit /decks — loaded deck enumeration with safe widget summaries.
deckctl reload
yes
Hit /reload — reload all decks on disk and push to clients.
deckctl deck <id>
yes
Hit /deck/<id> — force-switch all clients to the named deck.
Environment variables
Daemon
Variable
Used by
Purpose
TERMINAL
actions.py
Override auto-detected terminal emulator.
DECKD_PASSWORD
(user supply)
Typical name for passing the password to deckctl. Not read by the daemon itself.
VLC_HTTP_PASSWORD
(user supply)
Named in deck password_ref for VLC HTTP auth.
XDG_CONFIG_HOME
auth.py
Base for the default password-file path ($XDG_CONFIG_HOME/deckd/password).
XDG_CURRENT_DESKTOP
platform.py, diagnostics.py
Desktop-env identification for focus-backend selection and /health.
XDG_SESSION_TYPE
platform.py
Wayland vs X11 detection.
SSL_CERT_FILE / NIX_SSL_CERT_FILE
mpris_art.py
CA bundle for iTunes art HTTPS lookups on NixOS.
Client
Variable
Purpose
DECKD_PASSWORD
Passed by vite.config.ts proxy to the daemon. Also consumed by local scripts.
VITE_BASE_PATH
Base URL when deploying to a subdirectory (e.g. GitHub Pages).
DECKD_UPSTREAM
Daemon origin the Vite dev-server proxy forwards /ws and /health to (default http://127.0.0.1:8765).
Development ports
Read by the Justfile, and written per-worktree into a gitignored ./.env by just worktree-adopt (see ONBOARDING.md). An explicit variable overrides the .env. The primary checkout also gets a .env if its defaults are already taken — e.g. by an installed deckd service.
Variable
Default
Purpose
DECKD_PORT
8765
Daemon port for every dev-* / run-* recipe, deckctl in status / diag / decks / metrics, and the pair just kill frees.
VITE_PORT
5173
Vite dev-server port. When overridden, --strictPort is dropped so Vite can fall through.
DECKD_E2E_PORT
8975
Playwright fixture daemon, so two worktrees can run just test-all at once.
DECKD_SMOKE_PORT
18765
In-process just smoke server, so two worktrees can smoke-test at once.
Authentication
The daemon uses a single shared password for all clients:
File:~/.config/deckd/password (mode 0640). First start generates a random 32-char password and logs it once at WARN.
WebSocket: Client sends {"type": "hello", "password": "..."} in the first frame. Invalid/auth-missing frames get {"type": "error", "reason": "unauthorized"} followed by a close (code 4401).
HTTP: Control endpoints (/reload, /deck/<id>, PUT /decks/<id>, POST /decks, /mpris/<row>/command) require X-Deckd-Password header.
Bypass:--no-auth disables all checks. /health, /diag, /metrics, GET /decks, and the art proxies are always open (read-only, no secret leak).
Rotation: Edit the password file and restart the daemon.
All are read-only and unauthenticated unless noted.
Endpoint
Description
GET /health
Host identity (hostname, os, desktop), sessions, current app, bind surface (bind, addresses, url). Used by deckctl status and the client Settings panel.
GET /diag
Full snapshot: focus watcher status (focus.healthy = context switching is working right now; focus.query_error = current poll failure), input sink, deck store, sessions, tasks, MPRIS state (if active). Machine-readable for AI-assisted debugging.
Enumeration of every loaded deck with safe widget summaries (id, kind, label, grid, has_action; no raw shell/dbus/url/text command bodies).
GET /actions/recent?limit=N
Bounded ring buffer of recent action attempts (id, outcome, timestamp; no command text). Default 64.
GET /mpris/players
Redacted MPRIS player snapshot.
GET /mpris/events/recent?limit=N
Bounded ring buffer of MPRIS subsystem events. Default 64.
POST /reload
Auth. Reload all decks on disk, push to clients.
POST /deck/<id>
Auth. Force-switch all clients to the named deck (runtime override, not sticky; singular /deck to distinguish from the write API below).
PUT /decks/<id>
Auth. Idempotent full-snapshot save of an existing deck (issue #84). URL <id> must equal match[0]; 409 on a match[0] change (rename). 400 sanitized structured Pydantic errors (loc/msg/type only); 404 unknown id. 200 echoes the canonical re-read ({ok, deck}); atomic temp-write + os.replace, natural watchfiles reload.
POST /decks
Auth. Create-on-first-save (issue #99). Body = a full deck snapshot; id/filename derived from slugified match[0]; 409 if the id (or slugified filename) already exists; 400 on validation failure / empty match; 200 echoes the canonical re-read.
POST /mpris/<row>/command
Auth. Dispatch a play-pause/next/previous command to the named MPRIS row.
GET /media/<widget_id>/art
Proxy VLC album art.
GET /mpris/<row_id>/art
Proxy MPRIS album art.
Diagnostic workflow
# Is the daemon running and which app has focus?
deckctl status
# What does the daemon believe about its own state?
deckctl diag
# Any action failures? MPRIS issues?
curl -s localhost:8765/metrics | grep -E 'deckd_(actions_total|mpris_)'
# What decks are currently loaded?
deckctl decks
# What was the last thing pressed?
curl -s localhost:8765/actions/recent | jq
# Find the stale process and restart cleanly
just kill
just run-daemon
Just recipes
Primary development operations. Run just (no args) to list all available recipes.
Recipe
What it does
just setup
Install dependencies (uv + npm). Auto-picks setup-linux or setup-macos.
just run-daemon
Start the daemon serving a built client.
just run-daemon-lan
Start the daemon bound to 0.0.0.0 (LAN-visible).
just dev
Dev stack: daemon (LAN, restart-on-edit) + Vite client (Tailscale HTTPS).
just dev-lan
Dev stack: daemon + Vite client on plain HTTP LAN.
just kill
Kill whatever is on :8765 and :5173.
just build-client
Build the client to client/dist/.
just test
Run the Python test suite (pytest).
just test-client
Run the client tests (Vitest unit + Playwright e2e).
just smoke
End-to-end smoke test (boots daemon in-process, fires every action primitive).
just status
Run deckctl status.
just diag
Run deckctl diag.
just decks
Run deckctl decks.
just metrics
Run deckctl metrics.
just watch-focus
Print active-app changes in real time.
just install-focus-extension
Install the GNOME Shell focus extension.
just install-focus-kwin
Install the KDE Plasma KWin focus script.
just worktree-adopt
Make the current git worktree checkout dev-ready: ports, .envrc, shared gitignored files, just setup. Idempotent.
just worktree-doctor
Diagnose a worktree that is not working; every failure prints its fix.
just worktree-list
All worktrees with their assigned ports and readiness.
just worktree-create BRANCH
git worktree add ../deckd-BRANCH on a new branch, then adopt it.
For the full wire shape, the authoritative source is daemon/deckd/protocol.py — its Pydantic models define every field, type, and constraint. The TypeScript mirror is generated by scripts/codegen_protocol_ts.py into client/src/protocol.generated.ts (drift guard: tests/test_protocol_ts_drift.py + just check-protocol, #76); client/src/protocol.ts re-exports it and adds the schema-layer types (Widget, Icon) imported by the rest of the client. The daemon never interprets action bodies.