任意のコマンドの状況をHerdrのagents欄に表示するためのコマンドラッパー
ターミナル・マルチプレクサとしてHerdrを使っている。複数のCoding Agentを並行で走らせていても、agents欄を見れば「誰がworkingで、誰がidleで、誰がblockedか」が一覧できる。これがとても快適。
快適になってくると、欲が出てくる。エージェント以外の動き、たとえば「デプロイ進行中」みたいな状態も、同じように把握したくなってきた。デプロイってけっこう時間がかかるので、走らせている間は別の作業をしていて、完了したことに気づかない…ということがよくある。完了したら知りたいですよね。
Herdrに最初から生えていた口
調べてみたら、Herdrにそのものずばりの口があった。
herdr pane report-agent <pane_id> --source ID --agent LABEL --state idle|working|blocked|unknown [--custom-status TEXT] [--seq N]
任意のpaneについて「このpaneでは LABEL というエージェントが、いまこういう状態です」と外部から報告できるAPIだ。--agent のラベルは自由な文字列なので、ここに「jbt deploy」と書けば、デプロイ作業がagents欄に並ぶことになる。
ちなみにこれは裏口ではない。Claude Codeの公式Herdr連携も、フックから同じ系統の公開API (pane.report_agent_session) を叩いて状態を報告している。つまり外部プロセスが状態を報告するための正面玄関で、うちのデプロイもそこから堂々と入場すればいい。「デプロイを任されたエージェント」と広義に捉えれば、意味論的にもまあ通るでしょう。
つくったもの: hw
というわけで、コマンドラッパー hw (Herdr Watch) をつくった。使い方はこう。
hw <ラベル> -- <コマンド...>
$ hw "jbt deploy" -- kamal deploy
$ hw "CI #123" -- gh run watch 123
第1引数がagents欄に表示される名前になって、-- より後ろが実際に実行されるコマンド。実行中はagents欄に「working / 実行中」と出て、成功すれば「done / ✅ (経過時間)」、失敗すれば「blocked / ❌ exit=N」に変わる。完了時にはトースト通知と音も出る。
CIのような手元で走らないタスクも、gh run watch みたいな「見張り役コマンド」を包めば同じ枠に乗るのがポイント。
スクリプト全文
bash + jq で70行ほど。~/.dotfiles/bin/hw に置いてある。
#!/bin/bash
# hw (Herdr Watch) — 長時間コマンドをHerdrのagent listに「擬似エージェント」として映すwrapper
#
# Usage: hw <ラベル> -- <コマンド...>
# 例: hw "jbt deploy" -- kamal deploy
# hw "CI #123" -- gh run watch 123
#
# 実行中は state=working、成功で done (✅ + 経過時間)、失敗で blocked (❌ + exit code)。
# 完了/失敗時は herdr notification でトーストも出す (config.tomlのui.toast有効時)。
set -u
usage() {
echo "usage: hw <label> -- <command...>" >&2
exit 2
}
[ $# -ge 3 ] || usage
label="$1"
shift
[ "$1" = "--" ] || usage
shift
[ $# -ge 1 ] || usage
pane_id=$(herdr pane current 2>/dev/null | jq -r '.result.pane.pane_id // empty')
if [ -z "$pane_id" ]; then
echo "hw: Herdrのpaneを特定できへんかったので、そのまま実行します" >&2
exec "$@"
fi
source_id="hwatch-$$"
seq=0
report() { # report <state> <custom-status>
seq=$((seq + 1))
herdr pane report-agent "$pane_id" \
--source "$source_id" --agent "$label" \
--state "$1" --custom-status "$2" --seq "$seq" >/dev/null 2>&1
}
elapsed() {
local s=$(($(date +%s) - start))
if [ "$s" -ge 60 ]; then
echo "$((s / 60))m$((s % 60))s"
else
echo "${s}s"
fi
}
on_interrupt() {
report blocked "⚠ 中断 ($(elapsed))"
exit 130
}
trap on_interrupt INT TERM
start=$(date +%s)
report working "実行中"
"$@"
status=$?
trap - INT TERM
if [ "$status" -eq 0 ]; then
report idle "✅ $(elapsed)"
herdr notification show "$label 完了" --body "$(elapsed) で完了" --sound done >/dev/null 2>&1
else
report blocked "❌ exit=$status $(elapsed)"
herdr notification show "$label 失敗" --body "exit=$status ($(elapsed))" --sound request >/dev/null 2>&1
fi
exit "$status"
トーストはHerdrのデフォルト設定では無効なので、~/.config/herdr/config.toml に設定を足す必要がある。
[ui.toast]
delivery = "herdr"
使ってみて副次的に気づいたこと
さっそく使ってみて、想定していなかったうれしさがあった。ラベルが「コマンドへのコメント」として機能する。
デプロイコマンドって、複数のプロジェクトで npm run deploy みたいに共通だったりする。素で実行しているとシェル履歴上はどれも同じ文字列で、履歴検索から「あのプロジェクトのデプロイ」を引き当てられない。それが hw "○○ deploy" -- npm run deploy と打つ習慣になると、プロジェクト名で履歴検索してそのまま実行できる。コマンドに意図の注釈が付いて、それが検索キーになる。
もうひとつ、herdr wait agent-status <pane_id> --status done というコマンドを使うと「デプロイの完了を待ってから次をやる」というのもできる。エージェントに「隣のpaneのデプロイが終わったら確認して」と頼む、みたいな連携もできるっぽい。
おまけ
これやりたい
https://jnbk.app/topics/任意のコマンドの状況をHerdrのagents欄に表示するためのコマンドラッパー.md
をお手元のエージェントに投げつけてもらえたら導入できると思います。
