SLOPSHOPPER

kisel-prompts

Takes prompts typed in Kisel's session card and submits them to this session

newtimer
v0.1.2NOASSERTIONupdated 2026-10-09Tiutiunov/Kisel/mods/kisel-prompts
A shopper browsing a rack in a slop shop
README

<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">

Чем занятаКакая она
MikuClaude Code: сессии, разрешения, вопросы, чатСтарательная, напевает под нос. Шлёпнешь, надуется
RinУведомления Windows: машет табличкой с названием программыШумная. От шлепка взрывается
LukaИнтернет: пинг, скорость, загрузки, замерСпокойная. На шлепок смотрит как на идиота
ZundamonМузыка из SpotifyХвастун, но паникует первым
TetoПроцессор, видеокарта, памятьСамодовольная, пока её не похвалишь

Главную выбираешь кликом по скамейке справа. Остальные никуда не деваются: сидят рядом, моргают, иногда корчат рожи.

Кто чем занят, можно переставить в настройках: хоть Тето на Claude Code, хоть Мику на музыку. Характер и любимая еда остаются при персонаже, работа переходит.

Таблетка

288 на 32 пикселя, живёт у любого края экрана. Что в ней показано, зависит от того, что сейчас происходит.

<img src="docs/img/bar.png" alt="Таблетка в разных состояниях" width="900">

Пара вещей, на которые ушло больше времени, чем стоило бы:

  • На скамейку и обратно они летят сальто через всю таблетку. Просто исчезнуть было бы скучно.
  • Пока Claude работает, Мику садится слева с его оранжевым зверьком в лапках, а рядом кто-нибудь составляет ей компанию. Они толкаются, перекидывают звёздочку, играют в прятки, тянутся к зверьку. Сценок больше сорока, и у каждой есть своя: над Тето смеются, и она замахивается метлой.
  • Замер скорости и чистка памяти запускаются прямо из таблетки, и пока они идут, таблетка наполняется жидкостью.
  • Если играет музыка и их хотя бы двое, время от времени начинается танец.
  • Минуту ничего не происходит, и таблетка уезжает за край. Если играет музыка, остаётся мини-плеером, и по её краю можно перематывать трек.
  • В полноэкранной игре она опускается к самому низу экрана, чтобы не висеть посреди картинки.
  • Двигается кнопкой со стрелками в шапке карточки: она отлипает от края, идёт за курсором как желе, а когда отпустишь, сама летит к ближайшему краю.
  • При запуске Мику выныривает снизу экрана, машет, и только потом появляется таблетка, в которую она улетает.

<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 при этом убираются, твои настройки остаются.

Как подружить с 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 в командной строке. Моды загружаются в сессиях, запущенных после установки.

Discord

По желанию 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.

Source 2 files
hooks/register.ts 105 lines
1import type { Register } from 'claude-code'
2
3import { clean, folder, pending, reply } from './inbox'
4
5export const register: Register = on => {
6  let stop: (() => void) | undefined
7  let inbox: string | undefined // this session's folder, once known
8  let said = 0
9
10  // What Claude says in the main conversation goes back to Kisel, one file per reply,
11  // so its chat shows both sides. Only while Kisel has made the folder (it is running).
12  on('turn.step', async function* ($, e, next) {
13    const result = yield* next(e)
14    const text = e.agentId === undefined ? reply(result.answer) : null
15
16    if (text !== null && inbox !== undefined) {
17      try {
18        if (await $.fs.exists(inbox)) {
19          await $.fs.write(`${inbox}/${await $.clock.now()}-${said++}.reply`, text)
20        }
21      } catch {
22        // Kisel's folder went away mid-write: nothing to tell
23      }
24    }
25
26    return result
27  })
28
29  on('session.start', async ($, e, next) => {
30    const result = await next(e)
31    stop?.()
32
33    const home = await $.env.get('USERPROFILE')
34    if (!home) {
35      return result // (not Windows, or no Kisel folder to share)
36    }
37
38    const dir = folder(home, await $.session.id())
39    inbox = dir
40    const done = new Set<string>() // handed over by this run (a receipt may fail to write)
41    let isBusy = false
42    let tick = 0
43    let lastReport = ''
44
45    const look = async (): Promise<void> => {
46      if (isBusy) {
47        return
48      }
49
50      isBusy = true
51
52      try {
53        // Kisel makes the folder when it first sees the session; until then there is nothing to do.
54        if (!(await $.fs.exists(dir))) {
55          return
56        }
57
58        if (tick++ % 2 === 0) {
59          await $.fs.write(`${dir}/alive`, String(await $.clock.now()))
60        }
61
62        // every ten looks: what the session knows of the account's limits, for the rings in Kisel's chat
63        if (tick % 10 === 1) {
64          const usage = await $.session.usage()
65          const report = JSON.stringify({
66            model: await $.session.model(),
67            limits: usage.rateLimits.map(l => ({ kind: l.kind, used: l.percentUsed, resetsAt: l.resetsAt ?? '' })),
68            context:
69              usage.context.percent === undefined
70                ? undefined
71                : { used: usage.context.percent, tokens: usage.context.tokens ?? 0, window: usage.context.window },
72          })
73
74          if (report !== lastReport) {
75            await $.fs.write(`${dir}/limits`, report)
76            lastReport = report
77          }
78        }
79
80        const names = (await $.fs.list(dir)).map(entry => entry.name)
81
82        for (const name of pending(names, done)) {
83          const text = clean(await $.fs.read(`${dir}/${name}.prompt`))
84          done.add(name)
85          await $.fs.write(`${dir}/${name}.taken`, '')
86
87          if (text !== null) {
88            // (the person's own words, typed in Kisel: read bare, in a turn of its own once the session is idle)
89            void $.prompt.submit({ text, asUser: true })
90          }
91        }
92      } catch {
93        // a file half-way through being replaced, or the folder just removed: the next look sees it whole
94      } finally {
95        isBusy = false
96      }
97    }
98
99    const timer = $.clock.every(1000, () => void look())
100    stop = () => timer.cancel()
101
102    return result
103  })
104}
105
hooks/inbox.ts 53 lines
1// The folder Kisel and this mod share for one session (Kisel's side is src/core/PromptRelay):
2//   alive        this mod's heartbeat
3//   <n>.prompt   a prompt Kisel wrote
4//   <n>.taken    this mod's receipt for it
5
6//
7// It is in the home folder, not under AppData: the Claude desktop app is a packaged app,
8// and Windows keeps what it (and this mod inside it) writes under AppData in a private
9// copy that Kisel never sees. (Kisel names the same folder in src/core/Paths.h.)
10
11const BACKSLASH = String.fromCharCode(92)
12
13export const folder = (home: string, sessionId: string): string => {
14  let base = home.split(BACKSLASH).join('/')
15
16  while (base.endsWith('/')) {
17    base = base.slice(0, -1)
18  }
19
20  return `${base}/.kisel/inbox/${sessionId}`
21}
22
23// The prompts still to be handed over, oldest first: a `.prompt` without its `.taken`,
24// not handed over already by this run. (Their names begin with the time Kisel wrote them.)
25export const pending = (names: readonly string[], done: ReadonlySet<string>): string[] => {
26  const taken = new Set(names.filter(n => n.endsWith('.taken')).map(n => n.slice(0, -'.taken'.length)))
27
28  return names
29    .filter(n => n.endsWith('.prompt'))
30    .map(n => n.slice(0, -'.prompt'.length))
31    .filter(n => !taken.has(n) && !done.has(n))
32    .sort()
33}
34
35// What Claude said, for Kisel's chat: nothing when it said nothing (a step that only
36// called tools), and cut where a bubble would stop being a bubble.
37export const reply = (answer: string): string | null => {
38  const body = answer.trim()
39
40  if (body === '') {
41    return null
42  }
43
44  return body.length > 12000 ? `${body.slice(0, 12000)}\n…` : body
45}
46
47// A prompt is text a person typed into a one-line field: bounded, and never empty.
48export const clean = (text: string): string | null => {
49  const body = text.trim()
50
51  return body === '' || body.length > 20000 ? null : body
52}
53