urok
Консольный помощник для учёбы. Объясняет темы, проверяет домашку, помогает разобраться в том, что не понял.
Работает сразу после установки: бэкенд — OpenCode Zen, модель
space-bunny-free, ключ public. Ничего настраивать не нужно.
Ноль зависимостей — только стандартная библиотека Python.
Установка
pip install urok
Три способа задать вопрос
urok # диалог, история в текущей сессии
urok "объясни дроби" # один вопрос, ответ, выход
urok - < task.txt # вопрос из файла или пайпа
Разовый режим не открывает диалог: один вопрос — один запрос к API — один ответ. Годится для скриптов и для bat-файла на рабочий стол.
Под конкретный урок
urok --grade 9 --subject физика
Многострочный вопрос
Если условие задачи не влезает в строку:
> !multi
... Реши задачу:
... Масса тела 2 кг,
... высота 3 м.
!end
Свой маркер конца, если в тексте задачи встречается !end:
!multi моя … !end моя.
Формулы
Модель сама пишет формулы юникодом — в системном промпте стоит инструкция с конкретными правилами (взяты из claude-math):
| Вместо | Пишет |
|---|---|
\frac{1}{2} |
1/2 |
x^{2} |
x² |
\sqrt{5} |
√(5) |
\leq \geq \neq \approx |
≤ ≥ ≠ ≈ |
\alpha \beta \theta \pi |
α β θ π |
\sum \prod \int \infty |
∑ ∏ ∫ ∞ |
\in \notin \subset \cup \cap |
∈ ∉ ⊂ ∪ ∩ |
\mathbb{R} |
ℝ |
Если модель всё же выдаст LaTeX, urok переведёт его на лету, не ломая
стриминг: незакрытая формула придерживается до закрывающей скобки, обычный
текст печатается сразу.
Знак $ разбирается по содержимому, а не по первому символу: $x^2$ — это
формула, а $HOME, $(whoami), ${HOME} и $5 — нет. Формула перед блоком
кода переводится, внутри блока не трогается.
Отключить перевод: флаг --raw-math.
Дроби линейные — 1/2, а не вертикальная раскладка. Терминал не рисует
двумерные формулы, а линейная форма переживает копирование, поиск, tmux и
SSH. Блок Unicode-букв вроде 𝐀 и 𝑉𝑎𝑟 не используется: эти кодовые точки
ломают копирование и скринридеры.
Вставка картинок
Три способа, все без набора пути:
urok --image photo.png "что на фото?" # сразу, разовый запуск
В диалоге — просто вставь путь и нажми Enter. Путь сам превращается в вложение, команда не нужна:
> C:\Users\me\Pictures\photo.png
┌ photo.png ────────┐
│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀ │
│ █████ │
└────────────────────┘
Ctrl+V: скопируй картинку, нажми Ctrl+V, затем Enter на пустой строке. Пустая строка означает «возьми то, что в буфере».
Работает в двух слоях, потому что терминалы ведут себя по-разному:
| Слой | Что делает | Где работает |
|---|---|---|
| распознавание пути | терминал прислал путь файлом — ловим сразу | Windows Terminal, перетаскивание файла |
буфер через WinAPI CF_DIB |
читаем сырые байты и сами собираем PNG | conhost, любой терминал |
Второй слой — чистый ctypes плюс свой энкодер PNG. Запуск PowerShell обошёлся
бы в секунду на каждое нажатие.
Временный файл из буфера удаляется сразу после отправки запроса — эфемерный режим обещает пустой диск, и обещание сдержано.
Поддерживаются png, jpg, gif, bmp, webp. Тип определяется по сигнатуре файла, а не по расширению.
Зрение работает не у всех моделей. Из бесплатных на Zen проверено вживую:
space-bunny-free — видит и описывает картинки. Платные требуют настоящего
ключа, а не public. Проверить свою:
urok --model deepseek-v4-flash-free --image photo.png "опиши"
Путь к вложенному файлу передаётся модели текстом: без этого она не может открыть картинку в инструменте и честно отвечает «файла у меня нет». Временная копия живёт только до конца ответа.
В истории хранится путь к файлу, а не base64. При загрузке сессии файл перекодируется заново; если файла нет, сообщение уходит без картинки, а не падает.
Команды
Четыре команды и всё. Всё остальное делается флагами при запуске.
| Команда | Что делает |
|---|---|
/drop |
убрать приложенные картинки |
/model |
какая модель сейчас; /model имя — переключить |
/wipe |
удалить всю сохранённую историю |
/exit |
выйти, Ctrl+D тоже |
/help |
эта подсказка, без неё тоже можно |
Интерфейс английский, ответы модели — на русском. Английский выбран потому, что терминал не переносит длинные кириллические строки аккуратно, а команды и так набираются быстрее латиницей.
Скорость
Главное тормозное место оказалось не в модели. urllib.request отправляет
Connection: close, поэтому каждый ход диалога платил новое TCP + TLS
рукопожатие. Замерено на этом же эндпоинте: ~300 мс, из них ~240 мс — TLS.
Сделано три вещи:
-
Одно соединение на весь диалог вместо соединения на ход. Тот же слой
http.client, на которомurllibпостроен, только без принудительного закрытия. Контрольный замер одного и того же вопроса три раза подряд:1-й ход 2-й 3-й было (urllib) 1.42 с 1.62 с 1.64 с стало (пул) 1.44 с 0.97 с 0.96 с -
Прогрев в фоне при старте: DNS, TCP и TLS уходят из первого вопроса, пока печатается баннер.
-
--effort noneбольше не даёт 400. На этом шлюзеreasoning_effort: "none"отклоняется, хотя у OpenAI значение документировано. Единственный способ отключить рассуждение — не отправлять поле, что теперь и делаетnone.
Что НЕ помогает, чтобы не тратить на это время: укоротить системный промпт
(он 1023 символа, ~255 токенов — на задержку это единицы миллисекунд), HTTP/2
(в стандартной библиотеке его нет, а на один последовательный запрос он даёт
меньше одного RTT), X-Accel-Buffering (это заголовок ответа nginx, клиент его
не читает), кеширование промпта (порог 1024 токена, мы ниже).
Остаточная задержка — серверная: ответ на «привет» у этого провайдера гуляет
от 1.4 с до 15 с в зависимости от загрузки. Модель space-bunny-free
бесплатная, и это её потолок, а не клиента.
Инструменты
Модель может считать, и по умолчанию это разрешено — калькулятор безопасен.
| Флаг | Что даёт |
|---|---|
| (по умолчанию) | калькулятор: calculate через безопасный разбор, без eval |
--allow-code |
плюс запуск кода на Python в отдельной папке с таймаутом |
--no-tools |
выключить всё, модель отвечает только текстом |
Код приходит от модели и не проверяется: --allow-code — это доверие, не
песочница. Без флага модель получает отказ и объясняет решение словами.
Черновик перед вызовом инструмента не печатается: если модель пишет «сейчас посчитаю» и вызывает калькулятор, пользователь видит только результат, а не оба абзаца.
Глубина размышления: --effort low (по умолчанию) быстро, medium, high,
none — поле не отправляется совсем, рассуждение выключено. Без ограничения
модель может молчать минутами на расплывчатом вопросе.
Как выглядит ответ
Всё оформление взято из бинаря Claude Code 2.1.284 — палитра, глифы, тайминги.
Ответ печатается на лету, без сырых markdown-маркеров: **жирный** становится
жирным, `код` — жёлтым, - пункт — с маркером, блок ``` — с полосой
слева, ссылка → «текст (url)».
Три блока, и каждый узнаётся с одного взгляда:
| блок | как выглядит | куда |
|---|---|---|
| вопрос | > привет, акцент + тусклый |
stdout |
| рассуждение | за тусклым бордюром │, без отступа |
stderr, только с --show-reasoning |
| вызов инструмента | ● calc 3**6 и результат под ним, максимум 6 строк |
stderr |
| ответ | отступ 2 колонки, без бордюра | stdout |
Бордюр и есть разделитель: stdout и stderr смешиваются в терминале, и без
явной метки рассуждение не отличить от ответа. Вызов инструмента показывает
результат, а не выражение — слово calculate повторяло то, что модель только
что сказала вслух, а 3**6 без ответа ничего не сообщало.
| Элемент | Как у Claude Code | У нас в 8-цветном терминале |
|---|---|---|
| акцент | #D77757 терракота |
bright red |
| тусклый | #999999 |
bright black |
| еле видный | #505050 |
bright black |
| рамка | #888888 |
bright black |
| успех / ошибка | #4EBA65 / #FF6B80 |
green / bright red |
| зависло | #AB2B3F |
bright red |
| разделитель | · (U+00B7) |
тот же |
| промпт ввода | > |
> |
| рамка приветствия | в версии 2 убрана | тоже убрана |
Пока модель думает, внизу пульсирует глиф из 12 кадров пинг-понгом
· ✢ * ✶ ✻ ✽ ✽ ✻ ✶ * ✢ ·, смена раз в 120 мс. Надпись нарастает сама, чтобы
долгое ожидание не выглядело зависанием: thinking → через 10 с
still thinking → 20 с thinking more → 30 с thinking some more → 45 с
deep in thought. Если 3 секунды нет ни байта, цвет уходит в красный: значит,
всё встало.
Никакого цвета там, где его не должно быть: NO_COLOR, перенаправленный stdout и
терминал без TrueColor отключают оформление сами. Без юникода глифы и рамки
переключаются на |/-\ и +-|.
Ошибки всегда идут в stderr, ответ — в stdout. urok "вопрос" > ответ.txt
сохранит только ответ.
Удобство ввода
↑ ↓ |
история вопросов |
Tab |
дополняет команды (/mo → /model) и пути файлов |
Ctrl+V + Enter на пустой строке |
картинка из буфера |
Ctrl+D |
выход |
Ctrl+C |
прервать текущий ответ, диалог остаётся |
При старте делается ровно один вывод в одну строку и ни одного сетевого запроса: сервер проверяется лениво, при первом вопросе. Раньше на старте летел запрос со списком моделей — из-за него вход ощущался медленным.
Рассуждение модели
Модели с thinking-режимом шлют два потока: рассуждение и ответ. По умолчанию
рассуждение скрыто. Включить — флагом urok --show-reasoning на весь запуск.
Рассуждение всегда идёт в stderr тусклым, ответ — в stdout. Это
означает, что urok "вопрос" > ответ.txt сохранит только ответ, без
рассуждения и без служебных строк.
Другие бесплатные модели
space-bunny-free по умолчанию
deepseek-v4-flash-free
muse-spark-1.3-contributor-free
muse-spark-1.2-contributor-free
mimo-v2.5-free
nemotron-3-ultra-free
longcat-2.5-preview-free
Полный список на сервере: https://opencode.ai/zen/v1/models. Переключить:
/model muse-spark-1.3-contributor-free или флагом
urok --model deepseek-v4-flash-free.
Другой провайдер
Через флаги:
urok --base https://api.groq.com/openai/v1 --key gsk_... --model llama-3.3-70b-versatile
Или через переменные окружения, чтобы не светить ключ в истории:
set UROK_BASE_URL=https://api.groq.com/openai/v1
set UROK_API_KEY=gsk_...
set UROK_MODEL=llama-3.3-70b-versatile
urok
Подходит любой OpenAI-совместимый адрес: Groq, OpenAI, LM Studio, Ollama,
llama.cpp на http://127.0.0.1:8080/v1.
История по умолчанию не пишется
Закрыл консоль — на диске ничего не осталось. Папка ~/.urok не создаётся
вообще, даже mkdir не вызывается. При выходе печатается проверка:
на диск ничего не записано, следов нет
Если папка всё же есть от прошлых запусков с --keep-history — выход скажет
об этом прямо и предложит /wipe.
Запомнить диалог между запусками:
urok --keep-history
--keep-history записывает каждый запуск отдельной сессией в ~/.urok.
Читать их обратно нечем: продолжения нет, /wipe удаляет папку целиком.
Флаг существует ради отладки и разбора того, что модель отвечала, — не ради
удобства.
Что остаётся на диске помимо истории
Пакет убирает за собой только историю диалога. Это остаётся и находится вне его досягаемости:
| След | Где | Как убрать |
|---|---|---|
| сам пакет | site-packages/urok |
pip uninstall urok |
| байт-код | site-packages/urok/__pycache__ |
pip uninstall urok |
| история команд PowerShell | ConsoleHost_history.txt |
Clear-History и удаление файла |
| сетевой лог | DNS/прокси школной сети | вне твоего контроля |
Запросы к opencode.ai видны сетевому логу в любом случае — это происходит
независимо от того, что лежит на диске.
Настройка своего промпта
urok --system "Объясняй как пятикласснику, только формулы и короткие примеры."
Тесты
python tests/run_all.py
Один набор — девять прогонов, каждый в отдельном процессе с пределом времени:
9/9 наборов, 281 проверка. Список: check_code, check_readme, test_ui,
test_math, test_tools, test_toolloop, test_images, smoke, drive_repl.
Отдельно полезно:
python tests/smoke.py # транспорт, диалог, эфемерность, ошибки
python tests/test_ui.py # цвета, markdown, рамки, спиннер, буфер
python tests/test_math.py # рендер формул и разбор $
python tools/probe_dollar.py # разбор $ на живых примерах, для глаза
python tools/bench_pool.py # переиспользуется ли соединение
python tools/bench_dialog.py # время каждого хода живого диалога
python tools/show_dialog.py # как это выглядит на самом деле
Все поднимают фейковый API в потоках и гоняют через него настоящий код. Ноль внешних зависимостей.
Откуда взяты решения
Код свёрялся с четырьмя референсами.
simonw/llm — эталонный CLI для LLM:
- событийная модель стрима вместо колбэка на кусок текста (
llm/cli.py:103) - разделение stdout/stderr для ответа и рассуждения (
llm/cli.py:106) - многострочный ввод
!multi/!end(llm/cli.py:176) - свой
User-AgentвместоPython-urllib(llm/cli.py:256) - привязка стрелок к истории через
readline(llm/cli.py:1270)
charmbracelet/bubbles, charmbracelet/lipgloss, charmbracelet/glow —
оформление терминала:
- markdown без подсветки синтаксиса, как в glow:
**→ жирный,---→ список, NO_COLORи непроглядывающий stdout отключают оформление, а не ломают его
Claude Code 2.1.284 — источник всей палитры и всех таймингов. Прочитан не по блогам, а вытащен из поставленного бинаря:
- 12 кадров спиннера пинг-понгом,
Math.floor(t/120) % 12, период 1,44 с - акцент
#D77757, тусклый#999999, еле видный#505050, зависло#AB2B3F - перерисовка 50 мс во время запроса и 100 мс потом, но только сам ряд со спиннером — остальное перерисовывается ~25 раз за ход вместо ~380
- надпись нарастает:
thinking→still thinking→thinking more - рамка приветствия в версии 2 убрана, осталась одна строка
- сразу после отправки показывается глиф: ощущаемая задержка 50 мс, а не TTFT
vladimirrott/claude-math — рендер формул в терминале:
- основное решение на уровне промпта: просить юникод, а не LaTeX
- не использовать блок Mathematical Alphanumeric Symbols (𝐀, 𝑉𝑎𝑟)
- дроби линейные: форма должна переживать копирование и поиск
Не взято: pydantic, click, httpx, rich, sqlite-utils, pytest-recording.
У llm это 145 КБ кода и четыре зависимости, здесь около двух тысяч строк
и ноль.
Рендер формул проверялся и на pylatexenc, flatlatex, pytexprint,
terminatex. Первые три — либо зависимость, либо ограниченный набор команд;
terminatex ближе всего, но тянет rich и графические протоколы, которые в
обычном терминале не работают. Свой рендерет — 216 строк, ноль зависимостей.
Metadata
Release files for urok 0.10.2
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| urok-0.10.2.tar.gz | 67.8 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| urok-0.10.2-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 127.5 kB
Release files / urok-0.10.2.tar.gz
| Download URL | urok-0.10.2.tar.gz |
|---|---|
| Size | 67.8 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
b40240c834b0de7496aa4a66bb99e6386ccce9e62e144c6bc67db99f5b0c83c5
|
|
BLAKE2b-256 checksum How to use checksums |
5e05c7d4ecc2f65d4931266581edcfd7cae59d5991d17658d7bbf65a42c9ca35
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.0
|
Release files / urok-0.10.2-py3-none-any.whl
| Download URL | urok-0.10.2-py3-none-any.whl |
|---|---|
| Size | 59.7 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
def308142098bcc01f1dfc7389d50e71dc2bb4f8b9fd6664ea745b6be039c5a2
|
|
BLAKE2b-256 checksum How to use checksums |
6cb7b7ef1f54b76a462bdeb6200af832c5e009fa4e0b65c66d4c72f8e33a5b88
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.0
|
Security for urok 0.10.2
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.