Metadata-Version: 2.4
Name: hhru-bot
Version: 0.1.1
Summary: CLI для поиска вакансий, откликов и поднятия резюме на hh.ru (Playwright).
Author: axisrow
License: MIT
Requires-Python: >=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: playwright>=1.59.0
Requires-Dist: PyYAML>=6.0
Requires-Dist: browser-cookie3>=0.20.1
Requires-Dist: snowballstemmer>=3.0
Requires-Dist: filelock>=3.16.0
Provides-Extra: dev
Requires-Dist: jsonschema>=4; extra == "dev"
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-xdist>=3; extra == "dev"
Requires-Dist: ruff>=0.6; extra == "dev"
Provides-Extra: ai
Requires-Dist: hermes-agent-axisrow>=0.20.1.2; extra == "ai"
Provides-Extra: calendar
Requires-Dist: google-api-python-client>=2.0; extra == "calendar"
Requires-Dist: google-auth-httplib2>=0.2; extra == "calendar"
Requires-Dist: google-auth-oauthlib>=1.2; extra == "calendar"
Dynamic: license-file

# hhru-bot

CLI-бот для поиска работы на hh.ru: ищет вакансии, откликается письмом,
поднимает резюме, следит за ответами и умеет редактировать само резюме.
Работает через Playwright (браузер), а не через API — hh.ru закрыл его для
соискателей в декабре 2025.

## Чем мы отличаемся от аналогов

Идея сравнения и разбор референсов — issue [#84](https://github.com/axisrow/hhru/issues/84).
Легенда: ✅ есть, ❌ нет, ⚠️ частично или не проверяли (не значит «нет»).

| Фича | s3rgeym | fikstt2 | Steev193 | tgeruzov | konard | hhru-bot |
|---|---|---|---|---|---|---|
| Поиск + отклик | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Поднятие резюме | ✅ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ✅ |
| Троттлинг и дневные лимиты | ✅ | ⚠️ | ⚠️ | ⚠️ | ⚠️ | ✅ |
| Редактирование и создание резюме | ⚠️ клон целиком | ❌ | ❌ | ❌ | ❌ | ✅ |
| Анализ резюме конкурентов | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
| Обучаемые ответы на анкеты | ❌ | ❌ | ❌ | ❌ | ⚠️ Q&A-файл, без обучения | ✅ |
| Честный статус «не знаю, дошло ли» | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
| Воронка и статистика по истории | ⚠️ | ❌ | ❌ | ❌ | ❌ | ✅ |

- **Редактирование и создание резюме** — `create-resume`, `edit-experience`,
  `edit-education`, `edit-skills`, `edit-languages`, `resume-position`,
  `publish-resume` и другие: собрать резюме с нуля или довести до публикации.
- **Анализ резюме конкурентов** (`competitors`) — с кем ты конкурируешь за
  вакансию: отчёт по ролям, зарплатам и навыкам других соискателей.
- **Обучаемые ответы на анкеты** (`questionnaire`) — бот подбирает ответ по
  подтверждённым формулировкам, для новых вопросов подключает LLM;
  неуверенные случаи идут в очередь на твоё решение.
- **Честный статус «не знаю»** (`uncertain`) — если связь с hh.ru оборвалась
  в момент клика, бот не врёт «готово»/«не готово», а говорит «не знаю».
- **Воронка и статистика** (`funnel`, `stats`, `responses`, `market`) —
  сколько откликов ушло, сколько ответов, где «мёртвая зона».

Проекты и ссылки — «Похожие проекты» ниже. Полный список команд —
«Справочник команд» ниже.

## Как это работает

Запускается вручную из терминала. Каждая команда печатает, что делает,
поддерживает `--dry-run` (план без единого клика по hh.ru) и ограничена
дневными лимитами и случайными паузами — чтобы не выглядеть подозрительной
автоматизацией для анти-фрод системы hh.ru. Опасные действия (отклик,
публикация, удаление резюме) без `--force` или подтверждения не выполняются.

Для регулярного запуска (например, поднимать резюме каждые 4 часа) подключи
внешний планировщик — cron/launchd или Docker (см. «Автопилот» ниже). Своего
фонового демона у бота нет.

## ⚠️ Важное предупреждение

Селекторы hh.ru в `src/hhru_bot/selectors.py` не проверены вживую — код
писался без доступа к hh.ru (DDoS-Guard блокировал автоматизированный браузер).

**Перед первым использованием:**
1. Открой hh.ru, сверь через F12 → Elements актуальные `data-qa` атрибуты
   карточек вакансий, кнопки отклика, формы письма, кнопки поднятия резюме
   с тем, что в `src/hhru_bot/selectors.py`.
2. Поправь несоответствия прямо в этом файле.
3. Прогони `search --dry-run`, затем `apply --dry-run`, и только потом —
   боевой запуск с малым `--limit`.

## Установка

```bash
pip3 install -r requirements.txt
python3 -m playwright install chromium
pip3 install -e .
```

## Настройка

```bash
hhru account create default
./scripts/run.sh --account default login
```

Отредактируй созданный `data/accounts/default/config.yaml`:
- `resumes` — твои резюме, у каждого свои фильтры поиска (`text`, `area`,
  `salary_from`, `experience`, `schedule`, `exclude_employers`,
  `exclude_keywords`) и опционально своё сопроводительное письмо.
- `exclude_keywords` — стоп-слова для заголовков вакансий (без учёта
  регистра), отдельно для каждого резюме. Пример — в `config/config.example.yaml`.
- `throttle` — паузы между действиями и дневные лимиты откликов/поднятий.
- В письме доступны плейсхолдеры `{vacancy_title}` и `{company_name}`.

## Структура каталогов

Всё изменяемое — конфиг, база, сессия hh.ru, логи — живёт в `data/`, целиком
в `.gitignore`.

```
data/
  accounts/
    default/
      config.yaml           # конфиг аккаунта (создаётся account create)
      history.db            # история аккаунта
  config.yaml               # шаблон — config/config.example.yaml
  history.db                # SQLite: история откликов, вакансии, ответы
  storage_state/
    hh_session.json         # сессия hh.ru — секрет, никогда не коммитить
  logs/
    hhru_bot.log            # лог CLI (дублируется в консоль)
    probe_*.html / .png     # дампы probe
    scheduled.log           # вывод запусков по расписанию

config/
  config.example.yaml       # шаблон формата, лежит в репозитории
```

Пути — относительно текущей директории: запускай команды из корня проекта
или указывай `--config`/`--history` явно. `hh_session.json` даёт доступ к
hh.ru наравне с паролем — не коммить `data/` и не включай в Docker-образ.

## Первый запуск: вход в аккаунт

```bash
./scripts/run.sh login
```

Откроется окно браузера на странице входа hh.ru. Войди в аккаунт вручную
(логин, пароль, СМС-код, капча — что бы ни попросил hh.ru), затем вернись
в терминал и нажми Enter. Сессия сохранится в
`data/storage_state/hh_session.json` — все последующие команды будут
переиспользовать её без повторного входа.

## Мультилогин: несколько аккаунтов hh.ru

Каждый аккаунт — своё имя и своя папка `data/accounts/<name>/`.

```bash
hhru account create marketing
./scripts/run.sh --account marketing login
# или, если куки hh.ru уже есть в профиле Chrome:
./scripts/run.sh --account marketing import-cookies --profile Default
```

Флаг `--account <name>` ставится до имени подкоманды:

```bash
./scripts/run.sh --account marketing apply --resume resume-name-1 --limit 5
```

Для плановых задач то же самое делает `HHRU_ACCOUNT` (понимает
`scripts/scheduled_run.sh`); явный `--account` в приоритете:

```bash
HHRU_ACCOUNT=marketing scripts/scheduled_run.sh --headless apply --limit 5
```

Список аккаунтов и их состояние: `./scripts/run.sh account list`.

У каждого аккаунта своя сессия, своя история откликов/поднятий и свои
дневные лимиты — они не суммируются и не переносятся между аккаунтами.
Разные аккаунты можно гонять параллельно; два одновременных прогона
**одного** аккаунта запрещены (второй получит `[FAIL]`).

`hh_session.json` даёт доступ к hh.ru наравне с паролем; файлы сессии
создаются с правами `0600`, каталог аккаунта — `0700` (проверка —
`diagnostics doctor`).

Чтобы убрать аккаунт — удали `data/accounts/<name>/` вручную.

## Команды

Все команды поддерживают `--resume <id>` (по умолчанию — все резюме из
конфига) и `--dry-run` (показать план действий без реальных кликов).

```bash
# Проверить, что находит поиск, без откликов
./scripts/run.sh search --resume resume-name-1 --dry-run

# Откликнуться на подходящие вакансии (максимум 5 за запуск)
./scripts/run.sh apply --resume resume-name-1 --dry-run --limit 5

# То же самое по-настоящему, без dry-run
./scripts/run.sh apply --resume resume-name-1 --limit 5

# Поднять резюме в поиске (hh.ru разрешает не чаще раза в 4 часа)
./scripts/run.sh bump --resume resume-name-1

# Полный цикл (apply + bump) для всех резюме из конфига
./scripts/run.sh run
```

### Профиль для внешних форм

Контактные данные аккаунта сохраняются автоматически после `login`. Для
данных, которых нет на hh.ru (например, Telegram) — задай вручную, с тем же
`--account`:

```bash
./scripts/run.sh --account default profile set "Telegram" "@username"
./scripts/run.sh --account default profile show
./scripts/run.sh --account default profile unset "Telegram"
```

Добавь `--headless`, если не нужно видеть окно браузера.

## Автопилот: запуск по расписанию

Регулярность задаёт внешний планировщик. Каждый вариант ниже запускает
обычную команду `run` (apply + bump) — те же лимиты и кулдаун, что и вручную.
Перед автоматизацией проверь `./scripts/run.sh run --dry-run`.

### Локаль (cron / launchd)

Скопируй `scripts/crontab.example`, замени `__REPO_ROOT__` и `__PYTHON_BIN__`
на абсолютные пути, добавь через `crontab -e`. Обёртка пишет в
`data/logs/scheduled.log` сама.

`responses --alert-new` пишет `***** НОВОЕ ПРИГЛАШЕНИЕ *****` при новом
приглашении; `HHRU_ALERT_CMD` опционально запускает свою команду при
обнаружении. Пример — в `scripts/crontab.example`.

На macOS вместо cron — готовые шаблоны `deploy/com.hhru.bot.apply.plist`
(ежедневный apply) и `deploy/com.hhru.bot.bump.plist` (bump каждые 4 часа).
Замени `__REPO_ROOT__`/`__PYTHON_BIN__`, установи:

```bash
cp deploy/com.hhru.bot.apply.plist ~/Library/LaunchAgents/com.hhru.bot.apply.plist
launchctl load ~/Library/LaunchAgents/com.hhru.bot.apply.plist
```

Выгрузить: `launchctl unload ~/Library/LaunchAgents/com.hhru.bot.apply.plist`.
Сгенерировать шаблон: `./scripts/run.sh schedule --format plist --action apply
--apply-time 10:00 --apply-limit 5`.

### Docker

`data/` монтируется с хоста и не попадает в образ. Подготовь конфиг на хосте,
затем разовый прогон:

```bash
mkdir -p data
./scripts/run.sh account create default
docker compose run --rm --entrypoint hhru hhru --headless --account default run
```

Непрерывный режим — `docker compose up -d`: `docker-compose.yml` запускает
`run --headless` каждые 4 часа без дрейфа расписания.

```bash
docker compose up -d
docker compose logs -f hhru
```

Остановить: `docker compose down`. Не монтируй `storage_state` в публичные
каталоги и не добавляй `data/` в образ или git.

## Сценарий: пришло приглашение

1. Планировщик распознаёт новое приглашение (код выхода 10):

   ```bash
   ./scripts/run.sh --headless responses --alert-new
   ```

2. Посмотреть, что нового: `./scripts/run.sh responses`.

3. Что подтянуть по требованиям из собранных вакансий:
   `./scripts/run.sh learn --resume <id>`.

4. Время собеседования CLI не угадывает — `responses --calendar-hint`
   печатает готовую команду `calendar event` с плейсхолдерами:

   ```bash
   ./scripts/run.sh responses --calendar-hint
   ./scripts/run.sh calendar event --summary "ООО Ромашка - 12345" \
     --start 2026-09-01T14:00:00+03:00 --end 2026-09-01T15:00:00+03:00
   ```

Если работодатель молчит после отклика — напомнить о себе:

```bash
./scripts/run.sh reply-employers --follow-up --after-days 7 --dry-run
./scripts/run.sh reply-employers --follow-up --after-days 7 --force
```

### VPS

```bash
ssh user@example.com 'cd /opt/hhru && docker compose up -d --build'
ssh user@example.com 'cd /opt/hhru && docker compose logs -f hhru'
```

Сессию создай локально (`./scripts/run.sh --account default login`) или в
контейнере с временно отключённым `--headless`. Обновление:
`git pull && docker compose up -d --build`.

## Claude Code и Codex plugin

Репозиторий — маркетплейс плагина `hhru-cc-plugin`: подключает команды бота
как инструменты и скиллы прямо из агентской сессии Claude Code или Codex.

### Установка

```bash
claude plugin marketplace add axisrow/hhru
claude plugin install hhru-cc-plugin@hhru --scope user
```

### Codex: установка и обновление

Одна команда покрывает первую установку, апгрейд и восстановление после
ошибки — она идемпотентна, повторный запуск безопасен:

```bash
hhru update
```

Проверить состояние без изменений: `hhru diagnostics doctor` (или
`./scripts/run.sh diagnostics doctor` из checkout). При рассинхроне между CLI,
marketplace и plugin cache печатает `[DRIFT]` и подсказывает `hhru update`.

Уже открытая задача Codex после обновления продолжает работать со старым
skill — начни новую задачу, чтобы подхватить изменения.

### Команда `/hhru`

```bash
/hhru whoami
/hhru search --resume <id> --dry-run --max-pages 3
/hhru competitors collect --text "AI" --auth-mode anonymous --detail-workers 10
/hhru apply --resume <id> --dry-run --limit 5
/hhru responses
```

Write-команды (`apply`/`bump`/`run`/...) сначала `--dry-run`, потом
подтверждение перед боевым запуском.

### Скиллы

- **`hhru`** — главный: CLI-справочник, правила безопасности, проверка готовности.
- **`hhru-apply`** — воркфлоу отклика (dry-run-first, safety-critical).
- **`hhru-market`** — анализ рынка (read-only).
- **`hhru-monitor`** — мониторинг/статус (read-only).

## Подготовка к интервью

Бот автоматизирует цепочку «пришло приглашение → follow-up» (см. «Сценарий:
пришло приглашение» выше). Само собеседование — нарратив, портфолио,
поведенческие вопросы — готовишь сам:

- подготовь проекты и портфолио под требования, которые показал `learn`;
- подготовь нарратив («расскажите о себе») и ответы на поведенческие вопросы;
- поставь реальное время интервью в `calendar event` вместо плейсхолдера.

## Справочник команд

Этот блок генерируется автоматически из `argparse` (см.
`scripts/gen_cli_docs.py`). Не редактируй его руками — правь код команд в
`src/hhru_bot/commands/` и перегенерируй. CI упадёт, если блок рассинхронизирован
с кодом.

<!-- BEGIN CLI REF -->

**Глобальные флаги** (до имени команды):

- `--config` — Путь к config.yaml
- `--history` — Путь к файлу истории (SQLite)
- `--account` — Имя аккаунта (data/accounts/<name>/config.yaml + history.db)
- `--headless` — Запустить браузер в headless-режиме
- `--verbose` — Подробное логирование
- `--quiet` — Не печатать поток прогресса

**Команды**:

### `about`

Генерирует текст раздела «Обо мне» через настроенный LLM. Сначала всегда показывает dry-run-предложение; сохранение требует --force или подтверждения. С --text LLM не нужен: готовый текст (например, перевод) сохраняется как есть (#326).

- `--resume` — Slug из конфига или реальный resume_id HH.ru (#319)
- `--text` — Готовый текст раздела «Обо мне» без LLM (#326): ai_profile/секция ai не требуются, текст сохраняется как есть
- `--dry-run` — Показать предложение без сохранения
- `--force` — Подтвердить сохранение без prompt

### `account`

Создание и управление локальными профилями аккаунтов hh.ru.

- (без аргументов)

#### `account create`

Создать data/accounts/<name>/ и скопировать туда шаблон конфига.

- (без аргументов)

#### `account delete`

Показать, что будет удалено в data/accounts/<name>/, а с --force выполнить необратимое удаление.

- `--dry-run` — Показать план и ничего не удалять (это поведение по умолчанию)
- `--force` — Выполнить удаление (необратимо; уносит конфиг, историю и сессию)

#### `account list`

- (без аргументов)

### `adaptive-report`

- `--format` — ASCII-таблица (по умолчанию: 'table')

### `adaptive-resume`

Генерирует заголовок, «Обо мне», порядок навыков и отбор мест работы/проектов под один из четырёх кластеров вакансий (#752). Без --apply только показывает предложение (браузер не открывается). С --apply применяет title/about/skills на hh.ru; боевая запись требует --force или интерактивного подтверждения. work_experience/projects этой командой на hh.ru не пишутся (#769: вне скоупа первой версии).

- `--resume` — Slug из конфига или реальный resume_id HH.ru (#319)
- `--cluster` — Кластер вакансий, под который адаптируется резюме
- `--apply` — Открыть браузер и применить title/about/skills на hh.ru (иначе только печать плана)
- `--dry-run` — С --apply: открыть формы и показать план по каждому шагу, ничего не сохраняя. Без --apply — no-op (команда и так не пишет на hh.ru, флаг оставлен для единообразия и совместимости)
- `--force` — С --apply: подтвердить запись без TTY prompt

### `apply`

- `--resume` — Slug из конфига или resume_id HH.ru (по умолчанию — все)
- `--dry-run` — Показать, что будет сделано, без реальных действий
- `--max-pages MAX_PAGES` — Явный максимум страниц поиска (по умолчанию — адаптивный)
- `--force` — Разрешить реальную отправку отклика с LLM-ответами на вопросы
- `--learn-questionnaires` — Спрашивать подтверждение сопоставления вопроса анкеты с шаблоном
- `--limit LIMIT` — Целевое число успешных откликов за запуск (0 = без ограничения кроме дневного лимита)
- `--approved ID` — Отправить ровно approved-запись review-очереди
- `--permit` — Одноразовый permit из `review approve`

### `backup`

- `--output OUTPUT` — Путь к tar.gz

### `blacklist`

- (без аргументов)

#### `blacklist add`

- `--reason`
- `--by` (по умолчанию: 'cli')

#### `blacklist list`

- (без аргументов)

#### `blacklist remove`

- (без аргументов)

### `bump`

- `--resume` — Slug из конфига или resume_id HH.ru (по умолчанию — все)
- `--dry-run` — Показать, что будет сделано, без реальных действий
- `--max-pages MAX_PAGES` — Максимум страниц поиска (по умолчанию: 5)

### `calendar`

Создание события только по явно подтверждённым --start/--end. Автоматического триггера из responses нет.

- (без аргументов)

#### `calendar auth`

- `--credentials` (по умолчанию: 'data/google_calendar/client_secret.json')
- `--token` (по умолчанию: 'data/google_calendar/token.json')

#### `calendar event`

- `--credentials` (по умолчанию: 'data/google_calendar/client_secret.json')
- `--token` (по умолчанию: 'data/google_calendar/token.json')
- `--calendar-id` (по умолчанию: 'primary')
- `--summary`
- `--start` — Начало, RFC3339, например 2026-08-20T10:00:00+07:00
- `--end` — Конец, RFC3339
- `--timezone` (по умолчанию: 'UTC')
- `--description`
- `--location`
- `--dry-run` — Показать payload без OAuth и записи

### `call-api`

- `-m, --method` — HTTP-метод (разрешён только GET) (по умолчанию: 'GET')

### `census`

- `--url` — Полный URL страницы hh.ru
- `--json` — Машиночитаемый JSON-вывод

### `clear-negotiations`

Отозвать один отклик по уникальному topic или все отклики аккаунта. Фильтры --vacancy и --resume формируют только план; боевой отзыв требует --topic или --account-wide и --force/подтверждение.

- `--topic` — Уникальный ID переписки для точечного отзыва
- `--vacancy` — ID вакансии (только план, не боевой отзыв)
- `--resume` — ID резюме (только план, не боевой отзыв)
- `--account-wide` — Явно отозвать все найденные отклики аккаунта
- `--dry-run` — Показать план без отзывов
- `--force` — Подтвердить боевой отзыв
- `--max-pages MAX_PAGES` — Максимум страниц переговоров (по умолчанию: 5)

### `clear-skipped`

Удалить записи из журнала отсева (таблица skipped). Без --reason чистит все причины. Без --dry-run удаляет, с --dry-run — только показывает, сколько записей ушло бы.

- `--reason` — Очистить только эту причину (по умолчанию — все причины)
- `--dry-run` — Показать, сколько записей будет удалено, без реального удаления

### `common`

Заполняет через UI поля common, включая условия работы. area, metro и citizenship пока не входят в команду.

- `--resume` — Slug из конфига или resume_id HH.ru
- `--first-name` — Имя
- `--last-name` — Фамилия
- `--birthday` — Дата в формате, который принимает форма hh.ru
- `--gender` — Пол
- `--phone` — Телефон
- `--area` — Точный leaf города из live-каталога hh.ru
- `--metro` — Точная станция метро
- `--citizenship` — Точное гражданство из live-каталога; можно повторять
- `--work-ticket` — «Наличие трудовой книжки» (true/false). Только в шейпе редактора профиля: на common-визарде черновика поле не рендерится (честный отказ), контрол work-ticket-selector визарда — это «Разрешение на работу» (#997)
- `--relocation` — Готовность к переезду
- `--schedule` — График работы; можно указать несколько раз
- `--employment` — Тип занятости; можно указать несколько раз
- `--work-format` — Формат работы; можно указать несколько раз
- `--business-trip, --business-trips` — Готовность к командировкам
- `--show` — Показать фактические значения полей common (включая предзаполненные hh.ru) и выйти
- `--dry-run` — Показать план без сохранения
- `--force` — Подтвердить запись без prompt

### `competitors`

READ hh.ru: competitors collect --text QUERY [--search-in SCOPE] [--max-pages N]; локальный отчёт: competitors report [--text QUERY] [--search-in SCOPE] [--auth-mode MODE] [--top N].

- (без аргументов)

#### `competitors collect`

- `--text` — Ключевое слово поиска резюме
- `--max-pages MAX_PAGES` — Необязательный safety-cap (по умолчанию — до конца видимой выдачи)
- `--resume` — Продолжить последний прерванный запуск того же запроса с checkpoint
- `--execution-mode` — Режим выполнения (по умолчанию foreground; background не поддерживается) (по умолчанию: 'foreground')
- `--progress-verbosity PROGRESS_VERBOSITY` — Поток прогресса: 1 — показывать, 0 — только финал/ошибки (по умолчанию 1) (по умолчанию: 1)
- `--items-per-page ITEMS_PER_PAGE` — Запрошенный размер страницы hh.ru (по умолчанию 100; для smoke можно 20) (по умолчанию: 100)
- `--search-in` — Область поиска --text на hh.ru: position — только желаемая должность (заголовок резюме), самая узкая и чистая (по умолчанию); keywords — по ключевым навыкам; full_text — по всему резюме (должность, навыки, описание опыта, достижения), самая широкая: запрос вроде «AI» так вытягивает дизайнеров с Adobe Illustrator (по умолчанию: 'position')
- `--auth-mode` — Сессия браузера: anonymous — чистый контекст без cookie (по умолчанию); authenticated — загрузить сохранённую сессию из конфига (по умолчанию: 'anonymous')
- `--detail-workers DETAIL_WORKERS` — Параллельные процессы деталей: 1–1000 (по умолчанию 10; для authenticated требуется 1) (по умолчанию: 10)

#### `competitors report`

- `--text` — Ограничить отчёт одним поисковым запросом
- `--search-in` — Ограничить отчёт одной областью поиска: один и тот же --text в разных областях — это РАЗНЫЕ выборки (full_text по «AI» тянет дизайнеров с Adobe Illustrator). Без флага отчёт охватывает все области
- `--auth-mode` — Ограничить отчёт одним режимом сессии: анонимная выдача hh.ru урезана относительно авторизованной. Без флага отчёт охватывает оба режима
- `--top TOP` — Число строк в каждом топе (по умолчанию 20) (по умолчанию: 20)

### `config`

Показать и локально изменить значения в config.yaml.

- `-p, --path` — Показать полный путь к config.yaml
- `-e, --edit` — Открыть config.yaml в $EDITOR
- `-k, --key` — Получить значение по ключу
- `-s, --set` — Установить значение ключа
- `-u, --unset` — Удалить ключ

### `copy-resume`

Создаёт копию резюме на hh.ru — то же, что «Дублировать» в меню резюме (в референсах — клонирование). WRITE-команда: боевой режим требует --force или интерактивного подтверждения; --dry-run ничего не отправляет.

- `--resume` — Slug из конфига или реальный resume_id HH.ru (#319)
- `--dry-run` — Показать, что будет сделано, без реальных действий
- `--force` — Подтвердить боевой запуск без интерактивного вопроса
- `--write-config` — Добавить новое резюме в config.yaml
- `--slug` — Slug нового резюме (по умолчанию <исходный>-copy)
- `--title` — Желаемая должность для созданной копии (после клонирования)

### `create-resume`

Открывает визард hh.ru и создаёт новый черновик резюме. Один запуск создаёт одно резюме с одной основной профессией; для нескольких профессий нужны отдельные резюме и отдельные запуски. С --allow-unresolved-area, если area отсутствует в каталоге, черновик создаётся с ролью-плейсхолдером «Другое» — профессию затем заменяют вручную через «Дополнить». WRITE-команда: по умолчанию только dry-run; боевой запуск требует --force или интерактивного подтверждения.

- `--area` — Профессия для выбора в визарде создания резюме. Если hh.ru показывает ровно одну подсказку автодополнения с однозначной ролью, она принимается автоматически; иначе выбирается дерево каталога — точным совпадением, а при его отсутствии единственным кандидатом фильтра (#920). Несколько подсказок автоматически не выбираются — перезапустите с точным именем одной из них
- `--title` — Одна основная профессия резюме
- `--allow-unresolved-area, --allow-unresolved` — Если area не найдена в каталоге — создать черновик с ролью-плейсхолдером «Другое» (id 40), профессия заменяется вручную (синоним: --allow-unresolved)
- `--dry-run` — Показать план без создания
- `--force` — Подтвердить боевое создание
- `--fill-common` — #985: если readback после создания вернёт draft_started с nextIncompleteScreenId=common — подтвердить экран common в том же прогоне («Сохранить и продолжить»; hh.ru предзаполняет его из профиля аккаунта) и перевести резюме к ready_to_publish. Значения не выдумываются: незаполненное в профиле поле — честный отказ до клика с вердиктом draft_started

### `delete-education-entry`

Удаляет ровно одну запись основного/дополнительного образования через UI hh.ru, адресуя её реальным id из /profile/edit/{kind}Education/{id}. --entry-id и --kind обязательны; по умолчанию выполняется только dry-run.

- `--entry-id` — Числовой id записи из URL /profile/edit/{kind}Education/{id}
- `--kind` — Основное (primary) или дополнительное (additional) образование
- `--dry-run` — Показать план без удаления (по умолчанию; --force включает боевой режим) (по умолчанию: True)
- `--force` — Подтвердить необратимое удаление

### `delete-photo`

По умолчанию скрывает фото из ОДНОГО резюме (пункт «Скрыть фото из резюме»; фото остаётся в библиотеке, возвращается select-photo). --from-library необратимо удаляет фото из библиотеки аккаунта — оно исчезает из ВСЕХ резюме, где установлено. WRITE-hh-ru: dry-run включён по умолчанию (read-only инвентарь библиотеки и пунктов more-меню «Действия с фото»); боевой режим (необратимый при --from-library) включает только --force и требует --photo-id.

- `--resume` — Slug из конфига или resume_id HH.ru
- `--photo-id` — Числовой id фото из dry-run инвентаря (обязателен в боевом режиме)
- `--from-library` — Удалить фото из библиотеки аккаунта (необратимо, бьёт по ВСЕМ резюме с этим фото), а не только скрыть из одного резюме
- `--dry-run` — Показать план и пункты меню, ничего не меняя (по умолчанию; --force включает боевой режим) (по умолчанию: True)
- `--force` — Подтвердить боевое удаление/скрытие

### `delete-resume`

Удаляет ровно одно резюме через UI hh.ru. --resume обязателен; по умолчанию выполняется только dry-run.

- `--resume` — Slug из конфига или реальный resume_id HH.ru (#319)
- `--dry-run` — Показать план без удаления (по умолчанию; --force включает боевой режим) (по умолчанию: True)
- `--force` — Подтвердить необратимое удаление

### `diagnostics`

- (без аргументов)

#### `diagnostics doctor`

Сравнивает версию, release/tag и commit SHA установленного CLI, marketplace snapshot и загруженного Codex plugin; проверяет права каталогов аккаунтов и файлов сессий.

- `--marketplace-path, --marketplace MARKETPLACE` — Путь к marketplace snapshot (для диагностики нестандартной установки)
- `--plugin-cache PLUGIN_CACHE` — Путь к Codex plugin cache (для диагностики нестандартной установки)

#### `diagnostics export`

- `--run-id`
- `--output OUTPUT`
- `--log LOG` (по умолчанию: PosixPath('data/logs/hhru_bot.log'))
- `--dom-dir DOM_DIR` (по умолчанию: PosixPath('data/logs'))

### `edit-education`

Составляет LLM-план образования и заполняет поля через UI hh.ru. --dry-run не нажимает Save; боевой режим требует --force или TTY-подтверждение.

- `--resume` — Slug из конфига или реальный resume_id HH.ru (#319)
- `--section` — Какой блок редактировать (по умолчанию: оба) (по умолчанию: 'both')
- `--source` — Контекст кандидата (переопределяет education.source)
- `--mode` — Режим планирования
- `--institution` — Учебное заведение (основное образование, без LLM)
- `--faculty` — Факультет (основное образование, без LLM)
- `--specialty` — Специальность (без LLM)
- `--year` — Год окончания (без LLM)
- `--primary-entry` — Готовая запись основного образования JSON без LLM (#326), можно несколько: '{"institution":..., "faculty":..., "specialty":..., "year":...}'
- `--additional-entry` — Готовая запись доп. образования JSON без LLM (#326), можно несколько: '{"institution":..., "organization":..., "specialty":..., "year":...}'
- `--dry-run` — Заполнить только локальную форму; Save не нажимать
- `--force` — Разрешить боевое сохранение

### `edit-experience`

Предлагает записи опыта через LLM и показывает их в dry-run. WRITE-hh-ru: боевой режим требует --force или подтверждения TTY. Опыт работы общий для всех резюме аккаунта (#782): --resume задаёт не 'куда писать', а какое резюме останется отмеченным в панели привязки hh.ru.

- `--resume` — Slug из конфига или реальный resume_id HH.ru (#319). Опыт общий для профиля (#782) — резюме определяет привязку, не место записи.
- `--mode` — Режим LLM-планирования, действует только вместе с --career: fill — до-заполнить существующие записи (по умолчанию), create — составить план с нуля. При --entry не действует: ручной путь сам читает живое состояние резюме; явный fill идентичен умолчанию, create будет отвергнут (#326). (по умолчанию: 'fill')
- `--career` — Факты карьеры для LLM (обязательно без --entry)
- `--entry` — Готовая запись опыта JSON без LLM (#326), можно несколько: '{"company":..., "position":..., "start_year":..., "start_month":..., "end_year":..., "end_month":..., "current":..., "duties":..., "achievements":[...], "company_url":...}'. start_month обязателен (число 1-12 строкой) — форма опыта hh.ru не сохраняется без месяца начала работы (#811). Записи только ДОБАВЛЯЮТСЯ к резюме (#957): существующие строки печатаются перед записью и не меняются, а дубликат (та же компания+должность+начало) отклоняется.
- `--existing EXISTING` — JSON-массив существующих записей для LLM-планирования: в режиме fill заменяет живое чтение hh.ru, в create уходит в контекст промпта и служит fallback-планом
- `--dry-run` — Показать план, не нажимая save
- `--force` — Разрешить запись без TTY prompt

### `edit-languages`

Без --language: LLM только предлагает языки (уровень CEFR не угадывается и не пишется на hh.ru). С --language NAME=CEFR: записывает явно подтверждённые языки. Раздел 'Языки' общий для всего профиля hh.ru: запись применяется ко всем резюме аккаунта, а не только к --resume.

- `--resume` — Slug из конфига или resume_id HH.ru — используется только для выбора аккаунт-сессии; языки общие для всего профиля
- `--mode` —  (по умолчанию: 'append')
- `--language` — Добавить язык вручную; CEFR: A1, A2, B1, B2, C1 или C2 (можно повторять)
- `--dry-run` — Показать план без записи (только с --language; без него запись не идёт всегда)
- `--force` — Подтвердить WRITE без prompt (только с --language)

### `edit-skills`

Предлагает навыки с уровнями и, после явного подтверждения, добавляет их в inline-форму hh.ru. Без --dry-run боевой запуск требует --force или TTY-подтверждение.

- `--resume` — Slug из конфига или реальный resume_id HH.ru (#319)
- `--mode` —  (по умолчанию: 'append')
- `--skill` — Добавить навык вручную; LEVEL: basic, intermediate или advanced (можно повторять)
- `--dry-run` — Показать план и отменить форму без сохранения
- `--force` — Подтвердить WRITE без интерактивного вопроса

### `fill-form`

- `--url` — Явный URL внешней формы
- `--resume` — ID резюме из конфига
- `--dry-run` — Обязательный режим: без submit и навигации формы

### `funnel`

- `--resume` — Slug из конфига или resume_id HH.ru (по умолчанию — все)
- `--search-query` — Группировать воронку по поисковому запросу вместо резюме
- `--format` — Формат вывода: table (по умолчанию) или md (по умолчанию: 'table')
- `--period PERIOD` — Срез за последние N дней (по умолчанию 30; 0 = за всё время) (по умолчанию: 30)
- `--dead` — Показать «мёртвую зону»: отклики без ответа старше --dead-days
- `--dead-days DEAD_DAYS` — Порог «мёртвой зоны» в днях (по умолчанию 14) (по умолчанию: 14)
- `--rejections` — Показать агрегат отказов по работодателю, поиску и вилке зарплаты

### `import-cookies`

- `--profile PROFILE` — Путь к профилю Chrome или имя профиля (Default, Profile 1) — имя резолвится от стандартного корня профилей Chrome

### `learn`

- `--resume` — Slug резюме для исключения уже указанных навыков
- `--limit LIMIT` — Сколько строк вывести (по умолчанию: 20)

### `list-resumes`

- `--status` — Дополнительно: можно ли поднять (кулдаун) и дата последнего поднятия
- `--local` — Без похода на hh.ru: только записи config.yaml (overlay настроек)

### `log`

- `-n, --lines LINES` — Количество строк (по умолчанию 50) (по умолчанию: 50)
- `-f, --follow` — Следить за логом (tail -f); прерывается по Ctrl-C
- `--prune` — Чистка дампов probe/apply (*.html/*.png) в data/logs; по умолчанию только план (dry-run), удаление — по --yes или TTY-подтверждению
- `--older-than DAYS` — Удалять дампы старше N дней (по умолчанию 14) (по умолчанию: 14)
- `--yes` — С --prune: подтвердить удаление без TTY prompt

### `login`

- (без аргументов)

### `login-code`

- `--login` — Email или телефон
- `--code-file CODE_FILE` — Файл с одноразовым кодом; без него код читается из stdin

### `mark`

- `--resume` — Slug из конфига или resume_id HH.ru (обязательно)
- `--vacancy` — ID вакансии (число из URL https://hh.ru/vacancy/<id>)
- `--status` — Статус для пометки (по умолчанию offer) (по умолчанию: 'offer')

### `market`

- `--estimates` — Достроить медиану эвристическими оценками ЗП для вакансий без указанной (помечаются ~). По умолчанию только реальные ЗП

### `probe`

- `--resume` — Slug из конфига или resume_id HH.ru (по умолчанию — все)
- `--dry-run` — Показать, что будет сделано, без реальных действий
- `--max-pages MAX_PAGES` — Максимум страниц поиска (по умолчанию: 5)
- `--vacancy-id` — ID целевой вакансии (число из URL https://hh.ru/vacancy/<id>)
- `--vacancy-url` — URL целевой вакансии (альтернатива --vacancy-id)
- `--start-page START_PAGE` — Начальная страница поиска (нумерация с 0)
- `--healthcheck` — Read-only проверка ключевых селекторов hh.ru (OK/NOT_FOUND) без отклика (#88)
- `--json` — Добавить machine-readable JSON-результат healthcheck
- `--negotiations` — Read-only дамп списка переговоров или чата без отправки (#107)
- `--topic` — ID topic из SSR-дампа negotiations для открытия чата (только чтение)
- `--questionnaires-only` — Read-only на hh.ru bulk-проверка анкет; подтверждённые вопросы пишутся в локальную SQLite, без заполнения, AI и submit
- `--limit-questionnaires LIMIT_QUESTIONNAIRES` — Остановить bulk-проверку после N подтверждённых анкет (0 — без лимита)

### `professional-roles`

Без --refresh читает только data/cache/professional_roles.json. --refresh открывает live-каталог поиска вакансий (фильтры /search/vacancy), но не выбирает профессии и не нажимает «Сохранить».

- `--query` — Короткое название профессии для локального поиска (можно повторять)
- `--limit LIMIT` — Максимум кандидатов в выводе (по умолчанию: 20)
- `--refresh` — Явно перечитать полный live-каталог поиска вакансий и атомарно обновить локальный кэш

### `profile`

Установить, показать или удалить ручные ответы профиля.

- (без аргументов)

#### `profile set`

- (без аргументов)

#### `profile show`

- (без аргументов)

#### `profile unset`

- (без аргументов)

### `publish-resume`

Публикует черновик резюме кликом по кнопке hh.ru. WRITE-hh-ru: боевой режим требует --force; --dry-run ничего не нажимает.

- `--resume` — Slug из конфига или реальный resume_id HH.ru (#319)
- `--dry-run` — Проверить состояние без клика
- `--force` — Разрешить боевой UI-клик

### `query`

Исполнить SELECT к history.db и вывести ASCII-таблицу или CSV. Только read-only (SELECT/WITH) — история меняется только через бот.

- `--csv` — Вывести CSV вместо ASCII-таблицы
- `-o` — Записать результат в файл вместо stdout

### `questionnaire`

Показать очередь, аудит и шаблоны, задать ответ или обучить шаблон.

- (без аргументов)

#### `questionnaire audit`

Что бот ответил в анкетах: ответ, уверенность, шаблон.

- `--resume` — Slug резюме или resume_id (по умолчанию — все)
- `--last LIMIT` — Сколько последних ответов показать (по умолчанию: 50)
- `--template` — Только ответы этого шаблона
- `--low-confidence` — Только вопросы, на которые бот не стал отвечать

#### `questionnaire learn`

Интерактивный разбор накопившихся вопросов анкет.

- `--resume` — Slug резюме или resume_id
- `--limit LIMIT` — Сколько вопросов разобрать (по умолчанию: 20)

#### `questionnaire pending`

Вопросы, на которые бот не стал отвечать сам.

- `--resume` — Slug резюме или resume_id (по умолчанию — все)
- `--limit LIMIT` — Сколько строк вывести (по умолчанию: 50)

#### `questionnaire set`

static — готовое значение; contextual — инструкция для LLM.

- `--mode` — static (значение) или contextual (инструкция)
- `--answer` — Готовый ответ (для --mode static)
- `--instruction` — Инструкция для LLM (для --mode contextual)
- `--example` — Формулировка вопроса, относящаяся к этому шаблону (можно повторять)
- `--cluster` — Тематический кластер вопроса
- `--resume` — Задать только для этого резюме

#### `questionnaire templates`

Шаблоны уровня аккаунта и переопределения резюме.

- `--resume` — Slug резюме или resume_id

#### `questionnaire unset`

Удаляет шаблон только из своего скоупа.

- `--resume` — Снять только переопределение этого резюме

### `refresh-token`

- `--force` — Пересохранить подтверждённую сессию в storage_state

### `reject`

- `--resume` — ID резюме
- `--vacancy` — ID вакансии
- `--reason` — Причина ручного отклонения
- `--generated-letter` — Сгенерированное письмо до ручной правки
- `--edited-letter` — Письмо после ручной правки

### `rename-resume`

Изменяет название одного резюме в списке hh.ru. WRITE-hh-ru: боевой режим требует --force или интерактивного подтверждения; --dry-run ничего не сохраняет. До подтверждения селектора поля названия в живом DOM боевой режим недоступен (fail-closed).

- `--resume` — Slug из конфига или resume_id HH.ru
- `--name` — Новое название резюме
- `--dry-run` — Показать план без записи
- `--force` — Подтвердить боевую запись

### `reply-employers`

Account-wide ответы в чатах: план из локальной истории, финальная проверка живого чата и запись аудита.

- `--dry-run` — Показать план без отправки
- `--limit LIMIT` — Максимум чатов за запуск (0 = все)
- `--max-pages MAX_PAGES` — Максимум страниц negotiations для SSR mapping (по умолчанию 5) (по умолчанию: 5)
- `--template TEMPLATE` — Текст ответа (по умолчанию cover_letter_default)
- `--suggest` — Сгенерировать и сохранить draft по входящему сообщению (без отправки)
- `--follow-up` — Режим напоминания (#710): вместо ответа на входящее — напомнить о себе там, где последнее слово уже за нами и работодатель молчит --after-days N
- `--after-days AFTER_DAYS` — Порог молчания работодателя в днях для --follow-up (обязателен вместе с ним)
- `--force` — Подтвердить боевой запуск

### `report-vacancy`

Открывает вакансию, кликает 'Ещё' -> 'Пожаловаться на вакансию', выбирает причину и заполняет комментарий. Останавливается ПЕРЕД финальной отправкой (issue #745): этот шаг не подтверждён живым DOM и намеренно не реализован. Всегда завершается [FAIL] — жалоба не отправляется этой командой ни при каких условиях.

- `--vacancy-id` — ID вакансии (число из URL https://hh.ru/vacancy/<id>)
- `--reason` — Причина жалобы (подтверждённый перечень hh.ru)
- `--comment` — Комментарий к жалобе (обязателен для всех причин на hh.ru)
- `--dry-run` — Показать план без открытия формы (по умолчанию; --force доходит до формы) (по умолчанию: True)
- `--force` — Дойти до заполненной формы жалобы (без отправки — см. описание команды)

### `responses`

- `--resume` — ID резюме из конфига (по умолчанию — все)
- `--max-pages MAX_PAGES` — Максимум страниц списка откликов (по умолчанию 5) (по умолчанию: 5)
- `--since-hours SINCE_HOURS` — Показать ответы, сменившие статус за последние N часов (по умолчанию 24). 0 — выполнить живой обход hh.ru и показать синхронизацию/историю. (по умолчанию: 24.0)
- `--detect-external-tests` — Прочитать последние сообщения работодателей и записать внешние тесты (#180)
- `--remindable` — Показать переписки, для которых hh.ru явно разрешает напоминание
- `--sync-applied` — Импортировать однозначные ручные/внешние отклики в dedup ledger
- `--alert-new` — Сообщить о новых приглашениях и вернуть специальный exit-код
- `--calendar-hint` — Для каждого нового приглашения напечатать готовую команду `hhru calendar event` с плейсхолдерами времени (#711)

### `restore`

- `--apply` — Выполнить восстановление (без флага — только показать состав)

### `resume-pool`

Копирует базовое резюме --source по разу на каждый недостающий кластер вакансий (resume_clusters.py) и помечает каждую копию своим кластером в config.yaml. WRITE-команда: боевой режим требует --force или интерактивного подтверждения; --dry-run показывает полный план без обращения к hh.ru.

- `--source` — Slug из конфига или реальный resume_id HH.ru базового резюме (#319)
- `--dry-run` — Показать план создания пула без реальных действий
- `--force` — Подтвердить боевой запуск без интерактивного вопроса
- `--write-config` — Добавить каждую успешно созданную копию в config.yaml с её кластером
- `--limit LIMIT` — Предел числа создаваемых за прогон копий (по умолчанию — все недостающие кластеры; троттлинга у copy-resume нет вовсе, поэтому предел обязателен для контролируемого batch-запуска)

### `resume-position`

- `--resume` — Slug из конфига или реальный resume_id HH.ru (#319)
- `--title` — Готовая желаемая должность без LLM (#326)
- `--specialization` — Точная профессия из live-каталога поиска вакансий hh.ru; для черновика одна, для опубликованного резюме можно несколько
- `--salary SALARY` — Зарплата (целое число, без LLM)
- `--currency` — Валюта зарплаты
- `--employment` — Тип занятости (пока только одно значение — #526)
- `--work-format` — Формат работы (пока только одно значение — #526)
- `--commute` — Время в пути
- `--business-trips` — Готовность к командировкам
- `--mode` — Режим LLM-планирования (по умолчанию fill); не сочетается с ручными полями
- `--dry-run` — Показать план без изменения hh.ru
- `--force` — Подтвердить запись без prompt
- `--allow-auto-publish` — Разрешить закрытие professional_role, после которого hh.ru может автоматически опубликовать резюме
- `--fallback-other` — Если специализация не найдена в дереве резюме — выбрать роль-плейсхолдер «Другое» (id 40) вместо отказа (#950)

### `resume-sections`

Заполняет аттестации и рекомендации по подтвержденным UI-маршрутам; умеет создать первую строку в пустом блоке. Сертификаты, портфолио и ссылки пока пропускаются, удаления не выполняются.

- `--resume` — Slug из конфига или реальный resume_id HH.ru (#319)
- `--attestation` — Готовая аттестация JSON без LLM (#326), можно несколько: '{"name":..., "organization":..., "specialty":..., "year":...}'
- `--recommendation` — Готовая рекомендация JSON без LLM (#326), можно несколько: '{"text":..., "company":..., "name":..., "position":...}'. text не поддерживается текущей формой HH.ru и приведёт к [FAIL] для этой строки, если непустой (#367).
- `--dry-run` — Показать план без изменений на hh.ru
- `--force` — Подтвердить WRITE без prompt

### `resume-views`

- `--resume` — ID резюме (по умолчанию — все резюме)
- `--limit LIMIT` — Максимум snapshots на резюме (по умолчанию 100) (по умолчанию: 100)
- `--max-pages MAX_PAGES` — Максимум страниц истории (по умолчанию 5) (по умолчанию: 5)

### `resume-visibility`

Изменяет режим видимости резюме и/или список работодателей whitelist/blacklist ('Кто видит'/'Кто не видит'). WRITE-hh.ru опасного уровня: боевой режим требует --force или подтверждения; --dry-run ничего не сохраняет. --resume all применяет одно и то же действие ко всем резюме аккаунта (основной сценарий: стоп-лист обычно общий для всех резюме).

- `--resume` — Slug из конфига, resume_id HH.ru или 'all' — все резюме аккаунта
- `--mode` — Новый режим видимости; без флага — режим не меняется, редактируется только список
- `--add-employer` — Добавить работодателя в активный whitelist/blacklist (можно повторять)
- `--remove-employer` — Убрать работодателя из активного whitelist/blacklist (можно повторять)
- `--dry-run` — Показать план без записи
- `--force` — Подтвердить боевую запись

### `review`

- (без аргументов)

#### `review approve`

- `--ttl TTL` (по умолчанию: 900)

#### `review edit`

- (без аргументов)

#### `review list`

- `--status`

#### `review requeue`

- (без аргументов)

#### `review skip`

- (без аргументов)

### `robot-queue`

- `--limit LIMIT` —  (по умолчанию: 50)

### `run`

- `--resume` — Slug из конфига или resume_id HH.ru (по умолчанию — все)
- `--dry-run` — Показать, что будет сделано, без реальных действий
- `--max-pages MAX_PAGES` — Явный максимум страниц поиска (по умолчанию — адаптивный)
- `--force` — Разрешить реальную отправку отклика с LLM-ответами на вопросы
- `--learn-questionnaires` — Спрашивать подтверждение сопоставления вопроса анкеты с шаблоном
- `--limit LIMIT` — Целевое число успешных откликов за запуск (0 = без ограничения кроме дневного лимита)

### `schedule`

- `--format` — Формат вывода: plist (launchd, по умолчанию) или crontab (по умолчанию: 'plist')
- `--action` — Какое действие планировать: bump/run (периодически) или apply (раз в день) (по умолчанию: 'bump')
- `--bump-interval-hours BUMP_INTERVAL_HOURS` — Интервал запуска bump/run в часах (по умолчанию 4, равен кулдауну) (по умолчанию: 4)
- `--apply-time` — Время ежедневного apply в формате HH:MM (по умолчанию 10:00) (по умолчанию: '10:00')
- `--apply-limit APPLY_LIMIT` — Лимит откликов за один apply-прогон (по умолчанию 5) (по умолчанию: 5)

### `search`

- `--resume` — Slug из конфига или resume_id HH.ru (по умолчанию — все)
- `--dry-run` — Показать, что будет сделано, без реальных действий
- `--max-pages MAX_PAGES` — Максимум страниц поиска (по умолчанию: 5)
- `--text` — Разовый текст поиска; можно использовать без --resume

### `select-photo`

Открывает вьюер фото на странице резюме кнопкой-карандашом, показывает библиотеку фото аккаунта и назначает выбранное фото этому резюме (в том числе замену существующего). WRITE-hh-ru: по умолчанию dry-run (read-only инвентарь библиотеки); боевой запуск требует --photo-id и --force или интерактивного подтверждения.

- `--resume` — Slug из конфига или resume_id HH.ru
- `--photo-id` — Числовой id фото из dry-run инвентаря (обязателен в боевом режиме)
- `--dry-run` — Показать библиотеку фото, ничего не назначая
- `--force` — Подтвердить боевое назначение

### `settings`

Показать все настройки, получить значение или установить ключ.

- (без аргументов)

### `skipped`

Показать записи skipped с данными вакансий из локальной истории.

- `--reason` — Показать только эту причину (по умолчанию — все причины)

### `stats`

- `--resume` — Slug из конфига или resume_id HH.ru (по умолчанию — все)
- `--period` — Период агрегации (по умолчанию all) (по умолчанию: 'all')
- `--format` — Формат вывода: table (по умолчанию), csv, md (по умолчанию: 'table')
- `--list` — Вместо сводки вывести список последних действий
- `--limit LIMIT` — Лимит строк в режиме --list (по умолчанию 50) (по умолчанию: 50)

### `uncertain`

- (без аргументов)

#### `uncertain inspect`

- (без аргументов)

#### `uncertain list`

- `--limit LIMIT` (по умолчанию: 50)

#### `uncertain reconcile`

- (без аргументов)

### `update`

Обновляет hhru и установленный Codex plugin из одного commit, проверяет provenance обоих компонентов и явно завершается ошибкой при частичном сбое. Уже открытая задача Codex продолжит использовать старый skill — после обновления начните новую задачу.

- `--codex` — Путь к Codex CLI (для диагностики и тестов; по умолчанию: codex) (по умолчанию: 'codex')

### `upload-photo`

Передаёт файл фото в скрытый file-input блока аватара на странице резюме. WRITE-hh-ru: по умолчанию только dry-run (read-only осмотр блока фото); боевой запуск требует --force или интерактивного подтверждения. Замена существующего фото не поддерживается.

- `--resume` — Slug из конфига или resume_id HH.ru
- `--photo PHOTO` — Путь к файлу jpg/jpeg/png
- `--dry-run` — Осмотреть блок фото, ничего не загружая
- `--force` — Подтвердить боевую загрузку

### `whoami`

- `--resume` — Slug из конфига или resume_id HH.ru (по умолчанию — все)
- `--online` — Проверить сессию на hh.ru (открывает браузер)

### `wizard-next`

Кликает «Сохранить и продолжить» на незавершённом экране визарда черновика. WRITE-hh-ru: боевой режим требует --force; --allow-auto-publish нужен только на последнем незакрытом экране — hh.ru публикует резюме сам (#900, #1012); --dry-run ничего не нажимает.

- `--resume` — Slug из конфига или реальный resume_id HH.ru (#319)
- `--screen` — Явный экран; по умолчанию — текущий nextIncompleteScreenId
- `--dry-run` — Проверить экран без клика
- `--force` — Разрешить боевой UI-клик
- `--allow-auto-publish` — Разрешить сабмит последнего незакрытого экрана: hh.ru опубликует резюме сам (#900, #1012). На промежуточных экранах не требуется

<!-- END CLI REF -->

## Как это устроено

- `config.py` — загрузка `data/config.yaml`.
- `history.py` — локальная SQLite-история (`data/history.db`).
- `throttle.py` — случайные паузы и дневные лимиты.
- `browser.py`/`auth.py` — запуск Playwright и вход в аккаунт.
- `search.py` — поиск вакансий и фильтрация.
- `apply.py` — отклик с сопроводительным письмом.
- `bump.py` — поднятие резюме.
- `selectors.py` — все CSS/data-qa селекторы hh.ru в одном месте.

Всё — в `src/hhru_bot/`.

## Логи

`data/logs/hhru_bot.log` и консоль, `--verbose` для подробностей. Ротация при
10 MiB (`hhru_bot.log.1`, `.2`...), архивы копятся без автоочистки.

## Похожие проекты (для идей)

Источник идей, не код для копирования — смотреть, а не переиспользовать.

> 15 декабря 2025 hh.ru закрыл соискательский API — отклик и работа с
> резюме для сторонних приложений отключены, остался поиск вакансий и
> `GET /me`. Все живые проекты 2026 года, как и наш, работают через
> браузер (Playwright), а не через API.

### Браузерные боты (наш стек — Playwright/Selenium)

- [Steev193/hh-ru-apply](https://github.com/Steev193/hh-ru-apply) — Node +
  Playwright, MIT. Вынесенные селекторы + codegen для их обновления.
- [tgeruzov/hh-auto-responder](https://github.com/tgeruzov/hh-auto-responder)
  — Tampermonkey userscript. Роутинг трёх исходов отклика.
- [YAMAKAYAMACO/hh-autoresponder](https://github.com/YAMAKAYAMACO/hh-autoresponder)
  — Python + Playwright + SQLite, ближе всего к нашей архитектуре.
- [fikstt2/hh-ai-agent](https://github.com/fikstt2/hh-ai-agent) — Python +
  Playwright. Персистентная сессия: вход руками один раз, потом headless.
- [semernyakov/hh-auto-apply](https://github.com/semernyakov/hh-auto-apply) —
  Python + Playwright, MIT. Парсер cooldown поднятия резюме из текста hh.ru.
- [beatwad/XX_Auto_Jobs_Applier](https://github.com/beatwad/XX_Auto_Jobs_Applier)
  — Python + Playwright, MIT. Config-driven (YAML), капча через Telegram.
- [s3rgeym/hh-applicant-tool](https://github.com/s3rgeym/hh-applicant-tool) —
  Python, API + Playwright, 500+ звёзд. Эталон по шаблонам писем, схеме
  SQLite и троттлингу. README запрещает коммерческое использование —
  только как референс, код не брать.

- [konard/hh-job-application-automation](https://github.com/konard/hh-job-application-automation)
  — Bun, Playwright и Puppeteer, Unlicense. Q&A-файл с нечётким матчем
  (Левенштейн + keyword overlap) для тест-вопросов формы.

### Поднятие резюме

- [Vlad9572324/hh.ru-clicker](https://github.com/Vlad9572324/hh.ru-clicker) —
  bump через API `/applicant/resumes/touch` (проверять актуальность после
  дек. 2025).
- [rycln/hhraiser](https://github.com/rycln/hhraiser) — Go. Джиттер
  расписания против антифрода.

### Адаптация и хранение резюме

Отдельный трек (issue [#671](https://github.com/axisrow/hhru/issues/671)) от
ботов-автооткликов выше: у нас резюме — внешний артефакт на hh.ru, эти
проекты работают с резюме как с локальными данными (JSON/PDF/веб-форма).
Разобран по коду один прошедший фильтр по лицензии и активности кандидат
из восьми рассмотренных — остальные отсеяны, причины ниже.

- [srbhr/Resume-Matcher](https://github.com/srbhr/Resume-Matcher) — Python
  (FastAPI) + TypeScript, Apache-2.0, ревизия
  `116f9cc3b00e1ac91734a6c2679bf41ea64a0edc` (2026-08-11). Единственный
  кандидат про адаптацию резюме под конкретную вакансию — задачу, которую
  наш проект пока не решает. Заимствуемая идея не код, а два механизма:
  - `apps/backend/app/services/improver.py` двигает содержимое резюме под
    вакансию через diff-патчи по regex-whitelist путей
    (`_ALLOWED_PATH_PATTERNS` — только `summary`, `description`-поля и
    списки навыков/языков/сертификатов) и явный blocklist полей
    (`_BLOCKED_FIELD_NAMES` — `company`, `institution`, `title`, `years` и
    т.п. трогать нельзя); LLM физически не может переписать факты вроде
    места работы или диплома, только формулировки.
  - `apps/backend/app/services/refiner.py::validate_master_alignment` —
    постфактум-проверка, что ни один навык, сертификат или работодатель в
    адаптированной версии не появился «из воздуха»: сверяет их с исходным
    («мастер») резюме и репортит `fabricated_skill`/`fabricated_cert`/
    `fabricated_company` как critical-нарушения.

  У нас похожий принцип уже есть с другой стороны — не пост-проверкой
  LLM-вывода, а входным гейтом до генерации (`STRICT_CLUSTERS` +
  `templates.is_compliance_text` в пакете `questionnaires/`, см.
  «Ключевые архитектурные решения» в `CLAUDE.md`). Whitelist путей на
  запись и alignment-проверка Resume-Matcher — предметный пример
  того же принципа «уверенная ошибка здесь необратима», применённый к
  тексту резюме, а не к ответам на анкеты; полезно как референс, если
  будет отдельная задача на адаптацию резюме под вакансию.

Отсеяны без разбора по коду (см. issue #671 для метрик):

- **Reactive-Resume**, **OpenResume** — веб-конструкторы резюме с нуля.
  У нас резюме уже существует на hh.ru и правится по DOM; локальный
  конструктор — не наша задача. Дополнительно у OpenResume лицензия
  AGPL-3.0 (портирование кода закрыто) и последний push — октябрь 2024.
- **RenderCV**, **sb2nov/resume**, **McDowell CV** — генерация
  предсказуемого PDF/LaTeX резюме как локального файла. Наш артефакт живёт
  в интерфейсе hh.ru, а не в файле, который мы рендерим сами — вне скоупа.
- **JSON Resume CLI** (`jsonresume/resume-cli`) — репозиторий
  архивирован; сама структура данных как формат остаётся жизнеспособной
  идеей, но брать нечего — код не развивается.
- **YAMLResume** — та же задача структурированного хранения резюме, что
  уже закрыта внутри проекта своими средствами (`CandidateFacts` в
  конфиге, issue #751), без заимствования у reference-проекта.

### Изучены точечно (отдельные селекторы, не полный аудит)

Разобраны по конкретным находкам (`docs/research/reference-selector-diff-audit.md`),
не по всему функционалу — сравнивать их с остальными в таблице фич нечестно.

- [Vadtop/hh-mcp-server](https://github.com/Vadtop/hh-mcp-server) — Python +
  Playwright, MIT.
- [AgentShekel/hh-bot](https://github.com/AgentShekel/hh-bot) — Python +
  Playwright, лицензия не подтверждена (NOASSERTION).
- [RumyantsevQa/hh-ai-auto-apply-assistant](https://github.com/RumyantsevQa/hh-ai-auto-apply-assistant)
  — Python, MIT.
- [kavotavochavo1-ctrl/hh-ai-job-bot](https://github.com/kavotavochavo1-ctrl/hh-ai-job-bot)
  — Python + Playwright, MIT.
- [lil-zon/hh-auto-apply](https://github.com/lil-zon/hh-auto-apply) — Python,
  без LICENSE.

### Обход DDoS-Guard / антидетект браузера

- [Kaliiiiiiiiii-Vinyzu/patchright-python](https://github.com/Kaliiiiiiiiii-Vinyzu/patchright-python)
  — drop-in замена Playwright, чинит Playwright-детект.
- [daijro/camoufox](https://github.com/daijro/camoufox) — антидетект-форк
  Firefox (автор предупреждает: не для стабильного прода).
- [ultrafunkamsterdam/nodriver](https://github.com/ultrafunkamsterdam/nodriver)
  / [cdpdriver/zendriver](https://github.com/cdpdriver/zendriver) — сильнее
  всех против DDoS-Guard, но не Playwright.

Гарантий обхода DDoS-Guard в headless нет; капчу приходится проходить руками
— это уже частично делает `login`.
