Files
team/scripts/tmux-cc-monitor.sh
arno 974ffe1d11
All checks were successful
CI / lint (push) Successful in 7s
功能: 添加 tmux cc 对话监控面板与实时消息流脚本
2026-04-19 23:12:21 +08:00

214 lines
6.0 KiB
Bash
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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