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 |
удалить всю сохранённую историю |
/copy |
скопировать последний ответ в буфер обмена, целиком |
/exit |
выйти, Ctrl+D тоже |
/help |
эта подсказка, без неё тоже можно |
/copy берёт последний ответ диалога и кладёт в буфер без разметки: **жирный**
становится жирный, escape-последовательности вырезаются. Рассуждение и служебные
строки в буфер не попадают. Без команды Ctrl+C копирует только что напечатанный
кусок, а половина ответа с картинками и формулами так теряется.
Интерфейс английский, ответы модели — на русском. Английский выбран потому, что терминал не переносит длинные кириллические строки аккуратно, а команды и так набираются быстрее латиницей.
Что пакет делает с системой
Коротко: ничего. Ни реестра, ни автозапуска, ни службы, ни своего .exe,
ни файлов вне %TEMP%, который удаляется сам. Ниже — подробно, потому что
именно это и проверяют антивирусы и корпоративные политики.
Читает
| что | когда | откуда |
|---|---|---|
| буфер обмена (только формат CF_DIB, то есть картинка) | только по Ctrl+V + Enter на пустой строке |
WinAPI OpenClipboard |
| буфер обмена (только текст) | только когда сам копирует ответ | WinAPI SetClipboardData |
| вопрос и файлы, которые ты назвал сам | каждый ход | — |
Буфер никогда не читается сам. Пока ты не нажмёшь Ctrl+V, пакет в него
не заглядывает. Ничего не перехватывает и не подменяет — только читает по
твоей команде и записывает, когда ты сам попросил скопировать.
Отправляет
Вопрос, картинка и результаты вызова инструментов уходят на
https://opencode.ai/zen/v1 — тот сервер, который указан по умолчанию. Это
видимое следствие вопроса: чтобы ответить, текст должен уйти. Своего сервера
пакет не поднимает, телеметрии не шлёт, ничего не скачивает и не запускает.
Пишет
| где | что | когда удаляется |
|---|---|---|
%TEMP%\urok-*.png |
картинка, вставленная из буфера | сразу после отправки запроса |
~/.urok |
история диалога | только с флагом --keep-history |
Осиротевшие urok-*.png (Ctrl+C или падение питания между вложением и
следующим ходом) подметаются при следующем запуске, если им больше часа.
Трогаются только файлы с нашим префиксом в системной папке temp.
Запускает
По умолчанию — ничего. Калькулятор считает в том же процессе, безопасным
разбором выражения, без eval. Запуск кода на Python — только за флагом
--allow-code, отдельным процессом, в отдельной временной папке, с таймаутом
и списком аргументов вместо shell=True. clip.exe не запускается никогда:
текст в буфер пишется прямо через WinAPI.
Проверяется автоматически
python tests/check_footprint.py — 31 проверка через ast, а не поиском по
тексту. Запрещены вызовы eval/exec/compile/__import__, shell=True,
запуск cmd/powershell/clip.exe и прочих LOLBin, WinAPI для инъекций и
повышения привилегий, запись в реестр, автозапуск, самостоятельное
добавление себя в исключения антивируса, обфускация и упаковка в
исполняемый файл. Тест падает, если что-то из этого появится, — чтобы
«потом пригодится» не превратилось в дыру.
Если сработало антивирус
Честный ответ: гарантировать, что не сработает, нельзя. Детекция обновляется незаметно, у каждой вендоры своя логика, и VirusTotal сам не выносит вердикты. Что можно — сделать пакет проверяемым: он состоит из 23 текстовых файлов на Python, без бинарника, без сборщика, без обфускации. Его можно прочитать целиком, и это лучшая защита от подозрений.
Если python.exe с аргументами вызывает вопросы у корпоративного антивируса
или EDR — исключение добавляет администратор на той машине, не пакет:
# Windows Defender: исключение по пути и по процессу
Add-MpPreference -ExclusionProcess "python.exe"
Add-MpPreference -ExclusionPath "$env:LOCALAPPDATA\Programs\Python"
Microsoft: https://learn.microsoft.com/en-us/defender-endpoint/defender-endpoint-false-positives-negatives Подать файл на анализ как разработчик: https://www.microsoft.com/en-us/wdsi/filesubmission
Чего пакет не делает и не будет делать: не обфускается, не пакуется в исполняемый файл, не добавляет себя в исключения сам, не прячет поведение. Обфускация ломает смысл учебного инструмента, а ASR-правило «Block execution of potentially obfuscated scripts» делает подозрительным ровно то, что выглядит безобидным.
Скорость
Главное тормозное место оказалось не в модели. 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)».
Модель часто пишет перед вызовом калькулятора «сейчас посчитаю» и повторяет это
же после результата. В терминале черновик затирается на настоящих строках, и
видно только итог. В пайпе или файле затереть нечего, поэтому ответ копится до
конца и печатается одним куском: urok "вопрос" > ответ.txt даёт в файле только
ответ, без кадров спиннера, escape-последовательностей и черновиков.
Три блока, и каждый узнаётся с одного взгляда:
| блок | как выглядит | куда |
|---|---|---|
| вопрос | > привет, акцент + тусклый |
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
Один набор — десять прогонов, каждый в отдельном процессе с пределом времени:
10/10 наборов, 314 проверок. Список: check_code, check_footprint,
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 # как это выглядит на самом деле
python tools/measure_footprint.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.11.3
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.11.3.tar.gz | 89.1 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| urok-0.11.3-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 159.1 kB
Release files / urok-0.11.3.tar.gz
| Download URL | urok-0.11.3.tar.gz |
|---|---|
| Size | 89.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
80910b936caf04f1543dd152ef8e508fa70f11f2d7bfacc766223e2c3589cedf
|
|
BLAKE2b-256 checksum How to use checksums |
21dc763d06068ff3599140ab2993f6d7ec154c38d442db45a686f80342b1d7e2
|
| 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.11.3-py3-none-any.whl
| Download URL | urok-0.11.3-py3-none-any.whl |
|---|---|
| Size | 70.0 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f63cd9ab22b33b49a4e09948274eda83e9095a816174ef267b5b5e297afef744
|
|
BLAKE2b-256 checksum How to use checksums |
feb49a2d26bdcfb28d9b49d0b5fb60e28df3551982afcb256fb8ae62c7d346f7
|
| 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.11.3
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.