Представим ситуацию - вы работаете над проектом и у вас блочится аккаунт.

Клиенты просят решение проблемы - ваш аккаунт Claude заблокировали, Cursor медленно работает, а для перехода на Kimi нужно пара часов на настройку.

Я столкнулся с этой проблемой и решил сделать мультиклиентское приложение, чтобы иметь возможность быстро переключиться между разными cli вендорами в случае необходимости.

Сейчас в приложении работают сlaude, codex, cursor, moonshot kimi.

Тот же Claude для работы без блокировок требует vps без рф ip. Именно под этот сценарий заточен это мультиcli. Ставим go агента на vps, запускаем установку нужного клиента, проходим процедуру авторизации безопасно и удаленно на vps без риска блокировки.

Ребята из Anthropic проверяют ваши попытки доступа, поэтому подключаться рекомендуется только через впс с локацией разрешенной для работы:
https://support.claude.com/ru/articles/8461763-где-я-могу-получить-доступ-к-claude

Соответственно я ставлю Planulix, через него ставлю нужные cli авторизуюсь по кнопке в приложении и работаю там где хочу.

Что такое Planulix

В текущем виде Planulix состоит из двух частей:

1. Go Gateway — сервер, который ставится на VPS, домашнюю машину или локально. Он запускает CLI-агентов, читает их историю и отдаёт API.

2. Flutter-клиент — desktop/mobile приложение для сессий, файлов, чата, настроек и стоимости.

Схематично:

macOS / Android app

        |

        |  HTTP API + token

        v

Planulix Gateway на VPS / localhost / Tailscale

        |

        +-- tmux sessions

        +-- Claude Code

        +-- Kimi Code

        +-- OpenAI Codex CLI

        +-- Cursor CLI agent

        +-- файловая система проекта

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

Как выглядит рабочий сценарий

На десктопе Planulix ближе всего к боковой IDE-панели. Слева список сессий, в центре файлы или история, справа чат:

Полный интерфейс: проводник, редактор и чат
Полный интерфейс: проводник, редактор и чат

Слева есть фильтры:

- All / Claude / Kimi / Codex / Cursor / Kiro / OpenCode;

- All / Starred / Active / Finished.

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

Отдельный сценарий — держать список сессий рядом с редактором:

На телефоне я чаще использую это как пульт: посмотреть, что происходит, выбрать проект, отправить короткую задачу, проверить статус:

Мобильный вид: Kimi-сессии
Мобильный вид: Kimi-сессии
Мобильный вид: Claude-сессии]
Мобильный вид: Claude-сессии]

Отдельный экран показывает примерную стоимость:

Аналитика стоимости по моделям и проектам
Аналитика стоимости по моделям и проектам

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

Быстрый старт: gateway на VPS и клиент

1. Ставим gateway

Для Linux VPS есть установочный скрипт:

curl -fsSL https://raw.githubusercontent.com/pyatkovpetr/Planulix/main/scripts/install_gateway_remote.sh \

  | AUTH_TOKEN='your-secret-token' bash -

Он делает обычные вещи:

- скачивает бинарь gateway из GitHub Releases;

- при необходимости собирает из исходников;

- создаёт systemd-сервис planulix-gateway;

- запускает API на 8990.

После установки клиент подключается к:

http://<host>:8990/api

AUTH_TOKEN должен совпадать с тем, который передали скрипту.

Я предпочитаю не выставлять такой сервис наружу и подключать его через Tailscale:

http://100.x.x.x:8990/api

2. Скачиваем клиент

Готовые сборки лежат в релизе:

https://github.com/pyatkovpetr/Planulix/releases/tag/v1.0.0

Там есть:

- Planulix-macOS-v1.0.0.zip;

- Planulix-Android-v1.0.0.apk;

- Linux gateway assets.

3. Настраиваем клиент

В клиенте нужно указать:

- Server URL;

- Auth Token;

- при необходимости API-ключи агентов;

- SSH-настройки, если нужен OAuth flow через браузер на VPS.

Экран настроек выглядит так:

Настройки и диагностика подключения
Настройки и диагностика подключения

В настройках есть диагностика:

- доступность gateway;

- корректность токена;

- какие CLI установлены на сервере;

- какие агенты готовы к работе;

- какие сетевые интерфейсы видит сервер.

4. Открываем проект

Дальше выбираем директорию проекта на той машине, где работает gateway:

/root/projects/smart-budget

После этого можно открыть файл, создать чат или продолжить старую сессию.

Например, задача может выглядеть так:

Проверь, почему release APK не собирается, и предложи минимальный фикс.

Под капотом: как устроен gateway

Gateway написан на Go. Внутри нет большой магии: это HTTP API, немного работы с tmux, адаптеры под CLI и парсинг истории.

REST API

Основные ручки:

GET    /api/sessions

GET    /api/sessions/:id

POST   /api/sessions

POST   /api/sessions/:id/message

DELETE /api/sessions/:id

GET    /api/capabilities

GET    /healthz

Авторизация простая — bearer token:

Authorization: Bearer your-secret-token

Я специально не стал тащить в первую версию полноценную пользовательскую модель, refresh tokens, workspace ACL и прочий "почти SaaS". Planulix в текущей архитектуре — это ваш личный gateway. У него есть один секрет на API, и этого достаточно для self-hosted сценария за Tailscale или private network.

API сразу проектировался так, чтобы клиент не зависел от конкретного агента. Для UI сессия выглядит примерно одинаково:

{

"id": "cd-1777863282528",

"agent": "codex-cli",

"cwd": "/root/projects/smart-budget",

"status": "running",

"messages": []

}

А дальше уже gateway решает, откуда взять историю, как отправить сообщение и как привести вывод к нормальному виду.

Живые интерактивные агенты запускаются через tmux:

tmux new-session -d -s <session-name> -c <cwd> bash -c <agent-command>


tmux выбран из практических соображений:

- процесс не умирает, когда клиент закрылся;

- можно отправлять Enter, Ctrl+C и текст;

- у сессии есть имя и состояние на сервере;

- проще переживать переподключения.

Одна из первых неприятностей: имена tmux-сессий. Если быстро нажать "создать" два раза, имя на основе Unix seconds легко сталкивается, и tmux честно отвечает ошибкой. Поэтому имена сессий ушли на миллисекунды:

codex-1777863282528

claude-1777863300123

kimi-1777863310456

Индексация существующих историй

Самое неприятное место — история. У каждого CLI свой формат:

- Claude Code пишет JSONL в ~/.claude/sessions;

- Kimi использует свои context.jsonl;

- Codex хранит данные под ~/.codex;

- Cursor имеет transcripts в ~/.cursor/projects/.../agent-transcripts.

Planulix читает эти файлы и показывает их в одном списке. Это не идеальная абстракция: форматы отличаются, некоторые агенты меняют структуру между версиями. Но для практического списка сессий этого хватает.

Отдельный слой — "хранимые" Planulix-сессии. Если сессия была создана через gateway, я сохраняю её метаданные: agent, cwd, tmux name, history path, внешние id конкретного агента. Это нужно, чтобы после рестарта gateway не потерять связь между карточкой в UI и реальным файлом истории.

В коде это выглядит как обычный компромисс между "читаем всё из чужих каталогов" и "ведём свою базу":

~/.claude/sessions/...jsonl          -> discovered Claude session

~/.codex/...jsonl                    -> discovered Codex session

~/.planulix/transcripts/<id>.jsonl   -> локальная история для fallback/headless ответов

Planulix stored metadata             -> связь id, cwd, tmux, external session

Без такого слоя сложно нормально обрабатывать случай: tmux умер, но история есть; или наоборот, tmux жив, но агент ещё не успел записать session id.

Отправка сообщений

Ещё один важный момент: "отправить текст в tmux" не всегда достаточно.

Например:

- Claude может продолжать через свой resume-механизм;

- Kimi лучше обрабатывать через его print/resume путь;

- Codex CLI в headless-сценарии удобнее вызывать через codex exec;

- Cursor agent работает через свой CLI.

Поэтому для каждого агента есть отдельный путь:

Planulix message

    |

    +-- Claude resume / fallback task

    +-- Kimi resume / print

    +-- Codex exec

    +-- Cursor headless prompt

Тут было несколько багов, которые хорошо показывают, почему "просто сделать общий CLI wrapper" не получилось.

Например, Kimi плохо переносил некоторые вставки через tmux send-keys, особенно когда текст не ASCII. Для него надёжнее использовать CLI-путь, который печатает ответ и обновляет его context.jsonl.

Claude, наоборот, удобнее продолжать через resume, когда известен настоящий Claude session id. Но он появляется не мгновенно, поэтому gateway некоторое время ждёт линковки JSONL и только потом переключается на resume. Если линковка не успела произойти, используется fallback task.

Codex CLI в новой версии перестал принимать один из старых флагов, который я раньше передавал в exec. Итог был банальный:

unexpected argument '--ask-for-approval'

Пришлось разделить интерактивный запуск и headless codex exec: не все флаги живут в обоих режимах.

Почему Codex оказался отдельной историей

С Codex пришлось отдельно повозиться. У него есть два разных режима:

В Planulix для Codex есть Codex default. В этом режиме gateway не передаёт --model, а отдаёт выбор самому CLI. API-only модель оставлена отдельной опцией для случая, когда используется OPENAI_API_KEY.

Ещё один нюанс Codex — OAuth callback. Я ожидал стандартный code=..., а в реальном successful redirect получил URL вида:.

Стоимость

Стоимость считается там, где из истории можно достать usage:

input tokens

output tokens

cache read

cache create

model

project

Из этого получается простая аналитика:

- суммарная стоимость;

- разбивка по моделям;

- разбивка по проектам;

- дневная динамика.

Подсчёт стоимости — тоже не "одна формула на всё". У агентов разные форматы usage, часть данных приходит из transcript, часть можно только оценить по модели. Поэтому я держу это как best-effort слой: если usage есть — считаем точнее, если нет — показываем приблизительно или пропускаем.

Инженерные проблемы, которые пришлось решать

1. Один UI для разных агентов

На бумаге Claude, Codex, Kimi и Cursor выглядят одинаково: отправил prompt, получил ответ. В реальности у каждого свой набор привычек:

- разные директории истории;

- разные форматы сообщений;

- разные способы продолжить сессию;

- разные переменные окружения;

- разные модели авторизации;

- разные ошибки при headless-запуске.

Поэтому в коде есть нормализация agent id:

Claude -> claude-code

Kimi   -> kimi-cli

Codex  -> codex-cli

Cursor -> cursor

UI может переключать "Codex", а gateway уже знает, что бинарь называется codex, истории лежат под ~/.codex, а отправка сообщения должна идти через codex exec.

2. Capabilities вместо захардкоженного UI

Клиент спрашивает gateway:

GET /api/capabilities

И получает список агентов, состояние установки, версию, smoke-test и модели. Это удобно: UI не должен гадать, установлен ли на VPS codex или kimi.

Примерно такие данные нужны клиенту:

{

"id": "codex-cli",

"label": "Codex",

"command": "codex",

"installed": true,

"configured": true,

"ready": true,

"version": "OpenAI Codex v0.128.0",

"models": [

    { "label": "Codex default", "id": "provider-default" },

    { "label": "GPT-5.2 Codex", "id": "gpt-5.2-codex" }

  ]

}

Важная деталь: клиент всё равно должен иметь fallback. Я поймал ситуацию, когда старый gateway отдавал только gpt-5.2-codex, а ChatGPT/OAuth Codex эту модель не принимал. Поэтому клиент теперь сам добавляет Codex default первым, даже если старый /capabilities его ещё не знает.

3. Smoke tests для CLI

Gateway делает smoke-проверки и возвращает их в capabilities. Это не полноценный e2e-test, но позволяет UI отличить:

- CLI не установлен;

- CLI установлен, но не настроен;

- CLI настроен, но headless-команда падает;

- всё готово.

4. Local transcript для fallback-ответов

Когда сообщение отправлено через headless-команду (codex exec, Cursor headless, fallback Claude task), ответ не всегда автоматически появляется в родной истории агента. Чтобы UI не терял такие ответы, Planulix пишет локальный transcript:

~/.planulix/transcripts/<session-id>.jsonl

Это не попытка заменить историю агента. Это страховка для интерфейса: если gateway сам получил ответ, он должен показать его и после refresh.

5. Чистка вывода Codex

codex exec может вернуть не только ответ, но и служебный transcript:

Reading additional input from stdin...

OpenAI Codex v0.128.0

--------

workdir: /root/projects/smart-budget

model: gpt-5.5

...

user

привет

codex

Привет. Что нужно сделать в проекте?

tokens used

1,238

Показывать это целиком в чате бессмысленно. Поэтому gateway чистит вывод: берёт блок после строки codex и до tokens used. Плюс аналогичная клиентская чистка есть в центральном экране сессии, чтобы старые уже сохранённые сообщения тоже выглядели нормально.

6. Desktop и mobile в одном Flutter-клиенте

Flutter позволил собрать macOS и Android из одного кода, но UI пришлось делать с разными сценариями:

- на десктопе важны три панели: сессии, файлы, чат;

- на телефоне важнее dashboard, быстрый выбор агента и короткие действия;

- настройки должны быть достаточно подробными, потому что gateway может жить на VPS, за Tailscale или локально.

Самая приземлённая "современная" фича здесь — не анимации, а нормальная диагностика. Приложение должно не просто сказать "не подключилось", а показать, что именно не так: URL, токен, capabilities, CLI, сеть.

7. Релизы без отдельной инфраструктуры

Для gateway сборка Linux-бинарей уехала в GitHub Actions. При пуше тега v* workflow собирает:

planulix-gateway-linux-amd64.tar.gz

planulix-gateway-linux-arm64.tar.gz

Для macOS и Android пока сборка локальная:

flutter build macos --release --no-tree-shake-icons

flutter build apk --release

Потом .app упаковывается в zip, APK кладётся в GitHub Release, рядом публикуются SHA256. Это не идеальный release pipeline, но для первого публичного релиза уже достаточно воспроизводимо.

Codex, Claude, Kimi и другие CLI: чем отличаются режимы

Текущий статус поддержки примерно такой:

| Агент | Как подключается | Что хранит | Нюанс |

| Claude Code | claude, OAuth/API key | ~/.claude/sessions | Хороший resume-механизм |

| Kimi Code | kimi, API key/config | ~/.kimi/.../context.jsonl | Нужны корректные Moonshot/Kimi env |

| Codex CLI | codex, ChatGPT OAuth или API key | ~/.codex | ChatGPT и API key — разные режимы |

| Cursor | Cursor CLI agent | ~/.cursor/.../agent-transcripts | Чаще удобен как transcript/headless |

| Kiro/OpenCode | CLI | свои файлы/команды | Поддержка развивается |

Planulix оставляет различия внутри адаптеров, а наружу отдаёт общий интерфейс.

Безопасность и границы self-hosted подхода

Planulix self-hosted, но это не автоматическая безопасность. Просто ключи и агенты остаются на вашей стороне.

Минимальный здравый смысл всё равно нужен:

- AUTH_TOKEN должен быть длинным и случайным;

- если gateway торчит в интернет, нужен firewall/TLS/reverse proxy;

- лучше использовать Tailscale или private network;

- OAuth/JWT не стоит копировать в публичные чаты;

- CLI-агенты имеют доступ к файлам проекта, значит gateway надо ставить туда, где вы готовы дать такой доступ.

Схема, которая мне нравится больше всего:

phone/laptop -> Tailscale -> VPS:8990 -> Planulix Gateway -> CLI agents

Что получилось и что дальше

Сейчас Planulix уже закрывает мой повседневный сценарий:

- несколько проектов;

- Claude/Kimi/Codex/Cursor в одном списке;

- gateway на VPS;

- доступ с macOS и Android;

- истории сессий;

- запуск задач;

- примерная стоимость.

Что ещё хочется довести:

- resume для разных CLI;

- нормальный installer/updater для desktop;

- более точную аналитику стоимости;

- UI в режиме мини-IDE;

- документацию по разным схемам установки.

Репозиторий:

https://github.com/pyatkovpetr/Planulix

Релиз v1.0.0:

https://github.com/pyatkovpetr/Planulix/releases/tag/v1.0.0

Если проект понравился - или есть идеи велком в гитхаб ставьте звезды, предлагайте пул реквесты.
Есть идеи для совместных проектов - пишите @VoyagerCode

Комментарии (0)