chore: import existing iTerm2 Claude panel code

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Andrey Epifancev 2026-10-18 19:12:16 +04:00
commit 0d2380d36d
8 changed files with 1852 additions and 0 deletions

1
.claude/code-paths Normal file
View File

@ -0,0 +1 @@
files/

54
README.md Normal file
View File

@ -0,0 +1,54 @@
# Claude — панель для iTerm2 Toolbelt
Сверху — дерево активных сессий Claude Code и их субагентов (модель, контекст, worktree),
снизу — лимиты подписки и баланс extra usage.
## Установка
```bash
tar xzf claude-panel.tar.gz
cd claude-panel
./install.sh
```
Затем в iTerm2: **View → Toolbelt → Show Toolbelt**, **Toolbelt → Claude**, и перезапустите
открытые сессии `claude`. Повторный запуск `install.sh` обновляет установленное.
Нужно: macOS, iTerm2, python3, jq (`brew install jq`, в macOS 15+ уже есть).
## Что ставится
| Что | Куда |
|---|---|
| скрипт панели | `~/Library/Application Support/iTerm2/Scripts/AutoLaunch/claude_agents.py` |
| хук Claude Code | `~/.claude/hooks/agents_panel_hook.sh` + записи в `~/.claude/settings.json` |
| пример настроек | `~/.config/claude-agents/config.example.json` |
Перед изменением `settings.json` и заменой файлов старые копии кладутся в
`~/.claude-panel-backup/<дата>/`. Ваши остальные хуки и настройки не трогаются.
## Работа с панелью
- клик по сессии или агенту — последние сообщения (копятся, пока окно открыто);
- ⌘-клик по сессии — перейти в её вкладку;
- клик по `⎇ папка` — открыть в ней `lf` в соседней панели;
- цвет точки: зелёный — работает, жёлтый — ждёт вас, серый — свободен;
- сессии внутри tmux (и `tmux -CC`) тоже видны: клик по `⎇` делит панель tmux, ⌘-клик переключает на неё;
- цвет токенов: жёлтый > 500k, красный > 700k.
## Настройки
Скопируйте `config.example.json` в `~/.config/claude-agents/config.json` и оставьте только
нужные ключи. Меняется на лету, кроме `port`.
## Если что-то не так
`http://127.0.0.1:8766/raw` — что панель видит: логи хука, причины скрытия сессий,
ошибки лимитов и баланса.
## Удаление
```bash
./uninstall.sh # настройки и логи остаются
./uninstall.sh --purge # удалить всё
```

61
files/agents_panel_hook.sh Executable file
View File

@ -0,0 +1,61 @@
#!/bin/sh
# Хук Claude Code для панели «Claude» в iTerm2.
# Пишет события сессий и субагентов в ~/.cache/claude-agents/<session_id>.jsonl
# вместе с тем, где запущена сессия: PID процесса claude, вкладка iTerm2 или панель tmux.
# Нужен jq (в macOS 15+ встроен, иначе: brew install jq).
# Никогда не мешает Claude Code: ничего не выводит и всегда выходит с кодом 0.
dir="$HOME/.cache/claude-agents"
input=$(cat)
sid=$(printf '%s' "$input" | jq -r '.session_id // empty' 2>/dev/null | tr -cd 'A-Za-z0-9_-')
[ -n "$sid" ] || exit 0
mkdir -p "$dir" 2>/dev/null || exit 0
# PID самого claude: идём вверх по родителям, пропуская оболочки, которыми запущен хук
pid=$PPID
comm=""
i=0
while [ "$i" -lt 4 ] && [ "${pid:-1}" -gt 1 ]; do
comm=$(ps -o comm= -p "$pid" 2>/dev/null | sed 's#.*/##; s/^-//')
case "$comm" in
sh|bash|zsh|dash|fish) pid=$(ps -o ppid= -p "$pid" 2>/dev/null | tr -d ' ') ;;
*) break ;;
esac
i=$((i + 1))
done
# Внутри tmux ITERM_SESSION_ID унаследован от сервера tmux и указывает не туда — не пишем его
iterm="${ITERM_SESSION_ID:-}"
tmux_socket=""
tmux_pane=""
if [ -n "${TMUX:-}" ] && [ -n "${TMUX_PANE:-}" ]; then
iterm=""
tmux_socket="${TMUX%%,*}"
tmux_pane="$TMUX_PANE"
fi
printf '%s' "$input" | jq -c --arg iterm "$iterm" --arg pid "${pid:-}" --arg pid_comm "$comm" \
--arg tmux_socket "$tmux_socket" --arg tmux_pane "$tmux_pane" '
def cut: if type == "string" then .[0:300] else . end;
{
hook_event_name, session_id, transcript_path, cwd, agent_id, agent_type,
model, session_title, source, from_model, to_model, tool_name, tool_use_id,
notification_type, agent_transcript_path, reason,
message: (.message | cut),
description: (.description | cut),
last_assistant_message: (.last_assistant_message | cut),
tool_input: (if (.tool_input | type) == "object"
then .tool_input | {subagent_type, description, model, name}
| map_values(cut) | with_entries(select(.value != null))
else null end),
t: now,
iterm: $iterm,
pid: ($pid | tonumber? // null),
pid_comm: (if $pid_comm == "" then null else $pid_comm end),
tmux_socket: (if $tmux_socket == "" then null else $tmux_socket end),
tmux_pane: (if $tmux_pane == "" then null else $tmux_pane end)
} | with_entries(select(.value != null))
' >> "$dir/$sid.jsonl" 2>/dev/null
exit 0

1506
files/claude_agents.py Normal file

File diff suppressed because it is too large Load Diff

18
files/config.example.json Normal file
View File

@ -0,0 +1,18 @@
{
"file_manager": "lf",
"agent_dir": "changed",
"done_linger_sec": 20,
"agent_idle_min": 15,
"poll_sec": 1.5,
"keep_logs_days": 2,
"use_profile_theme": true,
"port": 8766,
"context_warn": 500000,
"context_crit": 700000,
"history_max_chars": 30000,
"usage_refresh_sec": 120,
"balance_refresh_sec": 600,
"show_breakdown": true,
"show_balance": true,
"theme_variant": "auto"
}

78
files/settings_hooks.py Executable file
View File

@ -0,0 +1,78 @@
#!/usr/bin/env python3
"""Добавляет или убирает хуки панели в ~/.claude/settings.json, не трогая остальное.
settings_hooks.py add <settings.json>
settings_hooks.py remove <settings.json>
"""
import json
import os
import sys
MARK = "agents_panel_hook"
COMMAND = 'sh "$HOME/.claude/hooks/agents_panel_hook.sh"'
EVENTS = { # событие -> matcher (None — без фильтра)
"SessionStart": None, "SessionEnd": None, "UserPromptSubmit": None, "Stop": None,
"Notification": None, "SubagentStart": None, "SubagentStop": None,
"PostModelSwitch": None, "PreToolUse": "Agent|Task",
}
def ours(hook):
return isinstance(hook, dict) and MARK in str(hook.get("command", ""))
def add(cfg):
hooks = cfg.setdefault("hooks", {})
added = []
for event, matcher in EVENTS.items():
groups = hooks.setdefault(event, [])
if any(ours(h) for g in groups for h in (g.get("hooks") or [])):
continue # уже стоит — повторная установка ничего не дублирует
group = {"hooks": [{"type": "command", "command": COMMAND}]}
if matcher:
group = {"matcher": matcher, **group}
groups.append(group)
added.append(event)
return added
def remove(cfg):
hooks = cfg.get("hooks") or {}
removed = []
for event in list(hooks):
groups = []
for g in hooks[event]:
kept = [h for h in (g.get("hooks") or []) if not ours(h)]
if len(kept) != len(g.get("hooks") or []):
removed.append(event)
if kept:
groups.append({**g, "hooks": kept})
if groups:
hooks[event] = groups
else:
del hooks[event]
if "hooks" in cfg and not cfg["hooks"]:
del cfg["hooks"]
return sorted(set(removed))
def main():
action, path = sys.argv[1], os.path.expanduser(sys.argv[2])
cfg = {}
if os.path.exists(path):
with open(path, encoding="utf-8") as f:
text = f.read().strip()
cfg = json.loads(text) if text else {}
changed = add(cfg) if action == "add" else remove(cfg)
if changed:
os.makedirs(os.path.dirname(path), exist_ok=True)
tmp = path + ".tmp"
with open(tmp, "w", encoding="utf-8") as f:
json.dump(cfg, f, ensure_ascii=False, indent=2)
f.write("\n")
os.replace(tmp, path)
print(", ".join(changed) if changed else "—")
if __name__ == "__main__":
main()

101
install.sh Executable file
View File

@ -0,0 +1,101 @@
#!/bin/bash
# Установка панели «Claude» для iTerm2 Toolbelt: дерево сессий и агентов Claude Code,
# лимиты подписки и баланс. Повторный запуск безопасен — обновляет установленное.
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
FILES="$HERE/files"
AUTOLAUNCH="$HOME/Library/Application Support/iTerm2/Scripts/AutoLaunch"
HOOKS_DIR="$HOME/.claude/hooks"
SETTINGS="$HOME/.claude/settings.json"
CONFIG_DIR="$HOME/.config/claude-agents"
BACKUP_DIR="$HOME/.claude-panel-backup/$(date +%Y%m%d-%H%M%S)-$$"
PORT=8766
say() { printf '\033[1m==>\033[0m %s\n' "$*"; }
ok() { printf ' \033[32m✓\033[0m %s\n' "$*"; }
warn() { printf ' \033[33m!\033[0m %s\n' "$*"; }
die() { printf '\033[31mОшибка:\033[0m %s\n' "$*" >&2; exit 1; }
backup() { mkdir -p "$BACKUP_DIR"; cp -p "$1" "$BACKUP_DIR/"; }
# ---------- проверки ----------
say "Проверяю окружение"
[ "$(uname)" = "Darwin" ] || [ "${CLAUDE_PANEL_ANY_OS:-}" = 1 ] || die "нужен macOS с iTerm2"
python3 -c 'import sys; assert sys.version_info >= (3, 8)' 2>/dev/null \
|| die "нужен python3 ≥ 3.8 (xcode-select --install или brew install python)"
ok "python3: $(python3 -V 2>&1)"
if command -v jq >/dev/null; then ok "jq: $(command -v jq)"
else warn "нет jq — хук без него молча ничего не пишет. Установите: brew install jq"; fi
if [ -d "/Applications/iTerm.app" ] || [ -d "$HOME/Applications/iTerm.app" ]; then ok "iTerm2 найден"
else warn "iTerm2 не найден в /Applications — панель будет работать только в нём"; fi
# ---------- хук Claude Code ----------
say "Хук Claude Code"
mkdir -p "$HOOKS_DIR"
install -m 755 "$FILES/agents_panel_hook.sh" "$HOOKS_DIR/agents_panel_hook.sh"
ok "$HOOKS_DIR/agents_panel_hook.sh"
[ -f "$SETTINGS" ] && backup "$SETTINGS"
added=$(python3 "$FILES/settings_hooks.py" add "$SETTINGS") \
|| die "не смог обновить $SETTINGS (битый JSON?) — копия в $BACKUP_DIR"
if [ "$added" = "—" ]; then ok "settings.json: хуки уже на месте"
else ok "settings.json: добавлены хуки $added (копия старого — в $BACKUP_DIR)"; fi
# ---------- скрипт iTerm2 ----------
say "Скрипт iTerm2"
mkdir -p "$AUTOLAUNCH"
if [ -f "$AUTOLAUNCH/claude_usage.py" ]; then # прежняя отдельная панель лимитов
backup "$AUTOLAUNCH/claude_usage.py" && rm "$AUTOLAUNCH/claude_usage.py"
ok "старый claude_usage.py убран из AutoLaunch (копия в $BACKUP_DIR)"
fi
[ -f "$AUTOLAUNCH/claude_agents.py" ] && backup "$AUTOLAUNCH/claude_agents.py"
install -m 644 "$FILES/claude_agents.py" "$AUTOLAUNCH/claude_agents.py"
ok "$AUTOLAUNCH/claude_agents.py"
# ---------- настройки панели ----------
mkdir -p "$CONFIG_DIR"
install -m 644 "$FILES/config.example.json" "$CONFIG_DIR/config.example.json"
if [ -f "$CONFIG_DIR/config.json" ]; then ok "ваш config.json не тронут"
else ok "пример настроек: $CONFIG_DIR/config.example.json (скопируйте в config.json, чтобы менять)"; fi
# ---------- Python API в iTerm2 ----------
say "iTerm2"
iterm_running=0
pgrep -xq iTerm2 2>/dev/null && iterm_running=1
api=$(defaults read com.googlecode.iterm2 EnableAPIServer 2>/dev/null || echo 0)
if [ "$api" != 1 ]; then
if [ $iterm_running = 0 ] && command -v defaults >/dev/null; then
defaults write com.googlecode.iterm2 EnableAPIServer -bool true
ok "Python API включён"
else
warn "включите Python API: iTerm2 → Settings → General → Magic → Enable Python API"
[ $iterm_running = 1 ] && warn "(пока iTerm2 запущен, установщик не меняет его настройки — он бы их перезаписал)"
fi
else
ok "Python API уже включён"
fi
# старая копия панели держит порт — останавливаем, чтобы запустилась новая
if command -v lsof >/dev/null; then
pids=$(lsof -ti "tcp:$PORT" -sTCP:LISTEN 2>/dev/null || true)
if [ -n "$pids" ]; then kill $pids 2>/dev/null || true; sleep 1; ok "остановлена прежняя версия панели"; fi
fi
if [ $iterm_running = 1 ] && [ "$api" = 1 ]; then
if osascript -e 'tell application "iTerm2" to launch API script named "claude_agents"' >/dev/null 2>&1; then
ok "панель запущена"
else
warn "не смог запустить сам — Scripts → AutoLaunch → claude_agents.py"
fi
else
warn "панель запустится сама при следующем запуске iTerm2"
fi
cat <<EOF
Готово. Осталось:
1. В iTerm2: View → Toolbelt → Show Toolbelt, затем Toolbelt → Claude.
2. Перезапустите открытые сессии claude — хуки подхватываются при старте сессии.
Проверка, что всё работает: http://127.0.0.1:$PORT/raw
Удаление: $HERE/uninstall.sh
EOF

33
uninstall.sh Executable file
View File

@ -0,0 +1,33 @@
#!/bin/bash
# Удаление панели «Claude». С --purge удаляет ещё настройки и логи хука.
set -euo pipefail
HERE="$(cd "$(dirname "$0")" && pwd)"
AUTOLAUNCH="$HOME/Library/Application Support/iTerm2/Scripts/AutoLaunch"
SETTINGS="$HOME/.claude/settings.json"
BACKUP_DIR="$HOME/.claude-panel-backup/$(date +%Y%m%d-%H%M%S)-$$-uninstall"
PORT=8766
ok() { printf ' \033[32m✓\033[0m %s\n' "$*"; }
if command -v lsof >/dev/null; then
pids=$(lsof -ti "tcp:$PORT" -sTCP:LISTEN 2>/dev/null || true)
[ -n "$pids" ] && kill $pids 2>/dev/null && ok "панель остановлена"
fi
rm -f "$AUTOLAUNCH/claude_agents.py" && ok "скрипт убран из AutoLaunch"
if [ -f "$SETTINGS" ]; then
mkdir -p "$BACKUP_DIR" && cp -p "$SETTINGS" "$BACKUP_DIR/"
removed=$(python3 "$HERE/files/settings_hooks.py" remove "$SETTINGS")
ok "settings.json: убраны хуки ${removed} (копия — в $BACKUP_DIR)"
fi
rm -f "$HOME/.claude/hooks/agents_panel_hook.sh" && ok "хук удалён"
if [ "${1:-}" = "--purge" ]; then
rm -rf "$HOME/.config/claude-agents" "$HOME/.cache/claude-agents"
ok "настройки и логи удалены"
else
echo " Настройки (~/.config/claude-agents) и логи (~/.cache/claude-agents) оставлены; --purge удалит и их."
fi
echo "Панель «Claude» исчезнет из меню Toolbelt после перезапуска iTerm2."