功能: 添加 tmux cc 对话监控面板与实时消息流脚本
CI / lint (push) Successful in 7s

This commit is contained in:
2026-04-19 23:12:21 +08:00
parent d9f1990884
commit 974ffe1d11
4 changed files with 247 additions and 21 deletions
+213
View File
@@ -0,0 +1,213 @@
#!/bin/bash
# tmux-cc-monitor.sh — 实时监控 tmux pane 中 cc (Claude Code) 的消息流
#
# 用法:
# tmux-cc-monitor.sh watch %1 [%2 ...] # 监控指定 pane
# tmux-cc-monitor.sh watch-all # 监控当前 session 所有 pane
# tmux-cc-monitor.sh help
#
# 工作原理:
# 1. 每 200ms 轮询 tmux capture-pane 获取 pane 内容
# 2. 用内容 hash 跟踪已显示行,仅显示新增内容
# 3. 根据 ❯ 标记区分用户输入和模型输出
# 4. 彩色流式显示到当前终端
set -euo pipefail
POLL_INTERVAL=0.2
# Colors
C_INPUT='\033[1;32m'
C_OUTPUT='\033[0;36m'
C_SYSTEM='\033[0;33m'
C_RESET='\033[0m'
ts() { date +%H:%M:%S; }
strip_ansi() {
sed 's/\x1b\[[0-9;]*[a-zA-Z]//g; s/\x1b\[[0-9;]*m//g; s/\r$//g'
}
is_blank() {
local s="$1"
[[ -z "${s//[[:space:]]/}" ]]
}
# ─── 噪声过滤 ───
is_noise() {
local line="$1"
local trimmed
trimmed=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
[[ -z "$trimmed" ]] && return 0
# 状态栏
[[ "$trimmed" =~ ^[─═]+$ ]] && return 0
[[ "$trimmed" =~ 版本.*模型 ]] && return 0
[[ "$trimmed" =~ bypass ]] && return 0
[[ "$trimmed" =~ ^─+.*──[[:space:]]*$ ]] && return 0
# cc 加载动画 (含省略号 … 的行)
[[ "$trimmed" == *…* ]] && return 0
# cc 命令状态
[[ "$trimmed" =~ Running\ [0-9]+\ bash\ command ]] && return 0
[[ "$trimmed" =~ Ran\ [0-9]+\ bash\ command ]] && return 0
return 1
}
# ─── 行级去重 ───
# 用 (行内容hash) 跟踪已显示的行,避免重复输出
# 维护一个固定大小的环状缓冲区
SEEN_CAPACITY=500
declare -A SEEN_LINES=()
SEEN_ORDER=()
SEEN_POS=0
# 记录一行已显示,返回 true 如果是新行
mark_seen() {
local line="$1"
local hash
hash=$(echo "$line" | md5sum | cut -d' ' -f1)
if [[ -n "${SEEN_LINES[$hash]+x}" ]]; then
return 1 # 已存在
fi
SEEN_LINES["$hash"]=1
SEEN_ORDER+=("$hash")
# 裁剪缓冲区
if [[ ${#SEEN_ORDER[@]} -gt $SEEN_CAPACITY ]]; then
local old_hash="${SEEN_ORDER[0]}"
SEEN_ORDER=("${SEEN_ORDER[@]:1}")
unset 'SEEN_LINES[$old_hash]'
fi
return 0
}
# ─── 单 Pane 监控 ───
watch_pane() {
local pane_id="$1"
local state="idle"
local last_input=""
# 初始化: 将当前所有行标记为已读
local init_content
init_content=$(tmux capture-pane -t "$pane_id" -p 2>/dev/null || echo "")
while IFS= read -r line; do
is_blank "$line" || mark_seen "$line" || true
done <<< "$(echo "$init_content" | strip_ansi)"
echo -e "${C_SYSTEM}[$(ts)][${pane_id}] 开始监控${C_RESET}"
local prev_content="$init_content"
while true; do
local content
content=$(tmux capture-pane -t "$pane_id" -p 2>/dev/null) || {
sleep 0.5
continue
}
# 快速检测: hash
local prev_hash curr_hash
prev_hash=$(echo "$prev_content" | md5sum)
curr_hash=$(echo "$content" | md5sum)
[[ "$curr_hash" == "$prev_hash" ]] && { sleep "$POLL_INTERVAL"; continue; }
# 清洗内容
local clean_curr
clean_curr=$(echo "$content" | strip_ansi)
# ── 找出所有新行(基于行级去重)──
while IFS= read -r line; do
is_blank "$line" && continue
is_noise "$line" && continue
# 检查是否是新行
if ! mark_seen "$line"; then
continue
fi
# ── 分类 ──
if echo "$line" | grep -q ''; then
local input_text
input_text=$(echo "$line" | sed 's/.*//' | tr -d '\302\240' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')
# 去重: 用 trim 后的文本比较,避免前导空格导致的重复
if [[ -n "$input_text" && "$input_text" != "$last_input" ]]; then
echo -e "${C_INPUT}[$(ts)][${pane_id}][输入]${C_RESET} ${input_text}"
last_input="$input_text"
state="output"
elif [[ -z "$input_text" ]]; then
state="idle"
last_input=""
fi
else
if [[ "$state" == "output" ]]; then
echo -e "${C_OUTPUT}[$(ts)][${pane_id}][输出]${C_RESET} ${line}"
fi
fi
done <<< "$clean_curr"
prev_content="$content"
sleep "$POLL_INTERVAL"
done
}
# ─── Main ───
cmd="${1:-help}"
shift || true
case "$cmd" in
watch)
[[ $# -eq 0 ]] && { echo "用法: tmux-cc-monitor.sh watch <pane_id> [pane_id ...]"; exit 1; }
pids=()
for pane_id in "$@"; do
(
# 每个 pane 有独立的去重缓冲区
SEEN_LINES=()
SEEN_ORDER=()
watch_pane "$pane_id"
) &
pids+=($!)
done
cleanup() {
for pid in "${pids[@]}"; do
kill "$pid" 2>/dev/null || true
done
}
trap cleanup INT TERM
echo -e "${C_SYSTEM}[$(ts)] 监控已启动 (PIDs: ${pids[*]})Ctrl+C 停止${C_RESET}"
wait
;;
watch-all)
panes=$(tmux list-panes -F '#{pane_id}' 2>/dev/null || true)
[[ -z "$panes" ]] && { echo "错误: 未找到 tmux panes"; exit 1; }
exec "$0" watch $panes
;;
help|--help|-h)
echo "用法: tmux-cc-monitor.sh <command>"
echo ""
echo "命令:"
echo " watch <pane...> 监控指定 pane 的 cc 消息流"
echo " watch-all 监控当前 session 所有 pane"
echo ""
echo "输出:"
echo -e " ${C_INPUT}[时间][pane][输入]${C_RESET} 用户提示词"
echo -e " ${C_OUTPUT}[时间][pane][输出]${C_RESET} 模型响应(流式)"
;;
*)
echo "未知命令: $cmd" >&2
exit 1
;;
esac