Перевести README на русский
Единственный читатель этого расширения пишет и думает по-русски. Строки интерфейса, комментарии в коде и сообщения коммитов остаются английскими: их адресат — GNOME Shell и потенциальный сторонний читатель кода, а не владелец репозитория.
This commit is contained in:
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user