CLI-агенты
в фоне без API
Как точечно гонять агента из подписки headless — не устроить абьюз и не снести сервер.
У многих AI-инструментов есть два способа оплаты. Первый — API: каждый запрос считается в токенах, счёт растёт с каждым вызовом. Второй — подписка на CLI (программа в терминале: Claude Code, Antigravity, Grok CLI и похожие). Платишь фиксировано, сидишь в терминале, агент читает файлы и правит код.
Проблема: подписка обычно заточена под человека за клавиатурой. А на сервере иногда нужны короткие задачи без диалога: запустил → получил ответ → вышел. Такой режим называют headless (без «головы», без интерактивного экрана): агент работает как скрипт, а не как собеседник.
Отсюда соблазн: «подписка уже оплачена — накрутим на неё всё подряд». Так делать не надо. У провайдеров в правилах (ToS — условия использования) подписка почти всегда про человека и официальный продукт, а не про то, чтобы заменить собой платный API для ботов и массовых скриптов. Нарушение бьёт по аккаунту: 403, suspension, иногда перманентный бан. Параллельно есть второй риск — агент с shell на сервере сносит файлы, если нет клетки.
Этот выпуск — не рецепт «как бесплатно прокачать тысячи запросов». Это разбор осторожных, узких воркеров: тексты для «что нового», SEO-чат по кнопке, ночной отчёт раз в сутки, арбитраж ложного бана, автофикс с pull request без авто-мержа. Сначала — где граница здравого смысла и правила провайдеров, потом — как запускать и как держать в клетке.
Два счёта за один мозг
Задача. Нужен ИИ в автоматике: не чат в браузере, а ответ по запросу сервиса.
Почему это важно. API удобен для встраивания, но каждый вызов — отдельная строка в биллинге. Подписка CLI уже оплачена; дублировать её pay-per-token бессмысленно, если модель та же и задача узкая.
Как. Сервис (бэкенд, systemd-демон, cron) в узких случаях не ходит в HTTP API вендора, а запускает официальный CLI-процесс с флагами «один промпт → ответ в stdout». Авторизация — как у интерактивной сессии (OAuth / login CLI), credentials — в отдельном HOME служебного пользователя.
Итог. Модель та же, что в терминале. Меняется оболочка: скрипт с таймаутом вместо человека. Это не означает «подписка = замена API на любом объёме» — см. раздел про риски.
Простая аналогия: API — такси по счётчику, когда поездок много и маршрут чужой. Подписка CLI — свой автомобиль для своих коротких поездок. Headless — автопилот на заранее прописанном маршруте: без тормозов и без прав на дорогу (ToS) заканчивается аварией.
Headless: три слова, которые открывают дверь
Почти у всех CLI один и тот же каркас:
| Что нужно | Типичный флаг | Зачем простыми словами |
|---|---|---|
| Один промпт → поток | --print / -p | Без меню и «продолжить?» — как curl, только агент |
| Не спрашивать | --dangerously-skip-permissions / --yolo | Иначе процесс ждёт Enter, которого нет |
| Урезать shell | --sandbox (где есть) | Агент думает, терминал ограниченнее |
| Не висеть вечно | timeout 45 с … 20 мин | Если завис — убить процесс |
Без «пропустить подтверждения» headless не работает: агент честно ждёт человека. С «пропустить подтверждения» без клетки headless опасен: агент может выполнить rm, git push --force, остановить сервис.
Не надо автоматизировать «всё». Риски аккаунта
Две разные опасности
Говоря «безопасно», часто смешивают две вещи.
| Опасность | Кого бьёт | Суть |
|---|---|---|
| Сервер и данные | ваш прод | агент удалил, запушил, подключился к боевой БД |
| Аккаунт у провайдера | подписка / OAuth | решили, что вы превратили Plus/Pro/Max в дешёвый programmatic API |
Клетка на диске (sandbox, non-root, clone) не защищает от второго. Даже идеальный systemd не отменяет ToS. И наоборот: «мы вроде в рамках правил» не отменяет rm -rf, если skip-permissions без ограничений.
Что провайдеры явно не любят
Картина на 2026 год (отчёты пользователей, обсуждения, заявления в docs/форумах; это не юридическая консультация — перед боем читайте актуальный ToS своего тарифа):
- Anthropic (Claude Pro/Max). Жёстко режут OAuth-токен подписки в чужих CLI и обвязках (OpenClaw, OpenCode и аналоги). Подписка — для официальных продуктов (в т.ч. Claude Code / claude.ai); программный доступ «сбоку» — через API. Официальный headless (
claude -p) обычно часть продукта; «выдернули токен и крутим как API» — высокий риск бана. - Google (Gemini / Antigravity). В начале 2026 были массовые 403 ToS Violation у тех, кто гонял подписку через third-party harnesses. Официально: запрет harvest / piggyback on OAuth — «подоить» авторизацию чужим инструментом. Часть аккаунтов разбанивали с recertification; повтор — путь к перманентному бану. Официальный
agy/ Gemini CLI — другой разговор, чем «чужой бот на вашем OAuth». - OpenAI (ChatGPT Plus/Pro). Риск ниже «сразу за любой скрипт», выше за heavy automation: тысячи запросов, spam-like паттерны. Официальный Codex CLI на подписке — штатный путь.
- xAI (Grok / SuperGrok). Официально поддерживают headless в Grok Build CLI (
grok -p) под скрипты на подписке — заявленный путь, не обход. Злоупотребление volume всё равно упрётся в лимиты продукта.
Общий смысл: подписка — не безлимитный API для фермы ботов. Если картина снаружи выглядит как «тысячи дешёвых токенов в обход API» — ждут детекты и баны.
Чего не стоит делать
- Не превращать один Max/Pro в бэкенд для публичного SaaS (чужие пользователи жрут ваш OAuth).
- Не гонять тысячи однотипных запросов в сутки «потому что подписка безлимит».
- Не тащить third-party harness, который подставляет OAuth вместо официального бинаря — зона массовых банов 2026.
- Не обходить антифрод fingerprint'ами и «антидетектом» — это уже осознанный обход.
- Не автоматизировать критичные деньги и доступы без человека: выплаты, массовый разбан, деплой в main, смена секретов.
Если нужен стабильный поток «как у API» — платите за API. Headless на подписке — для редких, узких, своих задач.
Как выглядят «наши» задачи (точечно, не ферма)
Ниже — не оправдание «можно всё», а калибр: маленький радиус поражения и низкая частота.
| Задача | Частота | Что делает | Почему не ферма |
|---|---|---|---|
| Черновик «что нового» | по коммитам | только читает → markdown | 6 ходов, read-only, в UI не пишет |
| SEO-чат в панели | когда спросили | диалог через официальный agy | localhost-bridge, не публичный API |
| Ночная аналитика | раз в сутки (~05:30) | 3 разбора + синтез | цифры уже посчитаны кодом |
| Арбитр бана | кнопка «это не я» | JSON ≤45 с | нет записи в репо |
| Автофикс → PR | по реальной ошибке | правка в клоне | PR без merge; человек вливает |
Общий рисунок: событие → один (или несколько) коротких вызовов → узкий артефакт. Не цикл «пока квота не кончится». Не витрина «бесплатный Claude для клиентов».
Три вопроса перед кодом
- Это официальный CLI провайдера (или его заявленный headless) — или чужая обёртка на OAuth?
- Частота — раз в день / по кнопке / по ошибке, или «тысячи в час»?
- Выход — черновик/вердикт/PR, или «сразу в прод и к клиентам»?
Два «нет» или одно «тысячи / публичный SaaS» — не headless на подписке, а API.
Пять ролей, один приём
На одном железе одновременно живут разные headless-воркеры. Общее — spawn официального CLI. Разница — насколько жёсткая клетка и что агенту разрешено менять.
4.1. Черновик «что нового» (только чтение)
Задача. Из коммитов сделать человеческие карточки для колокольчика обновлений: без жаргона, без внутренних id.
Почему. Коммиты пишут разработчики и агенты. Пользователю нужно «появилась озвучка», а не feat(tts): wire clone voices.
Как. Скрипт берёт коммиты только с прошлого успешного запуска (курсор в state-файле). Shell отбрасывает шум (deploy, deps, sentry…). Дальше Grok CLI headless: --yolo, не больше 6 ходов, инструменты только чтение (файлы, поиск, список каталогов), прокси вычищены. Результат — markdown-черновик. В прод-список обновлений агент не пишет.
Итог. LLM переписывает смысл; человек решает, что попадёт в UI. Цена ошибки — плохой черновик, не сломанный сайт.
4.2. SEO-чат в панели аналитики
Задача. В дашборде спросить про запросы и семантику — с доступом к Wordstat (частотность Яндекса).
Почему. Чистый API без tools не «знает» Wordstat. CLI с MCP (подключаемый инструмент) может вызвать его.
Как. Маленький bridge на localhost: POST → spawn Antigravity (agy) с --print и skip-permissions. HOME и workspace отдельные. --sandbox выключен, потому что Wordstat MCP ходит наружу по HTTPS. Компенсация: bridge только на 127.0.0.1, не торчит в интернет.
Итог. Подписка Gemini через Antigravity закрывает SEO-чат без отдельного API-ключа в приложении. Цена — skip-permissions + сеть MCP.
4.3. Ночной отчёт по аналитике
Задача. Раз в сутки разобрать цифры продукта и отправить сжатый отчёт в Telegram.
Почему. Человек не обязан каждую ночь открывать дашборд. Но модель не должна сама считать — она врёт в арифметике.
Как. Сначала скрипт детерминированно собирает пакет чисел. Потом веер из трёх модулей Antigravity: --print --sandbox --dangerously-skip-permissions, Gemini 3.5 Flash (High), пустой workspace, таймаут модуля порядка 9 минут. Синтез — Claude CLI (Opus, max thinking). Второе мнение — короткий DeepSeek по API (pay-per-token на маленьком объёме). В БД — только цельный отчёт.
Итог. Подписочные CLI интерпретируют; цифры — из кода.
4.4. Арбитр ложных банов
Задача. Система безопасности банит IP. Иногда это обычный пользователь. Нужен разбор: false positive или реальная атака.
Почему. Четыре часа бана = потерянный клиент. Авторазбан всего = открытая дверь сканерам.
Как. Страница «это не я» → JSON (решения бана, кусок access-лога) →
agy --sandbox --print "<промпт + JSON>"
Таймаут 45 секунд. Ответ — строго JSON: false_positive или real_attack. Отдельный системный пользователь (не root), credentials 700/600, systemd: ProtectSystem=strict, ProtectHome=true, PrivateTmp=true. Если CLI упал — 503, попытку не сжигают.
Итог. Только классификация. Самый узкий радиус поражения из пятёрки.
4.5. Автофикс ошибок → pull request
Задача. Поймать продовую ошибку, понять «баг / шум / инфра / нужен человек», при реальном баге — правка и PR без авто-мержа.
Почему. Ночной on-call без человека. Но агент, который мержит сам, опаснее ошибки, которую чинит.
Как (общие принципы).
- Не live-дерево прод-репо. Полный
git cloneв каталог под/var/tmp/...(не/tmp: часто RAM; клоны забивают tmpfs). - Из env вырезают переменные с
DATABASE, чтобы тесты не подключились к боевой базе. - Промпт запрещает commit/push; контроль — снаружи.
- Вердикт в машинном блоке stdout — сервис решает, открывать ли PR.
Реализация A — Antigravity в служебной панели (поток Sentry). agy --print --dangerously-skip-permissions в клоне. Вердикты: real_bug / noise / infra / needs_human. PR только на real_bug.
Реализация B — официальный CLI DeepSeek V4 (deepcode) для демона автофикса. Триаж DeepSeek V4 Flash; фикс — deepcode + DeepSeek V4 Pro. Нюансы: deepcode требует TTY → pseudo-TTY (script); сам не выходит → маркер status: completed + kill; запасной таймаут 20 минут; в промпте запрет commit/push/PR.
Итог. Два CLI, одна философия: изолированный клон + вырезанные секреты + человек на merge.
Лестница безопасности
Не «включите sandbox и спите спокойно», а набор слоёв. Чем шире права агента, тем больше слоёв.
| Ур. | Что делает | Когда хватает |
|---|---|---|
| 0 | Только текст, tools выкл | Классификация, отчёт по готовым цифрам |
| 1 | Allowlist 2–4 read-only, лимит ходов | Черновики контента |
| 2 | Флаг sandbox CLI | Аналитика, арбитр бана |
| 3 | Отдельный user + HOME | Любой долгоживущий сервис |
| 4 | Clone sandbox в /var/tmp | Автофикс кода |
| 5 | Strip secrets / DATABASE* | Любой запуск с тестами |
| 6 | Systemd hardening | Демоны 24/7 |
| 7 | Нет auto-merge | Всё, что меняет код или прод-контент |
Практика на тех же ролях: колокольчик → 1+7; SEO → 3, без 2 (MCP); ночь → 0+2+3; арбитр → 0+2+3+6+короткий timeout; автофикс → 3…7.
CLI, установленный «на всю систему», не значит «процесс = root». Бинарь может быть в /usr/local/bin, процесс — под служебным пользователем. Credentials — в home сервиса (700), не session разработчика.
Грабли, которые не выдуманы
- Skip-permissions без RW-ограничений. Агент с full shell и env root = разработчик root без тормозов.
- ProtectHome=true и бинарь в /root/… Сервис не стартует (203/EXEC). Лечится: ProtectHome=read-only + ReadWritePaths для кэша CLI, либо бинари вне home.
- /tmp для клонов. Часто RAM. Песочницы —
/var/tmp/...на диске. - DATABASE в env родителя. Перед spawn вырезать ключи с
DATABASE(и write-DSN). - CLI, который «не умеет headless». Требовал TTY и не выходил → pseudo-TTY + маркер + SIGKILL.
- Sandbox vs MCP. Нужен внешний API — sandbox может мешать. Честно: sandbox выкл + localhost + отдельный user.
- Авто-merge. PR always, merge never automatic.
- «Подписка = бесплатный API для всего продукта». Публичный endpoint + высокая частота + third-party = бан. См. раздел 03.
Как это выглядит в голове сервиса
Событие (кнопка / таймер / ошибка)
│
▼
Собрать контекст (логи, JSON, коммиты, пакет цифр)
│
▼
spawn CLI (--print + skip-permissions [+ sandbox])
│ cwd = sandbox или empty workspace
│ env = без DATABASE, HOME = service
│ timeout = 45s … 20min
▼
stdout → распарсить (JSON / markdown / вердикт-блок)
│
├── ошибка CLI → retry / 503 / алерт
└── успех → действие узкое:
разбан | черновик | отчёт | PR без merge
Сервис — дирижёр. CLI — музыкант на один такт. Без метронома (timeout) и сцены (sandbox) палочку на ночь не отдают.
Что скопировать себе
В копируемых блоках ниже длинные тире заменены на запятые и двоеточия — удобнее в рабочих markdown-файлах. В тексте статьи тире обычные.
Чеклист headless CLI-воркера 0) СТОП-фильтр ToS (если хоть один пункт "да" — бери API, не подписку): - это публичный сервис для чужих пользователей на моём OAuth? - ожидаются сотни/тысячи запросов в сутки без человека? - кручу third-party CLI/harness вместо официального бинаря провайдера? - цель: заменить платный API "дешёвой" подпиской на потоке? 1) Задача точечная: по кнопке / по ошибке / раз в день, узкий выход (черновик, JSON, PR). 2) Официальный CLI провайдера + его headless-флаги. Не "выдернуть токен в свой HTTP". 3) Режим: один промпт, ответ в stdout (--print / -p). 4) Без интерактива: skip-permissions или yolo. Рядом обязательно клетка. 5) Отдельный OS-user или отдельный HOME с credentials (права 700/600). 6) Рабочая директория: empty workspace ИЛИ git clone в /var/tmp/..., никогда live prod tree. 7) Env: вырезать DATABASE* и лишние секреты; в логах маскировать токены. 8) Tools: allowlist по минимуму. Если только текст, tools не давать. 9) Timeout + kill. Если CLI не выходит сам, ловить маркер завершения. 10) Успех узкий: черновик / JSON-вердикт / PR. Никакого auto-merge и auto-deploy. 11) Systemd: ProtectSystem=strict, PrivateTmp, ReadWritePaths только state+sandbox. 12) Мониторинг: journald, алерт на ненулевой exit, метрика "завис дольше N". 13) Документ компромиссов: где sandbox выключен и почему (например MCP наружу). 14) Прочитал актуальный ToS/docs тарифа (правила меняются; 2026 уже показал волны банов).
Ты классификатор. Тебе дают факты в JSON. Инструменты для записи файлов не используй.
Верни ТОЛЬКО JSON одного вида:
{"verdict":"false_positive|real_attack","reason":"одна-две фразы по-русски"}
Не чини системы. Не предлагай команды. Не выходи за verdict/reason.
Факты:
<вставить JSON>
Ты в ИЗОЛИРОВАННОМ клоне репозитория. Текущая папка: корень клона.
Задача: минимально починить ТОЛЬКО описанную ошибку.
Правила:
- Никакого рефакторинга "заодно".
- Не трогай миграции, .env, секреты без прямой связи с ошибкой.
- НЕ делай git commit, git push, PR: только правки файлов. Коммит сделает обёртка.
- В конце выведи блок:
<<<VERDICT>>>
{"verdict":"real_bug|noise|infra|needs_human","summary":"2-4 предложения по-русски для не-разработчика"}
<<<END>>>
Ошибка:
<вставить>
Проверь headless CLI-воркер на этом сервере. Найди unit systemd / cron / bridge, команду spawn, user, HOME, cwd, флаги CLI, timeout, куда пишет, есть ли auto-merge/deploy. Выдай таблицу: слой | есть/нет | доказательство (файл/строка unit) | риск если нет Слои: non-root, sandbox/allowlist, clone not live tree, strip DATABASE, timeout kill, no auto-merge, credentials 600, listen localhost only. Не чини, пока не попросим. Только отчёт.
Что из этого следует
Headless CLI — не «хак вместо API» и не лицензия автоматизировать всё подряд. Это тот же агент, что в терминале, иногда встроенный в прод как короткий точечный worker. Экономия на API имеет смысл только там, где официальный CLI уместен, частота низкая, а выход узкий. Цена ошибки двойная: shell на диске и бан аккаунта.
Рабочая формула:
- Сначала ToS и калибр — официальный CLI, не ферма, не публичный SaaS на личном OAuth. Сомневаешься — API.
--print+ skip-permissions — чтобы работало без человека.- Клетка по размеру задачи — от allowlist tools до clone + systemd + non-root.
- Узкий выход — JSON, черновик, PR; никогда «делай что хочешь с продом».
- Человек на необратимом — merge, деплой, разбан при сомнении.
«CLI стоит в системе» ≠ «процесс = root». Бинарь общий, процесс — служебный, home — свой, sandbox — на диске, merge — руками. И ещё: не всё, что технически запускается, стоит запускать.
// Обсуждение
Можно писать анонимно. Укажите email, чтобы получать уведомления об ответах.