Metadata-Version: 2.5
Name: urok
Version: 0.6.0
Summary: Консольный помощник для учёбы: объясняет темы, проверяет домашку, помогает разобраться
License: MIT
Keywords: chat,cli,homework,study,zen
Classifier: Environment :: Console
Classifier: Intended Audience :: Education
Classifier: Programming Language :: Python :: 3
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# urok

Консольный помощник для учёбы. Объясняет темы, проверяет домашку, помогает
разобраться в том, что не понял.

Работает сразу после установки: бэкенд — OpenCode Zen, модель
`space-bunny-free`, ключ `public`. Ничего настраивать не нужно.

Ноль зависимостей — только стандартная библиотека Python.

## Установка

```bash
pip install urok
```

## Три способа задать вопрос

```bash
urok                              # диалог, история в текущей сессии
urok "объясни дроби"              # один вопрос, ответ, выход
urok - < task.txt                 # вопрос из файла или пайпа
```

Разовый режим не открывает диалог: один вопрос — один запрос к API — один
ответ. Годится для скриптов и для bat-файла на рабочий стол.

## Под конкретный урок

```bash
urok --grade 9 --subject физика
```

## Многострочный вопрос

Если условие задачи не влезает в строку:

```
> !multi
  ... Реши задачу:
  ... Масса тела 2 кг,
  ... высота 3 м.
  !end
```

Свой маркер конца, если в тексте задачи встречается `!end`:
`!multi моя` … `!end моя`.

## Формулы

Модель сама пишет формулы юникодом — в системном промпте стоит инструкция с
конкретными правилами (взяты из [claude-math](https://github.com/vladimirrott/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` переведёт его на лету, не ломая
стриминг: незакрытая формула придерживается до закрывающей скобки, обычный
текст печатается сразу.

Отключить перевод: `/math off` или флаг `--raw-math`.

Дроби линейные — `1/2`, а не вертикальная раскладка. Терминал не рисует
двумерные формулы, а линейная форма переживает копирование, поиск, tmux и
SSH. Блок Unicode-букв вроде 𝐀 и 𝑉𝑎𝑟 не используется: эти кодовые точки
ломают копирование и скринридеры.

## Вставка картинок

Три способа, все без набора пути:

```bash
urok --image photo.png "что на фото?"   # сразу, разовый запуск
```

В диалоге — просто **вставь путь и нажми Enter**. Путь сам превращается в
вложение, `/attach` помнить не надо:

```
> C:\Users\me\Pictures\photo.png
┌ photo.png ────────┐
│▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀     │
│     █████          │
└────────────────────┘
```

**Ctrl+V:** скопируй картинку, нажми Ctrl+V, затем Enter на пустой строке.
Пустая строка означает «возьми то, что в буфере». Явно — команда `/paste`.

Работает в двух слоях, потому что терминалы ведут себя по-разному:

| Слой | Что делает | Где работает |
| --- | --- | --- |
| распознавание пути | терминал прислал путь файлом — ловим сразу | Windows Terminal, перетаскивание файла |
| буфер через WinAPI `CF_DIB` | читаем сырые байты и сами собираем PNG | conhost, любой терминал |

Второй слой — чистый `ctypes` плюс свой энкодер PNG. Запуск PowerShell обошёлся
бы в секунду на каждое нажатие.

Временный файл из буфера удаляется сразу после отправки запроса — эфемерный
режим обещает пустой диск, и обещание сдержано.

Поддерживаются png, jpg, gif, bmp, webp. Тип определяется по сигнатуре файла,
а не по расширению.

Зрение работает не у всех моделей. Из бесплатных на Zen проверено вживую:
`space-bunny-free` — видит и описывает картинки. Платные требуют настоящего
ключа, а не `public`. Проверить свою:

```bash
urok --model deepseek-v4-flash-free --image photo.png "опиши"
```

В истории хранится **путь** к файлу, а не base64. При загрузке сессии файл
перекодируется заново; если файла нет, сообщение уходит без картинки, а не
падает.

## Команды

| Команда | Что делает |
| --- | --- |
| `/help` | список команд |
| `/exit` | выйти, Ctrl+D тоже |
| `/clear` | забыть текущий диалог |
| `/model` | какая модель сейчас |
| `/model имя` | переключить модель прямо сейчас |
| `/models` | список моделей на сервере |
| `/base` | адрес сервера |
| `/base адрес` | сменить сервер |
| `/system` | показать или сменить системный промпт |
| `/reasoning` | показать или скрыть рассуждение модели |
| `/math` | переводить формулы в юникод или печатать LaTeX |
| `/attach файл` | приложить картинку по пути |
| `/paste` | взять картинку из буфера обмена |
| `/images` | список приложенных картинок |
| `/wipe` | удалить всю сохранённую историю |
| `/load` | продолжить прошлую сессию (нужен `--keep-history`) |
| `/sessions` | список сохранённых сессий |
| `/copy` | скопировать последний ответ в буфер (Windows) |

Интерфейс английский, ответы модели — на русском. Английский выбран потому,
что терминал не переносит длинные кириллические строки аккуратно, а команды
и так набираются быстрее латиницей.

## Удобство ввода

| | |
| --- | --- |
| `↑` `↓` | история вопросов |
| `Tab` | дополняет команды (`/mo` → `/model`) и пути файлов |
| `Ctrl+V` + `Enter` на пустой строке | картинка из буфера |
| `Ctrl+D` | выход |
| `Ctrl+C` | прервать текущий ответ, диалог остаётся |

При старте делается ровно один вывод в одну строку и **ни одного** сетевого
запроса: сервер проверяется лениво, при первом вопросе. Раньше на старте
летел запрос на `/models` — из-за него вход ощущался медленным.

## Рассуждение модели

Модели с thinking-режимом шлют два потока: рассуждение и ответ. По умолчанию
рассуждение скрыто. Включить на лету — `/reasoning on`, или на весь запуск —
`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
```

Полный список: `/models`. Переключить: `/model muse-spark-1.3-contributor-free`
или флагом `urok --model deepseek-v4-flash-free`.

## Другой провайдер

Через флаги:

```bash
urok --base https://api.groq.com/openai/v1 --key gsk_... --model llama-3.3-70b-versatile
```

Или через переменные окружения, чтобы не светить ключ в истории:

```bash
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`.

Запомнить диалог между запусками:

```bash
urok --keep-history
urok --keep-history --load 1
```

## Что остаётся на диске помимо истории

Пакет убирает за собой только историю диалога. Это остаётся и находится вне
его досягаемости:

| След | Где | Как убрать |
| --- | --- | --- |
| сам пакет | `site-packages/urok` | `pip uninstall urok` |
| байт-код | `site-packages/urok/__pycache__` | `pip uninstall urok` |
| история команд PowerShell | `ConsoleHost_history.txt` | `Clear-History` и удаление файла |
| сетевой лог | DNS/прокси школной сети | вне твоего контроля |

Запросы к `opencode.ai` видны сетевому логу в любом случае — это происходит
независимо от того, что лежит на диске.

## Настройка своего промпта

```bash
urok --system "Объясняй как пятикласснику, только формулы и короткие примеры."
```

## Тесты

```bash
python tests/smoke.py        # 49: транспорт, диалог, эфемерность
python tests/test_math.py    # 50: рендер формул
python tests/test_images.py  # 28: картинки и мультимодальность
python tests/test_paste.py   # 27: вставка, буфер, автодополнение
```

Все четыре поднимают фейковый API в потоках и гоняют через него настоящий
код. Ноль внешних зависимостей. `tests/drive_repl.py` — ручной прогон REPL для
глазами: баннер, вставка, превью.

## Откуда взяты решения

Код свёрялся с двумя референсами.

**`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`)

**`vladimirrott/claude-math`** — рендер формул в терминале:

- основное решение на уровне промпта: просить юникод, а не LaTeX
- не использовать блок Mathematical Alphanumeric Symbols (𝐀, 𝑉𝑎𝑟)
- дроби линейные: форма должна переживать копирование и поиск

Не взято: pydantic, click, httpx, sqlite-utils, pytest-recording. Там 145 КБ
кода и четыре зависимости, здесь около тысячи строк и ноль.

Рендер формул проверялся и на `pylatexenc`, `flatlatex`, `pytexprint`,
`terminatex`. Первые три — либо зависимость, либо ограниченный набор команд;
`terminatex` ближе всего, но тянет `rich` и графические протоколы, которые в
обычном терминале не работают. Свой рендерет — 250 строк, ноль зависимостей.
