From adfc04c0f9c159bbabb840394101eef074c3f822 Mon Sep 17 00:00:00 2001 From: Yax Date: Tue, 21 Jul 2026 16:49:56 +0200 Subject: [PATCH] Add plugin tmux-agents-mom to monitor AI agents and create powerkit plugins to update status bar --- dot_tmux.conf.tmpl | 7 +++- .../src/plugins/agents_blocked.sh | 41 +++++++++++++++++++ .../tmux-powerkit/src/plugins/agents_idle.sh | 41 +++++++++++++++++++ .../src/plugins/agents_working.sh | 41 +++++++++++++++++++ .../bin/executable_agents-mon-count | 34 +++++++++++++++ 5 files changed, 163 insertions(+), 1 deletion(-) create mode 100644 dot_tmux/plugins/tmux-powerkit/src/plugins/agents_blocked.sh create mode 100644 dot_tmux/plugins/tmux-powerkit/src/plugins/agents_idle.sh create mode 100644 dot_tmux/plugins/tmux-powerkit/src/plugins/agents_working.sh create mode 100644 private_dot_local/bin/executable_agents-mon-count diff --git a/dot_tmux.conf.tmpl b/dot_tmux.conf.tmpl index 1cc6de7..2231e67 100644 --- a/dot_tmux.conf.tmpl +++ b/dot_tmux.conf.tmpl @@ -124,7 +124,7 @@ bind r source-file ~/.tmux.conf \; display-message "tmux config reloaded!" # plugin powerkit set -g @powerkit_theme 'nord' set -g @powerkit_theme_variant 'dark' -set -g @powerkit_plugins 'ssh,memory,datetime' +set -g @powerkit_plugins 'ssh,memory,group(agents_blocked,agents_working,agents_idle),datetime' set -g @powerkit_session_icon 'auto' set -g @powerkit_status_position 'bottom' set -g @powerkit_keybinding_conflict_action 'skip' @@ -139,10 +139,15 @@ set -g @tpad-scratch-dir "#{pane_current_path}" set -g @tpad-scratch-width "70%" set -g @tpad-scratch-height "70%" +# plugin agents-mon +set -g @agents-mon-key 'e' +set -g @agents-mon-display 'popup' + # Plugins set -g @plugin 'tmux-plugins/tpm' set -g @plugin 'fabioluciano/tmux-powerkit' set -g @plugin 'azorng/tmux-smooth-scroll' +set -g @plugin 'snirt/tmux-agents-mon' # tmux-tpad set -g @plugin 'Subbeh/tmux-tpad' diff --git a/dot_tmux/plugins/tmux-powerkit/src/plugins/agents_blocked.sh b/dot_tmux/plugins/tmux-powerkit/src/plugins/agents_blocked.sh new file mode 100644 index 0000000..2949089 --- /dev/null +++ b/dot_tmux/plugins/tmux-powerkit/src/plugins/agents_blocked.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# ============================================================================= +# Plugin: agents_blocked (managed by chezmoi) +# Description: Number of tmux-agents-mon agents in the "blocked" state. +# Type: conditional (hidden when the count is 0) +# Part of the group(agents_blocked,agents_working,agents_idle) segment. +# ============================================================================= + +POWERKIT_ROOT="${POWERKIT_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}" +. "${POWERKIT_ROOT}/src/contract/plugin_contract.sh" + +plugin_get_metadata() { + metadata_set "id" "agents_blocked" + metadata_set "name" "Agents Blocked" + metadata_set "description" "Count of blocked Claude agents" +} + +plugin_declare_options() { + declare_option "icon" "icon" "⣿" "Glyph shown for blocked agents" + declare_option "cache_ttl" "number" "2" "Cache duration in seconds" +} + +plugin_get_content_type() { printf 'dynamic'; } +plugin_get_presence() { printf 'conditional'; } + +plugin_collect() { + plugin_data_set "count" "$("$HOME/.local/bin/agents-mon-count" blocked)" +} + +plugin_get_state() { + local n + n=$(plugin_data_get "count") + [[ "${n:-0}" -gt 0 ]] && printf 'active' || printf 'inactive' +} + +# error -> nord error-base (#bf616a, red) +plugin_get_health() { printf 'error'; } + +plugin_render() { printf '%s' "$(plugin_data_get "count")"; } + +plugin_get_icon() { printf '%s' "$(get_option 'icon')"; } diff --git a/dot_tmux/plugins/tmux-powerkit/src/plugins/agents_idle.sh b/dot_tmux/plugins/tmux-powerkit/src/plugins/agents_idle.sh new file mode 100644 index 0000000..7beeda4 --- /dev/null +++ b/dot_tmux/plugins/tmux-powerkit/src/plugins/agents_idle.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# ============================================================================= +# Plugin: agents_idle (managed by chezmoi) +# Description: Number of tmux-agents-mon agents in the "idle" state. +# Type: conditional (hidden when the count is 0) +# Part of the group(agents_blocked,agents_working,agents_idle) segment. +# ============================================================================= + +POWERKIT_ROOT="${POWERKIT_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}" +. "${POWERKIT_ROOT}/src/contract/plugin_contract.sh" + +plugin_get_metadata() { + metadata_set "id" "agents_idle" + metadata_set "name" "Agents Idle" + metadata_set "description" "Count of idle Claude agents" +} + +plugin_declare_options() { + declare_option "icon" "icon" "⣿" "Glyph shown for idle agents" + declare_option "cache_ttl" "number" "2" "Cache duration in seconds" +} + +plugin_get_content_type() { printf 'dynamic'; } +plugin_get_presence() { printf 'conditional'; } + +plugin_collect() { + plugin_data_set "count" "$("$HOME/.local/bin/agents-mon-count" idle)" +} + +plugin_get_state() { + local n + n=$(plugin_data_get "count") + [[ "${n:-0}" -gt 0 ]] && printf 'active' || printf 'inactive' +} + +# good -> nord good-base (#a3be8c, green). NB: 'ok' would be dark grey, not green. +plugin_get_health() { printf 'good'; } + +plugin_render() { printf '%s' "$(plugin_data_get "count")"; } + +plugin_get_icon() { printf '%s' "$(get_option 'icon')"; } diff --git a/dot_tmux/plugins/tmux-powerkit/src/plugins/agents_working.sh b/dot_tmux/plugins/tmux-powerkit/src/plugins/agents_working.sh new file mode 100644 index 0000000..b1554ae --- /dev/null +++ b/dot_tmux/plugins/tmux-powerkit/src/plugins/agents_working.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +# ============================================================================= +# Plugin: agents_working (managed by chezmoi) +# Description: Number of tmux-agents-mon agents in the "working" state. +# Type: conditional (hidden when the count is 0) +# Part of the group(agents_blocked,agents_working,agents_idle) segment. +# ============================================================================= + +POWERKIT_ROOT="${POWERKIT_ROOT:-$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)}" +. "${POWERKIT_ROOT}/src/contract/plugin_contract.sh" + +plugin_get_metadata() { + metadata_set "id" "agents_working" + metadata_set "name" "Agents Working" + metadata_set "description" "Count of working Claude agents" +} + +plugin_declare_options() { + declare_option "icon" "icon" "⣾" "Glyph shown for working agents" + declare_option "cache_ttl" "number" "2" "Cache duration in seconds" +} + +plugin_get_content_type() { printf 'dynamic'; } +plugin_get_presence() { printf 'conditional'; } + +plugin_collect() { + plugin_data_set "count" "$("$HOME/.local/bin/agents-mon-count" working)" +} + +plugin_get_state() { + local n + n=$(plugin_data_get "count") + [[ "${n:-0}" -gt 0 ]] && printf 'active' || printf 'inactive' +} + +# warning -> nord warning-base (#ebcb8b, yellow) +plugin_get_health() { printf 'warning'; } + +plugin_render() { printf '%s' "$(plugin_data_get "count")"; } + +plugin_get_icon() { printf '%s' "$(get_option 'icon')"; } diff --git a/private_dot_local/bin/executable_agents-mon-count b/private_dot_local/bin/executable_agents-mon-count new file mode 100644 index 0000000..ef45ffe --- /dev/null +++ b/private_dot_local/bin/executable_agents-mon-count @@ -0,0 +1,34 @@ +#!/usr/bin/env bash +# Count tmux-agents-mon agents in a given state, for the powerkit segments. +# +# Usage: agents-mon-count +# Prints the number of agents currently in that state (0 if none). +# +# Uses `agents-mon scan` (always live TSV, one line per agent, state in field 4) +# rather than `agents-mon status` — the latter serves a cache up to 6s stale. +# The three powerkit plugins (agents_blocked/working/idle) each call this on every +# status refresh, so we cache the parsed counts for ~2s in a shared tmp file to +# avoid running three full `tmux list-panes -a` + `capture-pane` scans per cycle. +set -u + +state="${1:-}" +case "$state" in + blocked | working | idle) ;; + *) printf '0'; exit 0 ;; +esac + +BIN="${AGENTS_MON_BIN:-$HOME/.tmux/plugins/tmux-agents-mon/target/release/agents-mon}" +[ -x "$BIN" ] && command -v tmux >/dev/null 2>&1 || { printf '0'; exit 0; } + +cache="${TMPDIR:-/tmp}/agents-mon-counts-$(id -u)" + +# Refresh the cache when it is missing or older than ~2 seconds. +if [ -z "$(find "$cache" -newermt '-2 seconds' 2>/dev/null)" ]; then + "$BIN" scan 2>/dev/null | awk -F'\t' ' + { c[$4]++ } + END { printf "blocked=%d\nworking=%d\nidle=%d\n", c["blocked"], c["working"], c["idle"] } + ' >"$cache.tmp.$$" 2>/dev/null && mv -f "$cache.tmp.$$" "$cache" 2>/dev/null +fi + +# Emit the requested count (0 if the cache is unreadable for any reason). +awk -F= -v s="$state" '$1==s { print $2; found=1 } END { if (!found) print 0 }' "$cache" 2>/dev/null || printf '0'