Единственный читатель этого расширения пишет и думает по-русски. Строки интерфейса, комментарии в коде и сообщения коммитов остаются английскими: их адресат — GNOME Shell и потенциальный сторонний читатель кода, а не владелец репозитория.
136 lines
9.3 KiB
Markdown
136 lines
9.3 KiB
Markdown
# Claude Code Status
|
||
|
||
Индикатор для GNOME Shell, отвечающий на один вопрос: **какая сессия Claude
|
||
Code ждёт меня прямо сейчас?**
|
||
|
||
Когда открыто несколько сессий в разных проектах, дорого не «не знать, чем
|
||
каждая занята», а не заметить, что одна из них встала час назад. Панель
|
||
называет сессию, а не только состояние.
|
||
|
||
## Состояния
|
||
|
||
| В панели | Состояние | Что значит |
|
||
|---|---|---|
|
||
| диск в кольце | `blocked` | упёрлась в запрос разрешения — без вас не сдвинется |
|
||
| закрашенный диск | `waiting` | ход закончен, ждёт вашего ввода |
|
||
| кольцо | `busy` | работает |
|
||
| тусклое пунктирное кольцо | `idle` | запущена, но ничего не просили, либо ничего не запущено |
|
||
|
||
`blocked` и `waiting` разведены намеренно. Слитые в одно «требует внимания»,
|
||
законченная задача выглядит так же срочно, как заблокированная, — а именно это
|
||
различие и решает, переключаться сейчас или после текущей мысли.
|
||
|
||
Панель показывает **самую приоритетную** сессию и счётчик `+N`, если в том же
|
||
состоянии есть другие. Внутри состояния выигрывает **самая давняя**: забывается
|
||
та, что ждёт дольше всех, а не последняя.
|
||
|
||
**Уведомлений на рабочий стол нет** — сознательно. `notify-send` из хука было бы
|
||
куда дешевле сделать, но форма неверная: сессия, ждущая двадцать минут, должна
|
||
оставаться на виду, а уведомление исчезает в момент, когда его смахнули. Панель
|
||
здесь — единственный канал.
|
||
|
||
## Установка
|
||
|
||
```sh
|
||
git clone <этот репозиторий> ~/projects/private/claude-code-gnome-extension
|
||
cd ~/projects/private/claude-code-gnome-extension
|
||
|
||
# 1. хуки: научить Claude Code публиковать состояние сессий
|
||
./hooks/install.py
|
||
|
||
# 2. расширение: симлинк, компиляция схемы, включение
|
||
ln -s "$PWD" ~/.local/share/gnome-shell/extensions/claude-code-status@git.vakhrushev.me
|
||
glib-compile-schemas schemas/
|
||
gnome-extensions enable claude-code-status@git.vakhrushev.me
|
||
```
|
||
|
||
Уже запущенные сессии подхватывают хуки без перезапуска — Claude Code перечитывает
|
||
`settings.json` по мере изменения.
|
||
|
||
На Wayland правки *кода* расширения по-прежнему требуют релогина; первое включение
|
||
— нет.
|
||
|
||
`./hooks/install.py --uninstall` снимает регистрацию хуков, не трогая остальное в
|
||
`~/.claude/settings.json`. Копия `.bak` пишется при каждом запуске.
|
||
|
||
## Как это работает
|
||
|
||
Хуки Claude Code пишут по одному небольшому JSON-файлу на сессию в
|
||
`~/.local/state/claude-code-status/<session_id>.json`; расширение следит за этим
|
||
каталогом через `Gio.FileMonitor`. Ничего не опрашивается, демона нет — смена
|
||
состояния доходит до панели, как только хук завершился.
|
||
|
||
| Хук | Действие |
|
||
|---|---|
|
||
| `SessionStart` | сессия появляется как `idle`, мёртвые подчищаются |
|
||
| `UserPromptSubmit` | `busy` |
|
||
| `PostToolUse` | `busy` |
|
||
| `PreCompact` | `busy` |
|
||
| `Notification` | `blocked` или `waiting`, в зависимости от `notification_type` |
|
||
| `Stop` | `waiting` |
|
||
| `SessionEnd` | файл удаляется |
|
||
|
||
`PostToolUse` не избыточен: это единственное событие, срабатывающее после выдачи
|
||
разрешения, — без него сессия остаётся `blocked` в панели до конца хода. Пишет он
|
||
только при реальной смене состояния, так что обычный случай стоит запуска процесса
|
||
и нулевого ввода-вывода.
|
||
|
||
`Stop` и `SessionEnd` зарегистрированы синхронно, в отличие от остальных. Оба
|
||
срабатывают, когда процесс вот-вот затихнет, и асинхронный хук, проигравший гонку
|
||
с выходом, убивается раньше, чем успевает записать: у `claude -p` это наблюдалось
|
||
как сессия, навсегда застрявшая в `busy`.
|
||
|
||
Хуки одной сессии выполняются параллельно, поэтому весь цикл «прочитать — решить —
|
||
записать» идёт под `flock` на `<session_id>.json.lock`, а событие старше
|
||
сохранённого отвергается. Нужно и то, и другое: одна лишь проверка меток времени
|
||
всё ещё позволяет хуку, прочитавшему старое состояние до `Stop`, записать своё
|
||
устаревшее решение после него.
|
||
|
||
Сабагенты не показываются. Батч из восьми задач — это одна строка «работает
|
||
40 мин», и это правильная строка: пятый воркер из восьми ни о чём не просит.
|
||
|
||
## zellij
|
||
|
||
Если сессии живут в табах zellij, меню показывает **имя таба**, а клик
|
||
переключает на него. Поиск идёт через `zellij action dump-layout` — рабочий
|
||
каталог сессии сопоставляется с каталогами пейнов, потому что в дампе раскладки
|
||
нет id пейнов и `ZELLIJ_PANE_ID` для этого не годится. Отсюда следствия: две
|
||
сессии в одном табе неразличимы, а сессия, сменившая `cwd` после открытия пейна,
|
||
не найдётся. Строки, которые не разрешились, остаются некликабельными — вместо
|
||
того чтобы делать вид, будто клик что-то делает.
|
||
|
||
Если zellij не используется, выключите в настройках: он стоит одного процесса
|
||
раз в пару минут.
|
||
|
||
## Известные шероховатости
|
||
|
||
- **Протухшие сессии.** Убитый терминал не присылает `SessionEnd`. Живость
|
||
перепроверяется каждые 20 с по `/proc/<pid>`, файл удаляется — убитая сессия
|
||
исчезает в пределах этого окна, а не висит вечно. Если хук вообще не смог
|
||
опознать процесс claude, он записывает pid 0 — «неизвестно», что никогда не
|
||
путается с «мёртв», — и такие записи истекают по возрасту, через 36 часов.
|
||
- **`Stop` не отличает «закончил» от «сдался».** Оба читаются как «ждёт ввода»,
|
||
и решение для вас в обоих случаях одно и то же.
|
||
- **Фокус ведёт к табу zellij, а не к окну.** gnome-terminal держит все окна под
|
||
одним общим серверным процессом, так что окно нельзя сопоставить по pid. Окно
|
||
поднимается, только если в его заголовке есть имя zellij-сессии; когда
|
||
совпадения нет, таб всё равно переключается, а фокус остаётся на месте —
|
||
поднять произвольный терминал хуже, чем не поднимать никакой.
|
||
|
||
## Тесты
|
||
|
||
```sh
|
||
gjs -m tests/test-sessions.js # чтение состояния, порядок, живость, слежение
|
||
tests/test-hook.sh # события -> состояния, блокировка, снос файлов
|
||
```
|
||
|
||
`lib/sessions.js` намеренно ничего не импортирует из `resource:///org/gnome/shell`
|
||
— именно это позволяет гонять его в обычном `gjs`, вне композитора.
|
||
|
||
Чтобы посмотреть сырые события хуков, создайте файл-маркер, и каждое событие
|
||
будет дописываться в него:
|
||
|
||
```sh
|
||
touch ~/.local/state/claude-code-status/debug
|
||
```
|