No description
  • Python 84.7%
  • QML 15.3%
Find a file
Lumorian 619e87b386 feat: jump to a session's window from card click and notification Open button
Add a shared window-focus core (window_focus.py) that maps a Claude Code
session pid to its terminal window by walking the /proc parent chain and
matching the nearest ancestor against niri's window list, then focusing it
via `niri msg action focus-window`. Degrades to a silent no-op off niri or
under tmux/SSH.

- popout card: TapHandler/HoverHandler -> execDetached(focus-session.py),
  then dismiss the popout (handlers keep the DankFlickable scrollable)
- notification: notify-send -A open=Open (non-blocking Popen + reader thread);
  --print-id still drives auto-close. on_open is injected for testability
- focus-session.py launcher installed alongside run-monitor.py
- tests: window_focus, focus-session, extended notifier/monitor/install
  (108 passing, 87% coverage); qmllint clean
- docs: READMEs + design spec note the niri requirement and tmux/SSH limit
2026-06-10 03:59:34 +08:00
docs/superpowers/specs feat: jump to a session's window from card click and notification Open button 2026-06-10 03:59:34 +08:00
plugin feat: jump to a session's window from card click and notification Open button 2026-06-10 03:59:34 +08:00
scripts feat: jump to a session's window from card click and notification Open button 2026-06-10 03:59:34 +08:00
tests feat: jump to a session's window from card click and notification Open button 2026-06-10 03:59:34 +08:00
.gitignore feat: Claude Code session monitor plugin for DankMaterialShell 2026-06-09 13:15:38 +08:00
pyproject.toml feat: Claude Code session monitor plugin for DankMaterialShell 2026-06-09 13:15:38 +08:00
README.md feat: jump to a session's window from card click and notification Open button 2026-06-10 03:59:34 +08:00
README_zh.md feat: jump to a session's window from card click and notification Open button 2026-06-10 03:59:34 +08:00

Claude Code Monitor — DankMaterialShell plugin

A DankMaterialShell (Quickshell/QML) bar plugin that monitors your running Claude Code sessions and notifies you when one needs your input.

English | 简体中文

Features

  • DankBar pill with the Claude Code logo + a live count (running/total, or N! when a session is waiting), color-coded by aggregate state.
  • Left-click popout listing one card per session. The popout height auto-adjusts to the number of sessions and scrolls once it would get too tall.
  • Each card shows, with the Claude Code logo on the left: folder name · full path · status · PID · context usage % · token usage.
  • Click a card to jump to its window — the terminal hosting that Claude Code session is focused (and its workspace activated) via the compositor.
  • Desktop notifications when a session enters waiting (needs input/selection), with per-session cooldown/dedupe, auto-close when the session resumes, a sound, and the CC icon. Each waiting notification carries an Open action button that focuses that session's window.

How it works

Claude Code ──hooks──▶  $XDG_RUNTIME_DIR/claude-code-monitor/session-<id>.json   (state, cwd, pid)
            ──statusLine▶ $XDG_RUNTIME_DIR/claude-code-monitor/context-<id>.json  (ctx %, tokens)
                                   │
                       run-monitor.py (daemon)  ── polls + prunes dead PIDs
                                   │             ── fires notifications (notifier)
                                   ▼
                       one summary JSON line per change on stdout
                                   │
        QML widget (Quickshell.Io.Process) ── parses ──▶ pills + session-card popout

The QML widget launches the daemon as a long-lived Process (the cavaVisualizer pattern), so the daemon's lifetime is tied to the plugin — no PID files, no orphans. The daemon owns data + notifications (pure tested Python); QML is a thin reactive view.

Waiting detection comes only from Claude Code's Notification hook (permission_prompt / idle_prompt / elicitation_dialog) — never from scanning /proc — so tool execution is never mistaken for "waiting". A /proc scan is a fallback used only when no hook files exist yet.

This package is self-contained and uses its own claude-code-monitor namespace, so it coexists with the upstream claude-monitor tray app if you also have it installed.

Jumping to a session's window

Both the card click and the notification Open button resolve a session to its window the same way: the claude process runs as a descendant of its terminal emulator (claude → shell → terminal), so the monitor walks the /proc parent chain up from the session PID and matches the nearest ancestor against the compositor's window list, then focuses it.

  • Compositor support: niri (niri msg --json windows + niri msg action focus-window). On other compositors this degrades to a silent no-op; the seam in window_focus.py makes adding another backend (e.g. Hyprland) straightforward.
  • Limitation: if the session runs under tmux / screen / SSH, claude is not a descendant of a local window, so no window is matched and the focus is a no-op. Everything else still works.

Requirements

  • DankMaterialShell (Quickshell)
  • niri — for click-to-focus / notification Open (other compositors: features no-op gracefully)
  • Python ≥ 3.10 (standard library only — no pip install needed)
  • notify-send (libnotify) for notifications; optional gdbus (auto-close) and canberra-gtk-play/paplay/pw-play (sound)

Install

# 1. Register hooks + statusLine wrapper, copy the daemon to ~/.local/share/claude-code-monitor/
python3 scripts/install.py

# 2. Make the plugin visible to DankMaterialShell
ln -sfn "$PWD/plugin" ~/.config/DankMaterialShell/plugins/claude-code-monitor

# 3. Enable it + add it to the bar
#    - plugin_settings.json:  "claude-code-monitor": { "enabled": true }
#    - settings.json *Widgets array: add { "id": "claude-code-monitor", "enabled": true }
#    (or use the DankMaterialShell settings GUI: Plugins → enable, then add the widget to the bar)

# 4. Reload
dms restart

Hooks load at Claude Code session start — restart any running Claude Code sessions after installing so they begin reporting. New sessions are picked up automatically (a SessionStart hook is registered).

install.py backs up ~/.claude/settings.json to settings.json.bak, writes it atomically, merges its entries non-destructively (it never removes other tools' hooks or your env/ statusLine), wraps any existing statusLine (saved verbatim and delegated to), and removes the legacy dankbar prototype hooks if present.

Configuration

~/.config/claude-code-monitor/config.json (the daemon reloads it each poll, so changes apply within ~1s; the plugin's Settings panel writes this file for you):

Key Default Description
scan_interval_sec 1 Poll interval (seconds)
notify_cooldown_sec 5 Min seconds between notifications per session
enable_notifications true Send desktop notifications
notify_on_error true Notify on session errors
notify_sound "message" freedesktop sound event, a file path, or "" to disable

Project layout

plugin/                         # the DMS plugin (symlinked into ~/.config/DankMaterialShell/plugins/)
  plugin.json
  ClaudeMonitorWidget.qml       # pills + launches the daemon Process + popout wiring
  ClaudeMonitorPopout.qml       # PopoutComponent: header + auto-height scrollable card list
  SessionCard.qml               # one session card (all fields + CC logo + state dot)
  ClaudeMonitorSettings.qml     # toggles -> config.json
  assets/claudecode-color.svg
scripts/
  claude_code_monitor/          # pure-stdlib package
    models.py state.py hook_watcher.py scanner.py notifier.py
    window_focus.py             # pid -> terminal window resolution + focus (niri)
    monitor.py                  # the daemon (poll loop + summary emitter)
    hook_script.py              # Claude Code hook (writes session-<id>.json)
    statusline_script.py        # statusLine wrapper (writes context-<id>.json)
    paths.py
  run-monitor.py                # daemon launcher (fixes sys.path)
  focus-session.py              # window-focus launcher (called by the popout card click)
  install.py                    # settings.json merge + runtime install + legacy cleanup
tests/                          # pytest suite (87% coverage)

Development

python3 -m pytest --cov=claude_code_monitor    # from the repo root (pythonpath=scripts)
# QML can be syntax-checked offline:
qmllint -I /usr/share/quickshell/dms plugin/*.qml

Uninstall

python3 scripts/install.py --uninstall          # removes our hooks, restores original statusLine
rm ~/.config/DankMaterialShell/plugins/claude-code-monitor
# then remove the widget from the bar in settings.json and disable it in plugin_settings.json

License

MIT