browserskill
Расширения для Firefox и Chrome + CLI + скилл для Claude Code, дающие агентам полный доступк твоим настоящим браузерам. Никакого MCP-сервера: агент вызывает CLI как обычную команду,а как ей пользоваться — описано в скилле. Транспорт целиком локальный: stdio и Unix-сокет, ни одного HTTP-запросаи ни одного открытого порта.
Как устроено
┌─ Unix-сокет ~/.browserskill/firefox.sock ─▶ browserskill-host.js ─▶ расширение Firefox
агент ──скилл──▶ browserskill.js
└─ Unix-сокет ~/.browserskill/chrome.sock ──▶ browserskill-host.js ─▶ расширение Chrome
У каждого браузера свой native-хост и свой сокет (права 0600). Хост поднимается самимбраузером при старте расширения и работает как брокер: к нему подключаются вызовы CLI.Поэтому несколько агентов одновременно работают с одним браузером, а внешне не открытни один сетевой порт.
Один CLI обслуживает оба браузера — какой использовать, задаёт флаг -b chrome|firefoxили переменная BROWSERSKILL_BROWSER; без них берётся единственный запущенный.
Ответы больше ~700 КБ (скриншоты, дампы DOM) автоматически режутся на чанки и собираютсяобратно в хосте — ограничение native messaging на размер сообщения обходится прозрачно.
Установка
Одной командой (macOS):
curl -fsSL https://raw.githubusercontent.com/ruslankolmakov/browserskill/main/get.sh | bash
get.sh клонирует репозиторий в ~/.browserskill/app (другой путь — переменнаяBROWSERSKILL_SRC), ставит node через Homebrew, если его нет, запускает install.sh,открывает страницы расширений в найденных браузерах и кладёт путь к расширению в буферобмена — остаётся нажать «Загрузить распакованное». Повторный запуск обновляет установку.
Из клона вручную:
git clone [email protected]:ruslankolmakov/browserskill.git ~/Projects/browserskill
cd ~/Projects/browserskill
./install.sh
install.sh кладёт манифесты native-хоста в каталоги Firefox и всех найденных браузеров наChromium, создаёт лаунчеры с абсолютным путём к node (браузер из Finder не видит/opt/homebrew/bin) и ставит скилл в ~/.claude/skills/browserskill (другой каталог —переменная CLAUDE_SKILLS_DIR).
Затем ставим расширения — нужно хотя бы одно.
Firefox:
about:debugging#/runtime/this-firefox- Загрузить временное дополнение… → выбрать
extension/manifest.json
Временное дополнение живёт до перезапуска Firefox. Чтобы поставить навсегда, нуженFirefox Developer Edition / Nightly / ESR с
xpinstall.signatures.required = falseвabout:config— тогда собери.xpi(npm run build-xpi) и установи черезabout:addons→ Установить дополнение из файла. В обычном релизном Firefoxнеподписанные расширения навсегда поставить нельзя — это ограничение Mozilla.
Chrome:
chrome://extensions→ включить Режим разработчика- Загрузить распакованное расширение → выбрать каталог
extension-chrome - ID должен получиться
mifolcjccjkcdfdoebmkgklimogoemng— он зашит полемkeyв манифесте, и именно его разрешает манифест native-хоста - Желательно: в карточке расширения включить Разрешить пользовательские скрипты
Последний пункт нужен для eval на сайтах со строгим CSP — подробности ниже.Распакованное расширение переживает перезапуск Chrome, но требует включённого режимаразработчика.
Проверка обоих браузеров сразу:
./bin/browserskill.js doctor
./bin/browserskill.js browsers
Подключение к агентам
Отдельной настройки нет: install.sh кладёт SKILL.md в ~/.claude/skills/browserskill,и Claude Code подхватывает скилл сам. Скилл описывает, как звать CLI, какие операции естьи на какие грабли не наступать. Источник — skill/SKILL.md в репозитории, в него приустановке подставляется путь к репозиторию.
Любому другому агенту достаточно уметь запускать команду:
node /путь/к/browserskill/bin/browserskill.js <команда>
Выбор браузера
browserskill browsers # кто доступен
browserskill -b chrome tabs # разовый вызов в Chrome
BROWSERSKILL_BROWSER=chrome browserskill status
Без флага браузер выбирается сам: единственный запущенный, при двух живых — Firefox.Флаг всегда сильнее переменной окружения. Состояния «закреплённый браузер» между вызоваминет — в отличие от рабочего окна, которое живёт в расширении.
Команды и операции
Короткие подкоманды (browserskill --help): doctor, browsers, status, windows,use-window, new-window, tabs, open, snapshot, read, eval, api, call.
Всё остальное — через call <op> '<json>', это ~40 операций моста:
| Группа | Операции |
|---|---|
| Рабочее окно | session.newWindow, session.useWindow, session.target, windows.list/open/close |
| Вкладки | tabs.list, tabs.get, tabs.open, tabs.navigate, tabs.close, tabs.activate, tabs.reload, tabs.back, tabs.forward, tabs.waitLoad |
| Страница | page.snapshot, page.text, page.html, page.eval, page.click, page.fill, page.press, page.waitFor, page.find, page.screenshot |
| Отладка | logs.console, logs.network |
| Профиль | cookies.list/set/remove, history.search/delete, bookmarks.search/tree/create/remove, downloads.list/start, storage.get, containers.list, sessions.recent/restore |
| Всё остальное | browser.call, browser.describe |
Полный справочник с примерами вызовов — в skill/SKILL.md.
api — это и есть «полный доступ». Он вызывает любой метод WebExtension APIнапрямую, так что агент не ограничен готовым списком:
browserskill api tabs.query '[{"audible":true}]'
browserskill api browsingData.removeCache '[{}]'
browserskill api proxy.settings.set '[{"value":{"proxyType":"none"}}]'
call browser.describe показывает, что вообще доступно ({"path": "cookies"} → списокметодов). Наборы API у Firefox и Chrome разные, так что спрашивать стоит у того браузера,в котором собираешься работать.
Рабочее окно
По умолчанию операции без явного tabId идут в активную вкладку текущего окна — то естьтуда, где сейчас работаешь ты. Чтобы агент не мешал, закрепи за ним окно:
browserskill new-window https://example.com # своё окно, сразу закреплено
browserskill use-window 3 # закрепить существующее
browserskill call session.useWindow '{}' # показать текущее закрепление
browserskill use-window release # снять
После закрепления в это окно уходят все операции без tabId, туда же открываются новыевкладки, а tabs показывает только его вкладки (call tabs.list '{"allWindows":true}' — все).В windows закреплённое окно помечено target: true, а в попапе расширения виднострокой «Рабочее окно». Закрепление живёт отдельно в каждом браузере и снимается само,если окно закрыть.
Работа со страницей
snapshot возвращает интерактивные элементы с короткими uid — это дешевлескриншота и точнее селекторов:
{ "uid": "e12", "tag": "input", "type": "email", "text": "Email address", "rect": {...} }
Далее call page.fill '{"uid":"e12","value":"..."}' и call page.click '{"uid":"e15"}'.page.fill выставляет значение через нативный сеттер прототипа и шлёт input/change,поэтому корректно работает с React, Vue и Svelte.
eval выполняет код в контексте страницы — это тело async-функции, доступныawait и return:
return [...document.querySelectorAll("h2")].map(h => h.innerText)
Логи консоли и необработанные исключения собираются автоматически на каждой загруженнойстранице (буфер на 2000 записей), сетевые запросы — через webRequest. Читать черезcall logs.console и call logs.network. Вывод console.* из самого eval тожепопадает в буфер.
Чем Chrome отличается от Firefox
Расширение для Chrome — Manifest V3, и часть вещей там устроена иначе. Набор операцийодинаковый, отличается поведение:
| Firefox | Chrome | |
|---|---|---|
| Фон | постоянная фоновая страница | service worker, который браузер усыпляет |
| Произвольный JS | tabs.executeScript со строкой |
мир страницы + new Function, при отказе CSP — мир пользовательских скриптов |
| Скриншот всей страницы | одним вызовом | прокрутка и сшивка кусков (медленнее, до 25 экранов) |
| Скриншот неактивной вкладки | как есть | вкладку приходится на миг активировать |
| Контейнеры | containers.list работает |
аналога нет, вызов честно падает |
Что из этого важно на практике:
evalи CSP. Код выполняется в мире страницы, а там действует еёContent-Security-Policy. На сайтах со строгимscript-src(например GitHub)evalзапрещён. Обход — включить в карточке расширения «Разрешить пользовательскиескрипты»: тогда browserskill выполнит код в отдельном мире, где CSP страницы не действует.Без этого на таких сайтах придёт понятная ошибка с этой же подсказкой.Операцииpage.click,page.fill,page.snapshotи остальные структурныеоперации CSP не касается вообще — они внедряются готовыми функциями, а не строками.- Буферы логов и сон воркера. Service worker MV3 живёт от события до события. Расширениедержит его будильником и переподключается автоматически, но если Chrome всё же усыпитворкер, накопленные
logs.consoleиlogs.networkобнулятся. Закрепление рабочегоокна переживает это — оно лежит вstorage.session. - Служебные страницы.
chrome://*, Chrome Web Store и PDF расширениям недоступны —там любая операция со страницей вернёт ошибку. В Firefox то же самое сabout:*иaddons.mozilla.org.
Примеры CLI
./bin/browserskill.js doctor # диагностика обоих браузеров
./bin/browserskill.js browsers # кто сейчас на связи
./bin/browserskill.js status # версия браузера, число вкладок
./bin/browserskill.js tabs # список вкладок
./bin/browserskill.js open https://example.com
./bin/browserskill.js snapshot 12 # элементы вкладки 12
./bin/browserskill.js eval 'return document.title'
./bin/browserskill.js api tabs.query '[{"pinned":true}]'
./bin/browserskill.js call page.text '{"tabId":12}'
Браузер выбирается флагом --browser=chrome (или -b chrome), переменнойBROWSERSKILL_BROWSER, иначе автоматически:
./bin/browserskill.js -b chrome tabs
BROWSERSKILL_BROWSER=chrome ./bin/browserskill.js status
Тесты
./tests/run.sh
Живой браузер не нужен — браузеры и расширения заменяются заглушками. Проверяются: проходвызова CLI → сокет → хост → расширение, выбор браузера флагом, переменной и автоматическис маршрутизацией между двумя хостами, сборка чанкованных ответов, очистка сокета,отсутствие синхронных циклов при загрузке фона (отдельно для фоновой страницы Firefoxи для service worker Chrome) и, отдельно, устойчивость обоих перехватчиков консолик зацикливанию.
Последний тест не декоративный. В первой версии обмен со страницей шёл черезwindow.postMessage, а его видит и сама страница: если её код логирует полученныесообщения (частый паттерн у виджетов и аналитики), возникала петляconsole.log → postMessage → обработчик страницы → console.log. Каждая итерация уходилав фон отдельным runtime.sendMessage, забивая главный поток родительского процессабраузера, и он переставал реагировать на ввод. Сейчас обмен идёт приватнымCustomEvent, стоят защита от реентрантности, лимит 100 сообщений в секунду со страницы,батчинг раз в 250 мс и общий потолок 500 записей в секунду в фоне.
Безопасность
Это по построению очень мощный доступ: агент видит твои cookies, историю и авторизованныесессии и может действовать от твоего имени на любом сайте.
Подтверждение подключения
Каждое новое подключение клиента к сокету пользователь подтверждает вручную. Покане подтверждено — все вызовы (кроме health-check ping и __status) висят, а хостпоказывает нотификацию браузера. Клик по ней открывает popup расширения, где для запросапредлагается выбор области:
- это подключение — доступ только для текущего живого соединения; переподключился клиент — спросят снова;
- на эту сессию — до перезапуска браузера (запоминается токен клиента в памяти хоста);
- навсегда — токен клиента сохраняется в
~/.browserskill/allowed-<браузер>.json(0600) и переживает перезапуск.
Одобрение действует в пределах одного браузера: разрешив агенту Firefox, ты не разрешилему заодно и Chrome — там он спросит отдельно.
Клиент идентифицируется стабильным токеном из ~/.browserskill/client-token (0600) — онобщий для всех вызовов CLI с этой машины, поэтому одобрение «навсегда» даётся один раз. Еслипользователь не реагирует за BROWSERSKILL_APPROVAL_TIMEOUT_MS (по умолчанию 120000 мс), доступотклоняется (fail-closed). Полностью выключить гейт — BROWSERSKILL_APPROVAL=0 (для headless/CI).
Ограничение платформы: кнопок в нотификациях Firefox нет, поэтому подтверждать нужноименно в popup (нотификация только сигналит и открывает его).
- Сокеты лежат в
~/.browserskillс правами0600— доступны только твоему пользователю,никакой сети, ни локальной, ни внешней. - Буферы
logs.consoleиlogs.networkпишутся постоянно, со всех вкладок, и в нихоседает то, что страницы логируют сами. На практике это бывают почта, идентификаторыаккаунта, тариф и прочие персональные данные — например,claude.aiпечатает в консольполный набор трейтов аналитики. Любой подключённый агент прочитает это одним вызовом.Чистить буфер:browserskill call logs.console '{"clear":true}'. - Манифест хоста ограничивает подключение конкретным расширением:
allowed_extensionsсbrowserskill@localу Firefox,allowed_originsсchrome-extension://mifolcjccjkcdfdoebmkgklimogoemng/у Chrome. - Чтобы временно всё отключить — выгрузи расширение (
about:debugging/chrome://extensions) или закрой браузер: сокет исчезает вместе с хостом. - Логи хостов:
~/.browserskill/firefox.logи~/.browserskill/chrome.log.
Диагностика
| Симптом | Причина |
|---|---|
сокет не найден |
браузер не запущен либо расширение не загружено; browserskill browsers покажет, кто на связи |
расширение browserskill не подключено к хосту |
хост поднялся, но расширение отвалилось — жми «Переподключить» в попапе |
Значок расширения показывает off |
native-хост не запускается; смотри ~/.browserskill/<браузер>.log и проверь путь к node в лаунчере |
В Chrome ID расширения не mifolcjccjkcdfdoebmkgklimogoemng |
загружен каталог без поля key в манифесте — переустанови из extension-chrome |
страница запрещает выполнение кода своей CSP |
включи «Разрешить пользовательские скрипты» в карточке расширения Chrome |
скрипт не вернул результат |
это внутренняя страница (about:, chrome://, магазин расширений) — там расширения работать не могут |
Вызовы висят, значок показывает ! |
ждёт твоего подтверждения — открой popup browserskill и выбери область доступа |
доступ к … не подтверждён (таймаут) |
никто не нажал подтверждение за отведённое время; повтори вызов и подтверди в popup |
| Агент переспрашивает доступ каждый раз | одобрено «на подключение»; выбери «на сессию» или «навсегда» |
| В Chrome пропали логи консоли | service worker уснул и буферы обнулились — логи копятся заново с этого момента |