No description
Find a file
luvlaceeeee 63f86a552c
All checks were successful
ci / proxy (push) Successful in 24s
ci / test (push) Successful in 12m50s
fix(core): typos type checks value before trailing newline submit (VERTA-4465)
2026-10-25 16:31:13 +03:00
.claude/skills feat(contract): doctrine names contract operations (VERTA-4156) 2026-10-08 23:21:00 +03:00
.forgejo/workflows fix(proxy): verify ждёт первый сертификат acme (ref VERTA-4051) 2026-09-28 21:13:42 +03:00
browsers feat(seats): exclusive plan without seat ceiling, license window off 2026-10-20 01:20:55 +03:00
cdp feat(boundary): system plane and one implementation (VERTA-4365) 2026-10-08 22:40:19 +03:00
chooser feat(contract): secrets, proxies and sms through the contract (VERTA-4161) 2026-10-08 15:40:43 +03:00
cli feat(seats): exclusive plan without seat ceiling, license window off 2026-10-20 01:20:55 +03:00
client fix(client): write only after contract hash precheck (VERTA-4374) 2026-10-09 02:17:24 +03:00
contract feat(seats): exclusive plan without seat ceiling, license window off 2026-10-20 01:20:55 +03:00
core fix(core): typos type checks value before trailing newline submit (VERTA-4465) 2026-10-25 16:31:13 +03:00
docs fix(core): typos type checks value before trailing newline submit (VERTA-4465) 2026-10-25 16:31:13 +03:00
exec fix(core): page dialogs are answered by policy (VERTA-4461) 2026-10-25 02:22:48 +03:00
fixtures test(browsers): семь ручек профиля проверяются наблюдаемо, на живых браузерах (ref VERTA-3705) 2026-09-16 23:49:35 +03:00
http feat(http): http block requests via profile egress (VERTA-4218) 2026-10-07 04:49:00 +03:00
human feat(try): try block catches step failures before parking (VERTA-4214) 2026-10-07 06:27:48 +03:00
llm feat(contract): secrets, proxies and sms through the contract (VERTA-4161) 2026-10-08 15:40:43 +03:00
mail feat(contract): secrets, proxies and sms through the contract (VERTA-4161) 2026-10-08 15:40:43 +03:00
mcp fix(profiles): refuse proxy that is not an address (VERTA-4383) 2026-10-09 02:30:36 +03:00
polygon feat(contract): runs through the service contract (VERTA-4158) 2026-10-08 16:26:04 +03:00
profiles fix(profiles): refuse proxy that is not an address (VERTA-4383) 2026-10-09 02:30:36 +03:00
proxy feat(proxy): fraud score of my exit via mimic-proxy /fraud (VERTA-4122) 2026-10-02 11:21:52 +03:00
record feat(foreach): body runs on each found element with current target (VERTA-4109) 2026-10-03 06:51:59 +03:00
runs feat(exec): executor behind the service (VERTA-4363) 2026-10-08 22:09:36 +03:00
scenarios feat(scenarios): yt-warm без ниши по search=off 2026-10-25 16:07:24 +03:00
schemas fix(blocks): press and hotkey accept templated keys (VERTA-4462) 2026-10-25 03:49:19 +03:00
scripts fix(core): page dialogs are answered by policy (VERTA-4461) 2026-10-25 02:22:48 +03:00
secrets feat(contract): secrets, proxies and sms through the contract (VERTA-4161) 2026-10-08 15:40:43 +03:00
serve fix(serve): scenario update writes through symlink (VERTA-4466) 2026-10-25 16:13:36 +03:00
sheets feat(contract): secrets, proxies and sms through the contract (VERTA-4161) 2026-10-08 15:40:43 +03:00
sms feat(contract): secrets, proxies and sms through the contract (VERTA-4161) 2026-10-08 15:40:43 +03:00
stand test(probes): type with typos and trailing newline (VERTA-4465) 2026-10-25 16:15:02 +03:00
testing fix(core): ambiguous rung takes the only visible match (VERTA-4464) 2026-10-25 03:49:19 +03:00
trace fix(core): page dialogs are answered by policy (VERTA-4461) 2026-10-25 02:22:48 +03:00
update feat(update): бинарь накатывает релиз сам и доводит службу и скил (ref VERTA-3831) 2026-09-20 16:42:37 +03:00
.gitignore chore(scenarios): рабочие сценарии живут в репе (ref VERTA-4008) 2026-09-23 10:08:32 +03:00
.npmrc chore(ui): тулчейн verta-ui, гейты и docs/style.md (ref VERTA-3709) 2026-09-16 21:31:07 +03:00
AGENTS.md chore: bootstrap repo 2026-08-13 10:59:28 +03:00
biome.json feat(contract): ui client generated from the contract (VERTA-4364) 2026-10-08 18:39:48 +03:00
bun.lock feat(clipboard): probe page and clipboard fakes contract (VERTA-4098) 2026-10-03 03:45:27 +03:00
bunfig.toml fix(test): bun test keeps out of operator home (VERTA-4387) 2026-10-09 02:05:26 +03:00
CLAUDE.md feat(seats): exclusive plan without seat ceiling, license window off 2026-10-20 01:20:55 +03:00
cli.ts feat(contract): scenarios through the service contract (VERTA-4169) 2026-10-08 14:16:26 +03:00
CONTEXT.md feat(exec): executor behind the service (VERTA-4363) 2026-10-08 22:09:36 +03:00
docker-compose.yml chore(gate): приёмка судит нативный cloakserve без docker (ref VERTA-3694) 2026-09-14 22:42:57 +03:00
package.json test(ui): profile matrix smoke (VERTA-4199) 2026-10-09 00:23:27 +03:00
profiles.example.json fix(gate): stand url reaches acceptance scenario via {{base}} (VERTA-4181) 2026-10-03 01:28:38 +03:00
README.md chore(serve): drop legacy tails and manual tool list (VERTA-4397) 2026-10-09 01:38:11 +03:00
tsconfig.json feat(polygon): мишень поднимается одной командой и помнит вход (ref VERTA-3760) 2026-09-17 19:15:23 +03:00
worker.ts feat(exec): worker per profile, parallel run, compiled binary entry 2026-08-13 23:51:20 +03:00

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 прервали руками.