gwm-cli 1.6.1

git worktree manager — TUI + CLI, native libgit2, per-repo bootstrap
Documentation
---
title: Daemon consumers
description: First consumers of the gwm daemon — a compact statusline for shell prompts, the raw JSON-RPC one-liner, and an editor recipe (Zed / VS Code).
navigation:
  title: Daemon consumers
---

# Daemon consumers

[`gwm daemon`](/cli/reference#gwm-daemon-socket-path-poll-ms-ms-issue-38) is a long-running JSON-RPC 2.0 server over a unix domain socket (a named pipe on Windows, #439): editors, statusbars, and tooling connect once and call `list` / `doctor` / `path`, or `subscribe` for pushed `worktrees.changed` notifications — instead of spawning `gwm` per query. This page covers the **consumer** side: the bundled `gwm statusline`, the raw protocol one-liner, and an editor recipe.

> The daemon is **one per repo** — it answers for the repository it was launched in. Start it from inside the worktree set you want to watch (`cd` into any worktree of the repo, then `gwm daemon`). The default socket path is shared, so to run daemons for **several repos at once** give each a distinct `--socket` (and point that repo's `gwm statusline --socket` at the same path) — otherwise the second `gwm daemon` is refused by the live-socket guard.

## Start the daemon

```bash
# Binds $XDG_RUNTIME_DIR/gwm.sock (→ $TMPDIR → /tmp on macOS, where
# XDG_RUNTIME_DIR is unset). Override with --socket.
#
# Security note: when the chosen base dir is NOT owner-only (the shared
# /tmp last resort), the socket is isolated in a per-user owner-only
# sub-dir instead — $base/gwm-<uid>/gwm.sock — so another local user can't
# connect. The common $XDG_RUNTIME_DIR / per-user $TMPDIR paths (already
# private) are unchanged. The daemon prints its actual bound path to
# stderr ("gwm daemon listening on <socket>"), so a raw nc/socat consumer
# in that fallback case should read the path from there rather than
# hard-coding $base/gwm.sock.
gwm daemon

# A faster subscribe cadence (default 1000 ms):
gwm daemon --poll-ms 300

# Several repos at once: one socket each (the default path is shared, so a
# second daemon on it is refused). Point each consumer at the matching path.
gwm daemon  --socket "${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/gwm-api.sock"  # in repo A
gwm statusline --socket "${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/gwm-api.sock"
```

The daemon prints `gwm daemon listening on <socket>` to stderr **only once the socket is bound**, so a wrapper can treat that line as a readiness signal.

## Statusline — `gwm statusline`

The first bundled consumer. It connects to the daemon, asks for the worktree set, and renders a single compact line for a tmux / starship / zsh prompt:

```text
$ gwm statusline
feat/#309-daemon-consumer · 3 wt · * ↑1 · #309 · PR #310
```

The line is built **only** from the daemon's stable schema:

| Token            | Meaning                                              |
|:-----------------|:-----------------------------------------------------|
| `feat/#309-…`    | active worktree's branch (or its name when detached) |
| `3 wt`           | total worktree count                                 |
| `*`              | the active worktree has uncommitted changes          |
| `↑n` / `↓n`      | commits ahead of / behind the upstream               |
| `#309`           | linked issue number                                  |
| `PR #310`        | linked PR number                                     |
| `claude`         | an **active** AI-agent session in the active worktree (from the experimental `agents` field, #408). Idle sessions are not advertised here — the TUI overlay (`a`) has the full list. |

The **active** worktree is the one whose directory encloses the current working directory; outside any worktree the line collapses to just the count (`3 wt`).

> **No CI rollup.** The `CI passing 9/9`-style half of the original sketch is intentionally omitted: a CI rollup is not part of the daemon's stable schema, and fetching it would mean a `gh` call on every prompt redraw. It is left as a follow-up rather than smuggled into the wire protocol.

### Live updates — `--watch`

`--watch` subscribes to the daemon's `worktrees.changed` stream and reprints the line on every change, one line per update — ideal for a long-running command feeding a statusbar:

```bash
gwm statusline --watch
```

### Graceful degradation

When **no daemon is reachable**, `gwm statusline` prints an empty line and exits `0`, so a prompt substitution degrades to nothing instead of showing an error. That makes it safe to drop into a prompt unconditionally.

### Prompt recipes

zsh — right prompt (refreshed on each prompt; one socket round-trip to the running daemon):

```bash
# ~/.zshrc
function gwm_rprompt() { gwm statusline 2>/dev/null }
setopt PROMPT_SUBST
RPROMPT='$(gwm_rprompt)'
```

tmux — `status-right`, re-queried on the status interval (for push-driven updates instead of polling, run `gwm statusline --watch` into a pane and read its tail):

```bash
# ~/.tmux.conf
set -g status-interval 5
set -ag status-right ' #(cd #{q:pane_current_path} && gwm statusline)'
```

starship — custom command:

```toml
# ~/.config/starship.toml
[custom.gwm]
command = "gwm statusline"
when = true
shell = ["bash", "--noprofile", "--norc"]
format = "[$output]($style) "
```

## Raw protocol — the thin proof

No `gwm` binary required on the consumer side: the wire format is newline-delimited JSON, one request per line, one response per line. Anything that can write a line to a unix socket is a client.

```bash
# socat
printf '{"jsonrpc":"2.0","method":"list","id":1}\n' \
  | socat - UNIX-CONNECT:"${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/gwm.sock"

# nc / netcat
printf '{"jsonrpc":"2.0","method":"list","id":1}\n' \
  | nc -U "${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/gwm.sock"

# subscribe — holds the connection open and streams a `worktrees.changed`
# notification on every change (the first line is the current snapshot).
# `cat` keeps the write side open so the daemon keeps streaming; without it
# `printf` exits, the socket half-closes, and only the first snapshot
# arrives. Ctrl-C to stop.
{ printf '{"jsonrpc":"2.0","method":"subscribe","id":1}\n'; cat; } \
  | socat - UNIX-CONNECT:"${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/gwm.sock"
```

Methods: `list` (worktree array), `doctor` (health report with `severity` + `exit_code`), `path` (`{"params":{"pattern":"<fuzzy>"}}` → `{ name, path, branch }`), and `subscribe` (upgrades the connection to a one-way notification stream). Agent-session changes push too: a session appearing, vanishing, flipping `active` ↔ `idle`, or writing fresh activity (`last_activity`) triggers a `worktrees.changed` — unlike `age_seconds`, `last_activity` only moves on real agent writes, and the 30 s detection cache bounds the push frequency. The response schemas are the same stable DTOs the `--format=json` CLI flags emit — see [`docs/schema/`](https://github.com/kbrdn1/gwm-cli/tree/main/docs/schema) and the [`gwm daemon` reference](/cli/reference#gwm-daemon-socket-path-poll-ms-ms-issue-38).

## Editor recipe

An editor doesn't need a plugin — a task that shells out to the daemon (or to `gwm`'s `--format=json` surface) is enough to list worktrees and jump between them.

### Zed

`.zed/tasks.json` — a task that prints the live statusline and one that lists the worktrees through the socket:

```json
[
  {
    "label": "gwm: statusline",
    "command": "gwm statusline",
    "use_new_terminal": false,
    "reveal": "always"
  },
  {
    "label": "gwm: worktrees (via daemon)",
    "command": "printf '{\"jsonrpc\":\"2.0\",\"method\":\"list\",\"id\":1}\\n' | socat - UNIX-CONNECT:\"${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/gwm.sock\" | jq -r '.result[] | \"\\(.name)\\t\\(.path)\"'",
    "reveal": "always"
  }
]
```

### VS Code

`.vscode/tasks.json` — same idea, using the daemon socket to list worktrees:

```json
{
  "version": "2.0.0",
  "tasks": [
    {
      "label": "gwm: worktrees (via daemon)",
      "type": "shell",
      "command": "printf '{\"jsonrpc\":\"2.0\",\"method\":\"list\",\"id\":1}\\n' | socat - UNIX-CONNECT:\"${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/gwm.sock\" | jq -r '.result[] | \"\\(.name)\\t\\(.path)\"'",
      "problemMatcher": []
    }
  ]
}
```

To **jump** to a worktree by fuzzy pattern, call `path` instead of `list`:

```bash
printf '{"jsonrpc":"2.0","method":"path","params":{"pattern":"309"},"id":1}\n' \
  | socat - UNIX-CONNECT:"${XDG_RUNTIME_DIR:-${TMPDIR:-/tmp}}/gwm.sock" | jq -r '.result.path'
```

Without a running daemon, the same data is one `gwm` invocation away — `gwm list --format=json` and `gwm path <pattern> --format=json` emit the identical schemas — but the daemon avoids a process spawn per query.