Руководство по AI CLI · три инструмента командной строки
В ai-study-kit встроены три AI CLI, превращающие учебные материалы в три артефакта цикла: teach-generate производит курсы, grill-wrong — глубокий разбор ошибок, podcast-generate — подкасты для повторения. Всё работает на ваших собственных API-ключах LLM/TTS и с любым сервисом по OpenAI-совместимому протоколу (OpenAI / Zhipu GLM / DeepSeek / Kimi / Qwen / Doubao и др.).
У каждого CLI есть короткая команда в корне репозитория; далее в руководстве используются короткие формы (эквивалент node apps/quiz-app/scripts/<имя-скрипта>.mjs):
| Короткая команда | Скрипт | Продукт |
|---|---|---|
pnpm run ai:teach |
teach-generate.mjs |
HTML курсов (lessons/*.html) |
pnpm run ai:grill |
grill-wrong.mjs |
HTML глубокого разбора ошибок (study/wrong-questions/*.html) |
pnpm run ai:podcast |
podcast-generate.mjs |
сценарий подкаста + транскрипт + аудио (podcast-out/) |
Быстрый старт
Заголовок раздела «Быстрый старт»1. Настройте API-ключ
Заголовок раздела «1. Настройте API-ключ»cp .env.example .env# отредактируйте .env — как минимум три пункта: LLM_BASE_URL / LLM_API_KEY / LLM_MODELПолный список провайдеров и пояснения — в configuration.ru.md.
2. Запустите бэкенд quiz-app (нужен для grill)
Заголовок раздела «2. Запустите бэкенд quiz-app (нужен для grill)»pnpm run server # в другом терминале, поднимается на :87873. Запустите три CLI
Заголовок раздела «3. Запустите три CLI»# A. сгенерировать курс (из course-spec.json)pnpm run ai:teach -- --theme dev-intro
# B. сгенерировать глубокий разбор ошибок (выгружает ошибки с сервера)pnpm run ai:grill -- --theme dev-intro
# C. сгенерировать подкаст (из любых учебных материалов)pnpm run ai:podcast -- --input examples/dev-intro/lessons/git-basics.htmlВсе три ИИ-CLI поддерживают --json: человекочитаемые логи уходят в stderr, а stdout отдаёт единый JSON результата (манифест путей продуктов) — для потребления другими агентами / скриптовыми конвейерами (та же конвенция, что у mastery-report --json). Тупиковые пути (например, ошибок сейчас нет) тоже выдают JSON (status: "noop"), чтобы конвейер мог ветвиться.
teach-generate — генерация курса
Заголовок раздела «teach-generate — генерация курса»Превращает спецификацию темы (mission + resources + audience) в многоурочный самодостаточный HTML-курс.
Входные данные
Заголовок раздела «Входные данные»examples/<theme>/course-spec.json:
{ "theme": "react-basics", "mission": "学完能独立写一个 React 组件库", "audience": "有 JS 基础、第一次学 React 的开发者", "depth": "beginner", // beginner | intermediate | advanced "lessonsCount": 3, // 想要几节课 "outline": ["Hooks 基础", "状态管理", "组件设计"], // 可选,不填让 LLM 自动拆 "resources": [ // 可选,权威材料链接 { "title": "React 官方文档", "url": "https://react.dev" } ]}Выходные данные
Заголовок раздела «Выходные данные»examples/<theme>/lessons/0001-<slug>.html, 0002-<slug>.html…:
- каждый урок — самодостаточный HTML (общая ссылка на
../assets/styles.css) - структура: h1 + meta + lead + несколько h2 + callout’ы (ключевая мысль / предупреждение / совет) + quiz-anchor
- схема механизма: минимум один встроенный SVG на урок — крупная картинка, мало слов, только механизм (узлы + стрелки для потока / иерархии / контраста)
- обратные ссылки на источники: каждый урок завершается блоком
📚 Источникисо списком авторитетных ссылок — источники =resourcesизcourse-spec.json, объединённые сRESOURCES.mdтемы (авторитетный список ресурсов, принятый в workflow teach), с дедупликацией по URL; объединённый список идёт и в справочные материалы LLM, и в подвал урока (артефактная сторона принципа «понятия строятся по справочным материалам») - загрузка текста источников (v0.13): при генерации курса текст страниц по этим ссылкам загружается в контекст подготовки LLM (принцип «понятия строятся по справочным материалам» переходит со уровня ссылок на уровень содержания — загруженный текст — первая основа подготовки); локальный кеш дедуплицирует по URL (
apps/quiz-app/node_modules/.cache/teach-resources/), повторные запуски не загружают заново; недоступный источник деградирует до ссылки-URL, не прерывая генерацию - уроки сшиты ссылками prev/next
Использование
Заголовок раздела «Использование»pnpm run ai:teach -- --theme react-basicspnpm run ai:teach -- --theme X --lessons 5 # переопределить lessonsCountpnpm run ai:teach -- --theme X --lang en # курс на английскомpnpm run ai:teach -- --theme X --json # машиночитаемый вывод (для агентов)Без --theme по умолчанию берётся dev-intro. Образец: examples/dev-intro/course-spec.json.
grill-wrong — генерация глубокого разбора ошибок
Заголовок раздела «grill-wrong — генерация глубокого разбора ошибок»Выгружает ваши ошибочные ответы с сервера, LLM кластеризует их по пунктам экзамена и глубоко раскрывает каждый кластер.
Процесс
Заголовок раздела «Процесс»GET /api/progressвыгружает список ошибок (бэкенд задаётся переменной окруженияSERVER)- соединяется с
examples/<theme>/questions.json, чтобы получить полный текст вопросов - LLM кластеризует ошибки по пунктам экзамена (например, «git reset vs revert» — 3 вопроса, «коды состояния HTTP» — 2)
- по каждому кластеру LLM пишет HTML глубокого разбора (таблица ключевых различий + блок-схема принятия решений + предупреждения о ловушках + тренировка на вариантах)
- результат пишется в
examples/<theme>/study/wrong-questions/cluster-NN-<slug>.html(продукты из старого расположенияwrong-questions/распознаются и переносятся автоматически) - обновляется
examples/<theme>/study/wrong-questions/index.html— центральная страница разбора ошибок - заодно пишется профиль обучающегося: LLM дополнительно фиксирует причины ошибок по пунктам экзамена (wrongReasons / advice) в
examples/<theme>/study/records/profile.json(машиночитаемый; кластеры, пересекающиеся по id вопросов, сливаются в один пункт и накапливаются). Профиль — приватные данные обучающегося, они никогда не публикуются со сборкой; очередной запускmastery-reportили зонд/ask-coachподхватит их автоматически, и рекомендация станет конкретной: «EP-03 ошиблись 2 раза, причина: слабое владение комбинациями битов прав».
Использование
Заголовок раздела «Использование»# предусловие: бэкенд quiz-app должен работать, и вы уже отрабатывали вопросы и ошибалисьpnpm run server # в другом терминале
pnpm run ai:grill -- --theme react-basicspnpm run ai:grill -- --max-clusters 5 # не больше 5 кластеровpnpm run ai:grill -- --lang es # разбор на испанскомpnpm run ai:grill -- --json # машиночитаемый вывод (для агентов)SERVER=http://my-server:8787 pnpm run ai:grill # выгрузить ошибки с удалённого сервераПравила «выпуска» ошибочных вопросов (те же, что в quiz-app)
Заголовок раздела «Правила «выпуска» ошибочных вопросов (те же, что в quiz-app)»| wrongCount | Порог | Значение |
|---|---|---|
| 1 | 1 правильный ответ | новая ошибка: одного правильного ответа достаточно, чтобы её снять |
| 2 | 2 правильных ответа | ошиблись дважды: нужно 2 правильных ответа подряд для «выпуска» |
| 3+ | 3 правильных ответа | частая ошибка: нужно 3 правильных ответа подряд для «выпуска» |
mastery-report — отчёт о владении по пунктам экзамена (без ИИ)
Заголовок раздела «mastery-report — отчёт о владении по пунктам экзамена (без ИИ)»Инструмент-спутник grill: детерминированно выводит владение каждым пунктом экзамена (examPoint вопроса, EP-NN) из банка вопросов + прогресса ответов, без LLM. Общий для людей и агентов: люди читают таблицу, агенты потребляют --json (строка «слабые пункты» в снимке состояния /ask-coach берётся отсюда).
Критерии (четыре состояния)
Заголовок раздела «Критерии (четыре состояния)»| Состояние | Критерий |
|---|---|
| освоено | все вопросы пункта отвечены, все верны на последней попытке, невыпущенных ошибок нет, и все привязанные карточки выпущены |
| слабо | есть невыпущенные ошибки или неверный ответ на последней попытке |
| в процессе | отвечена часть без негативных признаков, либо всё верно, но привязанные карточки ещё не все выпущены |
| не начато | ни одного ответа |
Компонент «выпуска» карточек: карточки в flashcards.json могут нести необязательный examPoint (EP-NN, то же пространство имён, что у вопросов); для пункта с привязкой освоение дополнительно требует, чтобы эти карточки были выпущены в SRS (phase = review). Пункты без привязки не затрагиваются — критерий просто откатывается к вопросам. Панель «освоение по пунктам экзамена» на главной странице web app показывает те же критерии вживую (src/lib/mastery.ts).
Отчёт объединяется с профилем обучающегося (study/records/profile.json, пишет grill) — в строке каждого пункта видны причины ошибок и совет. Названия пунктов разбираются из таблицы распределения MISSION.md.
Четыре состояния устного канала (v0.14, поле oral в --json)
Заголовок раздела «Четыре состояния устного канала (v0.14, поле oral в --json)»Для каждой устной цели из журнала устных попыток (study/records/oral-attempts.json, детальные записи за каждый вопрос добавляет чат-слой) — все пункты таблицы распределения ∪ «голые» пункты из журнала (новые понятия из чата, вопросов по которым ещё нет) — считается чисто устное четырёхсостояние, без LLM:
| Состояние | Критерий |
|---|---|
| Освоено | недавняя взвешенная точность ≥ 0.85 (последние 5 попыток с весами 0.5/0.7/0.85/0.95/1.0, нормировка на сумму весов; потолки 0.5/0.8 после 1/2 ответов делают её недостижимой — одна случайная догадка ничего не доказывает) |
| Слабое | последняя попытка неверна (негативные доказательства приоритетны), или взвешенный балл < 0.5 |
| В процессе | попытки есть, негатива нет, балл ниже порога освоения (напр. 2/2 верных = 0.8) |
| Не начато | записей в журнале нет |
oral.weakRanked позволяет агенту назвать слабые устные цели; при слиянии с четырёхсостоянием канала вопросов негатив приоритетен (любое «слабое» → слабое), канал вопросов решает, когда данных достаточно, а без ответов устный канал поднимает пункт максимум до «в процессе» (проверка — решением задач). Существующий критерий пунктов экзамена (таблица выше) не меняется.
Проекция графа знаний (v0.14, --graph / --write-projection)
Заголовок раздела «Проекция графа знаний (v0.14, --graph / --write-projection)»С --graph <путь к graph.json> (или переменной окружения KNOWFLOW_GRAPH_JSON) отчёт загружает внешний граф знаний knowflow и вместе с сопоставлением пункт↔узел (study/records/graph-map.json, предлагает агент, подтверждает обучающийся) создаёт файл проекции только для чтения:
pnpm run mastery -- --graph /path/to/knowflow/graph/graph.json --write-projection# → пишет mastery-projection.json рядом с graph.json:# { version: 1, generatedAt, source, nodes: [{ id, mastery, oral: { asked, correct } }] }Поле graph в --json несёт сигналы графа: четыре состояния по узлам (сопоставленные узлы = состояние канала вопросов, слитое с устным каналом, негатив приоритетен; несопоставленные = чисто устный канал), счётчики сопоставления и результат проекции. Нет графа / нет сопоставления = тихая деградация к чистому виду по пунктам экзамена (graph.loaded = false) — это не ошибка; сам graph.json и страницы знаний никогда не меняются и не получают обратную запись (проекционный мост ADR-0005).
Предпосылки и порядок рекомендаций (graph.weakPrereqs / graph.weakOrdered): если рёбра графа несут метки отношений (словарь маркировщика отношений knowflow), отчёт отображает отношения класса «предпосылка» (предпосылка/зависимость/источник/ссылка/основание/использование/часть-от/производность) в порядок изучения «сначала to» — weakPrereqs даёт цепочку предпосылок каждого слабого пункта (с состоянием освоения каждой предпосылки), а weakOrdered — порядок рекомендаций, уважающий предпосылки (сначала предпосылки, транзитивно; циклы и рёбра без метки не участвуют). Обоснование рекомендации из «прорешивай EP-12» становится конкретным: «предпосылка EP-01 ещё слаба, сначала подтяни её». Нет графа / сопоставления / рёбер-предпосылок → этих полей нет, тихая деградация.
Использование
Заголовок раздела «Использование»pnpm run mastery # таблица для человека (по умолчанию dev-intro)pnpm run mastery -- --theme react-basics # выбрать тему (подходит и путь внешнего пакета темы)pnpm run mastery -- --json # машиночитаемый вывод (для зондов агентов)pnpm run mastery -- --progress /tmp/p.json # выбрать файл прогресса (по умолчанию apps/quiz-app/progress.json; # за серверным прогрессом сначала curl -sf $SERVER/api/progress -o /tmp/p.json)pnpm run mastery -- --panorama # панорама пунктов (v0.13): сигналы изучено/отработано/освоено # с группировкой по day и строками итогов (изучено = записи занятий # ∪ завершённые уроки; отработано = ответы или журнал устных попыток; # освоено = критерий четырёх состояний). Добавьте --json для агентов; # карту «доложи прогресс» skill берёт отсюдаОтсутствие файла прогресса означает просто пустой прогресс (всё «не начато») — это не ошибка.
podcast-generate — генерация подкаста для повторения
Заголовок раздела «podcast-generate — генерация подкаста для повторения»Превращает любые учебные материалы (HTML курса / вопросы / глубокий разбор ошибок) в подкаст-диалог двух ведущих, мужского и женского.
Входные данные
Заголовок раздела «Входные данные»--input принимает один файл; формат скрипт распознаёт сам:
| Формат | Обработка |
|---|---|
.html |
теги снимаются, извлекаются заголовок и текст |
.md |
как есть |
.json (questions.json) |
каждый вопрос форматируется в «формулировка + варианты + ответ + пояснение» |
.txt |
как есть |
Результат (три файла, пишутся в podcast-out/)
Заголовок раздела «Результат (три файла, пишутся в podcast-out/)»| Файл | Содержимое |
|---|---|
<slug>-script.json |
сценарий диалога (структурированный: title / source / generatedAt / массив script) |
<slug>-transcript.md |
Markdown-транскрипт (пометки 👩 ведущая / 👨 ведущий) |
<slug>.wav |
синтезированное аудио с двумя ведущими (если не указан --no-tts) |
Использование
Заголовок раздела «Использование»# базовый вариантpnpm run ai:podcast -- --input examples/dev-intro/lessons/git-basics.html
# управляем числом сегментов и стилемpnpm run ai:podcast -- --input examples/dev-intro/questions.json \ --segments 15 --style interview
# только сценарий, без синтеза аудио (экономит деньги на TTS)pnpm run ai:podcast -- \ --input examples/dev-intro/study/wrong-questions/cluster-01-*.html --no-tts
# диалог на другом языке (сначала проверьте сценарий с --no-tts, см. «Язык вывода»)pnpm run ai:podcast -- --input examples/dev-intro/questions.json --lang ru --no-tts
# машиночитаемый вывод (для агентов)pnpm run ai:podcast -- --input examples/dev-intro/questions.json --no-tts --jsonВарианты стиля (--style)
Заголовок раздела «Варианты стиля (--style)»| Значение | Стиль |
|---|---|
conversational (по умолчанию) |
непринуждённая беседа двоих: дополняют, переспрашивают, приводят примеры |
lecture |
один ведущий рассказывает, второй задаёт уточняющие вопросы и подводит итоги |
interview |
один играет эксперта, второй — интервьюера, задающего вопросы |
Настройка TTS
Заголовок раздела «Настройка TTS»Для синтеза аудио нужен настроенный TTS-провайдер (по умолчанию GLM-TTS) — см. configuration.ru.md. Режим --no-tts создаёт только сценарий диалога + транскрипт, без обращения к TTS — дешевле, либо синтезируйте позже другими инструментами (NotebookLM и т. п.).
Язык вывода (--lang / STUDY_LANG)
Заголовок раздела «Язык вывода (--lang / STUDY_LANG)»Все три CLI позволяют задать язык генерируемого контента:
pnpm run ai:teach -- --theme X --lang en # курс на английскомpnpm run ai:grill -- --theme X --lang es # разбор ошибок на испанскомpnpm run ai:podcast -- --input Y --lang ru # диалоги подкаста на русском
# или единым образом через переменную окружения (можно задать в .env)STUDY_LANG=en pnpm run ai:teach -- --theme XПоддерживаются zh (по умолчанию) / en / es / ru. Реестр языков — scripts/lib/langs.mjs; добавить новый язык — одна запись в реестре.
Соглашения о поведении:
--langвлияет только на генерируемый контент (текст курса, план, текст разбора, диалоги/заголовки подкаста) и фиксированные строки генерируемого HTML (навигация «предыдущий/следующий урок», подвал, атрибут<html lang>, обращения к ведущим в транскрипте);- логи и ошибки самих CLI остаются на китайском (оператор — мейнтейнер);
- текст вопросов (формулировки/варианты) не переводится никогда — цитаты в разборе остаются дословными, и это сознательно: вопросы обязаны совпадать с теми, что вы отрабатывали;
- нюанс про podcast: в TTS сейчас интегрирован только GLM-TTS; синтезируется ли не-китайский диалог — зависит от мультиязычности провайдера. Рекомендуем сначала прогнать
--lang X --no-ttsи посмотреть сценарий, а синтезировать аудио после подтверждения поддержки.
Многоязычность интерфейса тренажёра (переключатель zh/EN/ES/RU в верхней панели) — отдельный механизм, см. раздел «Многоязычность» в README.
Можно и без ИИ
Заголовок раздела «Можно и без ИИ»Три CLI — дополнительная возможность, а не обязательство. Если вам нужен просто тренажёр + карточки, можно полностью обойтись без LLM и без CLI — достаточно pnpm dev. Курсовые объяснения, глубокий анализ ошибок и подкасты для повторения разблокируются одним API-ключом.
Философия дизайна
Заголовок раздела «Философия дизайна»| Решение | Выбор | Обоснование |
|---|---|---|
| LLM-провайдер | OpenAI-совместимый протокол + baseURL | один код покрывает 95% провайдеров, китайских и зарубежных (OpenAI/GLM/DeepSeek/Kimi/Qwen/Doubao) |
| Интерфейс настройки | три переменные .env (LLM_BASE_URL + LLM_API_KEY + LLM_MODEL) |
минимально, управление в одном файле |
| Отказоустойчивость | parseJsonLoose + 3 повторных попытки с экспоненциальной задержкой + понятные ошибки |
LLM часто возвращает «ломаный JSON» или ловит rate limit — нужна толерантность |
| Тесты | чистые функции вынесены в lib/, юнит-тесты на node:test |
сами вызовы LLM юнит-тестами не покроешь, зато покрывается вся логика вокруг |
| Без привязки к AI-клиенту | CLI вместо agent skill | подходит пользователям ZCode / Claude Code / Cursor и даже CI |
Структура рабочей области темы (MISSION.md / RESOURCES.md / lessons/) и часть дисциплины составления вопросов (равная длина вариантов, формат не должен выдавать ответ) взяты из рабочего процесса teach skill — за это отдельная благодарность.
Полный методологический контекст — methodology.ru.md; три CLI — инженерное воплощение методологии.
Частые вопросы
Заголовок раздела «Частые вопросы»В: CLI падает с ошибкой «LLM 配置不完整» (неполная конфигурация LLM)
О: В .env не хватает полей. Скопируйте .env.example в .env и заполните тройку: LLM_BASE_URL / LLM_API_KEY / LLM_MODEL. Подробности — в configuration.ru.md.
В: JSON от LLM не парсится
О: Толерантный parseJsonLoose уже многое прощает (извлекает {...}, снимает markdown-ограждения). Если всё равно падает — вывод LLM сильно ушёл в сторону, попробуйте другую модель (gpt-4o-mini / glm-4.6 / deepseek-chat ведут себя стабильно).
В: Синтез TTS очень медленный
О: GLM-TTS тратит примерно 5–10 секунд на сегмент; диалог из 12 сегментов — около 2 минут. Если нужно быстрее, запустите с --no-tts (только сценарий) и синтезируйте аудио другим инструментом.
В: Качество сгенерированного курса/разбора хромает
О: Подкрутите поля audience / depth / resources в course-spec.json — чем конкретнее аудитория и ресурсы, тем выше качество. Зернистость регулируют --segments (podcast) и --lessons (teach).
В: Хочу подключить Claude / Gemini / другого провайдера не на протоколе OpenAI О: Текущий слой абстракции поддерживает только OpenAI-совместимый протокол. У Claude и Gemini есть OpenAI-совместимые прокси (например, LiteLLM Proxy, OpenRouter) — подключайтесь через них. Позже могут появиться нативные адаптеры.
