Files
claude-code-gnome-extension/NOTES.md
T
av 7fc7f63842 Show Claude Code session status in the GNOME panel
Answers one question at a glance: is any session waiting for me, and
which one. With several sessions open the cost is not knowing what each
is doing, it is noticing that one stopped an hour ago.

Claude Code hooks write one JSON file per session under
~/.local/state/claude-code-status; the extension watches the directory
with Gio.FileMonitor, so nothing polls and there is no daemon.

Two distinctions carry the design:

  * blocked (permission prompt) is kept apart from waiting (turn done).
    Merged, a finished task looks as urgent as a stuck one, which is
    exactly the judgement the indicator exists to make.

  * the panel names the oldest session in the top state, not the latest.
    The session you forget is the one that has been waiting longest.

PostToolUse is registered although it looks redundant: it is the only
event that fires after a permission is granted, so without it a session
stays blocked in the panel for the rest of the turn. It writes only on
an actual state change, so the usual case costs no I/O.

Stop and SessionEnd are synchronous, unlike the rest. Both fire as the
process is about to go quiet, and an async hook racing that exit gets
killed before it writes -- claude -p left a session pinned at busy.
Concurrent hooks for one session serialise on an flock plus a timestamp
guard; tests/test-hook.sh covers each separately, because the burst test
passes on the timestamp guard alone.

Sessions running in zellij are located by tab name rather than by path,
matched through dump-layout on the working directory. The dump carries
no pane ids, so ZELLIJ_PANE_ID cannot be used; rows that do not resolve
stay inert instead of pretending a click does something.

lib/sessions.js deliberately imports nothing from the shell resource
namespace, which lets the riskiest logic -- liveness, ordering, partial
reads, monitoring -- run under plain gjs in tests/test-sessions.js.
2026-08-09 18:11:27 +03:00

124 lines
7.7 KiB
Markdown
Raw 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.
# Индикатор статуса Claude Code в панели GNOME
Заметка для быстрого старта. Написана до кода — здесь замысел и проверенные
факты об окружении, а не описание существующего.
## Задача
Знать, **когда переключиться** на терминал с Claude Code и **на какой именно**,
не глядя в сами терминалы. Состояний три: работает / ждёт меня / тихо.
Сессий одновременно несколько (разные проекты, разные терминалы) — это основной
режим, а не краевой случай.
## Решения, принятые заранее
- **Уведомлений не будет.** `notify-send` отвергнут сознательно, хотя он и был бы
дешевле. Следствие: панель — единственный канал, значит расширение делается
сразу, «сначала уведомления, потом может быть индикатор» отпадает.
- **Панель называет проект, а не только состояние.** «Кто-то ждёт» оставляет
гадать, в каком из четырёх терминалов; знать «когда» без «куда» бесполезно.
- **Сабагенты внутри сессии не показываются.** Батч из восьми задач — это одна
строка «работает 40 мин», и это правильная строка: пятый воркер из восьми ни о
чём не просит. Хук `SubagentStop` для этого существует, но вешать на него
статус — шум. Если понадобится прогресс внутри батча (сделано N из 8), брать
его надо из учёта задач, а не из хуков.
## Окружение (проверено 2026-08-07)
- GNOME Shell **46.0**, сессия **Wayland**, `XDG_CURRENT_DESKTOP=ubuntu:GNOME`.
- `~/.claude/settings.json`: ключа `hooks` нет вовсе (`{}`) — место чистое,
ничего не сломаем. `statusLine` занят: `bash ~/.claude/statusline-command.sh`.
## Шаблон — своё же расширение
`~/projects/private/sing-box-gnome-extension`, оно же
`sing-box-status@git.vakhrushev.me`. Установлено **симлинком** из репозитория в
`~/.local/share/gnome-shell/extensions/<uuid>` — так же ставим и это.
Скелет копируется целиком:
```
extension.js 16 строк: enable() → Main.panel.addToStatusArea(uuid, indicator)
lib/indicator.js панель и меню
lib/format.js форматирование
prefs.js настройки
schemas/ gschema.xml + gschemas.compiled
metadata.json "shell-version": ["45","46","47","48"]
stylesheet.css
```
Меняется только источник данных: вместо опроса Clash API по HTTP —
**`Gio.FileMonitor`** на каталоге состояния. Это push, поллинг не нужен, выходит
проще оригинала.
Предполагаемый uuid: `claude-code-status@git.vakhrushev.me`.
## Источник данных — хуки Claude Code
Пишутся в глобальный `~/.claude/settings.json`, чтобы работало во всех проектах.
| Хук | Состояние |
|---|---|
| `SessionStart` | сессия появилась |
| `UserPromptSubmit` | работает |
| `Notification` | ждёт меня — разрешение или простой на вводе |
| `Stop` | ход закончен, ждёт ввода |
| `SessionEnd` | сессия исчезла |
`Notification` и `Stop` **разделять в панели**: первое горит (заблокирована на
разрешении), второе просто ждёт (задача сделана). Слитые в одно, законченная
задача выглядит так же срочно, как заблокированная.
### Файл состояния
Один файл на сессию, иначе параллельные сессии затирают друг друга:
```
~/.local/state/claude-code-status/<session_id>.json
```
Поля: состояние, `cwd`, метка времени последней смены, `$PPID` для проверки
живости. `SessionEnd` файл удаляет.
**Проверить на первом же прогоне** (по памяти, не подтверждено): хук получает на
stdin JSON с `session_id`, `cwd`, `transcript_path`, `hook_event_name`. Имена
полей сверить с реальным вводом, а не доверять этой строке.
Названия событий `SubagentStop`, `SessionEnd`, `UserPromptSubmit` подтверждены —
встречаются в конфиге плагина wakatime (`~/.claude/plugins/cache/wakatime/`),
там же можно подсмотреть рабочий пример hooks.json.
## Правила отображения
Приоритет агрегации: **хоть одна ждёт → «ждёт»**, иначе **хоть одна работает →
«работает»**, иначе тихо.
- Показывается **дольше всех ждущая** сессия, не последняя: последняя и так
свежа в голове, забывается именно давняя.
- Несколько ждущих — счётчиком: `✋ dev-skills +2`.
- Для работающей полезно «работает 6 мин» — по этому решаешь, ждать или уходить.
- В меню — список сессий с **полным путём**: два worktree одного репозитория по
basename не различаются.
## Известные шероховатости
- **Протухание.** Убитый терминал не пришлёт `SessionEnd`, файл останется.
Лечится меткой времени плюс проверкой живости `$PPID` — не идеально, но
практично; индикатор гасит протухшие.
- **Клик в меню не переключит фокус** на нужный терминал: на Wayland расширению
это просто так не даётся. Меню информационное.
- **`Stop` не отличает «закончил» от «упал»** — оба выглядят как «ждёт ввода».
Для задачи «пора переключиться» разницы нет, но знать стоит.
## Порядок работ
1. Хуки и формат файла состояния — в `~/.claude/settings.json`.
2. **Проверить на живых сессиях** через `watch cat`, что состояния переключаются
правильно и поля stdin те, что ожидались. Здесь же выяснится, когда реально
срабатывает `Notification`.
3. Индикатор поверх заведомо верных данных.
Порядок именно такой: отлаживать GJS и раскладку состояний одновременно — это
две неизвестные в одном уравнении.