Руководство пользователя

Редактировать на GitHub

Справка — career-ops-ui

Works with 18 LLM providers — Anthropic, OpenAI, Gemini, Qwen, OpenRouter, GitHub, DeepSeek, Kimi, MiniMax, Mistral, Ollama and more

Полный разбор каждой страницы — от запуска приложения до приглашения на интервью. Каждый ## заголовок ниже соответствует пункту бокового меню или фазе рабочего процесса. При первом запуске читайте сверху вниз; позже переходите к нужному разделу через оглавление в боковой панели справки.

Аудитория: все, кто только что положил этот UI внутрь career-ops checkout и запустил bash bin/start.sh. Предварительное знакомство с career-ops не требуется.

О career-ops

career-ops — open-source-система поиска работы, которая запускается как slash-команды внутри любого AI-CLI для разработки (Claude Code, Cursor, Codex, OpenCode, Antigravity CLI, Grok Build CLI, Qwen Code, Kimi, GitHub Copilot CLI, Gemini CLI (legacy) — другие Claude-совместимые CLI работают через тот же интерфейс slash-команд). Модель-агностична. Оценивает каждую вакансию против вашего CV по рубрике из пяти измерений плюс целостная общая оценка 0.0–5.0, генерирует подогнанные PDF-резюме и отслеживает каждую заявку локально на вашей машине.

Канонические источники (читайте по порядку при первой установке):

  • What is career-ops — система, принципы, инвентарь концептов.
  • Scan job portals — поиск вакансий; наполнение Pipeline.
  • Apply for a job — полный флоу подачи с Playwright form-read.
  • Batch-evaluate offers — оценка 10+ JD за раз через batch-runner.sh.
  • Set up Playwright — установка Chromium и регистрация MCP для PDF и заполнения форм.
  • How career-ops scores job listings — методология оценки: пять параметров плюс целостная общая оценка, порог отклика 4.0 и то, что система принципиально не делает. Также кратко изложено на cvstart.org/methodology на вашем языке.

Определяющие принципы (из career-ops.org/docs/introduction/what-is-career-ops):

  • Open source всерьёз — лицензия MIT, без платных тарифов, без вейтлистов, без телеметрии, без аккаунтов. Система работает без платных уровней, аккаунтов и сбора телеметрии. Изменения в коде проходят ревью сообщества до релиза.
  • Суверенитет данныхcv.md, config/profile.yml, data/, reports/, interview-prep/ никогда не покидают ваш ноутбук, пока вы сами их явно не запушите. Вы запускаете систему локально и сохраняете полный контроль над данными.
  • AI-agnostic-архитектура — career-ops НЕ поставляется с конкретной моделью. Это набор команд внутри уже установленных AI-CLI для разработки. Переключайте провайдеров (Anthropic ↔ Gemini ↔ OpenAI) — история ваших оценок остаётся согласованной.
  • Подаёт человек — career-ops составляет ответы и открывает форму, но Submit нажимаете вы. Система никогда не подаёт автоматически. Она даёт структуру и оценку; финальное право отправки остаётся за человеком.
  • Структурированный поиск — рассчитан на активный, осознанный поиск с большим числом откликов. Не инструмент одной заявки и не рекомендательная система. Установка занимает около 15 минут и предполагает уверенное владение терминалом.

Чем career-ops НЕ является (явные не-цели):

  • Не авто-аппликатор. Формы за вас он не отправит.
  • Не пересборщик резюме. Подгоняет под конкретный JD; опыт не выдумывает.
  • Не оптимизатор LinkedIn. Профиль — ваша зона ответственности.
  • Не замена таблицам с SaaS-обёрткой. Данные лежат как обычный markdown в вашей файловой системе.

Ключевые концепты (полный инвентарь — каждый артефакт, к которому career-ops прикасается):

Концепт Что это
Mode Шаблон промпта в modes/<slug>.md. Встроенные: oferta, deep, apply, pipeline, batch, contacto, followup, interview-prep, patterns, project, training, ofertas, auto-pipeline, pdf, latex, scan, tracker.
Архетип Профиль целевой роли в config/profile.yml. Рубрика взвешивает совпадения навыков относительно активного архетипа — самое важное поле.
Pipeline data/pipeline.md — инбокс JD-URL, ожидающих оценки.
Tracker data/applications.md — историческая GFM-таблица всех оценок и статусов заявок.
Report reports/<NNN>-<company>-<DATE>.md — полная оценка JD по секциям A–F со score и legitimacy в заголовке.
Scan history data/scan-history.tsv — append-only-журнал; предотвращает дубликаты между сканами.
Proof points STAR+R-блоки доказательств, извлечённые из cv.md; переиспользуются в оценке, ответах на форму и подготовке к интервью.
JD store jds/jd-<date>-<ts>.txt — описания вакансий, сохранённые при оценке для аудита.
Interview-prep interview-prep/<company>-<role>.md — глубокие исследования и one-pager под раунд.
Batch additions batch/tracker-additions/*.tsv — строки, поставленные в очередь скриптом batch-runner.sh для слияния в трекер.

career-ops vs career-ops-ui (это приложение)

career-ops (CLI) career-ops-ui (это приложение)
Где работает внутри Claude Code / Cursor / Codex / OpenCode / Antigravity CLI / Grok Build CLI / Qwen Code / Kimi / GitHub Copilot CLI / Gemini CLI (legacy) http://127.0.0.1:4317 в браузере
Поверхность slash-команды /career-ops <mode> сайдбар, по одной странице на этап
Заполнение форм да, через Playwright MCP нет — выдаёт чек-лист, остальное доделывается в CLI
PDF generate-pdf.mjs 📄 Generate PDF на #/cv, #/reports/:slug, #/evaluate, #/deep, #/interview-prep
Файлы данных общие с career-ops-ui общие с career-ops

career-ops-ui — чистая надстройка. Ничто внутри career-ops/ не меняется. Обе поверхности используют один и тот же cv.md, config/profile.yml, portals.yml, data/, reports/, interview-prep/, modes/.

Пороги действий по score

Как только у JD появилась оценка, score определяет следующий шаг (каноническая таблица из career-ops.org/docs/introduction/what-is-career-ops):

Score Следующий шаг
≥ 4.5 Запустите /career-ops apply — высокий fit, подавайтесь сразу.
4.0 – 4.4 Подавайтесь или /career-ops contacto для тёплого интро.
3.5 – 3.9 Запустите /career-ops deep — изучите компанию и роль перед решением.
< 3.5 Пропустите, если нет конкретной личной причины.

Страницы #/dashboard и #/tracker в career-ops-ui подсвечивают каждую строку со score ≥ 4.0, чтобы вы могли выбрать действие, не перезапуская ничего вручную.

Внешняя документация

Полная справка по движку career-ops (сканирование, рубрика оценки, batch-обработка, флоу подачи, установка Playwright) лежит на career-ops.org/docs:


1. Быстрый старт — пошаговое руководство от «создать CV» до «отклик отправлен, контакту написано»

Это канонический playbook кнопка за кнопкой. Пройдите его по порядку при первом запуске. Каждый шаг называет точный маршрут, точную кнопку и то, что вы увидите при успешном выполнении. Разделы 2–16 ниже — детальное погружение в каждую фазу.

Спросить справку. Откройте Спросить справку 💬 (боковая панель, под «Справкой») и задайте вопрос — ответ берётся только из этого руководства на вашем языке и никогда не читает ваше резюме. Этот же помощник доступен в один тап с любой страницы — кнопка-робот с чатом плавает в правом нижнем углу (в левом нижнем для языков с письмом справа налево); нажмите, чтобы спросить, не отрываясь от дел.

Запуск и инициализация одной командой. Из терминала можно выполнить весь bootstrap, не открывая интерфейс:

career-ops-ui setup      # установка зависимостей → doctor → запуск сервера
career-ops-ui init       # выбрать LLM-провайдера + вставить его ключ (вывод скрыт)
career-ops-ui doctor     # перепроверить в любой момент (код выхода 0 ⇔ все обязательное зелёное)
career-ops-ui run        # просто запустить сервер на http://127.0.0.1:4317
career-ops-ui open       # открыть + ПОДНЯТЬ вкладку дашборда в браузере

После setup/run вкладка браузера открывается и поднимается на передний план автоматически (v1.43.0); career-ops-ui open делает то же по требованию, так что не придётся искать вкладку дашборда. NO_OPEN=1 отключает авто-открытие для headless/CI-запусков.

setup сам прогоняет всю цепочку целиком. init записывает ключ в родительский career-ops/.env через тот же проверенный путь, что использует вкладка API-ключей #/config, и задаёт LLM_PROVIDER (auto | claude | gemini), который учитывают живые маршруты evaluate / deep / mode / авто-конвейера. Форма для CI: career-ops-ui init --provider claude --anthropic-key sk-ant-… --yes. Предпочитаете интерфейс? Продолжайте с шагов ниже.

A. Установка (один раз, ~5 минут)

career-ops-ui должен располагаться по пути career-ops/web-ui/ (вложенно внутри родительского проекта career-ops). Он читает ваши cv.md, config/ и data/ из родительской папки через ../ и не работает автономно. Если после git pull команда career-ops-ui init не найдена, выполните cd career-ops/web-ui && npm install && npx career-ops-ui init.

Шаг 1 — Откройте приложение на http://127.0.0.1:4317. Если сервер не запущен, выполните в терминале bash bin/start.sh из корня репозитория. Загрузится Dashboard (#/dashboard).

Шаг 2 — Кликните ❤ Health в левом меню. Каждая required-проверка должна быть зелёной:

  • cv.md, config/profile.yml, portals.yml существуют
  • API-ключ задан (хотя бы один из ANTHROPIC_API_KEY / GEMINI_API_KEY)
  • Playwright установлен (нужен только для Generate PDF)

Если что-то горит красным, страница точно покажет, какой файл или переменную окружения нужно поправить. Не идите дальше, пока Health не станет зелёным.

Шаг 3 — Кликните ⚒ App settings в меню. Откроется вкладка API keys & runtime.

  • Вставьте ANTHROPIC_API_KEY (предпочтительный — лучше структурный длинный вывод для оценок) и/или GEMINI_API_KEY. Получите ключи на https://console.anthropic.com/settings/keys или https://aistudio.google.com/apikey.
  • Нажмите 💾 Save. Затем нажмите ▶ Test Anthropic (или Gemini) — крошечный round-trip подтвердит, что ключ работает.

Шаг 4 — Переключитесь на вкладку Profile на той же странице. Это прямой YAML-редактор для config/profile.yml. Как минимум отредактируйте:

  • candidate.full_name — замените placeholder («Jane Smith») на своё настоящее имя
  • candidate.email, linkedin, github — используются в cover-letters
  • target.roles — должности, на которые вы откликаетесь
  • target.comp_total_min_usd — минимальная total comp; офферы ниже этой суммы помечаются красным в секции D каждой оценки
  • target.archetypes — карьерные паттерны, которые вы принимаете (самое влияющее поле)

Нажмите 💾 Save. Сервер валидирует YAML и проставляет канонический заголовок # Career-Ops Profile Configuration.

B. CV (один раз, ~10 минут)

Шаг 5 — Кликните ✎ CV в меню. Две колонки: редактор слева, живой preview справа.

Шаг 6 — Выберите способ заполнить редактор:

  • Загрузить готовое резюме — нажмите 📁 Upload CV, выберите любой из .docx / .doc / .odt / .rtf / .pdf / .html / .txt / .md. Сервер конвертирует файл в markdown через pandoc или pdftotext, выполняет санитайзинг от XSS и кладёт результат в редактор. Проверьте конверсию — у PDF особенно может потеряться вёрстка.
  • Вставить markdown напрямую — textarea это markdown-редактор; правая панель показывает то, что увидит LLM (и будущий рекрутер).
  • Советы по тону: один буллет — одно достижение с метрикой. Уложитесь в 1500 слов. Порядок секций: Summary, Experience, Projects, Education, Skills.

Шаг 7 — Нажмите 💾 Save (верхний правый угол страницы CV). Сервер выполняет санитайзинг (<script> / javascript: / inline-обработчики удаляются) и записывает cv.md. Тост: «Saved».

Шаг 8 (опционально) — Нажмите 📄 Generate PDF. Запустит generate-pdf.mjs в родительском проекте (требуется Playwright), и новый PDF автоматически скачается в браузер по завершении. Список внизу страницы хранит все ранее сгенерированные файлы.

C. Найти вакансии (~2 минуты на скан)

Шаг 9 — Кликните 🌐 Scan в меню. Убедитесь, что portals.yml перечисляет нужные вам доски (раздел 5 этой справки). Нажмите кнопку 🌐 Scan now. В правую панель идёт живой SSE-лог, пока сканер обходит Greenhouse / Ashby / Lever / Workable / SmartRecruiters / Workday (английские доски) и hh.ru / Habr Career / Trudvsem / GetMatch / GeekJob (российские доски, если включены).

Шаг 10 — Когда скан завершится, просмотрите результаты. Кликните любой тег компании, чтобы отфильтровать; кликните иконку ↗, чтобы открыть страницу карьеры компании в новой вкладке. Каждая вакансия, прошедшая title-фильтр, поставлена в Pipeline.

D. Оценить офферы (~30 секунд на JD)

Шаг 11 — Кликните Pipeline в меню. Видите все URL, которые поставил сканер. Кликните запись, чтобы развернуть inline-preview JD.

Шаг 12 — Нажмите ▶ Evaluate рядом с любым JD. Перейдёте на #/evaluate. С заданным API-ключом запускается live; без ключа получаете готовый prompt для копирования в свой LLM. Live-режим выдаёт оценку 0–5 против вашего CV по секциям A–F. Сохранение идёт в reports/<date>-<slug>.md.

Шаг 13 — Кликните Reports в меню и просмотрите свежую оценку. Всё ниже вашего comp_total_min_usd помечается красным в секции D. Всё с пометкой Verdict: pursue — ваш short-list.

E. Решить и глубоко исследовать выбранную компанию (~3 минуты)

Шаг 14 — Выберите вакансию, на которую стоит подаваться. Кликните Deep research в меню. Введите название компании и роль. Модель выдаст бриф из семи секций (миссия, недавние новости, tech-stack, hiring-сигналы, бенчмарки компенсации, риски, рекомендуемый угол подхода). Сохранение идёт в interview-prep/<company>-<role>.md.

F. Отклик (~5 минут на заявку)

Шаг 15 — Кликните Apply checklist в меню. Вставьте URL вакансии и JD. Хелпер сгенерирует пошаговый чек-лист подачи:

  • Cover-letter draft (используется ваш cv.md + profile.yml)
  • Конкретные ключевые слова из JD
  • Файлы для прикрепления (CV PDF — см. шаг 8)
  • Куда подавать (канонический careers-URL, не агрегатор-редиректы)
  • Напоминание: НИКОГДА не submit’ьте автоматически — финальный review и отправка всегда вручную.

Шаг 16 — Откройте careers-страницу в новой вкладке. Используйте apply-checklist как todo-список. Подавайтесь через настоящую форму компании. Прикрепите PDF, сгенерированный на шаге 8.

Шаг 17 — Напишите живому человеку. Откройте режим Outreach (#/contacto в меню). Модель составит короткое сообщение для LinkedIn / email, заточенное под бриф из шага 14. Персонализируйте opener (одна конкретная деталь из вашего deep-research-брифа). Отправьте.

G. Трекинг и follow-up (постоянно)

Шаг 18 — Кликните Tracker в меню и добавьте строку заявки: компания, роль, score, статус Applied, ссылка на отчёт, ссылка на deep-research-бриф. Дата проставится автоматически.

Шаг 19 — Через неделю откройте режим Follow-up (#/followup). Сгенерирует вежливое check-in-письмо со ссылкой на исходную заявку. Отправьте. Обновите статус в Tracker на Followed up.

Шаг 20 — Когда придёт приглашение на интервью, запустите режим Interview prep (#/interview-prep). Сгенерирует таргетированную подготовку под конкретную компанию и стадию (system design / behavioral / coding). Автоматически тянет данные из deep-research-брифа.

Шаг 21 — Получили оффер? Обновите статус в Tracker на Offer и вернитесь к секции компенсации в отчёте — минимальная сумма приёма уже там.

TL;DR — порядок сайдбара повторяет workflow

Health → App settings → Profile → CV → Scan → Pipeline → Evaluate → Reports → Deep research → Apply checklist → Outreach → Tracker → Follow-up → Interview prep → Activity log

Всё. 21 шаг, кнопка за кнопкой, от нуля до оффера.

Auto-pipeline в один клик (#/auto) — короткий путь вместо 21 шага

Чтобы быстро оценить одну конкретную вакансию, пропустите ручной проход. Сайдбар → ✨ Auto-pipeline (или кнопка ✨ на Dashboard): вставьте URL, нажмите Enter или ▶ Запустить весь пайплайн — сервер прогоняет всю цепочку за один наблюдаемый проход:

  1. Валидация URL — SSRF-безопасная проверка (isValidJobUrl).
  2. Загрузка описанияsafeGet (DNS-pinned) скачивает + санитизирует JD.
  3. Оценка по CV — Anthropic → Gemini → ручной промпт без ключа.
  4. Сохранение отчёта — пишет reports/<slug>.md (score + достоверность).
  5. Добавление в трекер — строка в data/applications.md.

Обратная связь — вертикальный stepper (упорядоченный список, aria-current на активном шаге, live-region для скринридеров). По завершении карточка ведёт к отчёту (Открыть отчёт · N/5) и в трекер. Упавший шаг помечается, кнопка снова активна — повтор без перезагрузки. Нет API-ключа? Ручной режим: шаги 3–5 сворачиваются, выдаётся промпт для копирования. Линкуется: #/auto?url=<enc>&go=1 автозапуск.

CLI (v1.38.0). Одна команда — вся цепочка: career-ops-ui setup. Verbs: career-ops-ui doctor (проверка env/ключей/тулинга — тот же движок, что Health; exit 1 при любом обязательном провале), career-ops-ui run, career-ops-ui init (мастер провайдер+ключ, v1.39.0). Провайдеры (v1.39.0). Во вкладке API-keys добавлены select LLM_PROVIDER (auto=Anthropic→Gemini · claude · gemini) и поле OPENAI_API_KEY (сторона Codex/OpenCode CLI). career-ops-ui init — интерактивный мастер того же.

Провайдеры (v1.57.0). Headless live-eval теперь охватывает Anthropic → Gemini → OpenAI → Qwen → OpenRouter → GitHub Models → Hermes → DeepSeek → GLM (Z.ai) → Kimi (Moonshot) → MiniMax → Mistral → Grok (xAI) → Together → Fireworks → Ollama → BytePlus Ark → Volcengine Ark (порядок auto; LLM_PROVIDER фиксирует один). OpenRouter — один OPENROUTER_API_KEY открывает 300+ моделей; выпадающий список OPENROUTER_MODEL грузит живой каталог OpenRouter (серверный прокси, кураторский offline-fallback). Также исправлено: ключи, вставленные с переносом строки / пробелами, тримятся до валидации — /#/config больше не показывает «validation failed» ни для одного провайдера.


2. Настройки приложения и API-ключи (#/config)

Новое в v1.55 → v1.56. Без ключа LLM красный баннер на каждом экране объясняет, что ⚡ Запуск вживую в режиме ручного промпта, и ведёт сюда; с ключом — тихий чип с активным провайдером. Перед каждой кнопкой ⚡ Запуск вживую (#/auto, #/evaluate, #/deep, режимы) показывается честная оценка стоимости (напр. «Оценочная стоимость: OpenAI gpt-5-codex · ~$0.04/eval», либо без затрат API в ручном режиме). #/scan прячет вторичные фильтры за раскрывашкой Расширенные фильтры; #/tracker добавляет кликабельные чипы воронки + опциональную серверную пагинацию; #/pipeline виртуализирует свыше 1000 строк.

Инструменты CLI для ИИ. Вкладка Инструменты CLI для ИИ показывает, какие агентские CLI (Claude Code, Cursor, Codex, Gemini, OpenCode, Copilot, Qwen, Antigravity, Kimi CLI, Grok Build CLI) установлены на сервере — сканирование PATH только для чтения, без запуска. Внешний вид → Показывать логотипы компаний (по умолчанию выключено) выводит favicon каждой компании в таблице сканирования, взятый с её собственного домена (не сторонний сервис).

Две вкладки:

  1. API keys & runtime — редактирование .env родительского проекта прямо из браузера (тот же файл, который Node-скрипты career-ops читают на старте). Вкладка также предлагает селекторы модели по провайдеру — OPENAI_MODEL (OpenAI/Codex) рядом с ANTHROPIC_MODEL и GEMINI_MODEL.
  2. Profile — прямой YAML-редактор для config/profile.yml. Сохранение проставляет канонический заголовок # Career-Ops Profile Configuration.

Сохранение в любой вкладке применяется немедленно — перезапуск сервера не нужен.

Настройка LLM-провайдера (пошагово). ⚡ live-оценка веб-UI работает headless и использует один API-ключ. Работает через “OR” — задайте любой один из них, и всё заработает; при нескольких заданных auto предпочитает их в таком порядке: Anthropic → Gemini → OpenAI → Qwen → OpenRouter → GitHub Models → Hermes → DeepSeek → GLM (Z.ai) → Kimi (Moonshot) → MiniMax → Mistral → Grok (xAI) → Together → Fireworks → Ollama → BytePlus Ark → Volcengine Ark. (сам career-ops CLI-агностичен — вы также запускаете его внутри Claude Code, Cursor, Codex, Gemini, OpenCode, Antigravity, Grok Build, Qwen, Copilot или Kimi; это отдельно от этого headless-ключа.)

  1. Откройте #/config → вкладку API keys & runtime.
  2. Выберите провайдера в LLM_PROVIDER: auto (использует тот ключ, что задан) или принудительно один из claude / gemini / openai / qwen.
  3. Заполните ключ + модель выбранного провайдера:
    • Anthropic — задайте ANTHROPIC_API_KEY (console.anthropic.com), опционально ANTHROPIC_MODEL (по умолчанию claude-sonnet-4-6).
    • Gemini — задайте GEMINI_API_KEY (aistudio.google.com/apikey), опционально GEMINI_MODEL (по умолчанию gemini-3.6-flash).
    • OpenAI — задайте OPENAI_API_KEY (platform.openai.com), опционально OPENAI_MODEL (по умолчанию gpt-5-codex).
    • Qwen — задайте QWEN_API_KEY (Alibaba Model Studio / DashScope, dashscope.console.aliyun.com), опционально QWEN_MODEL (по умолчанию qwen-max). Для endpoint материкового Китая задайте QWEN_BASE_URL в сыром .env.
  4. Нажмите Save. Ключи пишутся в .env родительского проекта; изменение вступает в силу немедленно — перезапуск сервера не нужен.
  5. Проверьте на #/evaluate: вставьте URL/описание вакансии и нажмите ⚡ Run live. Заголовок результата показывает, какой провайдер сработал (anthropic / gemini / openai / qwen). Если ключ нигде не задан → вы получите ручной промпт для копирования-вставки.

Секреты маскируются после сохранения и никогда не логируются. Поля id модели (*_MODEL) не являются секретами.

Вкладка Profile

v1.32.0 — форма по полям. Вкладка Profile больше не textarea с сырым YAML, а форма со сворачиваемыми секциями Кандидат / Нарратив / Компенсация. При сохранении отправляются только 14 смоделированных скалярных путей; сервер мёржит в config/profile.yml, поэтому archetypes, proof_points и кастомные ключи сохраняются нетронутыми. Компромисс: сохранение по полям пересериализует YAML и теряет #-комментарии — для их сохранения или правки вложенных массивов используйте раскрывашку Advanced: edit raw YAML внизу вкладки. v1.35.0 — редакторы массивов. Add/remove-редакторы для Target roles и Superpowers (списки строк), Archetypes (name/level/fit) и Proof points (name/url/hero-metric). Та же гарантия merge-not-replace; очистка списка удаляет ключ начисто. v1.54.3 — вкладка Modes как структурированная форма. modes/_profile.md больше не посекционный редактор сырого markdown, а форма полей, выведенная из документированной схемы. Списочные секции — Target Roles / Adaptive Framing / Comp Targets — это повторяемые построчные поля (добавить/удалить строки); прозаические секции — Exit Narrative / Location Policy — это подписанные textarea; любая неизвестная или несписочная секция откатывается к подписанному дословному textarea. Сохранение по-прежнему мёржит по секциям — преамбула, нетронутые и кастомные секции сохраняются байт-в-байт. Раскрывашка Advanced: raw markdown остаётся для правки всего файла — добавления/удаления секций или редактирования преамбулы.

  • Textarea показывает текущий config/profile.yml дословно.
  • Отредактируйте и нажмите 💾 Save. Сервер валидирует YAML (должен быть mapping, должен содержать candidate) и записывает файл.
  • Заголовок # Career-Ops Profile Configuration добавляется автоматически, если его нет.
  • Read-only-сводка на #/profile — визуальный компаньон.

Распознаваемые ключи

Ключ Что делает Где получить
ANTHROPIC_API_KEY Включает live-вызовы Anthropic SDK. Предпочтителен при наличии обоих ключей — лучше структурный длинный вывод для скоринга JD и deep research. https://console.anthropic.com/settings/keys
ANTHROPIC_MODEL Переопределяет дефолтный claude-sonnet-4-6. Попробуйте claude-opus-4-7 для сложных рассуждений, claude-haiku-4-5-20251001 для дёшево-и-быстро.
GEMINI_API_KEY Fallback при отсутствии Anthropic-ключа. Используется gemini-eval.mjs для режима oferta. Free tier работает для малого объёма. https://aistudio.google.com/apikey
GEMINI_MODEL Переопределяет дефолтную Gemini-модель.
(server uses default UA) Требуется для сканов hh.ru из-за пределов России (API возвращает 403 на обычные User-Agent). Зарегистрируйте приложение на https://dev.hh.ru/admin и используйте его UA-строку. dev.hh.ru
PORT Порт, на котором слушает Express. По умолчанию 4317.
HOST Bind-адрес. По умолчанию 127.0.0.1. Значение 0.0.0.0 открывает UI в локальной сети — аутентификации пока нет, см. Production-readiness doc.

Поведение

  • Чтение (GET /api/config) возвращает все распознаваемые ключи. Секретные (ANTHROPIC_API_KEY, GEMINI_API_KEY) маскируются — вы видите sk-ant•••••••a1b2, но никогда полное значение.
  • Сохранение (POST /api/config) валидирует каждое значение, пишет в <parent>/.env и сразу применяет к запущенному процессу. Перезапуск не нужен.
  • Пустое значение удаляет ключ. Полезно, если вы временно отключаете провайдера.

Smoke-test-кнопки

После сохранения нажмите ▶ Test Anthropic или ▶ Test Gemini — обе отправляют крошечный prompt (≤256 output-токенов), так что вы почти ничего не тратите, но убеждаетесь, что ключ подключён корректно. На успехе возвращается ~200-символьный sample.

Доктор настройки — находит незаполненные CV и профиль

Вкладка Доктор настройки на #/config выполняет проверку только для чтения: заполнены ли на самом деле ваши cv.md и config/profile.yml, и предупреждает, когда остались данные-примеры или заглушки, а также захардкоженные метрики в файлах промптов (modes/_shared.md, modes/_writing.md, batch/batch-prompt.md). Она разделяет ошибки (не хватает того, что нужно конвейеру, например нет cv.md) и предупреждения (остались данные-примеры, слишком короткое CV, подозрительная метрика). Ничего не записывается и никуда не отправляется — только читает и сообщает. Нажмите Перепроверить после правки этих файлов. В автономной установке без родительского проекта вкладка вместо этого показывает приглушённую строку «недоступно».


3. Профиль (#/profile — также доступно как #/settings)

Read-only-карточка с обзором config/profile.yml. Чтобы редактировать, перейдите в App settings → вкладка Profile (#/config → Profile). Сохранение пишет в тот же файл; эта страница перепарсивает его при перезагрузке.

Самые важные поля:

  • candidate.full_name — используется в каждом prompt. Замените шаблонное Jane Smith перед тем, как сканировать что-либо всерьёз, иначе сгенерированные cover-letters уйдут под placeholder-именем.
  • candidate.email, linkedin, github — упоминаются в генерации cover-letters и в apply-checklist.
  • target.roles — приемлемые job titles. Positive-фильтр сканера неявно опирается на этот список (через portals.yml::title_filter).
  • target.comp_total_min_usd — минимальная total comp. Секция D каждой оценки помечает офферы ниже этой суммы.
  • target.archetypesсамое важное поле. Карьерные паттерны, которые вы принимаете (например, Tech-Lead-Backend, Founding-Engineer, Data-Platform). Каждый JD матчится против них, и лучший архетип попадает в заголовок отчёта.

Страница Health выводит проверку Profile customized, которая проваливается, пока full_name совпадает с известным placeholder’ом.


4. CV (#/cv)

Единый источник истины для каждой оценки, deep research и cover letter. Хранится в cv.md в корне родительского проекта.

Способы редактирования

  • Вставить напрямую — textarea слева это markdown-редактор. Правая панель отражает то, что увидит LLM (и будущий рекрутер).
  • 📁 Upload CV — выберите локальный файл в любом из этих форматов, сервер сам сконвертирует его в markdown:
    • Текстовые форматы.md, .markdown, .txt, .html, .htm передаются без изменений (HTML идёт через pandoc → GFM markdown).
    • Office-форматы.docx, .doc, .odt, .rtf конвертируются через pandoc (brew install pandoc на macOS, apt install pandoc на Linux).
    • PDF.pdf извлекается через pdftotext из Poppler (brew install poppler / apt install poppler-utils).
    • Сконвертированный markdown попадает в редактор; нажмите 💾 Save, чтобы сохранить. Результат проходит санитайзинг (тот же XSS-страйп, что и при вставке).
    • Жёсткий лимит: 10 MB на загрузку. Файлы крупнее → 413.
  • Из LinkedIn — самый простой путь: откройте Claude Code в родительском проекте, запустите /career-ops, вставьте URL своего LinkedIn-профиля и попросите extract my CV from this and write it to cv.md.

Что подвергается санитайзингу

На серверной стороне каждый PUT на /api/cv проходит через stripDangerousMarkdown:

  • Теги <script>, <iframe>, <object>, <embed>, <svg>, <style>, <form> — удаляются целиком.
  • Inline-обработчики событий (onclick=, onerror= и так далее) — вырезаются.
  • URI-схемы javascript:, vbscript:, data:text/html — нейтрализуются.

Ответ содержит sanitized: true, если что-то из перечисленного было удалено, — так вы узнаёте, что в исходнике было что-то опасное.

Максимальный размер body: 1 MB. Больше — 413.

Прочие кнопки

  • sync-check — запускает cv-sync-check.mjs в родительском проекте. Помечает несоответствия: проект указан в CV, но отсутствует в data/applications.md для соответствующего архетипа, и тому подобное.
  • 📄 Generate PDF — стримит generate-pdf.mjs. Файлы появляются в output/*.pdf. Требуется Playwright (страница Health показывает, установлен ли он в node_modules родителя). По окончании генерации самый свежий PDF автоматически скачивается в дефолтную папку Downloads; список на странице сохраняет все ранее сгенерированные файлы.

Советы по тону и формату

  • Один буллет — одно достижение с метрикой. «Reduced p99 latency by 38%» лучше «improved performance» для каждой рубрики оценки.
  • Порядок секций: Summary (3–5 строк), Experience (в обратном хронологическом порядке), Projects (максимум 5), Education, Skills (без дубликатов, без багажа buzzword’ов).
  • Уложитесь в 1500 слов. Scoring-рубрика рассчитана на плотную информацию; раскидистое CV получает штраф за шум.

5. Порталы и источники (portals.yml)

Конфигурация сканера лежит в portals.yml в корне родителя. Три раздела имеют значение. Три SPA-секции ниже совпадают 1:1 с канонической схемой career-ops.org из scan-job-portals.

Шорткат: URL #/portals теперь резолвится прямо в App settings и (если настроен региональный источник) прыгает к группе Regional sources — так что сохранённая в закладках или набранная вручную ссылка #/portals больше не даёт 404 (v1.42.0).

title_filter

title_filter:
  positive: [backend, engineer, senior, tech lead, golang, php]
  negative: [junior, intern, frontend, ios, android, java]
  seniority_boost: [Senior, Staff, Lead, Principal]

Найденная вакансия проходит, если её title содержит хотя бы одно positive-слово И ни одного из negative-слов. Настраивайте оба списка. Сравнение — подстрочное, регистр не учитывается.

seniority_boost — третий ключ title-фильтра. Слова из этого списка ничего не отфильтровывают — они поднимают совпавшие вакансии выше в результатах, чтобы «Senior Backend Engineer» оказывался над просто «Engineer». По умолчанию: ["Senior", "Staff", "Lead"]. Подстройте под то, как формулируются ваши целевые роли.

Начните с 3–5 положительных слов для ясности; расширяйте позже.

location_filter (опционально — web-ui 1.33.0, parent #570)

location_filter:
  allow:
    - "Remote"
    - "United States"
    - "Atlanta"
  block:
    - "India"
    - "London"
    - "Germany"

Фильтрует отсканированные вакансии по строке локации (подстрока, регистронезависимо), применяется и в ATS-проходе, и в регионарном. Семантика идентична каноническому scan.mjs career-ops:

  • Нет location_filter → все локации проходят (по умолчанию).
  • Пустая/отсутствующая локация → проходит (отсутствие данных не штрафуется).
  • Совпадение в blockотклонена (block приоритетнее allow).
  • allow пуст → проходит (block уже отфильтровал).
  • allow непуст → должна совпасть хотя бы с одним ключевым словом.

Ключ верхнего уровня в portals.yml (сосед title_filter, не вложен в russian_portals).

content_filter (опционально — web-ui 1.75.0, parent #974). Опциональный sibling верхнего уровня для location_filter с теми же списками ключевых слов positive / negative, но сопоставляемыми с текстом описания / сниппета вакансии, а не с её локацией:

content_filter:
  positive: ["python", "machine learning"]
  negative: ["security clearance", "on-site only"]

Семантика идентична location_filter: нет ключа → проходит всё; вакансия с пустым/отсутствующим описанием проходит (отсутствие данных не штрафуется); совпадение в negative → отклонена; positive пуст → проходит; positive непуст → должна совпасть хотя бы с одним ключевым словом (подстрока, регистронезависимо). Применяется и в ATS-проходе, и в регионарном. Затрагиваются только источники, которые отдают описание/сниппет (например, RSS) — любая другая вакансия проходит — поэтому включение фильтра никогда не отбрасывает молча строки из источников, не несущих тело. Используйте его, чтобы отбросить прошедшую по заголовку вакансию, чьё тело вскрывает deal-breaker.

search_queries

search_queries:
  - name: "Greenhouse — Rails Engineer"
    query: 'site:job-boards.greenhouse.io "Rails Engineer" OR "Ruby on Rails" remote'
    enabled: true
  - name: "Ashby — Senior Backend"
    query: 'site:jobs.ashbyhq.com "Senior Backend" remote'
    enabled: false

search_queries запускают AI-powered Option B-скан (/career-ops scan внутри Claude Code / Cursor / Codex). Они НЕ выполняются in-process npm run scan (который ходит только по публичным API бордов). Используйте их, когда хотите находить роли в компаниях, которых ещё нет в tracked_companies. Установите enabled: false, чтобы сохранить запись, не запуская её.

tracked_companies

tracked_companies:
  - { name: Stripe,     enabled: true, careers_url: https://job-boards.greenhouse.io/stripe }
  - { name: Linear,     enabled: true, careers_url: https://jobs.ashbyhq.com/linear }
  - { name: JetBrains,  enabled: true, careers_url: https://jobs.lever.co/jetbrains }

Обязательные поля каждой записи: name и careers_url. Опциональные: api (явный эндпоинт Greenhouse / Ashby / Lever / Workable / SmartRecruiters / Workday), enabled: true|false для включения/исключения без удаления записи. ATS-сканер определяет ATS по паттерну URL (job-boards.greenhouse.io/<slug> → Greenhouse и так далее) и обращается к публичному boards-API каждой компании напрямую. Компании без распознаваемого ATS пропускаются (карточка Active Companies на /#/scan показывает их серыми с ).

rss (RSS / Atom boards)

tracked_companies:
  - { name: LaraJobs, enabled: true, provider: rss, rss: https://larajobs.com/feed }
  - { name: WeWorkRemotely, enabled: true, provider: rss, rss: https://weworkremotely.com/remote-jobs.rss }

Нацельте сканер на любой джоб-борд с RSS/Atom-фидом (LaraJobs, WeWorkRemotely, RemoteOK, golangprojects, …), добавив запись с provider: rss и ключом rss: (или feed_url:) — без правок кода. RSS-адаптер парсит каждый <item> (CDATA + HTML-сущности, заголовки/компании очищаются от тегов), нормализует в вакансию и прогоняет тот же title_filter / location_filter + дедуп + добавление в pipeline, что и ATS-источники. После этого RSS появляется как выбираемый источник в выпадающем фильтре на #/scan. (web-ui v1.62.x)

telegram_channels (публичные Telegram-каналы с вакансиями)

telegram_channels:
  enabled: true
  max_posts: 100          # default cap per channel (hard cap 300)
  channels:
    - { name: "PHP jobs", channel: rabotaphp }
    - { name: "Salary PM", channel: salary_pm, max_posts: 50 }

Сканируйте любой публичный Telegram-канал, просто перечислив его здесь — без токена бота и API-ключа. Каждый канал читается из публичного веб-превью https://t.me/s/<канал> — это обычный HTML, отрендеренный на сервере. channel: принимает любую запись (rabotaphp, @rabotaphp или полную ссылку t.me/…); max_posts: ограничивает число прочитанных свежих постов (по умолчанию 100, жёсткий потолок 300). Каналы живут в отдельном блоке верхнего уровня, а не в tracked_companies, потому что канал — не работодатель, и у него нет careers-URL, который можно определить.

Пост канала — это проза, а не запись о вакансии: структурированных полей нет. Источник берёт заголовок из первой содержательной строки, компанию / локацию / вилку — только из явных меток (Компания:, Локация:), а весь текст оставляет в описании. Отделять настоящие вакансии от рекламы и дайджестов — работа вашего title_filter. Закрытый или несуществующий канал сообщается как ошибка, а не как «вакансий нет». После этого Telegram появляется как выбираемый источник в фильтре #/scan. (web-ui v1.228.0)

russian_portals

russian_portals:
  sources: ["hh", "habr", "trudvsem", "getmatch", "geekjob"]      # или только один
  area: 113                 # 1=Москва, 2=СПб, 113=Россия, 1001=удалёнка
  per_page: 50
  only_remote: false
  queries:
    - "Senior PHP"
    - "Senior Go"
    - "Тимлид PHP"

queries — подстрочные совпадения без учёта регистра против заголовков вакансий на hh.ru и Habr Career. Аккуратнее с пересечениями с negative-списком — если "Senior PHP" стоит в queries, а "php" оказался в title_filter.negative, скан вернёт ноль результатов и консоль предупредит о конфликте.

Настройка русских порталов — подробное руководство

v1.29.0 поставляется с 5 русскоязычными адаптерами. Два не требуют ничего сверх дефолтного UA (habr-career — HTML-скрейп; trudvsem — государственный open-data API, без ключа и без гео-гейта). Два — HTML-скрейпы технических площадок (getmatch, geekjob — тоже без ключа). Один — каноничный hh.ru API, который может вернуть 403 с не-российских IP, если не выставить переменную HH_USER_AGENT через App settings → API keys & runtime (или не запускать сервер с российского IP / VPN-выхода).

Список источников

Ключ Метка Тип Auth Гео-ограничение
hh hh.ru JSON API опц. HH_USER_AGENT не-RU IP могут 403
habr Habr Career HTML нет нет
trudvsem Trudvsem JSON API (open-data) нет нет
getmatch GetMatch HTML нет нет
geekjob GeekJob HTML нет нет

Шаг 1 — Открой portals.yml

Файл лежит в корне родительского проекта career-ops/ (НЕ внутри web-ui/). Если его ещё нет, скопируй пример из родительского проекта:

# from the parent career-ops/ root (NOT web-ui/)
cp templates/portals.example.yml portals.yml
$EDITOR portals.yml

Шаг 2 — Включи все 5 источников

Добавь или обнови блок russian_portals, перечислив все источники, которые хочешь сканировать. Порядок в массиве не важен — сканер обходит их в порядке registry.

russian_portals:
  sources: ["hh", "habr", "trudvsem", "getmatch", "geekjob"]
  area: 113                  # 1=Moscow, 2=SPb, 113=Russia, 1001=remote
  per_page: 50               # how many vacancies per query per source
  only_remote: false         # set true to keep only remote postings
  queries:
    - "Senior PHP"
    - "Senior Go"
    - "Backend Senior"
    - "Тимлид PHP"

Шаг 3 — Настрой queries и фильтры

queries — это строки, по которым сканер ищет в каждом источнике. Каждый запрос исполняется по разу на источник — 4 query × 5 источников = 20 вызовов за скан. Держи список сфокусированным (3–7 запросов), чтобы скан укладывался в минуту. area — региональный код hh.ru (остальные источники его игнорируют). per_page — лимит вакансий на запрос на источник. only_remote: true фильтрует только remote на уровне адаптера (в таблице результатов также есть отдельный Remote-чип).

Типичные ошибки

Коллизия с negative-list’ом. Если слово из query ("php", "senior") также присутствует в title_filter.negative, все результаты вылетают на фильтре ещё до показа. Сканер на этапе скана пишет в stderr предупреждение — ищи строку ⚠ config: query "Senior PHP" contains "php" which is in the negative list. Решение — убрать конфликтующее слово из negative:

title_filter:
  positive: [backend, senior, lead, php, go, golang, python]
  negative: [junior, intern, frontend, ios, android]
russian_portals:
  queries:
    - "Senior PHP"     # OK — "php" no longer in negative list
    - "Senior Go"

Временно выключить один источник

Чтобы отключить источник без удаления данных, просто убери его ключ из sources:

russian_portals:
  sources: ["hh", "habr", "trudvsem"]   # only 3 of 5 sources will run

Проверка конфигурации

После сохранения portals.yml:

# 1. Save portals.yml.
# 2. In the SPA, switch to #/scan.
# 3. Click 🌐 Scan now.
# 4. Watch the SSE log for the per-source line per query:
#       "Senior PHP"
#         hh.ru    18
#         habr     21
#         trudvsem  3
#         getmatch  0
#         geekjob   2
#    A value of 0 is normal for some queries — it just means that
#    source had no matches. A "geo-blocked" or "timeout" line means
#    the adapter reached the site but couldn't read results.

CLI-bootstrap (scan-job-portals)

Канонический setup career-ops (запустите из корня родителя один раз):

cp templates/portals.example.yml portals.yml
$EDITOR portals.yml

Это весь bootstrap. Отредактируйте три раздела (title_filter, tracked_companies, search_queries, опционально russian_portals), сохраните — и можно сканировать.

Bootstrap-поведение SPA

При первом запуске сервер дописывает в portals.yml блок russian_portals: с комментариями, если его нет, — идемпотентно (второй старт ничего не делает, так как литерал russian_portals: уже на месте). Английские секции НЕ дописываются автоматически — они приходят из templates/portals.example.yml, который вы скопировали по каноническому bootstrap выше.

Найдите ATS-борду компании

Вверху #/portals есть поле Найти ATS-борду. Введите название компании (например, «Stripe»), и приложение прощупает Greenhouse, Ashby и Lever на предмет публичной доски вакансий с таким именем — только чтение, без ИИ, без браузера. Для каждого провайдера, у которого есть доска и сейчас опубликована хотя бы одна вакансия, вы получите совпадение с провайдером, URL карьеры и числом открытых вакансий. Нажмите Добавить в отслеживаемые, и эта доска допишется в tracked_companies: вашего portals.yml, так что со следующего скана сканер начнёт за ней следить. Дубликаты распознаются (вы увидите «Уже отслеживается»), и добавить можно только известные ATS-хосты — произвольные URL отклоняются. Прощупываются только Greenhouse, Ashby и Lever; компания на другом портале или без опубликованных сейчас вакансий совпадения не покажет.


6. Состояние (#/health)

Все шлюзы настройки в виде бейджей OK / OPTIONAL / FAIL. Прочитайте эту страницу до того, как заводить любой «не работает» issue.

Расход и стоимость ИИ. Страница Расход ИИ (💳, рядом со «Здоровьем») показывает токены живых генераций ИИ по провайдерам за 24 ч / 7 д / 30 д / всё время с оценкой стоимости в USD по редактируемой таблице цен (к оплате не выставляется). Компактный счётчик ИСПОЛЬЗОВАНИЕ также закреплён внизу левой боковой панели на каждой странице — те же суммы токенов за 24ч/7д/30д и оценка стоимости за 24 часа, обновляется вживую; меню всегда остаётся видимым над ним, а по клику на заголовок он сворачивается.

Required-проверки (без них система не запустится)

  • Node version ≥ 18 — сервер использует нативный fetch и node:test.
  • Project rootCAREER_OPS_ROOT (переменная окружения или авто-детект) существует.
  • cv.md, config/profile.yml, portals.yml, data/applications.md, data/pipeline.md, modes/oferta.md.

Optional-проверки (только предупреждения)

  • Profile customizedcandidate.full_name не совпадает с шаблонным placeholder.
  • GEMINI_API_KEY / ANTHROPIC_API_KEY — заданы в .env.
  • (server uses default UA) — имеет значение, только если вы сканируете hh.ru из-за пределов России.
  • Playwright (parent node_modules) — требуется для генерации PDF и check-liveness.mjs. Установите через cd $CAREER_OPS_ROOT && npm install && npx playwright install chromium.
  • Parent project dependenciescd $CAREER_OPS_ROOT && npm install, если зависимости не установлены.
  • Каталоги data/, reports/, output/, jds/ — создаются автоматически при первой записи.

Когда сервер открыт за пределами loopback (HOST=0.0.0.0), абсолютные пути и точная версия Node заменяются в ответе на "hidden", чтобы любопытный сосед в сети не мог снять fingerprint вашей установки.

Run-кнопки

  • ▶ Doctor запускает node doctor.mjs и показывает вывод в модальном окне.
  • ▶ Verify pipeline запускает node verify-pipeline.mjs.

7. Поиск (#/scan)

Сканер обходит каждую включённую доску, дедуплицирует против вашей истории и пишет хиты в data/last-scan.json и data/pipeline.md.

Поиск + Исключить. Поле «Поиск» трактует запятые как ИЛИ («роли для поиска»); новое поле «Исключить» скрывает строки с любым из слов через запятую. Оба сохраняются в сохранённых поисках.

One-click-скан (SPA)

🌐 Scan запускает каждый включённый источник одним проходом:

  • Greenhouse / Ashby / Lever / Workable / SmartRecruiters / Workday (ATS-проход) для каждой компании в tracked_companies с распознаваемым ATS-URL.
  • Агрегаторы v1.75.0 для каждой записи tracked_companies, которая их включает: RemoteOK / Remotive / Working Nomads (ленты удалёнки на уровне доски, provider: <slug>) и IBM / Arbeitsagentur / Glints / Jobstreet · SEEK (config-driven, блок <provider>: на каждую запись).
  • hh.ru API + Habr Career + Trudvsem + GetMatch + GeekJob для каждого query из russian_portals.

Две фазы — один клик (v1.29.2). Единственная кнопка 🌐 Scan запускает И ATS-проход, И регионарный — в одном SSE-стриме. В логе появятся два заголовка фаз, по очереди:

  1. ▶ ATS scan (Greenhouse + Ashby + Lever) — EN ATS-доски.
  2. ▶ Regional scan (hh.ru + Habr Career) — 5 RU-источников из registry.

Каждая фаза заканчивается итогом ✓ done · NEW=N. Если видишь только ATS-фазу — твой стенд на сборке до v1.29.2; обнови. До v1.29.2 SSE-клиент закрывался на первом done, и регионарная фаза тихо терялась (tests/scan-stream-multi-phase.test.mjs — регрессионный гейт).

Живой SSE-лог стримится в правую панель, пока скан идёт. Нажмите Stop (или просто уйдите со страницы), чтобы прервать — сервер отменит in-flight HTTPS-запросы через AbortController.

Фильтры результатов

Под логом таблица результатов рендерит строки из data/last-scan.json.

v1.78.1 — живое автообновление. Таблица результатов теперь обновляется автоматически, пока идёт скан, и ещё раз сразу после его завершения — без ручной перезагрузки или переключения страницы.

v1.80.0 — Max per source и карантин источников. Поле Max per source рядом с кнопкой Scan ограничивает, сколько вакансий вносит каждая доска (пусто/0 = без ограничения, по умолчанию) — удобно, когда одна огромная доска иначе доминировала бы. Отдельно: любой источник, возвращающий постоянную ошибку 404 / 410, записывается в data/scan-quarantine.json и пропускается при последующих сканированиях (самовосстановление: повторная попытка через 14 дней), поэтому мёртвые слаги перестают засорять лог. Отключается через scan_quarantine: false в portals.yml.

Фильтры:

  • Free text — подстрочное совпадение по title / company.
  • Source dropdown — Arbeitsagentur / Ashby / BambooHR / Breezy HR / Comeet / GeekJob / Glints / Greenhouse / GetMatch / Habr Career / hh.ru / IBM / Jobstreet · SEEK / Lever / Personio / Recruitee / RemoteOK / Remotive / RSS / SmartRecruiters / SolidJobs / Teamtailor / Trudvsem / We Work Remotely / Workable / Workday / Working Nomads (автозаполняется из GET /api/scan/sources).
  • Remote / Hybrid / Onsite dropdown.
  • Country dropdown (v1.78.0) — гео-фильтр, заполняемый из стран, обнаруженных в текущих результатах, каждая показана со своим эмодзи флага и счётчиком (напр. 🇩🇪 Germany (12)). Выберите одну, чтобы оставить только вакансии, привязанные к этой стране. Детектирование сопоставляет свободный текст локации вакансии (названия стран/алиасы + ~100 крупных городов рынка труда) со страной; оно консервативно и никогда не угадывает, поэтому вакансия, чью локацию не удалось определить — или чисто «Remote»-объявление — остаётся в All countries. Сочетайте его с выпадающим списком типа работы, чтобы находить вакансии, привязанные к стране, и удалённые.
  • Posted within dropdown (v1.80.0) — клиентский фильтр по возрасту (Последние 24 часа / 7 дней / 30 дней). Строки, чей pubDate старше, скрываются; строки без указанной даты проходят (отсутствие данных не штрафуется).
  • ★ Избранное (v1.80.0) — кликните по ☆ в любой строке, чтобы добавить вакансию в избранное (хранится в localStorage по URL); отметьте ★ Favorites в панели фильтров, чтобы показать только избранные строки. Звёздочки переживают сканирования и перезагрузки.
  • Сохранённые поиски (v1.80.0) — панель над фильтрами: задайте имя текущему набору фильтров и нажмите 💾 Save, затем применяйте его повторно из выпадающего списка или 🗑 Delete его. Хранится в localStorage; повреждённое/изменённое значение чисто сбрасывается в пустое.
  • Stack chips (PHP / Go / Backend / Senior / …) — авто-определяются по строке через Skills.detectTech и Skills.detectLevel. Multi-select работает как пересечение: выбор PHP + Senior покажет строки, где есть ОБА признака.
  • Dynamic chips под статичными stack-чипами — топ-25 самых частых capitalized-токенов из заголовков, чтобы UI адаптировался под роли, которые вы реально сканируете (маркетинг, дизайн, финансы…), а не был жёстко привязан к backend-engineer-словарю.

Карточка Active Companies

Сворачиваемая карточка со списком каждой компании из portals.yml и её статусом скана:

  • ✓ зелёный тег — прямая поддержка API (Greenhouse / Ashby / Lever / Workable / SmartRecruiters / Workday).
  • ○ серый тег — fallback на web-search-prompt (нет совпадения API).

Клик по имени компании → подставляет имя в фильтр результатов выше. Клик по иконке ↗ → открывает careers_url компании в новой вкладке.

CLI-флоу скана (scan-job-portals)

Два способа сканировать из CLI (оба кладут URL в тот же data/pipeline.md, который читает SPA):

Option A — прямой скрипт (~30 с, ноль AI-токенов):

npm run scan                          # все доски Greenhouse/Ashby/Lever
npm run scan -- --dry-run             # предпросмотр без сохранения
npm run scan -- --company Anthropic   # одна tracked-компания

Работает только для Greenhouse / Ashby / Lever / Workable / SmartRecruiters / Workday (распознаваемые ATS-URL). AI-токены не расходуются — скрипт стучится в публичные boards-API напрямую.

Option B — AI-powered browser-скан:

/career-ops scan

Внутри Claude Code / Cursor / Codex / OpenCode / Antigravity CLI / Grok Build CLI / Qwen Code / Kimi / GitHub Copilot CLI (Gemini CLI legacy). Использует токены модели. Заходит на каждую страницу tracked_companies напрямую и умеет находить доски без API (career-страницы, кастомные ATS, региональные порталы). Медленнее, но шире. Полезно, когда ATS-проход ничего не находит для цели, про которую вы точно знаете, что она нанимает.

Output (оба пути) — новые JD-URL добавляются в data/pipeline.md, каждый посещённый URL логируется в data/scan-history.tsv (дедуп на все будущие сканы), печатается сводка: companies scanned · jobs found · filtered by title · duplicates skipped · new offers added.

Пороги действий по score (применяйте после /career-ops pipeline, который batch-оценивает новые URL):

Score Рекомендуемый следующий шаг
≥ 4.5 /career-ops apply — высокий fit, подавайтесь сразу
4.0 – 4.4 подача или /career-ops contacto для тёплого интро
3.5 – 3.9 /career-ops deep — сначала изучите компанию
< 3.5 пропустите, если нет конкретной личной причины

Страницы #/dashboard и #/tracker в SPA подсвечивают каждую строку со score ≥ 4.0, чтобы вы могли выбрать действие, ничего не перезапуская.

Follow-up-команды

После оценки канонические продолжения такие:

  • /career-ops apply — заполнить заявку с подогнанными ответами
  • /career-ops contacto — составить LinkedIn / email-аутрич
  • /career-ops deep — глубоко исследовать компанию / роль
  • /career-ops tracker — посмотреть статус pipeline

hh.ru — парсится с сайта (с июля 2026 нужен российский IP)

hh.ru сканируется чтением публичной поисковой выдачи сайта (hh.ru/search/vacancy) — так же, как Habr Career: без ключа и настройки. Однако с июля 2026 hh.ru отдаёт HTTP 451 (региональная юридическая блокировка) для IP за пределами России, поэтому скан работает только с российского IP — запускайте сервер из России или через VPN с российской точкой выхода. При первом 451 (или анти-бот 403) сканер отключает hh.ru до конца прогона и пишет об этом в лог, так что остальные российские источники дорабатывают нормально. JSON API (api.hh.ru) намеренно не используется: он возвращает 403 forbidden любому программному клиенту независимо от IP и User-Agent.

Даже из сети, которая выглядит правильной, hh.ru может счесть исходящий IP VPN/прокси (любой датацентровый/хостинговый IP попадает под это) и отправить скан 302-редиректом на заглушку /vpncheeck («VPN мешает работе сайта»), которая отвечает HTTP 200 с нулём вакансий. Сканер распознаёт этот редирект, отключает hh.ru до конца прогона и сообщает об этом в логе. Решение — на стороне сети: убедитесь, что трафик действительно выходит через резидентный IP — системный VPN или прокси часто остаётся включённым, даже когда переключатель в браузере выключен (проверьте реальный исходящий IP, например на api.ipify.org).

8. Воронка (#/pipeline)

Инбокс URL, ожидающих оценки. Хранится в data/pipeline.md.

Полоса обзора. Компактная полоса сверху показывает пайплайн с одного взгляда — сколько URL во входящих, сколько отслеживается и счётчики Applied/Responded/Interview/Offer, каждый ведёт в трекер.

Добавление URL

Три способа:

  • Введите или вставьте URL и нажмите + Add.
  • Используйте глобальный поиск в верхней панели (его бейдж показывает Enter): вставьте любую ссылку http(s)://… и нажмите Enter, чтобы открыть авто-pipeline; введите любой другой текст, и Enter переходит на #/scan с предзаполненным этим запросом (v1.78.1). Ctrl/Cmd+K по-прежнему фокусирует поле там, где это разрешает браузер. Логотип бренда возвращает на дашборд.
  • Запустите Scan (см. выше) — свежие хиты идут в pipeline автоматически.

Каждый URL проходит на сервере через isValidJobUrl(). Loopback (localhost, 127.0.0.1), file://, javascript:, IP-литералы и строки с template-символами (<, >, ") — все возвращают 400.

Server-side preview pane

Клик по строке pipeline загружает превью справа. Большинство ATS-досок не отдают CORS-заголовки, так что браузер не может получить их напрямую; сервер проксирует запрос, вырезает <script> / <style> и HTML-теги и возвращает до 8 KB plain text.

Preview-прокси проходит по редиректам вручную с пер-хоповой SSRF-валидацией — каждый заголовок Location снова прогоняется через isValidJobUrl(), поэтому враждебная доска не сможет отбросить вас на loopback / приватный IP / file://. Лимит: 3 хопа, таймаут 15 секунд.

Действия в строке

  • — переход на #/evaluate?url=… с предзаполненным URL.
  • — удаляет URL из data/pipeline.md.

Кнопки в правом верхнем углу

  • ⚡ Evaluate first — открывает первый URL очереди на странице Evaluate, готовый к оценке.
  • Scan — вернуться к сканеру за новыми URL.

9. Оценить (#/evaluate)

Оценивает одно JD против cv.md и config/profile.yml. Возвращает структурированную оценку A–F по modes/oferta.md и score 0–5.

Input

Вставьте JD в textarea или придите сюда из #/pipeline с ?url=<href> — страница получит URL через тот же SSRF-safe-прокси, который используется для превью pipeline, и предзаполнит textarea.

Нажмите 💾 Save JD, чтобы сохранить JD в jds/jd-<date>-<ts>.txt для аудита (или передайте save: true в API-вызове — эффект тот же).

Цепочка fallback

  1. Anthropic — предпочтительный путь при заданном ANTHROPIC_API_KEY. Сервер бандлит cv.md, config/profile.yml, modes/_shared.md и modes/oferta.md в блок <project_context> перед промптом (каждый файл обрезается на 16 KB, общий prompt soft-cap 200 KB). Возвращает grounded markdown прямо на страницу.
  2. Gemini — при заданном только GEMINI_API_KEY. Сервер спавнит gemini-eval.mjs с JD как временным файлом. Free-tier-модель (gemini-3.6-flash) нормально справляется с рутинным скорингом.
  3. Manual — ключ не задан. Страница возвращает готовый prompt, который можно вставить в Claude Code, ChatGPT или любой другой LLM.

Output-секции (канонический A–F с career-ops.org)

Выравнивание v1.15.0. Буквы блоков теперь совпадают с канонической схемой career-ops.org. До v1.15 отчёты использовали A–G (с C=Risks, F=Verdict, G=Legitimacy); мы по-прежнему рендерим их как есть для обратной совместимости, но новые отчёты эмитят A–F с канонической семантикой ниже. Score и Legitimacy теперь живут в заголовке отчёта (score: 4.2/5, legitimacy: High|Medium|Low).

A. Role Summary — 3 буллета (риски вынесены инлайном). B. CV Match — топ-3 совпавших навыка и топ-3 недостающих. C. Strategy — рекомендация: apply now / contacto first / deep first / skip. До v1.15 называлось Risks. D. Compensation — относительно вашего target.comp_total_min_usd (legacy) или compensation.target_range (canonical). E. Personalization — угол подачи, framing по архетипу, хуки для cover letter / outreach. До v1.15 называлось Application Strategy. F. STAR stories — 1–3 готовых STAR-блока под роль. До v1.15 называлось Verdict (raw score); score теперь появляется в заголовке отчёта рядом с legitimacy.

Сохранение отчёта

Нажмите 💾 Save report (или используйте флаг save в API-вызове), чтобы сохранить markdown в reports/<date>-<company>-<role>.md. Распарсенный заголовок отчёта (Score / Legitimacy / URL) появится на странице Reports и на Dashboard.

Batch-оценка при 10+ JD

Для одного JD эта страница #/evaluate — правильный инструмент. Для 10+ URL, стоящих в pipeline, поштучный клик-через непрактичен — переходите к подразделу Batch evaluate в §14 (запуск ./batch/batch-runner.sh из родителя), дайте ему отработать ночью и возвращайтесь на #/reports / #/tracker за результатами. Полный флоу: batch-evaluate-offers.


10. Отчёты (#/reports)

Просмотр всех сохранённых оценок. Карточки показывают title, дату, флаг legitimacy и score (цветовая раскраска: зелёный ≥ 4.0, жёлтый ≥ 3.0, красный ниже).

Клик по карточке открывает полный markdown. Пагинация: 12 на страницу; элементы управления внизу.

В режиме одного отчёта также есть:

  • ← All reports — назад к сетке.
  • 🔗 Open JD — открывает оригинальную вакансию в новой вкладке.

Экспорт отчёта в PDF

Откройте сохранённый отчёт и нажмите 📄 Generate PDF. Запускается generate-pdf.mjs в родительском проекте, файл записывается в output/*.pdf (нужен Playwright — страница «Здоровье» показывает, установлен ли он). Ничего никуда не отправляется: проверьте PDF, прежде чем прикреплять его к отклику.


11. Трекер (#/tracker)

CRM. Одна строка — одна заявка; хранится в data/applications.md как GFM-таблица.

Поток статусов

EvaluatedAppliedRespondedInterviewOffer / Hired / Rejected / Discarded / SKIP.

Hired (v1.118.0) — финальный счастливый статус: оффер принят. Трекер отмечает его праздничным бейджем и встречает баннером «работа найдена».

Доска со вкладками стадий (v1.131.0). Над таблицей находится полоса вкладок стадий, которая позволяет пройти по воронке: вкладка All плюс по одной вкладке на каждый канонический статус, каждая показывает живой счётчик за всю историю — включая стадии с нулевым счётчиком, так что вся воронка всегда видна целиком. Клик по стадии фильтрует таблицу по ней; повторный клик возвращает к All. Вкладки (и свёртка локализованных/легаси-алиасов статусов в счётчиках) берутся из GET /api/tracker/stages, который читает тот же templates/states.yml, что использует сервер — поэтому набор вкладок автоматически остаётся синхронизирован со словарём статусов родителя, без захардкоженного списка на странице. Search, фильтр по оценке, сортируемые колонки и построчные report/PDF/легитимность не изменились; при включённых логотипах ячейка компании в каждой строке показывает брендовый знак.

Whitelist статусов проверяется на сервере; любое другое значение в POST /api/tracker сбрасывается на Evaluated. Канонический переход Evaluated → Applied происходит автоматически, когда вы подтверждаете Submitted. в конце /career-ops apply (см. §14).

Структура колонок

Колонка Что это
# Авто-нумерация с padding нулями (001, 002, …).
Date ISO-дата (YYYY-MM-DD). По умолчанию — сегодня.
Company Свободный текст. Пайпы (|) и переводы строки экранируются автоматически.
Role То же.
Score Формат N/5 (например, 4.2/5).
Status Whitelist enum.
PDF ✅, если generate-pdf.mjs уже отработал для этой строки.
Report Markdown-ссылка на соответствующий reports/*.md.
Notes Свободный текст, до 200 символов.

Фильтры

  • Status dropdown.
  • Score dropdown — ≥ 4.0 (high), ≥ 3.0 (mid), < 3.0 (low).
  • Search — подстрочное совпадение по company + role.

Любой фильтр сбрасывает пагинатор на страницу 1. По 25 строк на страницу.

Кнопки обслуживания

  • ▶ Normalize запускает normalize-statuses.mjs — каноникализирует написание статусов (appliedApplied, interviewInterview).
  • ▶ Dedup запускает dedup-tracker.mjs — удаляет дубликаты по (company, role) без учёта регистра.
  • ▶ Merge запускает merge-tracker.mjs — подтягивает ожидающие записи из batch/tracker-additions/*.tsv (куда batch-флоу родителя кладёт заявки, отправленные через Apply-хелпер). Дедуплицирует и архивирует обработанные файлы в batch/tracker-additions/merged/. Восходящий batch-флоу описан в batch-evaluate-offers.

Добавление строк

POST /api/tracker — body { company, role, score?, status?, url?, reportSlug?, notes?, date? }. Дедуп по (company, role) без учёта регистра. Из UI: страница Evaluate предлагает кнопку «Add to tracker» после успешного скоринга.

«Ещё активна?» — проверьте, открыта ли ATS-вакансия

Каждая отслеживаемая строка со ссылкой на вакансию, размещённую в ATS (Greenhouse, Lever, Ashby, Workday или SmartRecruiters), получает маленькую кнопку Ещё активна?. По нажатию приложение спрашивает у собственного публичного JSON-эндпоинта этого ATS, жива ли ещё вакансия — без браузера, без токенов ИИ, ничего не сохраняется. Вы получаете один из трёх бейджей:

  • Активна — вакансия всё ещё в списке.
  • Истекла — ATS вернул однозначное «удалено» (HTTP 404/410), значит вакансию сняли.
  • Неизвестно — проверка неубедительна (URL не распознан как ATS-вакансия, либо API ограничил частоту, истёк тайм-аут или ответил неоднозначно).

Проверка намеренно консервативна: она говорит Истекла только при явном 404/410, никогда по догадке, поэтому никогда не отпугнёт вас от вакансии, которая на самом деле ещё открыта. Публичный API Lever считается неавторитетным (он скрывает часть конфиденциальных вакансий), поэтому такие случаи дают Неизвестно, а не Истекла.

Записать итог отклика

Когда отклик подходит к концу — вы получили оффер, вышли на работу, вам отказали или просто не ответили — нажмите кнопку Итог в этой строке трекера, чтобы зафиксировать его. Откроется небольшое окно с предпросмотром и подтверждением:

  • Выберите, что произошло — Отказ, Получен оффер, Нанят / принят, Оффер отклонён, Нет ответа / игнор, или Прошёл на собеседование.
  • Заметка (необязательно) — короткая строка для себя.
  • Предпросмотр — приложение показывает, что именно оно сделает (например, «Установит #12 Acme → Отказ»), и пока ничего не пишет.
  • Записать итог — подтверждает.

Запись делает три вещи сразу: добавляет результат в журнал итогов (только на дозапись), архивирует отправленные вами резюме и сопроводительное письмо в папку итогов этого отклика и синхронизирует каноничный Статус строки — так Нанят или Отказ появляется в трекере и в статистике без ручного редактирования таблицы. Как и другие инструменты трекера, оно остаётся только для чтения, пока вы не нажмёте Записать итог, и ему нужен родительский проект career-ops рядом с приложением (кнопка скрыта, если этого проекта нет).


12. Глубокий рисёрч (#/deep)

Генерирует структурированный бриф компании: snapshot, инженерная культура, недавние новости, Glassdoor sentiment, процесс интервью, точки давления при переговорах, три умных вопроса для рекрутера.

Input

Два поля — название компании и (опционально) роль. Структуру задаёт шаблон режима (modes/deep.md).

Output-пути

Та же fallback-цепочка, что и у Evaluate:

  1. Anthropic live (предпочтительно) — bundleProjectContext инлайнит cv + profile + _shared.md + deep.md. Output: 10–30 KB grounded markdown, сохраняемого в interview-prep/<company>-<role>.md.
  2. Gemini live — вызов gemini-eval.mjs. То же место сохранения.
  3. Manual prompt — страница выдаёт готовый prompt для Claude Code (у которого есть WebFetch + WebSearch, и он может сделать настоящий ресёрч).

Подсказки

  • Anthropic на claude-sonnet-4-6 обычно возвращает ~13 KB полезного текста за 1–3 минуты на вызов.
  • У Anthropic SDK нет встроенного web search. Для ролей, где нужны свежие новости + Glassdoor sentiment, вставьте manual prompt в Claude Code и дайте ему воспользоваться инструментом WebFetch.
  • Live-вызовы платные; один deep-research-вызов Sonnet 4.6 стоит примерно $0.30–0.50.

13. Подсказки режимов (семь страниц /#/<mode>)

Доска каденции (v1.117.0). Страница Follow-up теперь открывается детерминированной доской каденции на данных родительского followup-cadence.mjs: срочность по каждой заявке (🔴 срочно / 🟠 просрочено / 🟡 ожидание / 🔵 остыло) с днями до следующего шага, плюс кнопка «Проставить даты фоллоу-апов», закрепляющая первую дату для каждой строки Applied (followup-seed.mjs --backfill). Без родительских скриптов доска честно сообщает «недоступно».

Семь prompt builders: Project-идеи, Training-планы, Follow-up-письма, Batch-оценки, Outreach к рекрутерам, one-pager Interview prep и Patterns-ретроспективы. Каждая оборачивает конкретный шаблон modes/<slug>.md:

Страница Slug Назначение
#/project project Подогнать портфолио-проект под целевую роль.
#/training training Анализ skill-gap → план обучения.
#/followup followup Драфт письма после интервью.
#/batch batch Prompt для batch-оценки нескольких JD.
#/contacto contacto Outreach-сообщение для рекрутера / referral.
#/interview-prep interview-prep One-pager под конкретный раунд интервью.
#/patterns patterns «Какие паттерны делали меня успешным?» — рефлексия.

Общая форма

У каждой страницы небольшая форма (поля специфичны для режима), кнопка ▶ Generate prompt (manual) и — при наличии Anthropic- или Gemini-ключа — кнопка ⚡ Run live, которая повышается до основной.

Клик по ▶ Generate prompt возвращает собранный prompt, в котором ваши значения формы JSON-stringify’нуты в блок User-supplied context:, а далее идёт дословный шаблон modes/<slug>.md. Копируйте и вставляйте в свой любимый LLM.

Клик по ⚡ Run live отправляет тот же prompt в Anthropic (или Gemini), с cv.md + profile.yml + _shared.md, инлайн через bundleProjectContext. Результат рендерится на странице, копируется и скачивается как .md.

Семь страниц — это явный allowlist. Режимы с отдельным маршрутом (oferta → Evaluate, deep → Deep research) и режимы, которые родительский проект поддерживает только внутри Claude Code (apply, scan, pipeline, tracker, pdf, latex, ofertas, auto-pipeline), сознательно не выведены в этот UI.


14. Чек-лист отклика (#/apply)

Когда вы решили подаваться, эта страница Apply helper генерирует чек-лист подачи для самого шага отправки. Она НЕ заполняет формы автоматически — этот флоу остаётся в /career-ops apply внутри Claude Code, который использует Playwright в родительском проекте.

Режим чек-листа в SPA (#/apply)

Чек-лист SPA рассчитан на пользователей, которые предпочитают заполнять форму вручную, без вызова Playwright. Он покрывает:

  1. Запустить /career-ops apply <url> в Claude Code, чтобы прочитать форму через Playwright (пропустите этот шаг, если заполняете вручную).
  2. Проверить, что вакансия ещё активна (check-liveness.mjs).
  3. Убедиться, что CV актуально (cv-sync-check.mjs, затем PDF, если score ≥ 4.0).
  4. Адаптировать cover letter / ответ на «Why us?» с использованием STAR+R proof points из cv.md.
  5. Честно ответить на вопросы EEO / sponsorship / start-date.
  6. Сохранить заполненные ответы в interview-prep/{company}-{role}.md до отправки.
  7. НИКОГДА не submit’ьте автоматически — финальную кнопку нажимаете вы (человек).
  8. После submit: добавить строку в data/applications.md (или записать TSV в batch/tracker-additions/).

Ручное заполнение vs Playwright-assisted

Два пути до фактической отправки:

  • Вручную — открыть careers-страницу в обычной вкладке браузера, пройти по SPA-чек-листу выше, копировать-вставлять ответы. Playwright не нужен. Подходит, когда форма короткая или у вас не установлен Chromium.
  • С помощью Playwright — запустить /career-ops apply <company> в Claude Code (в родительском проекте). Playwright открывает собственный браузер, читает каждое поле формы и возвращает пронумерованные черновики ответов. Submit вы по-прежнему нажимаете сами. Подходит, когда форма длинная, динамическая или вам нужен аудит-трейл «какой вопрос — какой ответ».

Полный CLI-флоу apply (apply-for-a-job)

Предусловия:

  1. Сначала запустите /career-ops pipeline, чтобы у JD появился evaluation-отчёт в reports/. Команда apply зависит от существующей оценки; если её нет, сначала прогоните pipeline.
  2. Отчёт и профиль должны быть загружены.
  3. Рекомендуется: установлен Playwright (npx playwright install chromium — см. раздел Playwright Setup ниже). При отсутствии происходит fallback на WebFetch (только текстовое превью формы, без click-fill).

Пронумерованный флоу (канонические 8 шагов):

  1. Запустите команду с названием компании:

    /career-ops apply <company>

    Пример: /career-ops apply Anthropic. Без аргумента на следующем шаге передайте скриншот формы, вставленный текст формы или URL заявки.

  2. Локализация отчёта. Система находит соответствующую оценку в reports/ (ту, что создал /career-ops pipeline или #/evaluate ранее).

  3. Открытие формы. Playwright автоматически запускает окно браузера — открывать его руками НЕ нужно.

  4. Чтение полей. Система читает и парсит каждое поле формы (label, type, required, options для select-ов).

  5. Генерация ответов. career-ops формирует подогнанные ответы для каждого поля на основе вашего профиля, proof points и роли.

  6. Возврат нумерованного списка. Вы получаете ответы в порядке полей формы — простые поля (имя, email) первыми, свободный текст (cover letter, «Why us?») последним. Помеченные пункты указывают, что требует внимания человека: salary anchor, недостающие детали резюме, опциональные вопросы.

  7. Ручное заполнение. Вы копируете и вставляете каждый ответ в соответствующее поле. Этот шаг ручной, не автоматизированный. Вы сначала проверяете каждый ответ.

  8. Пользователь нажимает Submit. Submit нажимаете вы сами. career-ops никогда не нажимает Submit. Подтвердите завершение, напечатав в чате:

    Submitted.

Автоматические обновления на Submitted.:

  • Статус меняется Evaluated → Applied в data/applications.md.
  • Заполненные ответы остаются в секции G отчёта для будущих ссылок.

Handoff в трекер:

/career-ops tracker

Следите за статусом всего пайплайна, независимо от score роли.

Batch evaluate (batch-evaluate-offers)

Когда у вас 10+ JD для оценки за раз (по-штучный #/evaluate в SPA непрактичен для такого объёма), используйте batch-раннер из CLI.

Файл ввода — batch/batch-input.tsv (tab-separated):

Колонка Назначение
id Уникальный последовательный номер
url Полная ссылка на вакансию
source Платформа-источник (LinkedIn, Greenhouse и так далее)
notes Опциональная контекстная заметка

Пример строки:

1<TAB>https://jobs.example.com/senior<TAB>LinkedIn<TAB>

Флаги ./batch/batch-runner.sh:

  • --dry-run — предпросмотр ожидающих офферов без оценки. Всегда запускайте это первым, чтобы провалидировать TSV.
  • --parallel N — запускает N воркеров одновременно (рекомендуются значения 1, 2 или 3).
  • --min-score X.X — пропускает сохранение офферов со score ниже порога. Полезно, когда хочется хранить отчёты только по high-fit-ролям.
  • --retry-failed — повторно обрабатывает только офферы, упавшие в предыдущем запуске (сетевые сбои, лимит запросов).
  • --max-retries N — пытается обработать упавшие офферы до N раз (по умолчанию: 2).
  • --model NAME — модель Claude для claude -p --model (career-ops 1.8.0, #504). Не задано = модель по умолчанию подписки Claude Max. Для больших батчей бери дешевле, напр. claude-sonnet-4-6. В #/batch — поле Модель (web-ui 1.31.0).
  • --start-from N — пропустить ID офферов меньше N (продолжить частично обработанный батч). В #/batch — поле Старт с # (web-ui 1.31.0).

Стандартная последовательность:

  1. Отредактируйте batch/batch-input.tsv — одна строка на JD.

  2. Dry-run (рекомендуется первым):

    ./batch/batch-runner.sh --dry-run
  3. Запуск — последовательно или параллельно:

    ./batch/batch-runner.sh                       # по одному
    ./batch/batch-runner.sh --parallel 2          # два параллельно
    ./batch/batch-runner.sh --parallel 3          # три параллельно
    ./batch/batch-runner.sh --parallel 2 --min-score 4.0  # хранить только high-fit
  4. Повтор упавших (сеть / rate-limit):

    ./batch/batch-runner.sh --retry-failed --max-retries 3
  5. Отчёты появляются в reports/ как {id}-{company}-{YYYY-MM-DD}.md. Сводные строки добавляются в batch/tracker-additions/.

  6. Merge в трекер:

    node merge-tracker.mjs                 # применить batch additions
    node merge-tracker.mjs --dry-run       # предпросмотр merge

    Команда merge дедуплицирует записи и архивирует обработанные файлы в batch/tracker-additions/merged/.

SPA отображает получившиеся отчёты в #/reports (пагинация, цветовая раскраска score-pill) и tracker-строки в #/tracker — ровно так же, как если бы вы добавили каждую через #/evaluate. Совмещайте с кнопкой обслуживания ▶ Merge на #/tracker, если не хочется уходить в CLI.

Установка Playwright (set-up-playwright)

Требуется для двух фич career-ops:

  • Form-fill в /career-ops apply (шаг 3 выше — Playwright открывает браузер, читает label’ы полей, предлагает ответы).
  • Генерация PDF через /career-ops pdf и кнопку 📄 Generate PDF в SPA на #/cv / #/reports/:slug / #/evaluate / #/deep / #/interview-prep.

Fallback при отсутствии Playwright: apply-флоу падает на WebFetch (только текстовое превью формы, без click-fill). Генерация PDF просто завершается ошибкой.

Core setup (запустите из корня career-ops-родителя):

# Установить Chromium для Playwright
npm install
npx playwright install chromium

# Зарегистрировать Playwright MCP, чтобы Claude Code мог управлять формами
claude mcp add playwright npx @playwright/mcp@latest

# Проверить все три компонента (Chromium, библиотека Playwright, MCP)
npm run doctor

Альтернативная регистрация MCP — добавьте в .claude/settings.local.json:

{
  "mcpServers": {
    "playwright": {
      "command": "npx",
      "args": ["-y", "@playwright/mcp@latest"]
    }
  }
}

Особенности поведения:

  • Headless по умолчанию. Playwright работает в фоне. Чтобы увидеть браузер, скажите Claude: open up with playwright the browser and fill out the entire form.
  • Три роли в одном пакете — npm-установка Playwright даёт вам библиотеку браузерной автоматизации, движок рендеринга PDF для /career-ops pdf и (через MCP) workflow form-fill внутри Claude Code.
  • Проверьте до того, как полагаться на негоnpm run doctor подтверждает, что все три компонента работают. Страница Health в SPA выводит проверку Playwright (parent node_modules), которая быстро падает, если Playwright отсутствует.

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

Это пост-research, пре-interview фаза. Три артефакта этого приложения сходятся здесь:

  1. Сохранённые deep-research-файлы в interview-prep/, по одному на пару «компания-роль», которую вы исследовали. Просматривайте со страницы Deep research или напрямую через /api/interview-prep.
  2. Режим Patterns (#/patterns) — генерирует self-reflective prompt: «По моим последним N интервью / офферам / отказам — какие паттерны держатся?» Полезно, когда у вас уже накопилось 5+ строк в трекере.
  3. Режим Interview-prep (#/interview-prep) — предзаполняет one-pager под конкретный предстоящий раунд (behavioral, technical, system design). Output идёт в ту же папку interview-prep/.

Рекомендуемый workflow

Для каждого интервью, которое у вас в календаре:

  1. Перезапустите Deep (или откройте сохранённый файл) накануне.
  2. #/interview-prep — сгенерируйте one-pager под конкретный раунд. Вставьте в свои заметки.
  3. Раунды system design / coding — откройте #/training и попросите 30-минутный таргетированный refresher по той подсистеме, на которой делается акцент в JD.
  4. Раунды по компенсации — откройте deep-research-файл и перейдите к секции «Negotiation leverage points». Принесите 2–3 конкретных data points (Glassdoor band, недавнее финансирование, сравнимый оффер от другой компании).
  5. Behavioral-раунды — вытащите STAR+R-истории из вашего cv.md, которые попадают в секцию B исходного отчёта Evaluate.

Сразу после интервью:

  1. Обновите строку в трекере: status → Responded (затем Interview, Offer и так далее).
  2. Запустите #/followup, чтобы составить thank-you-письмо.
  3. Если вы узнали новую информацию (диапазон компенсации, состав команды, неожиданная деталь tech-stack), отредактируйте сохранённый interview-prep/<company>-<role>.md, добавив секцию ## Post-round notes, — будущий вы скажет спасибо.

16. Activity log и Troubleshooting

Activity log (#/activity)

Аудит-трейл каждого state-changing-запроса, который попадает на сервер. Записывает: добавления в pipeline, записи в tracker, save’ы CV, save’ы JD, запуски evaluate, запуски deep-research, запуски скана, изменения конфигурации, запуски mode’ов.

Секреты (ANTHROPIC_API_KEY, GEMINI_API_KEY) редактируются на входе; реальное значение ключа никогда не попадёт в data/activity.jsonl.

Фильтр по префиксу действия (pipeline., cv., evaluate, scan. и так далее). По 25 строк на страницу; сервер возвращает до 500 последних событий.

Troubleshooting

Симптом Вероятная причина Решение
Health красная по cv.md Первый запуск, файла ещё нет touch $CAREER_OPS_ROOT/cv.md, затем refresh.
Health красная по Profile customized candidate.full_name всё ещё Jane Smith Отредактируйте config/profile.yml.
hh.ru: HTTP 403 в логе скана Не-российский IP, нет (server uses default UA) Зарегистрируйтесь на dev.hh.ru/admin, используйте российский IP / VPN.
gemini-eval.mjs: ERR_MODULE_NOT_FOUND Зависимости родителя не установлены cd $CAREER_OPS_ROOT && npm install.
Generate PDF выдаёт ошибку Playwright не установлен в родителе cd $CAREER_OPS_ROOT && npx playwright install chromium.
/career-ops apply говорит «no report found» Pipeline ни разу не оценивал этот JD Сначала запустите /career-ops pipeline (или #/evaluate); см. предусловия §14.
batch-runner.sh: no such file Запуск из неверной директории cd $CAREER_OPS_ROOT перед вызовом ./batch/batch-runner.sh.
Сервер ругается EADDRINUSE: 4317 Старый инстанс ещё работает pkill -f 'node server/index.mjs', затем перезапустите.
Live-вызов LLM висит > 2 минут Огромный prompt или Anthropic тормозит Проверьте /api/health (флаг Anthropic); сервер делает soft-cap промптов на 200 KB и возвращает 413.
Pipeline-превью показывает (unsafe redirect) Вакансия редиректит на приватный IP / loopback Это защитная мера (REVIEW-B1). Целевой URL отклоняется, исходный URL не меняется.
Текст строки трекера ломает таблицу Пайп в имени компании на pre-v1.9.1 Обновитесь до v1.9.1+ — пайпы экранируются end-to-end (BF-1).
npm test падает на свежем клоне Тесты предполагают layout родителя Используйте CAREER_OPS_ROOT=$(mktemp -d) и поднимите фикстуры.

Для более глубокой диагностики: запустите ▶ Doctor на странице Health, скопируйте вывод и поищите проблему в issue-трекере на https://github.com/Fighter90/career-ops-ui/issues.


17. Как добавить новый источник для скана

career-ops-ui рассматривает каждый job-сайт как adapter — единый файл в server/lib/sources/<slug>.mjs, который умеет fetch’ить и нормализовать результаты одного сайта. На текущий момент registry server/lib/sources/ поставляется с 92 адаптерами — 87 английских + 5 русских досок. Английский набор охватывает основные ATSes (Greenhouse / Ashby / Lever / Workable / SmartRecruiters / Workday), агрегаторы уровня всей доски, выбираемые через явный provider: (RemoteOK, Remotive, We Work Remotely, NoDesk, Get on Board, Amazon, …), и ATSes на тенант, автоматически определяемые по хосту careers_url или явному api: URL (BambooHR, Personio, Recruitee, Teamtailor, Avature, SAP SuccessFactors, …). Полный список никогда не нужно пересчитывать здесь вручную — он автоматически обнаруживается из server/lib/sources/ и показывается вживую в выпадающем списке Source на #/scan. См. §5 для YAML и docs/portals-examples.md для готовых copy-paste-записей.

v1.69.0 (P-14) — авторегистрация по принципу drop-in. Добавление 12-го источника теперь — это просто одни файл. Реестр (server/lib/sources/registry.mjs) больше не содержит список с ручной поддержкой — при старте он сканирует папку (readdirSync + динамический import()) и собирает блоки export const meta из каждого *.mjs. Напиши адаптер, объяви его meta — и он немедленно появится в сканере, в dropdown фильтра #/scan и в RU-диспетчере без каких-либо правок в registry.mjs. (RU-источникам по-прежнему нужна одна строка в portals.yml родительского проекта; см. Шаг 5.)

Шаг 1 — Пишем adapter

Создай server/lib/sources/<slug>.mjs. Два паттерна в зависимости от того, есть ли у сайта JSON API или только HTML:

Источник с JSON API (самый чистый — используй его, когда у сайта есть открытый endpoint):

// server/lib/sources/example.mjs
const ENDPOINT = 'https://example.com/api/v1/vacancies';
const UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ...';

// v1.69.0 (P-14) — self-describing metadata. The registry auto-discovers
// this block at boot; THIS is what registers the source (see Step 2).
export const meta = {
  value: 'example',          // ← must equal job.source written below
  label: 'Example.com',      // ← shown in the #/scan filter dropdown
  region: 'ru',              // ← 'en' (ATS sweep) | 'ru' (regional dispatcher)
  configKey: 'example',      // ← RU only; the key used in portals.yml
};

export async function searchExample(query, opts = {}) {
  const { onlyRemote = false, fetchImpl = fetch, signal } = opts;
  const res = await fetchImpl(`${ENDPOINT}?text=${encodeURIComponent(query)}`, {
    signal,
    headers: { 'User-Agent': UA, Accept: 'application/json' },
  });
  if (!res.ok) {
    const err = new Error(`Example: HTTP ${res.status}`);
    err.status = res.status;
    throw err;
  }
  const data = await res.json();
  return (data.items || []).map(normalizeExample);
}

function normalizeExample(item) {
  return {
    id: `example-${item.id}`,
    title: item.title || '',
    company: item.company?.name || '',
    url: item.url || '',
    salary: item.salary || '',
    location: item.location || '',
    isRemote: !!item.remote,
    workplaceType: item.remote ? 'Remote' : 'Onsite',
    relocates: false,
    date: item.posted_at || '',
    snippet: (item.description || '').slice(0, 240),
    source: 'example',           // ← must match the registry `value` exactly
  };
}

Источник с HTML-скрейпом (когда нет API — см. getmatch.mjs и geekjob.mjs для полных примеров):

const BASE = 'https://example.com';

export async function searchExample(query, opts = {}) {
  const { fetchImpl = fetch, signal } = opts;
  const res = await fetchImpl(`${BASE}/vacancies?q=${encodeURIComponent(query)}`, {
    signal,
    headers: { 'User-Agent': UA, Accept: 'text/html' },
  });
  if (!res.ok) {
    throw Object.assign(new Error(`Example: HTTP ${res.status}`), { status: res.status });
  }
  return parseExampleCards(await res.text());
}

export function parseExampleCards(html) {
  // …regex-based card extraction. Return [] on parse failure (DON'T throw):
  // a healthy 200 with no parseable cards is "no results", not "error",
  // so the multi-source scanner can keep going.
}

Три контракта, которые ОБЯЗАН соблюдать каждый адаптер:

  • Экспортировать валидный блок meta (см. Шаг 2). Без него реестр молча пропустит файл при старте (одно console.warn) — источник никогда не появится.
  • Принимать { onlyRemote, fetchImpl, signal } в opts. fetchImpl делает адаптеры тестируемыми без сети; signal нужен для пробрасывания отключения клиента (REVIEW-B3).
  • Возвращать записи единой формы{ id, title, company, url, salary, location, isRemote, workplaceType, relocates, date, snippet, source }, где source совпадает с meta.value.

Шаг 2 — Объяви meta адаптера (авторегистрация)

Это и есть весь шаг регистрации. registry.mjs редактировать не нужно. Просто убедись, что адаптер экспортирует блок meta — реестр авто-обнаружит его при старте:

// at the top of server/lib/sources/example.mjs
export const meta = {
  value: 'example',          // job.source value AND #/scan option.value
  label: 'Example.com',      // display label in the dropdown
  region: 'ru',              // 'en' | 'ru'
  configKey: 'example',      // RU only — key in portals.yml::russian_portals.sources
};

Правила валидации при обнаружении (файл, нарушивший любое правило, пропускается с одним предупреждением [sources/registry], поэтому частично мигрированная ветка остаётся диагностируемой):

  • value — непустая строка. ДОЛЖНА совпадать с job.source из адаптера.
  • label — непустая строка.
  • region — строго 'en' или 'ru'; всё остальное отклоняется.
  • configKeyобязателен для region: 'ru', игнорируется для 'en'.

region: 'en' подключает к ATS-свипу (авто-обнаружение по URL-паттернам tracked_companies); region: 'ru' — к региональному диспетчеру. Публичное API (SOURCES, SOURCES_BY_REGION, RU_CONFIG_KEYS, getRegionalSources) пересобирается из каждого обнаруженного meta, сначала en, затем ru, в алфавитном порядке по label внутри каждого региона — порядок в dropdown стабилен для пользователей.

Шаг 3 — Подключи к dispatcher’у (только RU)

EN ATS-источники авто-обнаруживаются по URL-паттернам tracked_companies — никаких дополнительных настроек не нужно. Для RU-источников открой server/lib/ru-scanner.mjs, найди таблицу RU_DISPATCH и добавь строку:

import { searchExample } from './sources/example.mjs';
// …
const RU_DISPATCH = {
  // …existing…
  example: { label: 'example.com', search: searchExample },
};

Цикл диспетчера вызывает entry.search(query, opts) для каждого ключа, присутствующего в cfg.sources. Больше никаких правок кода не нужно.

Шаг 4 — Тест (моки, никакой живой сети)

Создай файл tests/sources-<slug>.test.mjs. Живая сеть в тестах запрещена (CI-isolation contract):

import test from 'node:test';
import assert from 'node:assert/strict';
import { searchExample } from '../server/lib/sources/example.mjs';

test('searchExample normalizes one record', async () => {
  const fetchImpl = async () =>
    new Response(
      JSON.stringify({ items: [{ id: 1, title: 'Backend Engineer' }] }),
      { status: 200, headers: { 'content-type': 'application/json' } }
    );
  const out = await searchExample('q', { fetchImpl });
  assert.equal(out.length, 1);
  assert.equal(out[0].source, 'example');
});

Шаг 5 — Активируй в своём portals.yml

portals.yml родительского проекта — это пользовательский конфиг. Добавь configKey нового источника в массив:

russian_portals:
  sources: ["hh", "habr", "trudvsem", "getmatch", "geekjob", "example"]
  area: 113
  per_page: 50
  only_remote: false
  queries:
    - "Senior PHP"
    - "Senior Go"

Перезагрузи #/scan в браузере. Dropdown source-фильтра подхватит новую запись автоматически (единый источник правды: GET /api/scan/sourcesregistry.mjs). Кнопка 🌐 Scan теперь включает новый источник в каждый региональный свип.

Референсные адаптеры (зеркаль их для новых источников)

Файл адаптера Тип Заметки
hh.mjs JSON API Канонический RU API-адаптер; гео-зависимый UA fallback.
trudvsem.mjs JSON API Открытые данные правительства РФ; без IP-гейта.
habr.mjs HTML scrape Российская tech-доска; парсер карточек на регулярках.
getmatch.mjs HTML scrape Защитный парсер, [] при промахе парсинга.
geekjob.mjs HTML scrape Тот же защитный стиль, что и у GetMatch.
greenhouse.mjs JSON API Канонический EN ATS-адаптер; использует URL-паттерн tracked_companies.

Типичные ошибки

  • Забыт экспорт meta. Начиная с v1.69.0 именно блок meta — это единственное, что регистрирует источник. Нет meta (или он некорректен) — файл молча пропускается при старте с единственным предупреждением [sources/registry] <file> has no valid \export const meta` — skipped`, и источник никогда не появится в dropdown. Проверь лог сервера, если новый адаптер не отображается.
  • Рассинхрон поля source. Строка, которую пишет твой адаптер, ДОЛЖНА точно совпадать с meta.value. Если они разойдутся, dropdown фильтра #/scan покажет источник, но при его выборе отфильтрует все строки (потому что проверка равенства — r.source === fs).
  • Бросок исключения при сбое парсинга. HTML-скрейперы ДОЛЖНЫ возвращать [] при здоровом 200 без распознаваемых карточек. Бросок исключения ломает цикл мультиисточникового dispatcher’а — одна плохая HTML-структура убивает все остальные источники для того же запроса.
  • Забыть fetchImpl / signal. Без них твой адаптер нельзя покрыть юнит-тестами без обращения к живой сети, и отключения клиента не пробрасываются (фоновый fetch остаётся живым после того, как пользователь закрыл вкладку).
  • Полагаться на tracked_companies для RU. Этот список — только для EN ATS источников. RU-адаптеры приводятся в движение сами из russian_portals.queries — без записей по компаниям.

18. Уведомления (🔔 в верхней панели)

v1.58.34 — каждый toast, появляющийся в правом нижнем углу, также записывается в in-memory журнал (лимит 50, старые вытесняются). Клик по 🔔 в шапке открывает правый drawer Уведомления, чтобы перечитать всё пропущенное. Журнал — per-tab, per-session — закрытие вкладки очищает его.

Drawer открывается только по клику на колокольчик (или Enter / Space при клавиатурном фокусе). Сам не появляется. Красный badge показывает счётчик не просмотренных с прошлого открытия; при открытии — обнуляется.

Категории уведомлений

Категория Когда срабатывает Визуальный сигнал
Success Сохранено, Скопировано, Обновлено, скан завершён, CV импортирован, действия apply-checklist (Copy unchecked, Reset), профиль сохранён, URL добавлен в pipeline зелёная левая полоса в drawer; зелёный фон toast
Error Невалидный URL (должен начинаться с http:// / https://, без script/template-символов), API-ошибки с хвостом (METHOD /path · HTTP NNN), сетевые сбои (server down), pipeline-400 дубликаты, doctor / verify-pipeline ненулевой exit красная левая полоса; красный фон toast; технический хвост спрятан в блок Подробнее (U-4 / v1.58.24)
Info / progress Running doctor.mjs…, Running verify-pipeline.mjs…, Обновление…, Loading…, Generating prompt…, прогресс скана серая левая полоса; стандартный фон toast

Каждая запись в drawer показывает:

  • Время (HH:MM:SS локализовано под текущий язык SPA).
  • Сообщение (человеческая фраза; технический хвост убран из заголовка по U-4).
  • Детали (если есть — (METHOD /path · HTTP NNN) API-вызова или иной технический фрагмент, monospace).

Что НЕ является уведомлением

  • Модалка результата Doctor / verify-pipeline (полный stdout/stderr) — это модальное окно, не toast, в журнал не попадает.
  • SSE log-строки на #/scan и #/auto — пишутся в тело страницы, минуя toast-конвейер.
  • Spinner-only состояния загрузки (через UI.withSpinner без toast).

Клавиатура

  • Клик или фокус + Enter / Space на колокольчике → открыть drawer.
  • Esc, клик по ×, или повторный клик по колокольчику → закрыть; фокус возвращается на колокольчик.
  • Tab при открытом drawer → проходит по close-кнопке и focusable элементам внутри; drawer aria-modal="false", фокус НЕ ловится — остальная страница доступна.

19. Локализация приложения на ваш язык

Интерфейс поставляется на 17 языках (English, Español, Français, Português, 한국어, 日本語, Русский, 简体中文, 繁體中文, Polski, Українська, Dansk, العربية, Deutsch, Italiano, Türkçe, हिन्दी). Каждая надпись на экране берётся из словаря переводов, и вы можете добавить или поправить язык, не трогая логику приложения.

Где лежат переводы. Начиная с v1.60.0 каждый язык — это отдельный файл в public/js/lib/locales/i18n-dict.en.js, i18n-dict.es.js, i18n-dict.ru.js и т. д. — простой список пар 'ключ': 'текст'. Общий i18n-dict.aliases.js позволяет ключам, которые должны читаться одинаково (надпись в боковом меню и заголовок её страницы), указывать на один перевод. i18n-dict.js собирает их при загрузке; его править не нужно.

Исправить или добавить фразу. Откройте файл вашего языка, найдите ключ (например, 'nav.scan') и отредактируйте текст. Чтобы добавить новую надпись, добавьте один и тот же ключ во все 8 языковых файлов с переводом и используйте его на странице через t('ваш.ключ'). Запустите npm test — он падает, если в каком-то языке ключа нет, поэтому ничего не выходит наполовину переведённым.

Добавить новый язык. Скопируйте i18n-dict.en.js в i18n-dict.<код>.js, переведите все значения, затем зарегистрируйте код в i18n.js (список языков + автоопределение браузера), в сборщике i18n-dict.js и добавьте строку <script> в index.html. Полный чек-лист — включая снимок (snapshot) для тестов и сопутствующие файлы справки / README — в docs/LOCALIZATION.md.

Полезно знать. Переключатель языка — в подвале боковой панели; выбор запоминается для каждого браузера. Диагностические сообщения сервера намеренно остаются на английском (чтобы логи читались единообразно) — переводится только экранный интерфейс.

Полное пошаговое руководство по локализации см. в файле docs/LOCALIZATION.md в репозитории.

20. Статистика по целевым ролям (#/stats)

Страница Analytics → Статистика по целевым ролям превращает скудные данные, которые уже собирают ваши сканирования, в картину рынка по тем ролям, на которые вы действительно нацелены: количество вакансий и уровни зарплат по странам, а также тренд, который можно отслеживать во времени. Ничего не выдумывается: страница лишь агрегирует то, что нашли сканеры, и честно показывает, насколько мала выборка.

Откуда берутся цифры

  • Целевые роли считываются из вашего профиля (config/profile.yml → target roles) — они никогда не прописаны жёстко. Сначала задайте их на #/profile; без ролей страница показывает подсказку «задайте целевые роли» вместо пустых графиков.
  • Вакансии берутся из вашего последнего сканирования (сначала запустите его на #/scan). Локация каждой вакансии сопоставляется со страной (тем же детектором, что и фильтр по стране в сканере), а строка с зарплатой парсится и нормализуется в USD по приблизительной таблице курсов.
  • Всё агрегируется в вашем браузере: никакие данные не покидают вашу машину, и единственное, что эта страница когда-либо записывает, — это снимок, который вы сохраняете явно.

Как читать графики

  • Вакансии по странам — сколько подходящих вакансий приходится на каждую страну. Используйте фильтры Роль и Страна вверху, чтобы сузить до одной целевой роли или одной страны.
  • Медианная зарплата по странам (USD) — срединная распарсенная зарплата по каждой стране. Учитываются только вакансии с разбираемой зарплатой; размер выборки показан рядом с графиком, а суммы конвертируются по грубым курсам, поэтому читайте это как ориентировочное, а не точное значение. Одиночный ¥ (неоднозначный между японской иеной и китайским юанем) отбрасывается, а не угадывается, чтобы избежать сильного искажения FX.
  • Когда в текущем сканировании нет разбираемых зарплат, график зарплат сообщает об этом, а не выдумывает числа.

Сохранение снимков и отслеживание тренда

  • Нажмите Сохранить снимок, чтобы добавить текущий агрегат в data/role-stats.jsonl. Каждый снимок получает метку времени на сервере; снимки — единственное, что записывает эта страница, и они никогда не затрагивают ваше резюме или профиль.
  • График тренда отображает количество вакансий по вашим сохранённым снимкам — сохраняйте по одному периодически (например, после каждого еженедельного сканирования), чтобы наблюдать, как со временем меняется рынок для ваших целевых ролей.

21. Ваш two-pager — соответствие кандидата рынку (#/two-pager)

Большая часть career-ops-ui отвечает на вопрос «подходит ли эта вакансия моему резюме?». Two-pager отвечает на вторую половину: «подходит ли эта вакансия тому, чего я на самом деле хочу?». Он смоделирован по образцу «Mnookin two-pager» из книги Never Search Alone — короткого заявления от первого лица о том, что вас заряжает, что вам необходимо и с чем вы не готовы мириться. Откройте его через Setup → Two-pager 🎯.

ИИ-автозаполнение + экспорт (v1.100). «✨ ИИ-помощник заполнения» теперь заполняет все поля вживую из вашего резюме (проверьте и сохраните); 👁 Просмотр и экспорт отображает two-pager и экспортирует его в Markdown, PDF или DOCX.

Что вы заполняете

  • Кто я — несколько фраз от первого лица о вашем послужном списке и о том, в какой роли вы раскрываетесь.
  • Целевая среда — размер компании, стадия и культура, которые вам нужны.
  • Пять списков-чипов — введите текст и нажмите Enter (или запятую), чтобы добавить каждый пункт, кликните ×, чтобы удалить:
    • Что я люблю — то, что заряжает (удалёнка, ответственность, greenfield, менторство…).
    • Обязательные условия — жёсткие требования (нижний порог зарплаты, страна, стек…).
    • Что я ненавижу — то, что истощает (дежурства, бесконечные митинги, только legacy…).
    • Deal-breakers — абсолютные «нет» (только офис, без спонсорства, ниже определённой суммы…).
    • Непреложные границы — рамки (локация, удалёнка, нижний порог зарплаты…).

Нажмите Save two-pager, чтобы сохранить. Файл записывается в пользовательский слой вашего родительского проекта career-ops по пути config/two-pager.yml, поэтому — как ваше резюме и профиль — он никогда не перезаписывается при обновлении системы.

Помощник AI-заполнения

Не уверены, как это сформулировать? Нажмите ✨ AI fill assistant. Он собирает готовый к запуску промпт (в формате Mnookin, с вашим резюме и профилем внутри) и показывает его в диалоговом окне. Запустите этот промпт в любой LLM, затем вставьте полученные YAML-поля обратно в форму. Помощник использует только ваши собственные резюме и профиль — он никогда не выдумывает факты о вас, и по этой кнопке не делается ни одного живого вызова API.

Оценка соответствия тому, чего вы хотите

Как только вы сохраните two-pager, каждая вакансия на #/scan получает небольшой бейдж ◎ N (0–100). Он сравнивает тип работы каждой вакансии (удалёнка/гибрид/офис), страну, нижний порог зарплаты и релокацию с вашим two-pager — зелёный бейдж означает сильное соответствие, красный — что сработал deal-breaker. Наведите курсор, чтобы увидеть детали (✓ что совпало, ✗ что нарушил deal-breaker).

Он намеренно честен: когда вакансия не даёт ни одного сопоставимого сигнала (например, все ваши предпочтения — свободный текст, который строка сканирования не может подтвердить), бейдж не показывается вообще — система никогда не выдумывает число. Нарушение жёсткого deal-breaker весит больше, чем мягкая ненависть к тому же самому. Помимо бейджа, ваш сохранённый two-pager встраивается в каждую оценку через LLM, поэтому заявленные вами предпочтения формируют и письменный вердикт, а не только совпадение «резюме против описания вакансии».

22. Пробное интервью (#/mock-interview)

Читать подготовку к интервью — это одно; проговаривать ответы вслух — совсем другое. Страница Пробное интервью (откройте её через Interview prep → Mock interview 🎤 в боковой панели) проводит пошаговую репетицию под конкретную роль, опираясь на ваши собственные резюме, профиль, two-pager и банк историй. Это не заготовленный список вопросов — интервьюер реагирует на то, что вы действительно говорите.

Как начать сессию

  • Укажите целевую роль (и при желании компанию). Вставьте также описание вакансии, если оно у вас есть, — вопросы станут заметно точнее.
  • Нажмите Start interview. Интервьюер открывает беседу одним сфокусированным вопросом, подобранным под роль и ваш опыт.
  • Введите ответ и нажмите Send answer. Повторяйте столько, сколько захотите — это разговор, а не фиксированная викторина.

Что даёт каждый ход

После каждого ответа интервьюер отвечает тремя частями:

  • Обратная связь — что сработало (сильные стороны) и чего не хватило, в терминах STAR+R (Situation, Task, Action, Result, Reflection). Она называет конкретное измерение, которое вы упустили.
  • Оценка — быстрая N/5 с однострочным обоснованием, чтобы вы чувствовали прогресс в течение сессии.
  • Следующий вопрос — уточняющий вопрос, который намеренно прощупывает самую слабую часть вашего последнего ответа.

Всё опирается на ваши реальные материалы: cv.md, config/profile.yml, config/two-pager.yml и ваш банк историй STAR+R (interview-prep/story-bank.md) целиком встраиваются в промпт. Интервьюер будет давить на настоящие пробелы, но никогда не выдумывает опыт, которого у вас нет. Если ключ LLM не задан, страница выдаёт вам готовый к запуску промпт, который можно вставить в любой ассистент, — тот же честный запасной вариант, что используется в других местах приложения.

Сохранение и возврат к сессиям

Нажмите Save transcript, чтобы сохранить завершённую репетицию. Она записывается в пользовательский слой вашего родительского проекта по пути interview-prep/mock-{company}-{role}-{date}.md, поэтому живёт рядом с остальными заметками по подготовке к интервью и никогда не перезаписывается при обновлениях системы. Список Saved sessions внизу страницы позволяет снова открыть любую расшифровку или удалить её. Используйте New interview, чтобы начать заново с другой ролью.

23. Нетворкинг и глубокое исследование компаний (#/networking)

Подать заявку через парадную дверь — это лишь половина дела; вторая половина в том, чтобы знать кого-то, или хотя бы знать, к кому обратиться и что сказать. Страница Networking (откройте её через Deep research → Networking 🤝 на боковой панели) превращает компанию в конкретный план того, как получить собеседование, опираясь на ваш собственный CV, профиль и two-pager.

Как построить план

  • Укажите компанию (обязательно) и, по желанию, должность и описание вакансии. Описание вакансии заостряет зацепки «почему я подхожу».
  • Нажмите Build plan. С ключом LLM он выполняется в реальном времени и отображает план прямо на странице; без ключа он выдаёт готовый промпт, который можно вставить в любой ассистент (тот же честный запасной вариант, что используется по всему приложению — ничего не выдумывается).

Что содержит план

План возвращается в четырёх разделах:

  • Досье на компанию — краткая сводка о том, чем занимается компания, недавние сигналы, которые стоит упомянуть, и две-три зацепки «почему я подхожу», взятые из вашего реального опыта.
  • К кому обращаться — от трёх до пяти целевых персон (нанимающий менеджер команды, внутренний рекрутер, senior-инженер в команде, тёплый контакт или связь по alumni) с конкретной строкой поиска в LinkedIn для нахождения каждого. Он никогда не выдумывает реальные имена — он подсказывает, как найти нужных людей.
  • Самый тёплый путь к знакомству — единственный наиболее реалистичный тёплый маршрут именно для вашего опыта: общий работодатель, учебное заведение или сообщество; связь второго уровня; либо холодное сообщение с сильным сигналом, когда это действительно лучший вариант.
  • Черновики обращений — короткие, конкретные сообщения (три-пять предложений, без воды) для ваших ключевых персон, опирающиеся на ваши реальные доказательства, чтобы они не выглядели шаблонными.

Как сохранять и возвращаться к планам

Нажмите Save plan, чтобы сохранить план. Он записывается в пользовательский слой родительского проекта по пути networking/net-{company}-{role}-{date}.md — это ваш собственный файл, который никогда не перезаписывается системными обновлениями. Список Saved plans внизу страницы позволяет заново открыть или удалить любой план. Поскольку черновики и персоны опираются только на ваши реальные материалы, относитесь к ним как к сильному первому черновику для доработки — а не как к сценарию для рассылки вслепую.

24. CV Studio (#/cv-studio)

Добавить в CV (v1.117.0). Новая карточка превращает проект, публикацию или страницу портфолио (URL или вставленный текст) в ATS-готовые пункты, основанные ТОЛЬКО на этом источнике — метрики, работодатели и даты, которых нет в источнике, опускаются, а не выдумываются. Вы просматриваете предложения и сами вставляете принятые в редактор CV; автоматически ничего не записывается, а URL-источники загружаются через тот же SSRF-безопасный валидатор, что и пайплайн.

Страница #/cv — это место, где вы пишете своё резюме; CV Studio (откройте его через Setup → CV Studio 🎨 в боковой панели) — место, где вы его оттачиваете. Он даёт вашему cv.md три честных инструмента, два из которых никогда не покидают ваш браузер.

Адаптировать под вакансию (v1.101). Вставьте описание вакансии — CV Studio создаст адаптированное резюме и подходящее сопроводительное письмо, прогнав их через рекрутерский чек-лист-гейт (ошибки блокируют, предупреждения советуют), опираясь только на ваши материалы.

Диагностика резюме

В момент открытия страницы он оценивает ваше резюме по шкале до 100 и перечисляет замечания по каждой проверке, каждое с кратким пояснением, чтобы вы сами решали, что менять (он никогда не переписывает молча):

  • Длина — укладывается ли резюме в здоровый диапазон в одну-две страницы?
  • Измеримый вклад — какая доля ваших пунктов содержит реальное число или метрику? Рекрутеры выхватывают именно их при беглом просмотре.
  • Сильные глаголы действия — помечает слабые формулировки вроде «отвечал за» или «помогал».
  • Модные словечки — помечает пустые клише («ориентированный на результат», «командный игрок»).
  • Ключевые разделы — проверяет наличие Резюме, Опыта, Образования и Навыков.
  • Контактные данные — убеждается, что указан email.

Всё это выполняется целиком в вашем браузере без LLM — числа детерминированы, и ничего не выдумывается.

Маска приватности

Прежде чем поделиться резюме как образцом письма или скриншотом, Маска приватности скрывает персональные идентифицирующие данные: email, телефон, ссылки/никнеймы и почтовый адрес, а также ваше имя → инициалы, если вы включите это и введёте его. Переключайте каждую категорию, копируйте замаскированную версию и делитесь ей безопасно. Всё происходит целиком в браузере, сообщается ровно, сколько элементов было скрыто, а оригинал никогда не сохраняется и не передаётся.

Сделать по-человечески (подстройка под ваш голос)

Вставьте сухую строку или абзац — тот самый шаблонный текст в духе безликого ИИ — и Сделать по-человечески перепишет его вашим голосом. Переписывание опирается на стороне сервера на ваш voice-dna.md (как читается ваш текст) и ваши writing-samples/ (ваша реальная проза). Жёсткое правило: он может переставлять, ужимать и перенастраивать голос, но никогда не введёт факт, метрику или достижение, которых нет во вставленном тексте. С ключом LLM он переписывает вживую; без ключа он выдаёт готовый промпт, чтобы вставить его в любого ассистента. Затем редактируйте своё резюме на странице #/cv как обычно — CV Studio предлагает, решаете вы.

Подсказка «Переиспользовать прошлое CV?»

Когда вы выбираете сохранённое описание вакансии в CV Studio, приглушённая подсказка в одну строку говорит, не подгоняли ли вы уже CV под похожую роль. Приложение сравнивает выбранное описание с другими сохранёнными (детерминированный балл пересечения слов плюс проверка сеньорити — без ИИ, ничего не сохраняется) и показывает единственное лучшее совпадение как один из трёх вердиктов:

  • переиспользовать — очень похоже на сохранённое описание; то CV можно переиспользовать почти как есть.
  • переиспользовать с правками — похоже; переиспользуйте то CV, но подправьте.
  • сгенерировать заново — близко похожего сохранённого описания нет, так что подгоните новое CV.

Подсказка появляется автоматически, как только у вас есть хотя бы два сохранённых описания, и обновляется при смене выбранного описания. Это только подсказка — она никогда не меняет ваше CV или ваши описания.

25. Память (#/memory)

Все остальные страницы каждый раз начинают с чистого листа. Память (откройте её через Настройка → Память 🧠 в боковой панели) — единственное место, где вы говорите ассистенту что-то один раз, и это закрепляется. Она хранит короткую редактируемую заметку в духе «запомни это обо мне», которая подставляется в каждый запрос к ИИ.

Для чего она

Используйте её для устойчивых предпочтений и стиля работы, например:

  • Типы ролей и компаний, на которые вы нацелены (и те, которые вы никогда не хотите видеть).
  • Как вам нравится, чтобы писались ответы — сжато или подробно, старший тон, без воды.
  • Жёсткие ограничения, которые стоит повторять — только удалёнка, минимальная зарплата, без дежурств.

Держите её в рамках предпочтений и направления. Это не место для фактов о вашем опыте — ваши навыки, работодатели и достижения живут в вашем CV, профиле и двухстраничнике, которые остаются единственными источниками всего, что появляется в ваших CV и сопроводительных письмах. Заметка памяти определяет, как ассистент работает с вами, но никогда что он утверждает о вас.

Как она доходит до всего

Когда вы нажимаете Сохранить память, заметка записывается в пользовательский слой родительского проекта в config/memory.md и встраивается в общий контекст проекта. Это значит, что она автоматически путешествует с каждым запросом к ИИ — оценками, пробными собеседованиями, планами нетворкинга, переписываниями в CV Studio — и через каждого настроенного вами провайдера. Напишите её один раз; не нужно повторяться на каждой странице. Как и другие ваши файлы пользовательского слоя, она никогда не перезаписывается при обновлении системы и покидает вашу машину только внутри тех запросов к LLM, которые вы решаете выполнить.

Предложить из ваших данных

Не знаете, что написать? ✨ Предложить из моих данных читает ваш трекер откликов и составляет набор поведенческих пунктов — закономерности в том, что вы преследуете, принимаете и отклоняете. Запустите выданный им запрос в любом LLM, просмотрите предложения и вставьте отредактированную версию в заметку. Он опирается только на ваш собственный трекер и никогда не выдумывает факты; вы всегда проверяете, прежде чем что-либо сохранится.

26. Статистика (#/stats)

Вкладка «Паттерны отказов» (v1.117.0). Четвёртая вкладка запускает родительский analyze-patterns.mjs (только чтение) и показывает распределение исходов, практические рекомендации и долю продвижения по каждому ATS-вендору (сигнал «алгоритмической монокультуры» — Bommasani et al., FAccT 2026). Вендоры ниже минимальной выборки помечены звёздочкой; без родительского проекта вкладка честно сообщает об этом.

Страница Статистика сводит три вида в один раздел: сгенерированный ИИ отчёт по рынку, аналитику по вашему собственному pipeline и тренд вакансий по вашим целевым ролям из ваших сканов. Переключайтесь между ними вкладками наверху.

Отчёт по рынку

Вкладка Отчёт по рынку запрашивает у модели анализ зарплат и рынка труда по вашим целевым ролям — она читает ваш CV и профиль, чтобы понять, какие роли и уровень охватить. Введите Регион / рынок (например, Russia, EU-remote, US или Germany), выберите Валюту и нажмите Сгенерировать отчёт по рынку. Вы получаете структурированный отчёт с executive-резюме, зарплатой по грейдам (медиана плюс P10/P25/P75/P92), топ-работодателями, таблицей востребованных навыков, частотой бенефитов, разбивкой офис/гибрид/удалёнка, трендами на 12–24 месяца, включая влияние ИИ, и рекомендациями по переговорам. Каждая цифра — это ориентировочная оценка из обучающих знаний модели, а не спарсенные или живые данные, и отчёт прямо это указывает; относитесь к числам как к диапазонам, а не к котировкам. Без заданного API-ключа вы получаете промпт для копирования и вставки вместо сфабрикованного отчёта. Используйте Скачать .md, Сохранить как PDF или Копировать, чтобы вынести отчёт из приложения.

Мой pipeline

Вкладка Мой pipeline строит графики по вашему собственному трекеру заявок — ничего внешнего. Она показывает, сколько ролей вы отследили, распределение ваших оценок, воронку статусов, ваши топ-компании и роли, заявки во времени и коэффициенты конверсии (какая доля заявок доходит до Applied, Responded, Interview и Offer). Это честное зеркало вашего поиска: оно всегда отражает только то, что уже есть в data/applications.md.

Тренд по целевым ролям

Вкладка Тренд по целевым ролям — это исходный вид: количество вакансий и медианная зарплата по странам для ваших целевых ролей, агрегированные из вашего последнего скана, с селектором валюты и обзором Вакансии по целевой роли. Сохранить снимок записывает текущий агрегат, чтобы вы могли наблюдать, как количество вакансий меняется со временем, а линия тренда считывает эти снимки обратно. Разреженность данных ожидаема и помечается как ориентировочная — их никогда не дополняют выдуманными числами.

За всё время и компенсация

Вкладка «За всё время» (v1.118.0) ретранслирует в режиме только для чтения два родительских скрипта с нулевой стоимостью токенов: stats.mjs — сводку трекера за всё время, накопленные доли воронки (ответы / интервью / офферы), итоги сканера и покрытие порталов — и salary-gap.mjs — желаемую vs заявленную vs фактическую компенсацию по каждой заявке, собранную из Machine Summary отчётов и data/salary-observations.tsv. Малые выборки помечаются как ориентировочные; без родительского проекта вкладка показывает честную заметку.

Журнал самооценки навыков (#/assessments)

#/assessments — это простой журнал пройденных вами оценок навыков: тест по программированию на платформе компании, домашнее задание, квиз для сертификации. Запишите событие — компания, платформа, навык, необязательный проходной порог % и ваш балл %, плюс заметка свободным текстом — и оно допишется, по одной строке на событие, в data/assessments.tsv родительского проекта. Страница также перечисляет всё записанное и сводит по платформам, чтобы вы видели динамику во времени. Это явная запись, которую запускаете вы; когда родительского проекта нет, страница показывает приглушённую строку «недоступно».

27. Карьерный план (#/career-plan)

Страница Карьерный план превращает ваше резюме и профиль в конкретный, персонализированный план развития — такой, какой вы составили бы с карьерным коучем, но сгенерированный из ваших собственных материалов и доступный вам для редактирования.

Генерация плана

Выберите Горизонт (6, 12 или 24 месяца), при желании введите Фокус (например, «перейти в менеджмент», «уйти на удалёнку» или «переключиться на Go») и нажмите Сгенерировать план. Модель читает ваше резюме, профиль, two-pager и заметку памяти (через общий контекст проекта) и пишет структурированный план: честный снимок отправной точки, SWOT сильных сторон и зон роста, цели, выраженные как SMART / OKR / WOOP, альтернативные карьерные траектории с их компромиссами, план по хард- и софт-скиллам, помесячную дорожную карту для выбранного горизонта, как отслеживать прогресс, вероятные подводные камни и поддерживающие шаги. Каждая рекомендация опирается на то, что действительно показывают ваши материалы — план смотрит вперёд, но никогда не выдумывает факты о вашей истории. Если ключ API не задан, вы вместо этого получаете промпт для копирования и вставки.

Редактирование и сохранение

План появляется в редактируемом текстовом поле — правьте что угодно, затем нажмите Сохранить план. Он записывается в пользовательский слой родительского проекта, в config/career-plan.md, поэтому переживает обновления системы и отправляется только внутри тех промптов LLM, которые вы решите запустить. Предпросмотр отрисовывает ваш Markdown, чтобы вы могли прочитать его в отформатированном виде перед сохранением.

Экспорт

Используйте Скачать .md, Сохранить как PDF или Копировать, чтобы вынести план из приложения — те же элементы экспорта, что используются во всех ИИ-отчётах приложения. PDF проходит через существующий встроенный генератор PDF; Markdown — это прямая загрузка.

28. Карьерная ориентация (#/orientation)

Страница Карьерная ориентация отвечает на вопрос «какие направления мне действительно подходят?» — такой ответ вы получили бы от профориентационного теста, но выведенный из вашего собственного резюме и профиля, а не из анкеты.

Что она выдаёт

Нажмите Сгенерировать профиль, и модель прочитает ваше резюме, профиль, two-pager и заметку памяти и напишет профиль карьерной ориентации: ваши наиболее подходящие карьерные векторы (какие из восьми архетипов — Функционалист, Администратор, Коммуникатор, Специалист, Аналитик, Новатор, Управленец, Предприниматель — подходят лучше всего, с подтверждениями из вашего резюме), склонность к карьерному типу, набор рекомендуемых ролей, ваши профессиональные сильные стороны, привязанные к тому, что показывает резюме, особенности стиля работы («как читается ваше резюме» по нескольким осям) и рекомендации по развитию, расширяющие вашу пригодность.

Как она генерируется

Это ИИ-размышление о том, как читается ваше резюме, — а не психометрический тест. Промпт полностью опирается на ваши собственные материалы: он не выдумывает достижения и никогда не приводит числовые результаты тестов так, будто они были измерены. Если ключ API не задан, вы вместо живого профиля получаете промпт для копирования и вставки, чтобы запустить его в любом LLM. На диск ничего не записывается — профиль генерируется заново каждый раз.

Экспорт

Используйте Скачать .md, Сохранить как PDF или Копировать, чтобы сохранить профиль — те же элементы экспорта, что используются во всех ИИ-отчётах приложения. PDF проходит через существующий встроенный генератор PDF; Markdown — это прямая загрузка.

29. Манифест CareerOps

career-ops — родительский проект, интерфейс к которому предоставляет это приложение, — первая референсная реализация Манифеста CareerOps (родитель v1.20.0). Манифест называет практику, ради которой существует весь этот инструментарий: вести поиск работы так, как инженеры ведут продакшн, — с доказательствами, дисциплиной и инструментами на стороне кандидата за столом переговоров.

Что он провозглашает

Шесть принципов — «подавайтесь лучше, но реже», «сигнал важнее объёма», «доказательства важнее ключевых слов», «решение принимает человек», «локально по умолчанию», «достоинство обеих сторон за столом» — плюс билль о правах кандидата для эпохи найма, опосредованного ИИ: вы невидимы по умолчанию, никто не выдвигает вас без вашего согласия, ваше согласие даёт человек и его нельзя делегировать агенту, вы никогда не платите, ваши данные принадлежат вам. Приложение по замыслу следует этим правилам: ничто никогда не отправляется автоматически, всё выполняется локально, и каждое утверждение в сгенерированном CV прослеживается до ваших собственных материалов.

Как его прочитать и подписать

Ссылка в футере боковой панели открывает страницу манифеста. Вы также можете прочитать MANIFESTO.md в родительском проекте или выполнить там npm run manifesto, чтобы открыть страницу подписания. Подписание необязательно и занимает десять секунд — ваша подпись становится публичным коммитом в реестре SIGNATURES.md родительского репозитория. Ничто в приложении не зависит от того, подписали вы манифест или нет.

30. Hermes & Telegram

Hermes от Nous Research — это открытый автономный агент: вызов инструментов, навыки и более 20 каналов обмена сообщениями, включая Telegram. Начиная с v1.151.0, Hermes — подключённый LLM-провайдер: запустите его OpenAI-совместимый API Server (hermes gateway), задайте HERMES_API_KEY в Настройках приложения, и career-ops-ui выполняет живые оценки через ваш локальный Hermes. Этот раздел также описывает запуск приложения на облачном сервере и мост его событий в Telegram через Hermes — эти две части предназначены для операторов, а не являются функциями приложения. Полное руководство — docs/integrations/HERMES.md, а скилл hermes-bridge проводит по шагам.

Что такое Hermes

Hermes — это агентный runtime, подключающийся к вашим собственным LLM-провайдерам, — и он также выставляет OpenAI-совместимый API Server: hermes gateway обслуживает POST /v1/chat/completions по адресу http://127.0.0.1:8642/v1 с Bearer-ключом (его API_SERVER_KEY). Поэтому career-ops-ui рассматривает его как ещё одного провайдера (путь «Shape A» из руководства): приложение делает POST на этот локальный эндпоинт так же, как для OpenAI или Qwen, а Hermes направляет запрос к модели, настроенной внутри него. Он стоит последним в auto-порядке, поэтому никогда не переопределяет существующую конфигурацию Anthropic/Gemini/OpenAI/Qwen.

Запуск на облачном сервере

career-ops-ui по умолчанию слушает 127.0.0.1. Чтобы достучаться до агента Hermes на сервере, вы аккуратно выходите за пределы loopback. Оставьте приложение на loopback и поставьте перед ним reverse proxy (nginx или Caddy), терминирующий HTTPS; запускайте под systemd или pm2 от имени непривилегированного пользователя; и сохраните нетронутым контракт «только чтение» с родительским career-ops на headless-машине. Оболочка безопасности должна пережить переезд: Content-Security-Policy без встроенных скриптов, защита от SSRF при каждом запросе пользовательского URL, граница markdown/XSS и никаких секретов в логах. В руководстве есть полный чек-лист — никогда не открывайте 0.0.0.0 напрямую в интернет.

Telegram через Hermes

Мост доставляет события приложения — завершённое сканирование, новый отчёт, фоллоу-ап, который только что стал срочным — в чат Telegram через Hermes. Токен бота Telegram хранится в конфигурации самого Hermes, а не в career-ops-ui. Отправляйте только полезный минимум: «Сканирование завершено — 12 новых совпадений» и ссылку, которую вы открываете сами. Никогда не отправляйте в канал текст резюме, суммы зарплат, тела отчётов, ключи API или внутренние URL — правилом служит список модели угроз «что НЕЛЬЗЯ раскрывать» из руководства. Эта страница доступна из #/help, а встроенный ассистент документации отвечает на вопросы на её основе.

31. Запуск всего стека в облаке

Большинство людей запускают career-ops на собственном ноутбуке. Но пайплайн раскрывается по-настоящему, когда работает постоянно — сканирует площадки, пока вы спите, держит трекер актуальным, доступен в браузере с любого устройства. Этот раздел — сквозной рецепт того, как разместить весь стек на небольшом облачном сервере: родительский пайплайн career-ops, этот вьюер career-ops-ui, и движок, который выполняет ИИ-запросы — либо вашу подписку Claude (через CLI Claude Code), либо локальный шлюз Hermes. Раздел опирается на облачные заметки о Hermes из §30; полный чек-лист оператора — в docs/integrations/HERMES.md.

Три движущиеся части

Стек состоит из трёх взаимодействующих частей, и их полезно чётко разделять. career-ops (родитель) — это ИИ-пайплайн поиска работы: он владеет вашими cv.md, config/, reports/ и portals.yml, и им управляет CLI агента. career-ops-ui (это приложение) — по большей части read-only веб-вьюер, который располагается внутри career-ops как web-ui/ и показывает те же файлы в браузере; он записывает данные обратно только по явным действиям пользователя. Движок — это то, что на самом деле отвечает на ИИ-запросы. Две из трёх частей одинаковы на сервере и на ноутбуке — меняются только выбор движка и способ сетевой публикации.

Разверните и установите

Арендуйте небольшой VPS (1 vCPU / 1 ГБ RAM вполне достаточно для вьюера) с актуальным Linux и установите Node ≥ 18 (рекомендуется 22.5+ — он включает SQLite-индекс трекера у родителя). Установите git. Затем сделайте ровно то же, что и при локальной установке: склонируйте родительский career-ops, а этот репозиторий склонируйте внутри него как career-ops/web-ui/. Ключи провайдеров положите в .env родителя (никогда не коммитьте его — .env / .env.* в gitignore; начните с .env.example). Запустите вьюер командой npm start — но оставьте его привязанным к 127.0.0.1; вы будете открывать доступ через прокси, а не напрямую (см. ниже).

Выберите свой движок

career-ops не привязан к конкретной CLI, поэтому у вас есть три честных варианта для ИИ. Ваша подписка Claude — установите на сервере CLI Claude Code и выполните claude login со своим планом Pro/Max; тогда агент родителя будет использовать вашу подписку без поминутной оплаты по токенам. Hermes — запустите hermes gateway на том же сервере (он открывает OpenAI-совместимый API на http://127.0.0.1:8642/v1) и укажите HERMES_API_KEY в настройках приложения; живые оценки career-ops-ui пойдут через него (последний в автоматическом порядке провайдеров). API-ключи — укажите ANTHROPIC_API_KEY (или любой из семи провайдеров: Anthropic → Gemini → OpenAI → Qwen → OpenRouter → GitHub Models → Hermes → DeepSeek → GLM (Z.ai) → Kimi (Moonshot) → MiniMax → Mistral → Grok (xAI) → Together → Fireworks → Ollama → BytePlus Ark → Volcengine Ark) в .env родителя, и ⚡ живые действия будут работать без участия человека. Их можно комбинировать: подписку Claude — для тяжёлой агентной работы родителя, а дешёвого или локального провайдера — для быстрых оценок во вьюере.

Откройте доступ безопасно

Уход с 127.0.0.1 означает, что безопасность, которую loopback давал бесплатно, теперь нужно выстроить явно — код идентичен, меняется только способ публикации. Оставьте приложение привязанным к loopback и поставьте перед ним reverse proxy (nginx или Caddy), который терминирует HTTPS (Let’s Encrypt / автоматический TLS) и перенаправляет на 127.0.0.1:4317; запускайте его под systemd или pm2 от имени выделенного непривилегированного пользователя с Restart=on-failure. Поставьте перед ним и аутентификацию — у самого приложения нет собственного логина, так что именно прокси (basic-auth, SSO forward-auth или частная сеть / VPN) не даёт посторонним попасть внутрь. Когда вы задаёте HOST=0.0.0.0, чтобы прокси мог достучаться до приложения, встроенное усиление защиты, которое на loopback было no-op’ом, включается и становится по-настоящему рабочим: ограничение частоты запросов к LLM, защита от DNS-rebinding в safeGet и санитизация имён путей. Четыре инварианта должны переживать этот переезд, и их нельзя ослаблять: CSP (никаких встроенных скриптов, frame-ancestors 'none' — прокси не должен срезать эти заголовки), защита от SSRF при любом запросе URL, заданного пользователем, граница markdown/XSS (stripDangerousMarkdown() на сервере + UI.md() с escape-first на клиенте), и никаких секретов в логах. Контракт «только чтение» с родителем сохраняется и на headless-машине — сервер только читает ваши cv.md / config/ / reports/ и пишет только по явным действиям.

32. Запуск из OpenWorker (ИИ-коллега)

Предпочитаете десктопного ИИ-коллегу браузеру? career-ops-coworker — это коллега для OpenWorker (открытого, локального по умолчанию приложения ИИ-коллеги от Эндрю Ына), который ведёт весь этот пайплайн за вас — сканирует площадки, оценивает соответствие вашему резюме, готовит выверенное резюме + сопроводительное письмо, ведёт учёт откликов, набрасывает фоллоу-апы — и по запросу может запустить этот дашборд. Это одна «персона» в Markdown без кода; OpenWorker не выполняет ничего из неё как программу — инструкции лишь направляют агента. Его руководство поставляется на всех 17 языках.

Что делает коллега

Работая внутри папки вашего проекта career-ops, коллега выполняет те же шаги, что и пайплайн: он сканирует площадки из вашего portals.yml (без расхода токенов), оценивает каждую вакансию по шкале 0–5 относительно вашего cv.md + config/profile.yml и — по запросу — готовит резюме под конкретную роль + сопроводительное письмо, опираясь только на факты, уже имеющиеся в вашем резюме (он никогда не выдумывает работодателя, дату или показатель). Он обновляет data/applications.md, набрасывает письма-фоллоу-апы и может расставлять слоты для собеседований — при этом каждая отправка или запись требует подтверждения, ровно как в собственной доктрине career-ops. Чтобы открыть этот вьюер, он выполняет bash web-ui/bin/start.sh (или npm start) и даёт вам http://127.0.0.1:4317.

Установка и запуск

Установите OpenWorker и добавьте ключ модели (Anthropic / OpenAI / Google или локальная Ollama). Быстрее всего — одна команда: она готовит конвейер career-ops, которым управляет coworker, и идемпотентна (переиспользует существующие career-ops / web-ui, не перезаписывая ваши данные):

curl -fsSL https://raw.githubusercontent.com/Fighter90/career-ops-coworker/main/install.sh | bash

Затем добавьте coworker в панели Install a coworker OpenWorker — по GitHub URL (https://github.com/Fighter90/career-ops-coworker), из .zip (со страницы Releases) или импортом career-ops.md. Откройте сессию Job-Search Coworker, выберите папку career-ops и попросите что-то реальное — «просканируй мои доски и дай топ-5 совпадений за неделю» или «открой дашборд». Коннекторы (Gmail, Google Calendar, GitHub) и полное руководство — в руководстве репозитория. Установка проверена против загрузчика OpenWorker и его репозиторного установщика.