Перейти к содержимому

Руководство по 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/)

Terminal window
cp .env.example .env
# отредактируйте .env — как минимум три пункта: LLM_BASE_URL / LLM_API_KEY / LLM_MODEL

Полный список провайдеров и пояснения — в configuration.ru.md.

Terminal window
pnpm run server # в другом терминале, поднимается на :8787
Terminal window
# 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"), чтобы конвейер мог ветвиться.


Превращает спецификацию темы (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
Terminal window
pnpm run ai:teach -- --theme react-basics
pnpm run ai:teach -- --theme X --lessons 5 # переопределить lessonsCount
pnpm 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 кластеризует их по пунктам экзамена и глубоко раскрывает каждый кластер.

  1. GET /api/progress выгружает список ошибок (бэкенд задаётся переменной окружения SERVER)
  2. соединяется с examples/<theme>/questions.json, чтобы получить полный текст вопросов
  3. LLM кластеризует ошибки по пунктам экзамена (например, «git reset vs revert» — 3 вопроса, «коды состояния HTTP» — 2)
  4. по каждому кластеру LLM пишет HTML глубокого разбора (таблица ключевых различий + блок-схема принятия решений + предупреждения о ловушках + тренировка на вариантах)
  5. результат пишется в examples/<theme>/study/wrong-questions/cluster-NN-<slug>.html (продукты из старого расположения wrong-questions/ распознаются и переносятся автоматически)
  6. обновляется examples/<theme>/study/wrong-questions/index.html — центральная страница разбора ошибок
  7. заодно пишется профиль обучающегося: LLM дополнительно фиксирует причины ошибок по пунктам экзамена (wrongReasons / advice) в examples/<theme>/study/records/profile.json (машиночитаемый; кластеры, пересекающиеся по id вопросов, сливаются в один пункт и накапливаются). Профиль — приватные данные обучающегося, они никогда не публикуются со сборкой; очередной запуск mastery-report или зонд /ask-coach подхватит их автоматически, и рекомендация станет конкретной: «EP-03 ошиблись 2 раза, причина: слабое владение комбинациями битов прав».
Terminal window
# предусловие: бэкенд quiz-app должен работать, и вы уже отрабатывали вопросы и ошибались
pnpm run server # в другом терминале
pnpm run ai:grill -- --theme react-basics
pnpm 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, предлагает агент, подтверждает обучающийся) создаёт файл проекции только для чтения:

Terminal window
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 ещё слаба, сначала подтяни её». Нет графа / сопоставления / рёбер-предпосылок → этих полей нет, тихая деградация.

Terminal window
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 как есть
Файл Содержимое
<slug>-script.json сценарий диалога (структурированный: title / source / generatedAt / массив script)
<slug>-transcript.md Markdown-транскрипт (пометки 👩 ведущая / 👨 ведущий)
<slug>.wav синтезированное аудио с двумя ведущими (если не указан --no-tts)
Terminal window
# базовый вариант
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
Значение Стиль
conversational (по умолчанию) непринуждённая беседа двоих: дополняют, переспрашивают, приводят примеры
lecture один ведущий рассказывает, второй задаёт уточняющие вопросы и подводит итоги
interview один играет эксперта, второй — интервьюера, задающего вопросы

Для синтеза аудио нужен настроенный TTS-провайдер (по умолчанию GLM-TTS) — см. configuration.ru.md. Режим --no-tts создаёт только сценарий диалога + транскрипт, без обращения к TTS — дешевле, либо синтезируйте позже другими инструментами (NotebookLM и т. п.).


Все три CLI позволяют задать язык генерируемого контента:

Terminal window
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) — подключайтесь через них. Позже могут появиться нативные адаптеры.