A line above the prompt: how many tokens the prompt cache holds for this conversation

<img src="docs/img/hero.png" alt="Kisel" width="900">
<a href="https://github.com/Tiutiunov/Kisel/releases/latest"><img src="https://img.shields.io/github/v/release/Tiutiunov/Kisel?label=%D0%B2%D0%B5%D1%80%D1%81%D0%B8%D1%8F&color=39C5BB" alt="Последняя версия"></a> <img src="https://img.shields.io/badge/Windows-10%20%2F%2011-E0405A" alt="Windows 10 и 11"> <img src="https://img.shields.io/badge/Qt%206-QML%20%2B%20C%2B%2B20-B9DC6B" alt="Qt 6"> <a href="README.en.md"><img src="https://img.shields.io/badge/README-English-F5A3C0" alt="English"></a>
<h3 align="center"> <a href="https://github.com/Tiutiunov/Kisel/releases/latest">Скачать для Windows</a> </h3>
Kisel это маленькая тёмная таблетка у края экрана, в которой сидят пять моти. Изначально она нужна была для одного: видеть, чем занят Claude Code, не переключаясь в терминал, и отвечать на его «можно я запущу эту команду?» одним кликом. Потом в таблетку переехали плеер, уведомления, монитор железа и замер интернета, и теперь я её просто не закрываю.
Внутри нет ни одной картинки. Персонажи, иконки, искры, даже звуки сделаны кодом.
<img src="docs/img/cast.png" alt="Miku, Rin, Luka, Zundamon и Teto" width="900">
| Чем занята | Какая она | |
|---|---|---|
| Miku | Claude Code: сессии, разрешения, вопросы, чат | Старательная, напевает под нос. Шлёпнешь, надуется |
| Rin | Уведомления Windows: машет табличкой с названием программы | Шумная. От шлепка взрывается |
| Luka | Интернет: пинг, скорость, загрузки, замер | Спокойная. На шлепок смотрит как на идиота |
| Zundamon | Музыка из Spotify | Хвастун, но паникует первым |
| Teto | Процессор, видеокарта, память | Самодовольная, пока её не похвалишь |
Главную выбираешь кликом по скамейке справа. Остальные никуда не деваются: сидят рядом, моргают, иногда корчат рожи.
Кто чем занят, можно переставить в настройках: хоть Тето на Claude Code, хоть Мику на музыку. Характер и любимая еда остаются при персонаже, работа переходит.
288 на 32 пикселя, живёт у любого края экрана. Что в ней показано, зависит от того, что сейчас происходит.
<img src="docs/img/bar.png" alt="Таблетка в разных состояниях" width="900">
Пара вещей, на которые ушло больше времени, чем стоило бы:
<img src="docs/img/bar-side.png" alt="Таблетка у бокового края" width="900">
Клик по главной раскрывает карточку. У каждой своя.
<table> <tr> <td width="50%"><img src="docs/img/card-miku.png" alt="Карточка Мику"><br><b>Miku.</b> Что делает последняя сессия и три кольца лимитов: пять часов, неделя и заполненность контекста. Под ними написано, когда лимит сбросится.</td> <td width="50%"><img src="docs/img/card-permission.png" alt="Запрос разрешения"><br><b>Разрешения.</b> Claude хочет что-то запустить или поправить, и карточка открывается сама. За тебя Kisel не отвечает никогда, только твой клик.</td> </tr> <tr> <td><img src="docs/img/card-question.png" alt="Вопрос от Claude"><br><b>Вопросы.</b> Варианты кнопками, ответ уходит обратно в Claude Code.</td> <td><img src="docs/img/card-done.png" alt="Задача готова"><br><b>Готово.</b> Мику празднует, вокруг летает лук-порей. У каждой своя любимая еда.</td> </tr> <tr> <td><img src="docs/img/card-zunda.png" alt="Плеер"><br><b>Zundamon.</b> Обложка, название, кнопки, перемотка. Берёт данные из системного медиа-сеанса Windows, так что Spotify трогать не надо.</td> <td><img src="docs/img/card-teto.png" alt="Монитор"><br><b>Teto.</b> Нагрузка за последнюю минуту и кнопка, которая чистит память. Сколько освободила, расскажет сама и не без гордости.</td> </tr> <tr> <td colspan="2"><img src="docs/img/card-chat.png" alt="Чат"><br><b>Чат.</b> Пишешь здесь, а уходит это в твою запущенную сессию Claude Code как обычный промпт. Ответы возвращаются сюда же, слева лимиты и модель. Ключ API не нужен.</td> </tr> <tr> <td><img src="docs/img/card-luka.png" alt="Сеть"><br><b>Luka.</b> Пинг и текущая скорость. Кнопка Test меряет канал без захода на speedtest. Чем быстрее интернет, тем она довольнее, на медленном расстраивается.</td> <td><img src="docs/img/card-rin.png" alt="Уведомления"><br><b>Rin.</b> Держит табличку с названием программы, пока не посмотришь. Клик по ней открывает эту программу. Про новую версию Kisel тоже сообщает она.</td> </tr> </table>
<img src="docs/img/card-settings.png" alt="Настройки" width="620">
Настройки разложены по вкладкам: Claude Code, сервисы, персонажи, вид и звук, место, сам Kisel.
Все анимации и сценки можно погонять отдельно: открой в браузере design/miku-editor.html. Там же ползунки для внешности.
Скачай KiselSetup со страницы релизов и запусти. Ставится в профиль пользователя, права администратора не нужны. Нужна Windows 10 (1803+) или 11.
Установщик не подписан, сертификат стоит денег. Поэтому SmartScreen ругнётся: жми «Подробнее», потом «Выполнить в любом случае».
Обновляется сам. Раз в сутки спрашивает GitHub, не вышло ли чего, и если вышло, Рин поднимает табличку с номером версии. Дальше одна кнопка в настройках: скачает, сверит контрольную сумму, поставит и перезапустится. Проверку можно выключить там же.
Удаляется через «Приложения» в параметрах Windows. Хуки из настроек Claude Code при этом убираются, твои настройки остаются.
Карточка → шестерёнка → Connect Claude Code. Kisel покажет, что именно допишет в ~/.claude/settings.json, и не тронет файл, пока не нажмёшь. Перед записью делает копию.
Дальше Claude Code на каждое событие дёргает крошечную kisel-hook, а она передаёт его Киселю. Если Kisel не запущен, хук молча выходит и Claude Code ничего не замечает. Если хуки пропали или устарели после обновления, Kisel скажет об этом и предложит починить.
Там же, в настройках, кнопка Install mods. Она ставит в Claude Code два плагина, которые идут вместе с Kisel:
kisel-prompts связывает сессию с чатом Kisel: промпты туда, ответы и лимиты обратно.cache-band показывает над полем ввода в Claude Code, сколько токенов лежит в кеше.Kisel для этого запускает родные команды claude plugin ... и показывает их заранее, так что нужен установленный claude в командной строке. Моды загружаются в сессиях, запущенных после установки.
По желанию Kisel показывает в профиле Discord «Работает с Claude Code» с таймером. Выключено, пока не включишь в настройках. Название сессии показывается, только если включить и это. Общается он только с Discord на твоём компьютере.
Секреты (токен GitHub для виджета) лежат в диспетчере учётных данных Windows, в файлы не пишутся.
Телеметрии нет. В сеть Kisel ходит за виджетом GitHub, пингует 1.1.1.1 для Луки, раз в сутки проверяет обновления и по кнопке меряет скорость через Cloudflare. Всё это отключается или не запускается само. Чат идёт через твою локальную сессию Claude Code, а не через сеть Kisel.
Подробно, по пунктам: docs/PRIVACY.md. Условия использования: docs/TERMS.md.
Из уведомлений читается только название программы и количество. Текст не читается.
Настройки лежат в %LOCALAPPDATA%\kisel\kisel.conf, обычный текстовый файл.
Нужны Visual Studio 2022 с C++, CMake, Ninja и Qt 6.5+ для MSVC. Из «x64 Native Tools Command Prompt»:
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=C:\Qt\6.10.3\msvc2022_64
cmake --build build
cmake --install build --prefix dist
dist\bin\kisel.exe
Установщик: cmake --build build --target installer.
Без Claude Code можно посмотреть так: kisel.exe --demo разыграет выдуманную сессию. kisel.exe --speed-test просто померит интернет и напечатает результат. Как всё устроено внутри, написано в docs/ARCHITECTURE.md.
Kisel начал Антон Куклин как спутник для KDE Plasma. Эта ветка — версия для Windows, которая с тех пор обросла всем остальным. Форму персонажей подсмотрели у Mochi из Coucou.
Код под MIT. Это касается только кода.
Персонажи не мои. Kisel это фанатская некоммерческая штука, с правообладателями она не связана и ими не одобрена. Нарисовано всё своим кодом и в своём стиле, чужих иллюстраций, голосов и музыки в проекте нет.
この作品はピアプロ・キャラクター・ライセンスに基づいてクリプトン・フューチャー・メディア株式会社のキャラクター「初音ミク」「鏡音リン」「巡音ルカ」を描いたものです。
Kisel не имеет отношения к Anthropic. Claude и Claude Code это их товарные знаки, и маленький оранжевый зверёк, которого Мику обнимает за работой, тоже их персонаж из Claude Code.
Внутри Qt 6 по LGPL v3, подключён динамически: библиотеки лежат рядом с kisel.exe как есть, их можно заменить своей сборкой. Исходники Qt на download.qt.io. Тексты лицензий ставятся в папку licenses, остальное перечислено в THIRD-PARTY-NOTICES.md.
hooks/register.tsx 61 lines1import { atom, read, update } from 'claude-code'
2import type { Register } from 'claude-code'
3
4import type { CacheCount } from '../types'
5import { line } from './format'
6
7const last = atom({ plugin: 'cache-band', key: 'last' } as const, null)
8
9export const register: Register = on => {
10 // A session picked up mid-way: what its last response reported, if the engine still has it.
11 on('session.start', async ($, e, next) => {
12 const result = await next(e)
13 const usage = (await $.session.usage({ breakdown: 'summary' })).context.breakdown?.apiUsage
14
15 if (usage && (await read($, last)) === null) {
16 const seen: CacheCount = {
17 read: usage.cache_read_input_tokens,
18 written: usage.cache_creation_input_tokens,
19 fresh: usage.input_tokens,
20 }
21 await update($, last, () => seen)
22 }
23
24 return result
25 })
26
27 // Every model response of the main conversation (a subagent has a cache of its own).
28 on('turn.step', async function* ($, e, next) {
29 const result = yield* next(e)
30
31 if (e.agentId === undefined && result.usage) {
32 const seen: CacheCount = {
33 read: result.usage.cache_read_input_tokens,
34 written: result.usage.cache_creation_input_tokens,
35 fresh: result.usage.input_tokens,
36 }
37 await update($, last, () => seen)
38 }
39
40 return result
41 })
42
43 on('ui.render', { component: 'AbovePrompt' }, async ($, e, next) => {
44 const seen = await read($, last)
45
46 if (e.props.hasSurvey || seen === null) {
47 return next(e)
48 }
49
50 const { Box, Text } = $.ui.resolve(e)
51
52 return (
53 <Box>
54 <Text key="cache" dimColor>
55 {line(seen)}
56 </Text>
57 </Box>
58 )
59 })
60}
61hooks/format.ts 14 lines1// 182431 -> "182.4k", 950 -> "950", 1234567 -> "1.23M"
2export const short = (n: number): string =>
3 n >= 1_000_000 ? `${(n / 1_000_000).toFixed(2)}M` : n >= 1000 ? `${(n / 1000).toFixed(1)}k` : `${n}`
4
5// What the band says. In the cache after a response: what it read from the
6// cache plus what it added to it.
7export const line = (c: { read: number; written: number; fresh: number }): string => {
8 const held = c.read + c.written
9 const all = held + c.fresh
10 const share = all > 0 ? Math.round((c.read / all) * 100) : 0
11
12 return `В кеше ${short(held)} токенов · из кеша ${short(c.read)} (${share}%) · дописано ${short(c.written)} · мимо кеша ${short(c.fresh)}`
13}
14types/index.d.ts 16 lines1// What the last response of the main conversation said about the prompt cache.
2export type CacheCount = {
3 /** input tokens the cache served */
4 read: number
5 /** input tokens the response wrote to the cache */
6 written: number
7 /** input tokens that went past the cache */
8 fresh: number
9}
10
11declare module 'claude-code' {
12 interface PluginState {
13 'cache-band': { last: CacheCount | null }
14 }
15}
16