Skip to main content

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.

Сделано три вещи:

  1. Одно соединение на весь диалог вместо соединения на ход. Тот же слой http.client, на котором urllib построен, только без принудительного закрытия. Контрольный замер одного и того же вопроса три раза подряд:

    1-й ход 2-й 3-й
    было (urllib) 1.42 с 1.62 с 1.64 с
    стало (пул) 1.44 с 0.97 с 0.96 с
  2. Прогрев в фоне при старте: DNS, TCP и TLS уходят из первого вопроса, пока печатается баннер.

  3. --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

Один набор — десять прогонов, каждый в отдельном процессе с пределом времени: 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.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for urok 0.11.1
File Size Uploaded Provenance
urok-0.11.1.tar.gz 87.2 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for urok 0.11.1
File Interpreter ABI Platform
urok-0.11.1-py3-none-any.whl Python 3 none any Details

Total release size: 155.8 kB

Release files / urok-0.11.1.tar.gz

Download URL urok-0.11.1.tar.gz
Size 87.2 kB
Tags Source
SHA-256 checksum
How to use checksums
9ab5c151a973e9cac74203a421c371711721111f6b73610e86407b75b38d0068
BLAKE2b-256 checksum
How to use checksums
158dff635ecc55083cc32d02c48f760831da0406ab58fce40eb6f300aafd30de
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.1-py3-none-any.whl

Download URL urok-0.11.1-py3-none-any.whl
Size 68.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
30574aea94769ad4e3538e53e0bf3cf62411647305c886231774e7ec06f2aa13
BLAKE2b-256 checksum
How to use checksums
78e258a81823554a3d371bb7b457203d44283b3a2407b64b6f6a287bd2de0608
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.1

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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page