| .claude/skills | ||
| .forgejo/workflows | ||
| browsers | ||
| cdp | ||
| chooser | ||
| cli | ||
| client | ||
| contract | ||
| core | ||
| docs | ||
| exec | ||
| fixtures | ||
| http | ||
| human | ||
| llm | ||
| mcp | ||
| polygon | ||
| profiles | ||
| proxy | ||
| record | ||
| runs | ||
| scenarios | ||
| schemas | ||
| scripts | ||
| secrets | ||
| serve | ||
| sheets | ||
| sms | ||
| stand | ||
| testing | ||
| trace | ||
| update | ||
| .gitignore | ||
| .npmrc | ||
| AGENTS.md | ||
| biome.json | ||
| bun.lock | ||
| bunfig.toml | ||
| CLAUDE.md | ||
| cli.ts | ||
| CONTEXT.md | ||
| docker-compose.yml | ||
| package.json | ||
| profiles.example.json | ||
| README.md | ||
| tsconfig.json | ||
| worker.ts | ||
Mimic
Движок автоматизации браузера. Сценарий — обычный JSON из готовых блоков, рантайм их
исполняет. Браузер Mimic поднимает сам — отделённым процессом со своим каталогом
профиля — и дальше говорит с ним снаружи по CDP (Chrome DevTools Protocol). Прогон может
умереть, рантайм можно перезапустить: браузеры переживают и то и другое, а гасит их
только явная команда оператора (docs/decisions/ADR-8-mimic-owns-launch.md). Подключиться
к уже работающему браузеру движок тоже умеет — так он работает с любым Chromium и с
антидетект-браузером, где у каждого профиля свой отпечаток.
Ключевой принцип: сценарий это данные, а не код. Ни eval, ни генерации TypeScript —
модель отдаёт JSON, он проходит проверку схемой до запуска.
Документы
| Что | Где |
|---|---|
| Что система делает и для кого | docs/prd.md |
| Как устроена | docs/design.md |
| Почему принято так | docs/decisions/ |
| Словарь терминов | CONTEXT.md |
План работы живёт в Linear (product:mimic, Initiative Mimic), не в этом файле.
Стек
TypeScript на Bun 1.2+, TS strict, Zod для схем блоков, воркеры Bun для параллелизма.
Ноль зависимостей в слое CDP — свой тонкий клиент на нативном WebSocket
(docs/decisions/ADR-1-own-cdp-client.md). Поставка — один бинарь; на хосте ему нужен
вендорский стек CloakBrowser, потому что стелс-argv собирает вендор, а не мы
(docs/decisions/ADR-8-mimic-owns-launch.md).
Установка на macOS
Браузеры живут на том же хосте, что и Mimic (docs/decisions/ADR-8-mimic-owns-launch.md).
Весь нативный стек ставится одной командой самого Mimic — клон репы для этого не нужен,
скрипт установки едет внутри бинаря (ADR-61):
mimic setup # uv → cloakbrowser → ключ → Chromium → автозапуск → MCP → скил
mimic setup --yes # без вопросов: согласие на установку uv дано заранее
mimic setup --cloakserve # плюс апстримный cloakserve — он нужен только за CLOAK_BASE
mimic setup --no-daemon # без автозапуска при логине
mimic setup --no-mcp # не регистрировать сервер MCP у Claude Code и Codex
mimic setup --no-skill # не класть скил с доктриной в каталоги скилов хостов
mimic setup --port 8080 # порт службы: он же в автозапуске, он же в записи MCP
mimic setup --check # ничего не ставить, а судить поставленное
mimic setup --check --json # то же машиночитаемо
Служба встаёт в автозапуск: LaunchAgent зовёт при логине тот же mimic start (ADR-62),
и «служба не поднята» перестаёт возвращаться после каждой перезагрузки. Отдельно это
mimic daemon install [--port <n>] и mimic daemon uninstall; снятие агента гасит
службу, но НЕ браузеры профилей. Стоит ли автозапуск, говорит mimic status.
Последними шагами установка доводит до рабочего состояния агента: регистрирует сервер MCP
у Claude Code и Codex и кладёт скил с доктриной копией из бинаря (ADR-63) — подробности
в §«Агент: mimic mcp».
Ключ лицензии оператор набирает скрытым вводом: он уезжает вендору потоком и не
появляется ни в аргументах (их видно в ps), ни в окружении, ни в журнале, ни эхом на
экране. cloakbrowser login не вызывается ни разу. Ключ уже заведён — шаг пропускается.
Нет сети при проверке ключа — сказано словами, ключ не сохранён, установка продолжается.
Шаги идут до конца: не вышедший называет себя и не отменяет остальные. Коды выхода:
0 прошло (в том числе с пропущенными шагами) · 1 шаг не вышел · 2 кривые аргументы.
Что на машине стоит и что из этого сломано, говорит mimic setup --check (ADR-64): он
ничего не ставит, а печатает строками вендора с тарифом и лицензией, ключ (есть и откуда,
никогда значение), Chromium с пробой запуска, места, службу, автозапуск, запись MCP у
обоих хостов и скил. Скил судится хэшем против вшитого в бинарь — правка руками видна
командой, а не на глаз. Коды у него СВОИ: 0 порядок · 1 поставлено и сломано · 2 не
поставлено; «нет» и «сломано» лечатся по-разному, поэтому и различаются. Молчащий
лицензионный сервер — это «неизвестно», а не «ноль мест» (ADR-27), и кода он не роняет.
Вендора доктор спрашивает живьём, и вендорская проба поднимает браузер: одно место занято
на это время, ждать до трёх минут. --json отдаёт тот же отчёт машиночитаемо.
CloakBrowser Manager mimic setup не ставит: Mimic его не использует, а запущенный он
занимает одно из пяти мест лицензии. Из клона тот же скрипт доступен напрямую —
./scripts/setup-cloakbrowser.sh [--cloakserve|--check|--manager].
Запуск браузеров руками не нужен: их поднимает сам Mimic (служба, mimic profile start <id>), и
--license-through-proxy он дописывает в argv сам. Без этого флага проверка лицензии
уходит напрямую, и при перекрытом прямом маршруте (системный прокси вроде ClashX)
Pro-бинарь выходит с кодом 78 примерно через 12 секунд. Симптом обманчив: выглядит
как «браузеры умирают между прогонами».
Остаётся альтернативный путь — мультиплексор cloakserve за CLOAK_BASE (удалённый
стенд, где супервизора нет). Поднимают его руками, и там все три флага обязательны:
cloakserve --idle-timeout=0 --headless=new --license-through-proxy
export CLOAK_BASE=http://127.0.0.1:9222
--idle-timeout=0 — иначе бездействующий сид чистится вместе со своим профилем ровно в
окне между двумя прогонами. --headless=new — иначе на macOS открываются окна. Цена
этого пути названа в ADR-8: cloakserve стирает каталог профиля при гашении, то есть
разлогинивает аккаунт, и семь операторских ручек запуска туда не доезжают.
Та же ошибка в CloakBrowser Manager лечится тем же флагом — в поле «Advanced launch
arguments» профиля; --manager дописывает его каждому профилю сам, сняв бэкап базы.
Manager при этом должен быть закрыт.
Установка бинарём
Поставка — один бинарь: тег v* собирает два таргета и кладёт их в Releases репы вместе
с checksums.txt. Клон для работы с движком не нужен — нужен для работы над движком.
V=0.1.7; A=mimic-$V-macos-arm64 # версия — свежая из Releases; linux: mimic-$V-linux-x64
B=https://forgejo.vertaspace.com/verta/mimic/releases/download/v$V
curl -fsSLO $B/$A && curl -fsSLO $B/checksums.txt
# суммы считает сам релиз; на macOS вместо sha256sum — shasum -a 256
sha256sum --ignore-missing -c checksums.txt
chmod +x $A && mv $A /usr/local/bin/mimic
mimic cheat # шпаргалка по движку: бинарь живой
mimic start # служба резидентом, дом ~/.mimic (ADR-60)
Этот однострочник нужен один раз. Дальше бинарь обновляет себя сам: mimic update
качает релиз своей платформы, сверяет sha256 против checksums.txt, подменяет себя,
перезапускает службу и доводит скил с записью MCP (ADR-73). Только спросить, не
накатывая, — mimic update --check. Ставить движок вторым каналом (установщиком инфры)
больше не надо: он единственный владелец своего обновления.
Вендорский стек CloakBrowser бинарь с собой не несёт — без него браузер не поднимется
(ADR-8); ставит его сам бинарь — mimic setup, клон не нужен (ADR-61).
Запуск из клона
bun install
# самая короткая проверка живости: печатает document.title первой страницы
bun run scripts/probe.ts --ws <browser-level endpoint>
# проверка формы сценария службой по схемам блоков — браузер не нужен вовсе
bun run cli.ts scenario validate --document @scenarios/login-upload.json
# схемы блоков → schemas/*.json (артефакт в git, вход компилятора сценариев)
bun run schemas:dump
# что именно будет запущено: бинарь, его версия и полный argv на профиль службы
bun run cli.ts doctor --only p1
# поднять профиль через службу и получить его адрес (живой подхватывается)
bun run cli.ts profile start p1 --json
# прогон сценария на всех профилях
bun run cli.ts run scenarios/login-upload.json --profiles profiles.json --run-id run-1
# шпаргалка по движку: порядок работы, а не алфавит флагов (клиент GET /cheat — нужна служба)
bun run cli.ts cheat
# сборка бинаря: воркер обязан быть второй точкой входа
bun build --compile --outfile mimic cli.ts worker.ts
Агент: mimic mcp
Поверхность агента — подкоманда, а не ручка службы (ADR-29): хост спавнит её сам и
говорит по stdio. Инструменты — вызовы того же HTTP API, которым живёт страница
оператора, поэтому своей правды у переходника нет вовсе, а «служба не поднята» он
объясняет словами и не висит.
Регистрировать руками не надо: mimic setup делает это сам — записывает сервер у Claude
Code и у Codex и кладёт скил с доктриной копией из бинаря (ADR-63). Существующую запись
он не переписывает, чужой каталог скила не трогает, а в конфиге Codex правит ровно свою
таблицу. Не нужно — mimic setup --no-mcp --no-skill.
Руками, если всё-таки понадобилось:
# Claude Code — одна строка, --scope user раздаёт сервер на все проекты
claude mcp add --scope user mimic mimic mcp
# служба на другом порту: флаги сервера отделяются --, иначе их съест сам claude
claude mcp add --scope user mimic -- mimic mcp --base http://127.0.0.1:8080
Codex — в ~/.codex/config.toml (или проектный .codex/config.toml). Имя сервера это
сегмент пути таблицы, а не ключ name:
[mcp_servers.mimic]
command = "mimic"
args = ["mcp"]
[mcp_servers.mimic.env]
MIMIC_BASE = "http://127.0.0.1:7777"
Инструменты и ресурсы собираются из контракта службы, ручного перечня здесь нет: живой
список — tools/list и resources/list у сервера MCP, вся правда об операциях (вход, ответ,
отказы, подсказки) — schemas/api.json (bun run api:dump). Инструмент MCP и команда CLI —
одна операция: profile_stop — это и mimic profile stop.
Два умолчания стоит знать заранее: разрушительные операции (гашение, сброс и удаление
профиля, снятие прогона, вердикт с abort — полный перечень в schemas/api.json, поле
destructive) помечены destructiveHint и подтверждения не требуют — спросить человека
решает хост; гашение на cloakserve стирает и каталог профиля; run_watch
ограничен по времени и до конца прогона не ждёт — он отдаёт порцию событий и курсор
(after), с которым зовут снова. Итог прогона с saveAs-переменными — run_result, файл
исполнения — run_artifact: диск службы клиенту читать не нужно (ADR-157).
Адрес службы: --base, иначе MIMIC_BASE, иначе http://127.0.0.1:7777.
doctor ничего не спавнит и ничего не гасит: он показывает найденный бинарь, версию
из файла-маркера вендора и argv, с которым браузер будет запущен. Стелс-часть argv
собирает вендорский build_args (ADR-8), наши флаги ложатся поверх; пароль прокси
в печати замаскирован. Профиль без fingerprint — отказ doctor_profiles_invalid (код
выхода 2): без сида вендор кладёт случайный отпечаток на каждый запуск. Это операция
службы: судятся её профили, служба должна быть поднята.
--ws ждёт browser-level endpoint — поле webSocketDebuggerUrl из
GET /json/version. document.title с него не читается: page-таргет клиент находит
сам через Target.getTargets и цепляется Target.attachToTarget {flatten: true}.
Годится любой уже запущенный Chromium, CloakBrowser не нужен:
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
--headless=new --remote-debugging-port=9333 --user-data-dir=/tmp/mimic-probe about:blank &
bun run scripts/probe.ts --ws "$(curl -s http://127.0.0.1:9333/json/version | jq -r .webSocketDebuggerUrl)"
Два таймаута названы и разведены по типам ошибки, чтобы в трассе было видно, что
именно не уложилось: CDP_COMMAND_TIMEOUT_MS (одна команда CDP, умолчание 10000,
читается из окружения) и params.timeoutMs шага (умолчание 15000).
Браузеры и приёмочный стенд живут своей жизнью — браузеры переживают смерть рантайма:
bun run cli.ts profile start p1 --json # браузер через службу: {id, wsEndpoint}
bun run stand # приёмочный стенд
bun run cli.ts doctor # что будет запущено у службы; ничего не спавнит
CLOAK_BASE уводит на мультиплексор cloakserve по этому адресу; умолчания у него нет —
путь выбирается явно, и выбранный провайдер команда называет в stderr. Работает и из
worktree: пути фикстур относительные, монтировать нечего.
Профили описываются файлом profiles.json (образец — profiles.example.json):
[{id, options, browser, vars}]. options — параметры отпечатка, уходят провайдеру как
есть и ядру неизвестны (ADR-4); browser — семь операторских ручек запуска, они
типизированы (ADR-17); vars — переменные сценария, провайдер их не видит.
Пароль и прокси-адрес лежат в файле ссылкой, а не значением: "$secret:KEY" законен
и в vars, и в любом значении options, а само значение живёт в macOS Keychain и
раскрывается на краю пути — перед сборкой argv и перед отправкой payload воркеру.
Значение кладёт оператор, и только через stdin:
printf %s 'socks5://user:pass@host:1080' | bun run cli.ts secrets set --key PROXY_MAIN
bun run cli.ts secrets list # только имена ключей
bun run cli.ts secrets delete --key PROXY_MAIN
Ключа в keychain нет — отказ с кодом 2 до спавна, с именем ключа и командой. В
GET /profiles, run.json, трассе и логах видна ссылка; значение, пришедшее резолвом,
маскируется в трассе по происхождению, как бы переменная ни называлась.
Погасить профиль можно только явной командой:
bun run cli.ts profile stop p1
Прогон её не зовёт ни на успехе, ни на падении — браузеры обязаны переживать смерть
рантайма. Профиль на диске гашение переживает, поэтому следующий profile start поднимает
ту же личность. С местами Pro это ещё и про деньги: зависший браузер держит одно из пяти.
Стенд (stand/) в поставку не входит — сценарии гейта ходят на него, а не на живой
сайт; он на http://127.0.0.1:3000, там же, где браузеры. Фикстуры загрузки лежат в
fixtures/<profileId>/, и профиль называет их относительным путём — блок uploadFile
резолвит его против cwd рантайма.
Проверка
bun test # CDP-клиент, таймауты, схемы, маскирование, round-trip трассы
bun run smoke:seeds # два сида одновременно + фикстуры на месте
bun run smoke:idle # --idle-timeout=0: сид переживает 5 минут простоя
./scripts/gate.sh # приёмка: 4 исполнения профиля + проверка изоляции
Гейт судит по двум вещам — коду выхода и summary.json. Логи и формат трассы на вердикт
не влияют. Ретраев нет.
Ему нужен поднятый стенд (bun run stand); браузеры он поднимает сам через CLI и не
гасит, docker не зовёт вовсе. Задан CLOAK_BASE — гейт краснеет первой строкой, до
единого спавна: приёмка судит нативный супервизор (ADR-8).
Что делает: преflight (scripts/doctor.ts — функция mimic doctor: бинарь, вендорский build_args на
каждый профиль, места; стенд отвечает; фикстура каждого профиля на месте), пауза на
проверку лицензии — браузер, который её не прошёл, исчезает за ~12 секунд, и гейт
называет причину вместо загадочного красного шага, — затем два вызова CLI по два профиля
(gate-1, gate-2, между ними процесс умирает целиком), сверка browser-id и тройки
личности по реестру и ps, затем шаг «залогинен после ротации места» (вход, гашение,
повторный подъём, страница по-прежнему авторизована), затем негативный прогон: профилю
подменяют файл на соседский, и он обязан покраснеть.
Переменные: MIMIC_PROFILES (умолчание profiles.example.json), MIMIC_SCENARIO,
MIMIC_ARTIFACTS, MIMIC_RUN (MIMIC_RUN=./mimic — гнать собранный бинарь),
MIMIC_STAND_URL (умолчание http://127.0.0.1:3000; доезжает до сценария приёмки
переменной профиля {{base}}), MIMIC_LICENSE_WINDOW_S.
Коды выхода: 0 прошло · 1 прогон упал · 2 кривой сценарий или конфиг (браузеры не
трогали) · 130 прервали руками.