Перевести README на русский

Единственный читатель этого расширения пишет и думает по-русски.
Строки интерфейса, комментарии в коде и сообщения коммитов остаются
английскими: их адресат — GNOME Shell и потенциальный сторонний
читатель кода, а не владелец репозитория.
This commit is contained in:
av
2026-08-09 18:26:53 +03:00
parent 223876a91a
commit 58409106c3
+84 -84
View File
@@ -1,134 +1,134 @@
# Claude Code Status # Claude Code Status
GNOME Shell indicator that answers one question at a glance: **is any Claude Индикатор для GNOME Shell, отвечающий на один вопрос: **какая сессия Claude
Code session waiting for me, and which one?** Code ждёт меня прямо сейчас?**
With several sessions open in different projects, the cost is not knowing what Когда открыто несколько сессий в разных проектах, дорого не «не знать, чем
each one is doing — it is noticing that one of them stopped an hour ago. The каждая занята», а не заметить, что одна из них встала час назад. Панель
panel names the session, not just the state. называет сессию, а не только состояние.
## States ## Состояния
| Panel | State | Meaning | | В панели | Состояние | Что значит |
|---|---|---| |---|---|---|
| disc inside a ring | `blocked` | stuck on a permission prompt — it cannot proceed without you | | диск в кольце | `blocked` | упёрлась в запрос разрешения — без вас не сдвинется |
| filled disc | `waiting` | turn finished, waiting for your input | | закрашенный диск | `waiting` | ход закончен, ждёт вашего ввода |
| ring | `busy` | working | | кольцо | `busy` | работает |
| dim dashed ring | `idle` | started, nothing asked yet, or nothing running | | тусклое пунктирное кольцо | `idle` | запущена, но ничего не просили, либо ничего не запущено |
`blocked` and `waiting` are kept apart on purpose. Merged into one "needs you", `blocked` и `waiting` разведены намеренно. Слитые в одно «требует внимания»,
a finished task looks as urgent as a blocked one, and the distinction is exactly законченная задача выглядит так же срочно, как заблокированная, — а именно это
what decides whether to switch now or after the current thought. различие и решает, переключаться сейчас или после текущей мысли.
The panel shows the **highest-priority** session and, when others share that Панель показывает **самую приоритетную** сессию и счётчик `+N`, если в том же
state, a `+N` count. Within a state the **oldest** one wins: the session you состоянии есть другие. Внутри состояния выигрывает **самая давняя**: забывается
have forgotten about is the one that has been waiting longest, never the latest. та, что ждёт дольше всех, а не последняя.
There are deliberately **no desktop notifications**. `notify-send` from a hook **Уведомлений на рабочий стол нет** — сознательно. `notify-send` из хука было бы
would have been far cheaper to build, and it is the wrong shape: a session that куда дешевле сделать, но форма неверная: сессия, ждущая двадцать минут, должна
has been waiting twenty minutes needs to stay visible, and a notification is оставаться на виду, а уведомление исчезает в момент, когда его смахнули. Панель
gone the moment it is dismissed. The panel is the whole channel. здесь — единственный канал.
## Install ## Установка
```sh ```sh
git clone <this repo> ~/projects/private/claude-code-gnome-extension git clone <этот репозиторий> ~/projects/private/claude-code-gnome-extension
cd ~/projects/private/claude-code-gnome-extension cd ~/projects/private/claude-code-gnome-extension
# 1. hooks: teach Claude Code to publish session state # 1. хуки: научить Claude Code публиковать состояние сессий
./hooks/install.py ./hooks/install.py
# 2. extension: symlink, compile the schema, enable # 2. расширение: симлинк, компиляция схемы, включение
ln -s "$PWD" ~/.local/share/gnome-shell/extensions/claude-code-status@git.vakhrushev.me ln -s "$PWD" ~/.local/share/gnome-shell/extensions/claude-code-status@git.vakhrushev.me
glib-compile-schemas schemas/ glib-compile-schemas schemas/
gnome-extensions enable claude-code-status@git.vakhrushev.me gnome-extensions enable claude-code-status@git.vakhrushev.me
``` ```
Running sessions pick the hooks up without restarting — Claude Code re-reads Уже запущенные сессии подхватывают хуки без перезапуска — Claude Code перечитывает
`settings.json` as it changes. `settings.json` по мере изменения.
On Wayland, changing extension *code* still needs a logout; enabling it for the На Wayland правки *кода* расширения по-прежнему требуют релогина; первое включение
first time does not. — нет.
`./hooks/install.py --uninstall` removes the hook registration and leaves the `./hooks/install.py --uninstall` снимает регистрацию хуков, не трогая остальное в
rest of `~/.claude/settings.json` untouched. A `.bak` copy is written on every `~/.claude/settings.json`. Копия `.bak` пишется при каждом запуске.
run.
## How it works ## Как это работает
Claude Code hooks write one small JSON file per session to Хуки Claude Code пишут по одному небольшому JSON-файлу на сессию в
`~/.local/state/claude-code-status/<session_id>.json`; the extension watches `~/.local/state/claude-code-status/<session_id>.json`; расширение следит за этим
that directory with `Gio.FileMonitor`. Nothing polls, and there is no daemon — каталогом через `Gio.FileMonitor`. Ничего не опрашивается, демона нет — смена
a state change reaches the panel as soon as the hook returns. состояния доходит до панели, как только хук завершился.
| Hook | Effect | | Хук | Действие |
|---|---| |---|---|
| `SessionStart` | session appears as `idle`, and dead sessions are swept | | `SessionStart` | сессия появляется как `idle`, мёртвые подчищаются |
| `UserPromptSubmit` | `busy` | | `UserPromptSubmit` | `busy` |
| `PostToolUse` | `busy` | | `PostToolUse` | `busy` |
| `PreCompact` | `busy` | | `PreCompact` | `busy` |
| `Notification` | `blocked` or `waiting`, depending on `notification_type` | | `Notification` | `blocked` или `waiting`, в зависимости от `notification_type` |
| `Stop` | `waiting` | | `Stop` | `waiting` |
| `SessionEnd` | file removed | | `SessionEnd` | файл удаляется |
`PostToolUse` is not redundant: it is the only event that fires after a `PostToolUse` не избыточен: это единственное событие, срабатывающее после выдачи
permission is granted, so without it a session stays `blocked` in the panel for разрешения, — без него сессия остаётся `blocked` в панели до конца хода. Пишет он
the rest of the turn. It writes only when the state actually changes, so the только при реальной смене состояния, так что обычный случай стоит запуска процесса
usual case costs a process spawn and no I/O. и нулевого ввода-вывода.
`Stop` and `SessionEnd` are registered synchronously. Both fire as the process `Stop` и `SessionEnd` зарегистрированы синхронно, в отличие от остальных. Оба
is about to go quiet, and an async hook racing that exit gets killed before it срабатывают, когда процесс вот-вот затихнет, и асинхронный хук, проигравший гонку
writes — `claude -p` was observed leaving a session pinned at `busy` forever. с выходом, убивается раньше, чем успевает записать: у `claude -p` это наблюдалось
как сессия, навсегда застрявшая в `busy`.
Hooks for one session run concurrently, so the whole read-decide-write runs Хуки одной сессии выполняются параллельно, поэтому весь цикл «прочитать — решить —
under an `flock` on `<session_id>.json.lock`, and an event older than the stored записать» идёт под `flock` на `<session_id>.json.lock`, а событие старше
one is refused. Both are needed: the timestamp guard alone still lets a hook сохранённого отвергается. Нужно и то, и другое: одна лишь проверка меток времени
that read the old state before a `Stop` write its stale decision afterwards. всё ещё позволяет хуку, прочитавшему старое состояние до `Stop`, записать своё
устаревшее решение после него.
Sub-agents are not shown. A batch of eight tasks is one line, "working 40 min", Сабагенты не показываются. Батч из восьми задач — это одна строка «работает
which is the right line: the fifth of eight workers is not asking for anything. 40 мин», и это правильная строка: пятый воркер из восьми ни о чём не просит.
## zellij ## zellij
When sessions run in zellij tabs, the menu shows the **tab name** and a click Если сессии живут в табах zellij, меню показывает **имя таба**, а клик
switches to it. The lookup goes through `zellij action dump-layout`, matching a переключает на него. Поиск идёт через `zellij action dump-layout` — рабочий
session's working directory against pane directories — the layout dump carries каталог сессии сопоставляется с каталогами пейнов, потому что в дампе раскладки
no pane ids, so `ZELLIJ_PANE_ID` cannot be used for it. Two sessions in one tab нет id пейнов и `ZELLIJ_PANE_ID` для этого не годится. Отсюда следствия: две
are therefore indistinguishable, and a session whose `cwd` has moved since the сессии в одном табе неразличимы, а сессия, сменившая `cwd` после открытия пейна,
pane opened will not match. Rows that cannot be resolved stay inert rather than не найдётся. Строки, которые не разрешились, остаются некликабельными — вместо
pretending a click does something. того чтобы делать вид, будто клик что-то делает.
Turn it off in Settings if you do not use zellij; it costs one process every Если zellij не используется, выключите в настройках: он стоит одного процесса
couple of minutes. раз в пару минут.
## Known rough edges ## Известные шероховатости
- **Stale sessions.** A killed terminal never sends `SessionEnd`. Liveness is - **Протухшие сессии.** Убитый терминал не присылает `SessionEnd`. Живость
rechecked every 20 s against `/proc/<pid>` and the file is deleted, so a перепроверяется каждые 20 с по `/proc/<pid>`, файл удаляется — убитая сессия
killed session disappears within that window rather than lingering. When the исчезает в пределах этого окна, а не висит вечно. Если хук вообще не смог
hook cannot identify the claude process at all it records pid 0 — "unknown", опознать процесс claude, он записывает pid 0 — «неизвестно», что никогда не
never confused with "dead" — and those entries expire on age instead, after путается с «мёртв», — и такие записи истекают по возрасту, через 36 часов.
36 h. - **`Stop` не отличает «закончил» от «сдался».** Оба читаются как «ждёт ввода»,
- **`Stop` cannot tell "finished" from "gave up".** Both read as "waiting for и решение для вас в обоих случаях одно и то же.
input", which is the same decision for you either way. - **Фокус ведёт к табу zellij, а не к окну.** gnome-terminal держит все окна под
- **Focus follows the zellij tab, not the window.** gnome-terminal runs every одним общим серверным процессом, так что окно нельзя сопоставить по pid. Окно
window under one shared server process, so a window cannot be matched by pid. поднимается, только если в его заголовке есть имя zellij-сессии; когда
A window is raised only when its title names the zellij session; when no совпадения нет, таб всё равно переключается, а фокус остаётся на месте —
window matches, the tab still switches and focus is left alone, because поднять произвольный терминал хуже, чем не поднимать никакой.
raising an arbitrary terminal is worse than raising none.
## Testing ## Тесты
```sh ```sh
gjs -m tests/test-sessions.js # state reading, ordering, liveness, monitoring gjs -m tests/test-sessions.js # чтение состояния, порядок, живость, слежение
tests/test-hook.sh # event -> state machine, locking, teardown tests/test-hook.sh # события -> состояния, блокировка, снос файлов
``` ```
`lib/sessions.js` imports nothing from `resource:///org/gnome/shell`, which is `lib/sessions.js` намеренно ничего не импортирует из `resource:///org/gnome/shell`
what keeps it runnable outside the compositor. — именно это позволяет гонять его в обычном `gjs`, вне композитора.
To watch the raw hook events, create the marker file and every event will be Чтобы посмотреть сырые события хуков, создайте файл-маркер, и каждое событие
appended to it: будет дописываться в него:
```sh ```sh
touch ~/.local/state/claude-code-status/debug touch ~/.local/state/claude-code-status/debug