Codster — AI-агент для VS Code и CursorCursor-подобный AI-ассистент прямо в VS Code: чат с агентом, инструменты, правки файлов, терминал и диффы. Ключ провайдера не нужен — бесплатный лимит включается при первом запуске. Сайт проекта — codster.app.
Чат, режимы Agent / Ask / Plan, правки файлов, поиск по коду, терминал, диффы, подтверждения и откат изменений. Модели — «Хокку», «Танка», «Ода» и «Эпос»: они различаются глубиной размышлений, по умолчанию «Ода» ( Почему Codster
Скриншоты выше — панель расширения в VS Code; они снимаются с живой разметки ( Что умеет
Первый запуск сразу рабочий: пробный режим включается сам, а экран из УстановкаИз MarketplaceСтраница расширения → Install, либо из терминала:
После установки: значок Codster на Activity Bar или команда Codster: Открыть чат. В Cursor расширение ставится из Из готового
|
| Переменная | Значение |
|---|---|
CODSTER_UPSTREAM_BASE_URL |
https://api.deepseek.com — любой OpenAI-совместимый провайдер |
CODSTER_UPSTREAM_API_KEY |
ключ провайдера; без него прокси отвечает 503 upstream_not_configured |
Модели, их окно контекста, цены и глубина размышлений читаются из базы сервера (/v1/models), поэтому новая модель не требует релиза расширения. Доступ к модели — тоже данные сервера: таблица model_grants открывает её организации или человеку, models.is_public оставляет открытой для всех, а models.max_reasoning_effort задаёт глубину, с которой модель работает (клиент берёт её из каталога, а прокси повторно ограничивает запрос этим значением). Имя модели у провайдера хранится отдельно, в models.upstream_model (0018_catalog_names.sql): каталог — это продукт Codster, и под ним вызывается нужный апстрим, а в ответе model переписывается обратно на id каталога, поэтому имя провайдера не доходит до клиента ни в каталоге, ни в стриме, ни в ошибках.
Аккаунт Codster
С аккаунтом запросы идут через прокси бэкенда (/v1/chat/completions), а каталог моделей и цены приходят из базы (/v1/models).
- Секция Аккаунт Codster в настройках стоит первой: вход по email/паролю, кнопка «Создать аккаунт» (регистрация заводит организацию), список организаций и ролей. Вход и регистрация — две вкладки одной формы, поэтому кнопка «Создать аккаунт» открывает форму сразу на нужной вкладке.
- Регистрация присылает письмо: сервер отправляет на указанный адрес ссылку
/verify/<токен>, и клик по ней подтверждает почту (users.email_verified_at). Аккаунт работает и без подтверждения — письмо нужно, чтобы адрес можно было использовать для приглашений и восстановления, а не как турникет. Строка «Почта … не подтверждена» с кнопкой «Отправить письмо ещё раз» (POST /v1/auth/verify/resend) появляется в разделе «Аккаунт», пока адрес не подтверждён: ссылка живёт 7 дней, а новое письмо отменяет старое. Без настроенного SMTP (см.CODSTER_SMTP_*) письмо не уходит, и строка просто не появляется — регистрация от этого не ломается. - Refresh-токен хранится в защищённом хранилище:
SecretStorageв VS Code/Cursor, зашифрованный файлcloud-refresh.binв десктопе. Access-токен не персистится и обновляется заранее; сервер ротирует refresh-токен, новая версия сохраняется до выдачи access-токена. - Переключателя «через прокси» нет: без сессии запрос не уходит вовсе, потому что ключ провайдера держит сервер. Клиент не отправляет свои счётчики: расход списывает бэкенд по usage апстрима, и локальная оценка тут была бы двойным списанием.
- Статистику расхода считает только бэкенд: прокси метрит каждый запрос по usage апстрима, а клиент рисует
GET /v1/usage/summaryкак есть — день за днём и сумма за месяц, с лимитом изfree. Локального счёта и полей с ценами в интерфейсе нет: две суммы рядом неизбежно расходились бы со счётом. - Цены живут в таблицах провайдера, а не одним числом на модель:
providers(0017_providers.sql) — это вендор с адресом и своим расписанием,provider_peak_windowsдержит часы полного тарифа в минутах UTC (у DeepSeek это будни 01:00–04:00 и 06:00–10:00, остальное и все выходные — вдвое дешевле),model_prices— две строки на модель с тремя числами: попадание в кэш промпта, промах и выход. Прокси выбирает строку по моменту запроса и делитprompt_tokensна кэш и остальное, поэтому ночной ход стоит половину, а повторный префикс — около пятидесятой части. В каталоге (GET /v1/models) отдаютсяpricing.peak,pricing.offpeak,pricing.peak_windowsиoff_peak— какая ставка действует прямо сейчас. - Расписание считается по UTC, а не по зоне сервера: иначе переезд инстанса менял бы счёт. Окно может пересекать полночь и тогда принадлежит дню, в который открылось; пустой список дней — «каждый день»; вендор без окон продаёт одну ставку, и это полная.
- Адрес бэкенда — настройка
codster.cloudBaseUrl; поле живёт в разделе «Модель». - Если бэкенд недоступен, чат не падает: ход не начинается, окно открывает раздел «Аккаунт», а причина приходит тостом.
Бесплатный лимит
Аккаунт получает бесплатный лимит на календарный месяц (UTC), считается по фактическому расходу из таблицы потребления. Значение задаётся на сервере: CODSTER_FREE_MONTHLY_USD (по умолчанию 1.00, 0 выключает лимит). Лимит не трогает каталог, умения и /v1/me — только POST /v1/chat/completions.
Месяц — не единственное окно. Как в платных ассистентах, сумма расхода проверяется по четырём окнам сразу, от узкого к широкому:
| Окно | Длина | Доля месячного лимита |
|---|---|---|
session |
5 часов, повторяется в течение суток | 3.5 % |
day |
календарные сутки UTC | 7 % |
week |
ISO-неделя с понедельника | 30 % |
month |
календарный месяц UTC | 100 % |
Доли — это store.LimitShape: узкие окна выводятся из месячной суммы, а не хранятся отдельно. Поэтому CODSTER_FREE_MONTHLY_USD масштабирует всю форму разом, новый тариф получает торможение без сида, а строки plan_limits только переопределяют конкретное окно. Пропорции подобраны под реальное поведение: два полных сеанса в день упираются в дневное окно, четыре полных дня — в недельное, и только потом наступает месяц. Сессия открывается первым платным запросом, а не чтением карточки тарифа.
GET /v1/meи ответы логина/регистрации отдаютfree: { limit_usd, used_usd, resets_at }иplan.limits: [{ window, limit_usd, used_usd, resets_at, started }]— по записи на окно, от узкого к широкому. Если лимит выключен,freeнет вовсе.- В настройках под аккаунтом появляется полоска «Бесплатный лимит», а в разделе «План и потребление» — полоски всех четырёх окон: 5 часов, день, неделя, месяц.
- Когда кончается узкое окно, прокси отвечает
402 plan_limit_reachedс текстом «Лимит на 5 часов исчерпан: израсходовано $X из $Y. Новая сессия откроется в HH:MM UTC» — покупка тарифа тут не единственный выход, окно обнулится само. Месяц отвечает общим текстом с датой обнуления. - Обхода лимита на клиенте нет: без сессии запросы не отправляются, поэтому лимит — единственный счётчик расхода.
Тарифы и оплата
Каталог тарифов — такие же данные, как модели: таблица plans (server/migrations/0006_billing.sql) с сидом Free / Pro 990 ₽ / Max 4 990 ₽ на 30 дней (сюда же входил Team 2 990 ₽, выведенный из каталога миграцией 0016_org_balance.sql). Бесплатный тариф остаётся операторской настройкой CODSTER_FREE_MONTHLY_USD, у платных месячный лимит берётся из строки тарифа (monthly_usd_limit), а окна — из общей формы (0009_limits_seed.sql только фиксирует то, что продаётся, чтобы это было видно оператору).
GET /v1/plansотдаёт каталог,GET /v1/meи ответы входа —plan(slug,name,price_rub,limit_usd,used_usd,resets_at,status,paid,auto_renew,limits),org_balances— балансы команд, в которых состоит аккаунт (с лимитом и расходом самого участника), иcredits— личный баланс.POST /v1/billing/checkout {plan_id, auto_renew?}создаёт заказ и платёж у провайдера. Тариф всегда личный: организация платит с баланса, поэтомуorg_idв теле получает400 org_has_no_plan.auto_renew: trueпросит провайдера запомнить карту; ответ{order_id, status, confirmation_url, auto_renew}эхом говорит, сохранилась ли она на самом деле. Клиент открывает ссылку в браузере и опрашивает/v1/me, пока тариф не включится.- Оплату подтверждает только вебхук
POST /v1/billing/webhook. Тело уведомления не подписано, поэтому сервер перечитывает платёж у провайдера, сверяет сумму с заказом и лишь затем включает тариф. Повторное уведомление ничего не продлевает: заказ занимается ровно один раз. - Расход идёт водопадом: сначала окна личного тарифа, затем личный баланс, затем баланс организации участника (не выше его личного месячного предела). Запрос, оплаченный командой, пишется в
usage_events.org_id, поэтому личная полоска и баланс команды не считают одно и то же дважды. Когда исчерпано всё —402 plan_limit_reachedс названием тарифа. - У команды больше нет тарифа: она делит один пополняемый баланс, а
org_allocations(PUT /v1/orgs/{id}/allocations/{userID}) задаёт участнику необязательный месячный предел трат с него:limit_usd: null(или отсутствие поля) снимает предел, а0— прямой запрет тратить деньги команды. Предел проверяется вместе с остатком баланса, поэтому один человек не вычерпает деньги команды. - Тариф Team выведен из каталога (
0016_org_balance.sql): он продавал ровно тот пул, которого больше нет. Остатки оплаченных пулов переведены в баланс команд, поэтому ничего не сгорает. - Провайдер задаётся окружением:
CODSTER_BILLING_PROVIDER=off|dev|yookassa, ключиCODSTER_YOOKASSA_SHOP_ID/CODSTER_YOOKASSA_SECRET_KEYиCODSTER_PUBLIC_BASE_URL(из него строятсяreturn_urlи адрес вебхука; провайдеру нужен публичный HTTPS).
Автопродление
Подписка продлевается сама, без возврата в приложение (server/migrations/0011_autorenew.sql). ЮKassa списывает сохранённую карту вне сессии пользователя: первая оплата помечена save_payment_method, провайдер отвечает payment_method.id, и каждая следующая оплата называет этот id вместо страницы подтверждения.
POST /v1/billing/autorenew {enabled, org_id?}включает и выключает продление. Включить можно только с сохранённой картой — иначе409 no_saved_methodс подсказкой отметить галочку при оплате; выключить можно всегда. Включение ничего не списывает: первый платёж назначается на конец текущего периода.- Галочка «Запомнить карту и продлевать» стоит в списке тарифов, а в карточке плана есть переключатель, который её отражает. Без сохранённой карты переключатель заблокирован и объясняет, как карту сохранить: показывать рабочий тумблер, который нечем оплатить, — обман.
- Планировщик живёт в процессе
codsterd(RunRenewals, интервалCODSTER_RENEW_INTERVAL, по умолчанию 10 минут) и берёт только строки, у которыхauto_renew, есть карта иrenew_next_at <= now. Поэтому продление не требует ни одного действия пользователя и не дублируется при нескольких подряд прогонах. - Политика повторов: списание в конце периода, затем через день, затем через три; после третьей неудачи подписка переходит в
past_due, продление выключается, и карточка честно говорит, почему тариф кончился. Пока идут повторы, период ещё не истёк по-настоящему — тариф продолжает работать. - Продление создаёт обычный заказ с
renewal = true, поэтому счёт и акт печатаются как продление, а не как дубль первой оплаты. Период стыкуется с концом предыдущего (renewal_grace— 4 дня), так что тик воркера и дни повторов не съедают оплаченные сутки. - Провайдер без
Charge(например,devвыключен, а платёжный шлюз не умеет внесессионные списания) честно продаёт тарифы, но не заявляет продление: сервер не сохраняет карту и отвечаетauto_renew: false.
Баланс (доплата вне квоты)
Пополнение — второй способ заплатить, не меняя тариф: когда все окна исчерпаны, ход оплачивает баланс, и ждать обнуления не нужно (server/migrations/0010_credits.sql).
POST /v1/billing/topup {amount_rub, org_id?}создаёт заказ видаcreditи платёж. Ответ:{order_id, status, confirmation_url, amount_rub, credit_usd}. Доллары считает сервер по курсуCODSTER_CREDIT_RATE_RUB, клиент называет только рубли — курс с маржой не должен приходить снаружи. Границы одной доплаты —CODSTER_CREDIT_MIN_RUB/CODSTER_CREDIT_MAX_RUB.GET /v1/billing/credits[?org_id=]отдаёт{balance_usd, rate_rub, min_rub, entries: [{amount_usd, reason, created_at}]}. Читать баланс организации может любой участник, пополнять — только владелец или администратор. В карточке плана баланс следует выбору «Лично / организация»: у пула свой кошелёк, и показывать под ним личный было бы чужой счёт.- Баланс — append-only журнал
credit_ledger: пополнение пишет+, списание хода пишет−, ручная правка — ещё одна строка. Строка пополнения ссылается на заказ, аUNIQUE(order_id)гарантирует, что повторный вебхук не начислит дважды. Рядом таблицаcredit_balances(server/migrations/0012_credit_balances.sql) держит текущую сумму: она двигается тем же оператором, что пишет строку журнала, поэтому чтение баланса не пересчитывает всю историю. - Обработка оплаты атомарна:
ApplyOrderпомечает заказ оплаченным и выдаёт тариф или баланс в одной транзакции. Падение между этими шагами раньше означало «деньги взяты, тарифа нет, повторный вебхук уже ничего не делает» — теперь заказ остаётсяpending, и повторная доставка доводит его до конца. - Списание идёт после ответа модели, рядом с записью
usage_events, поэтому упавшая запись не ломает уже полученный ответ. CODSTER_CREDIT_RATE_RUB=0выключает доплату целиком:/v1/billing/topupотвечает503 billing_not_configured, а в интерфейсе блока «Баланс» нет.
Документы по оплатам
По каждой оплате сервер формирует счёт и акт в PDF — свои, без браузера и внешних конвертеров: GET /v1/billing/orders отдаёт список оплаченных заказов, GET /v1/billing/orders/{id}/invoice.pdf и /act.pdf — сами документы. В разделе «План и потребление» у каждого заказа есть кнопки «Счёт PDF» и «Акт PDF»: оболочка скачивает файл с токеном сессии (браузерная ссылка не умеет нести Bearer) и открывает его системным просмотрщиком.
- Реквизиты продавца — настройки:
CODSTER_SELLER_NAME,CODSTER_SELLER_INN,CODSTER_SELLER_KPP,CODSTER_SELLER_ADDRESS,CODSTER_SELLER_VAT. Пока не заданы имя и ИНН, ручки документов отвечают503 seller_not_configured— лучше отказ, чем счёт с пустыми реквизитами. - Покупатель: физлицу счёт выставляется на имя и email аккаунта; для организации печатаются её реквизиты (
orgs.legal_name/inn/kpp/address), которые владелец или админ задаёт в том же разделе. - PDF собирается пакетом
server/internal/pdfdoc: шрифт (DejaVu/Liberation на Linux, Arial на macOS) встраивается в документ, кириллица и текст на выборку работают. Путь к шрифту переопределяетсяCODSTER_PDF_FONT. - Нумерация документов —
billing_orders.number(identity), поэтому счёт № 12 остаётся № 12 и после перезапуска.
Пробный режим без регистрации
Первый запуск не должен упираться в форму регистрации, поэтому POST /v1/auth/guest выдаёт одноразовый аккаунт: без email и пароля, с отдельным небольшим лимитом (CODSTER_GUEST_MONTHLY_USD, по умолчанию 0.20).
- В ответе
user.guest: true; клиент по этому флагу показывает «Пробный доступ» вместо имени и предлагает создать аккаунт. - Гостю доступны
GET /v1/modelsи прокси, а организации, отчёт о потреблении и каталог умений отвечают403 account_required— они имеют смысл только у постоянного аккаунта. Расход гостя считает сам прокси, поэтому отправлять счётчики ему нечем и незачем. - Когда пробный лимит исчерпан, прокси отвечает
402 trial_limit_reachedс советом создать аккаунт; в чате это кнопка Создать аккаунт, а не «подождите месяц». 0вCODSTER_GUEST_MONTHLY_USDвыключает пробный режим целиком: неограниченный гость — это открытый прокси. В этом случаеPOST /v1/auth/guestотвечает403 guest_disabled.device_idвыбирает клиент, поэтому одного лимита на аккаунт мало:CODSTER_GUEST_DAILY_PER_IP(по умолчанию3) ограничивает число новых триалов с одного адреса в сутки, а превышение даёт429 trial_budgetс советом создать аккаунт. Перезапуск приложения на устройстве, у которого триал уже есть, бюджет не тратит — иначе человек запер бы себя сам.- Сессия гостя переживает перезапуск, как и обычная: refresh-токен лежит в том же хранилище.
- Пробный режим включается сам при первом запуске оболочки (
Account.autoStartTrial): чат работает сразу, форма регистрации не всплывает. Флаг «уже пробовали» хранится вglobalStateрасширения /sessions.jsonдесктопа, поэтому выход из аккаунта не включает триал заново, а неудачная попытка (бэкенд недоступен) повторяется на следующем запуске.
Устройство и адрес входа
У гостя нет ничего, кроме токена, поэтому бэкенд записывает, откуда начался триал. Клиент один раз на установку генерирует device_id (src/device.ts) и хранит его рядом с сессией; дальше POST /v1/auth/guest отправляет его вместе с client, platform, arch, os_version и app_version, а сервер добавляет IP и User-Agent.
- Таблица
devices(server/migrations/0004_devices.sql) хранит по строке на установку:user_id,device_id, платформу, версию приложения, IP,user_agent, флагguest,created_atиlast_seen_at. Адрес — этоRemoteAddrсокета.X-Forwarded-Forчитается только приCODSTER_TRUST_PROXY=true(и тогда берётся последняя запись, добавленная нашим прокси): без этого флага все пользователи за прокси попали бы в одно ведро лимитера, а с ним без прокси любой мог бы подделать адрес. device_idуникален и при повторном запросе возвращает тот же гостевой аккаунт (ON CONFLICTобновляет только «откуда» и время последнего визита). Перезапуск приложения не плодит пустых гостей и не выдаёт новый лимит; переустановка с потерейdevice_idвыглядит как новое устройство — это и есть единственная защита от таких обходов.- Ошибка записи устройства не отменяет сессию: пользователь получает токены, а проблема попадает в лог сервера.
Тесты серверной части — server/internal/httpapi/quota_test.go (границы месяца, лимит на аккаунт, отключённый лимит) и server/internal/httpapi/guest_test.go (создание, отдельный лимит, 403 на аккаунтные ручки, выключенный режим, ротация токена, запись устройства и повторный запуск с тем же device_id); в server/internal/store/pg/pg_test.go — upsert устройства.
API аккаунта: POST /v1/auth/register, POST /v1/auth/login, POST /v1/auth/guest, POST /v1/auth/refresh, POST /v1/auth/logout, GET /v1/me, GET /v1/models. Клиент — src/api/cloud.ts, связка с оболочками — src/account.ts.
llm-tolk gateway
В ../Dev/llm-tolk настроен доступ к DeepSeek Flash через OpenAI-совместимый шлюз (https://llm2.tolk.tech/routes/ds-flash-think). Расширение этот адрес больше не выбирает: шлюз включается на сервере —
CODSTER_UPSTREAM_BASE_URL=https://llm2.tolk.tech/routes/ds-flash-think
CODSTER_UPSTREAM_API_KEY=<тот же ключ, что LLM_API_KEY в llm-tolk/.env>
Сам llm-tolk — голосовой ReAct-агент с телефонными инструментами (end_call, wait_user, SIP/RTP), поэтому его тулы не подходят для редактирования IDE-проекта. Codster использует тот же DeepSeek-шлюз и похожую схему tool-loop, но файловые инструменты выполняются локально в extension host, где у них есть доступ к workspace и VS Code API.
Инструменты
Имена и семантика повторяют подход Cursor. В Ask/Plan режимах доступны только read-only инструменты.
| Инструмент | Назначение | Agent | Ask | Plan |
|---|---|---|---|---|
read_file |
Чтение файла с номерами строк и диапазоном | ✓ | ✓ | ✓ |
list_dir |
Структура директорий | ✓ | ✓ | ✓ |
grep_search |
Поиск по регулярному выражению через ripgrep | ✓ | ✓ | ✓ |
code_navigation |
LSP: определение, использования, символы файла | ✓ | ✓ | ✓ |
file_search |
Поиск файлов по имени/пути | ✓ | ✓ | ✓ |
codebase_search |
Локальный лексический поиск по смыслу | ✓ | ✓ | ✓ |
read_lints |
Диагностика VS Code | ✓ | ✓ | ✓ |
web_search |
Веб-поиск через DeepSeek Search API | ✓ | ✓ | ✓ |
task |
Субагент-исследователь с отдельным контекстом | ✓ | ✓ | ✓ |
review_changes |
Ревью своего diff свежим контекстом | ✓ | ✓ | — |
await_command |
Чтение вывода фоновой команды | ✓ | ✓ | — |
edit_file |
Точечная замена old_string → new_string |
✓ | — | — |
write_file |
Создание/перезапись файла | ✓ | — | — |
delete_file |
Удаление файла (с подтверждением) | ✓ | — | — |
run_terminal_cmd |
Команда с таймаутом или в фоне (background: true) |
✓ | — | — |
todo_write |
План из 3–6 шагов, виден в чате | ✓ | — | — |
codebase_search не использует embeddings и не отправляет код в отдельный индекс: это быстрый локальный ранжированный поиск по файлам. Для точных символов лучше grep_search.
grep_search работает через ripgrep: берётся бинарник, вшитый в редактор (@vscode/ripgrep), либо rg из PATH, либо путь из CODSTER_RIPGREP. Это даёт уважение .gitignore, пропуск бинарников и скрытых папок и поиск за миллисекунды. Если бинарника нет или шаблон использует синтаксис, которого нет в Rust-regex (lookaround, backreferences), инструмент откатывается на встроенный скан с --pcre2 и JavaScript-regex, так что поведение остаётся прежним.
code_navigation — второй слой поверх поиска: спрашивает language server редактора через vscode.executeDefinitionProvider, vscode.executeReferenceProvider и vscode.executeDocumentSymbolProvider. В отличие от текстового поиска понимает области видимости и типы, не путает комментарии и одноимённые слова, различает одноимённые методы разных классов. Требует установленного расширения с поддержкой LSP для нужного языка; в desktop-сборке недоступен.
В desktop-сборке grep_search берёт rg из PATH или путь из CODSTER_RIPGREP, а без них работает прежним встроенным сканом.
Параллельные вызовы. Независимые read-only вызовы одного шага выполняются одновременно (PARALLEL_TOOL_LIMIT в src/agent/loop.ts), но результаты ложатся в историю в том порядке, в котором их запросила модель. Всё, что пишет файлы, запускает команды или спрашивает подтверждение, идёт строго по одному: его порядок и диалоги видны пользователю.
Субагенты. task запускает вложенный ход в отдельной истории (src/agent/subagent.ts): субагент читает репозиторий через read-only набор инструментов и возвращает короткий отчёт с path:line. Основной чат получает один результат вместо десятков прочитанных файлов. Вложенность ограничена одним уровнем — у субагента нет ни task, ни review_changes, — а его токены учитываются в общей статистике сессии.
Ревью изменений. review_changes берёт git diff против HEAD плюс список неотслеживаемых файлов (src/agent/git.ts) и отдаёт их субагенту-ревьюеру, у которого свой контекст и своя задача: найти настоящие проблемы, а не пересказать diff.
Фоновые команды. run_terminal_cmd с background: true запускает процесс в отдельной группе и сразу возвращает id (src/agent/background.ts). Дальше await_command читает накопленный лог, ждёт появления нужной строки (pattern) или завершения. Процессы живут до 30 минут и переживают конец хода, поэтому dev-сервер можно поднять в одном сообщении и проверить в следующем.
web_search использует встроенный поисковик DeepSeek (POST /v1/tools/search): возвращает заголовки, URL, выжимки и, по флагу include_content, текст страниц. Параметр count ограничен 15 результатами.
Правила проекта
Поддерживаются те же форматы, что и в Cursor:
.codster/rules.md
.cursor/rules/*.mdc
.cursorrules
AGENTS.md
.github/copilot-instructions.md
Для .mdc читается frontmatter:
---
description: Правила для TypeScript
globs: src/**/*.ts
alwaysApply: false
---
Используй строгий TypeScript и не добавляй any.
Правила с alwaysApply: true или подходящими globs попадают в системный промпт. Дополнительно можно задать codster.customInstructions в настройках.
Настройки
Одна страница вместо отдельных диалогов: слева список разделов, справа содержимое — «Аккаунт», «План и потребление», «Организация», «Модель», «Агент», «Статистика», «Умения», «Рутина». В VS Code страница открывается вкладкой редактора (как в Cursor), а не модалкой поверх чата: чат остаётся на месте, страницу можно держать рядом, а повторный Codster: Настройки показывает уже открытую вкладку и переключает раздел. В десктопе страница занимает окно справа от рейла. «Отмена»/крестик в VS Code закрывают вкладку, в десктопе — прячут страницу. Кнопка расхода в строке состояния открывает сразу «Статистику», Codster: Умения и Codster: Рутина — свои разделы. В узкой панели список разделов становится строкой вкладок. Кнопки «Сохранить»/«Отмена» есть только у разделов с полями формы (модель, агент); в разделе «Модель» модели включаются галочками из каталога — свободного поля для ID модели нет, как и выбора глубины размышлений: её задаёт модель. Аккаунт, умения и рутина применяются сразу, а «Сохранить» в открытой вкладке её не закрывает.
«План и потребление» — карточка текущего личного тарифа и остаток лимита (/v1/me), список тарифов из /v1/plans с кнопкой Оформить, личный баланс и документы. Тарифы личные: команда не покупает тариф, у неё свой баланс в разделе «Организация», поэтому здесь только выбор тарифа, галочка «Запомнить карту и продлевать» и переключатель продления. Под тарифами — список оплат с кнопками «Счёт PDF» и «Акт PDF». Кнопка оплаты открывает страницу провайдера в браузере, карточка обновляется сама, когда вебхук подтвердит платёж. docs/plan.png.
«Организация» — отдельный раздел: Создать организацию (имя спрашивается на месте), участники с ролями, приглашение по email и общий баланс. Раздел показывает по одному состоянию: гостю — ссылку в «Аккаунт», аккаунту без организации — создание, участнику — список команды и остаток общего баланса, а пополнение, лимиты участников и реквизиты для счетов — только владельцу/админу. Команда больше не требует тарифа: баланс пополняется в рублях и расходуется по ценам каталога моделей, когда личный лимит участника израсходован. Приглашение доставляет ссылка: сервер отправляет письмо сам, если настроен SMTP (CODSTER_SMTP_*), иначе ссылка копируется и менеджер передаёт её вручную. Что именно произошло, видно в ответе (emailed) и строке тоста: «письмо отправлено» или «ссылка скопирована». Ссылка ведёт на страницу /join/<код> с кнопкой vscode://codster.codster/join?code=…, которая присоединяет к команде без ввода кода. Приглашение привязано к адресу и живёт 14 дней. Исключение участника работает всегда; аккаунт и его чаты остаются, уходит только членство, а владельца исключить нельзя — иначе балансу некому распоряжаться. Удаление организации возвращает остаток баланса на личный баланс владельца. docs/org.png.
| Настройка | По умолчанию | Описание |
|---|---|---|
codster.model |
oda |
ID модели из каталога сервера |
codster.enabledModels |
[] |
Включённые модели каталога; пустой список — все доступные |
codster.reasoningEffort |
high |
Глубина размышлений: задаётся моделью, из каталога, устаревшая настройка |
codster.confirmEdits |
false |
Подтверждать правки |
codster.confirmCommands |
false |
Подтверждать команды |
codster.autoContext |
true |
Прикладывать активный файл/выделение |
codster.customInstructions |
"" |
Дополнительные инструкции |
codster.cloudBaseUrl |
https://api.codster.app |
Адрес бэкенда Codster (аккаунт, модели, прокси) |
Бюджет ответа берётся у модели из каталога (max_output_tokens в БД, 32000 по умолчанию): размышления списываются с того же лимита, поэтому одно число на всех либо обрывало ход на середине мысли, либо просило больше, чем модель продаёт. Прокси всё равно обрезает запрос до каталога, так что старый клиент ничего не сломает. Температура (0.2) и бюджет шагов агента (100) не настраиваются: это не пользовательские ручки, а параметры клиента, которые легко сломать. Бюджет шагов при этом мягкий — за два шага до потолка модель получает просьбу закругляться, а после исчерпания один запрос всё равно уходит без инструментов, чтобы turn закончился итогом, а не пустым сообщением про лимит. Если модель упирается в лимит размышлениями, ход один раз повторяется без размышлений, а пустой ответ теперь подсказывает написать «Продолжи».
Ключа провайдера в настройках нет: он живёт на сервере, и в клиенте нечего хранить. Refresh-токен аккаунта — единственный секрет сессии: SecretStorage в VS Code/Cursor, зашифрованный файл cloud-refresh.bin в десктопе.
Секреты для умений
Умение не хранит токены в своём файле. Значения лежат в хранилище секретов (VS Code SecretStorage, на десктопе — файл под safeStorage: Keychain / DPAPI / libsecret), в настройки и в подсказки уходят только имена. Раздел «Секреты» — в разделе «Умения» страницы настроек (sidebarSkills), там же видно, каких секретов не хватает.
Умение объявляет нужные ключи во frontmatter полем secrets:
---
name: publish-npm
description: Публикует пакет в npm.
icon: package
secrets: npm-token
---
# Publish npm
1. Добавьте в «Умения» → «Секреты» ключ `npm-token`.
2. Запустите `npm publish` через `run_terminal_cmd` с `secrets: ["npm-token"]` — значение придёт переменной окружения `NPM_TOKEN`, в чат и в файл оно не попадает.
Так агент знает заранее: в индексе умений у строки видно secrets: npm-token, недостающие помечены (not stored); при read_skill список повторяется в ответе инструмента. Если значение нужно текстом (локальный конфиг, генератор), есть get_secret; прочитанное значение маскируется при сохранении сессии и в экспорте истории.
Иконка умения
Иконку выбирает пользователь, а не модель: в разделе «Умения» иконка рядом с именем — кнопка, она открывает набор из 20 рисунков (пазл, инструменты, эксперименты, пакет, поиск, релиз, терминал, база, git, безопасность и т. д.). Клик пишет одно поле во frontmatter:
icon: shield
- Набор — из Lucide (ISC, https://lucide.dev), 20 иконок: их геометрия лежит инлайном в
media/chat.js, без зависимости и без шрифта, поэтому иконки одинаковы на любой системе и не зависят от эмодзи-шрифта. Условия лицензии —media/lucide-license.txt, он уезжает и в VSIX, и в приложение. - В
SKILL.mdхранится только ключ набора. Сетка 24×24, обводка 2 → на экране 15–18 px иконка читается как часть остального интерфейса. - Переписывается ровно строка
icon:: остальной frontmatter и тело не трогаются, файл остаётся в git-диффе одной строкой. - Модель иконку не выбирает:
skill-creatorи ответ чата её не возвращают, при создании умение получает стандартный пазл. Неизвестное значение во frontmatter тоже сводится к пазлу, без «случайной» иконки по хешу имени. - Устаревшие эмодзи из старых файлов (
🚀,🧪) по-прежнему понимаются при чтении и сопоставляются с ключом набора. - У встроенного умения нет файла на диске, поэтому его иконка не меняется — кнопка неактивна и объясняет почему.
Скрипты и история умений
Умение — это папка .codster/skills/<имя>/, а не один файл: рядом с SKILL.md можно держать scripts/ и references/.
.codster/skills/release-check/
├── SKILL.md # инструкции, frontmatter
├── scripts/check.sh # запускаемый код
└── references/notes.md # длинные описания, примеры
read_skillперечисляет файлы умения, чтобы модель не гадала об их наличии.read_skill_fileчитает один файл: путь относительный,..и абсолютные пути отклоняются.run_terminal_cmdс параметромskill: "release-check"делает рабочей папкой папку умения и экспортируетSKILL_DIRиCODSTER_SKILL— скрипт запускается какbash "$SKILL_DIR/scripts/check.sh". Подтверждение команды и правила безопасности те же, что и для обычных команд.skill_historyвозвращает последние коммиты, затрагивающиеSKILL.md. Отдельного версионирования нет: умения лежат в репозитории проекта, историю ведёт git (--follow, чтобы пережить переименование). В разделе «Умения» это кнопка «История».
Каталог умений
В разделе «Умения» две вкладки: «Проект» — то, что лежит в .codster/skills, и «Каталог» — общие умения из бэкенда. Личные умения никуда не уезжают сами: в облако попадает только то, что опубликовано кнопкой «Опубликовать в каталог», и только в одну сторону.
- Кнопка «Опубликовать» отправляет
SKILL.md(описание,secrets, тело) плюс файлы из папки:scripts/,references/— до 20 файлов, 64 КБ каждый, 200 КБ всего. slug— имя, под которым умение видно всем. Оно уникально в каталоге: тот же slug от другого автора даёт409 slug_taken, а повторная публикация своим автором обновляет запись.- «Установить» пишет папку целиком в
.codster/skills/<slug>/и засчитывает установку на аккаунт. Если такое умение уже есть и отличается, кнопка превращается в «Перезаписать» — локальные правки не теряются молча. - Пути файлов проверяются на сервере и на клиенте:
.., абсолютные пути иSKILL.mdво вложениях отклоняются, иначе публикация могла бы писать вне папки умения. - Верификация (
verified) — отметка модерации, её ставит сервер; публикация её не меняет. - Секреты из каталога приезжают только именами: значения остаются в хранилище системы на устройстве.
HTTP: GET /v1/skills?q=&mine=1, GET /v1/skills/{slug}, POST /v1/skills, DELETE /v1/skills/{slug}, POST /v1/skills/{slug}/install.
Рутина
Задачи, которые агент повторяет сам: «прогони тесты и почини падения», «проверь обновления зависимостей», «прибери мёртвый код». Раздел «Рутина» в настройках; на десктопе его открывают ещё кнопкой с часами в композере, пунктом в боковом меню и Cmd/Ctrl+Shift+A, в VS Code — командой Codster: Рутина и значком в заголовке панели чата. docs/automations.png.
- У задачи есть папка, промпт, название (по умолчанию — первая строка промпта), режим и расписание. Расписаний три:
- по интервалу — от 5 минут до двух недель. Период до суток — чистый интервал, и часовой пояс ему не нужен;
- по дням недели — выбрали «по дням недели» и отметили Пн…Вс, задача идёт каждую неделю в эти дни и в указанное время: «по понедельникам и четвергам в 08:30»;
- раз в месяц — «1-е число» … «31-е» или «последний день»: то, что нужно для отчёта в конце месяца. День, которого в месяце нет (31-е в апреле), приходится на последний день месяца — не пропускается.
- От суток и выше — ещё и время. Выбрали «каждый день», «каждые 2 дня» или «каждую неделю» — рядом появляется поле «Во сколько» (
<input type="time">, по умолчанию 09:00). Тогда запуск привязан к часам, а не к моменту создания: задача, заведённая в 15:30 с временем 09:00, впервые сработает завтра утром, а не через сутки. Слоты считаются по локальному календарю, поэтому переход на летнее время не сдвигает запуск на час. Недельная задача помнит свой день недели и называет его в списке: «каждую неделю по средам в 09:00». - Пропущенный слот не сдвигает график. Если приложение было закрыто три дня, дневная задача выполнится один раз при открытии и вернётся на своё время. У задачи без выбранного часа поведение прежнее — чистый интервал от последнего запуска.
- Задача по суткам и больше без времени — тоже допустима (старые задачи и вызовы без формы): тогда работает интервал, как раньше.
- Папка закрепляется за задачей. Список в форме — это открытые папки; новая задача по умолчанию берёт текущую. Перед запуском оболочка делает нужную папку активной, а в VS Code ещё и подменяет хост на представление одной папки (
src/host/scoped.ts), чтобы относительные пути и поиск не уходили в соседние корни multi-root окна. Если папку удалили с диска, запуск падает с понятной причиной в журнале, а не выполняется в чужом проекте. - Все запуски идут в один чат на задачу. Первый запуск создаёт чат, следующие дописывают в него. Иначе задача «каждый час» забивала бы список чатов своими копиями и вытесняла разговоры пользователя.
- Чат рутины помечен в рейле. Слева от названия — часы того же рисунка, что и у пункта «Рутина». Пока последний запуск новее момента, когда чат открывали, значок жёлтый; открыл чат (клик в рейле, вкладка или отправка сообщения) — снова серый. Отметка «просмотрено» лежит в
localStorageвебвью, поэтому жёлтый значок переживает перезапуск, а не мигает при каждом старте. - Запуски идут по одному, в порядке ближайшего срока: два агента одновременно дрались бы за один терминал и одну рабочую папку.
- Настройки подтверждений действуют и здесь: если соответствующий флаг включён, правка файла или команда в автоматическом чате ждёт ответа, а без ответа запрос отклоняется по таймауту, как в обычном чате.
- Пока редактор закрыт, задач нет. Первый тик после запуска наверстывает пропущенное: задача, у которой срок прошёл ночью, выполнится при открытии, а не через час.
- Ручной запуск («Запустить сейчас») не меняет расписание: срок всё равно считается от последнего запуска, иначе кнопка сдвигала бы график.
- О результате сообщает уведомление с кнопкой Открыть чат: в VS Code — информационное сообщение, на десктопе — системное уведомление. Короткий итог и статус (
ok/ошибка/остановлено) видны в списке и в журнале последних запусков. - Задачи живут в
globalStateрасширения и вsessions.jsonдесктопа, то есть принадлежат пользователю, а не папке проекта.
Код: модель и хранение — src/agent/automations.ts, часы — src/agent/automationScheduler.ts, сообщения обеих оболочек — src/chat/automationBridge.ts. Запуск в чат реализуют сами оболочки (ChatViewProvider.runAutomation, ChatEngine.runAutomation), потому что только они умеют начинать ход агента.
Команды
| Команда | Действие |
|---|---|
Codster: Открыть чат |
Фокус на чате |
Codster: Новый чат |
Очистить историю |
Codster: Настройки |
Открыть настройки вкладкой (аккаунт, план, модель, агент, умения, рутина) |
Codster: Рутина |
Задачи агента по расписанию |
Codster: Отменить последние изменения агента |
Откатить последний чекпоинт |
Codster: Объяснить выделение |
Prefill запроса по выделению |
Codster: Исправить выделение |
Prefill запроса на исправление |
Архитектура
Общее ядро (без зависимостей от VS Code)
├── src/agent/loop.ts — ReAct loop
├── src/agent/toolDefs.ts — JSON Schema инструментов
├── src/agent/tools.ts — файловые операции, поиск, терминал (через WorkspaceHost)
├── src/agent/prompts.ts — системный промпт и сборка контекста
├── src/agent/context.ts — активный файл, выделение, @-mentions, диагностика
├── src/agent/rules.ts — Cursor-совместимые правила
├── src/agent/skills.ts — умения: разбор SKILL.md, файлы умения, индекс
├── src/agent/skillMarket.ts — публикация умения в каталог и установка из него
├── src/agent/skillHistory.ts — история коммитов умения через git
├── src/agent/automations.ts — задачи по расписанию: модель, хранение, журнал запусков
├── src/agent/automationScheduler.ts — часы: что уже пора запускать и по очереди
├── src/agent/checkpoints.ts — снимки файлов и откат
├── src/api/client.ts — DeepSeek/OpenAI SSE, tool calls, web search, retry
├── src/chat/turn.ts — один ход агента: правила, инструменты, события, чекпоинт
├── src/chat/html.ts — разметка чата, общая для webview и десктопа
├── src/chat/automationBridge.ts — сообщения рутины для обеих оболочек
├── src/host/host.ts — интерфейс WorkspaceHost
├── src/host/vscode.ts — реализация на API VS Code
└── src/host/node.ts — реализация на node:fs (десктоп)
Поверхности
├── src/chat/ChatViewProvider.ts — VS Code: webview, команды, workspaceState
├── media/chat.js + chat.css — UI, markdown, diff, approvals, @-mentions
├── media/desktop.css — десктоп: рейл папок и двухколоночный layout
└── desktop/main.ts + engine.ts — Electron: окно, IPC, папки, JSON-хранилище
desktop/fileApps.ts — кто в системе открывает этот тип (LaunchServices)
desktop/appIcons.ts — иконки приложений для меню «открыть»
Бекенд (отдельный Go-модуль, в VSIX не попадает)
├── server/ — аккаунты, организации, каталог моделей и умений, потребление, прокси LLM
Агентный цикл: system + history → stream → tool_calls → approvals → tools → tool results → stream → … до финального ответа или лимита шагов. Каждый запрос оборачивается чекпоинтом; изменения применяются через WorkspaceEdit (VS Code) или fs.writeFile (десктоп) и сохраняются на диск.
Ядро общается с окружением только через WorkspaceHost: файлы, glob-поиск, диагностика, активный редактор, лог. Поэтому одна и та же логика агента работает и в расширении, и в отдельном окне; новая поверхность добавляет только реализацию хоста и транспорт.
Бекенд
server/ — отдельный Go-модуль: аккаунты, организации и роли, refresh-токены,
каталог моделей с ценами, приём агрегированного потребления и прокси
POST /v1/chat/completions к провайдеру. Ключ провайдера живёт только на
сервере: клиент авторизуется своим access-токеном и не видит наружу ничего,
кроме каталога моделей. Чаты, промпты и секреты остаются на устройстве.
Проксирование ограничено лимитом тарифа аккаунта: у бесплатного это
CODSTER_FREE_MONTHLY_USD, у платного — plans.monthly_usd_limit, а когда
личный лимит исчерпан, платят пулы организаций, где аккаунт состоит. Когда
исчерпано всё — 402 plan_limit_reached с датой обнуления. Одноразовым гостевым
аккаунтам (POST /v1/auth/guest) прокси отвечает отдельным, меньшим лимитом
(CODSTER_GUEST_MONTHLY_USD) — это пробный режим без регистрации.
make run-dev # локальная разработка: PostgreSQL в Docker, codsterd на хосте
make up # весь бекенд в Docker, ждёт /healthz
make smoke # сквозная проверка API из контейнера
make logs # логи обоих сервисов
make down # остановить, база сохраняется
make reset # остановить и удалить том с базой
Локальная разработка на macOS
make run-dev — ежедневный сценарий: в Docker поднимается только
PostgreSQL, всё остальное работает нативно, без контейнеров и без пересборки
образов на каждую правку.
make run-dev # проверит toolchain, поднимет базу, соберёт и запустит codsterd
make dev-status # контейнер базы, pid и /healthz
make dev-logs # хвост лога сервера
make dev-stop # погасить сервер, база остаётся
make dev-reset # снести том с базой и поднять заново
Что делает run-dev:
preflight— проверяет docker (и что демон запущен),docker compose, curl, go;env— создаёт.envиз.env.example, если его нет;dev-db—docker compose up -d postgresи ожиданиеpg_isready;dev-serve— собираетserver/bin/codsterd, запускает его в фоне на127.0.0.1:8080сCODSTER_STORE=postgresи миграциями при старте;dev-health— ждёт/healthz, иначе печатает лог и падает.
Сервер слушает только localhost, лог и pid — в .dev/ (в git не попадают).
Повторный run-dev перезапускает сервер; данные в PostgreSQL сохраняются.
Порты и секреты берутся из .env (POSTGRES_PORT=5433, CODSTER_PORT=8080,
CODSTER_JWT_SECRET). Redis проекту не нужен: кэша и очередей на сервере нет,
единственный контейнер — база.
docker-compose.yml в корне описывает и полный стек: postgres (порт 5433 на
хосте, чтобы не спорить с локальным PostgreSQL), codsterd (порт 8080) и два
сервиса в профиле tools — smoke (сквозной тест API) и gotest (весь
Go-набор вместе с интеграцией в PostgreSQL). Секреты и порты берутся из .env;
make env создаёт его из .env.example.
Сервер отдельно, без Docker:
make server-run # codsterd на in-memory хранилище
make server-test # тесты без базы
make db # только PostgreSQL из compose
make server-test-pg # плюс интеграция с PostgreSQL
make docker-test # то же, но целиком внутри Docker
Подробности — в server/README.md: эндпоинты, переменные окружения, схема
таблиц, политика токенов и что именно уходит на сервер.
Сейчас это самостоятельный сервис: расширение и десктоп пока работают со своим ключом DeepSeek, подключение клиента к бекенду — следующий шаг.
Безопасность и ограничения
- Агент выполняет команды от вашего пользователя. Подтверждения по умолчанию выключены — за скорость платим вниманием: включите
codster.confirmCommandsиcodster.confirmEdits, если не хотите отпускать агента без присмотра. Катастрофические команды блокируются при любой настройке, а удаление файла спрашивает подтверждение всегда. - Заблокированы катастрофические команды (
sudo,mkfs,rm -rf /,dd of=/dev/...,curl | sh, fork bomb,shutdownи т.п.). - Скрипты умений запускаются тем же
run_terminal_cmdи подчиняются тем же подтверждениям и запретам;run_skill-обхода нет.read_skill_fileне выходит за папку умения и не читает бинарники. run_terminal_cmdне поддерживает интерактивные программы и долгоживущие dev-серверы; для них есть обычный терминал VS Code.- Поиск и
codebase_searchне индексируют.git,node_modules,dist,build,target,.venvи похожие каталоги..envисключён, если не указать его явно какpath. - Рабочая область — первый workspace folder. Multi-root поддержан частично.
- Только desktop VS Code/Cursor, не web-версия: нужен Node.js API для терминала и файлов.
- Чекпоинты сохраняются в workspace state (примерно до 4 МБ на чат); если снимков слишком много, самые старые после перезапуска окна пропадут.
- Чаты работают параллельно и независимо, но правят один и тот же workspace: если два чата меняют один файл, побеждает последняя запись, а откат чекпоинта может вернуть файл к более старому состоянию. Пока в другом чате выполняется запрос, откат блокируется.
- Активный файл, выделение, правила проекта и
@-файлы отправляются в DeepSeek как контекст запроса. Не прикладывайте секреты. - История автоматически сжимается перед отправкой, чтобы не выйти за контекстное окно; старые результаты инструментов могут быть сокращены.
web_searchотправляет только текст запроса в DeepSeek Search API (POST /v1/tools/search), но не напрямую: запрос идёт на бэкенд Codster, который подставляет ключ провайдера. Апстрим должен поддерживать/v1/tools/search— на сторонних шлюзах без него инструмент вернёт ошибку. Найденные страницы приходят уже в виде сниппетов и выжимок.- Нет embeddings-индекса:
codebase_searchработает локально, код не отправляется в отдельный индекс. - Нет tab-completion и отдельного inline-режима «Cmd+K»: основной сценарий — чат-агент, который правит файлы через те же инструменты.
- Проект не аффилирован с Cursor или DeepSeek. Названия инструментов использованы для совместимости workflow.
Разработка
Всё через make: без аргументов он печатает список задач.
make help # список задач
make install # npm ci
make check # typecheck + 268 тестов + бандл
make test # только тесты
make package # codster-<version>.vsix
make desktop-build # esbuild → dist-desktop/
make icons # перегенерировать PNG-иконки из media/icon.svg
make clean # снести dist, release, coverage
Вручную то же самое:
npm install
npm run watch # esbuild watch
npm run typecheck
npm run test
npm run build
npm run package
Запуск extension host: откройте папку в VS Code и нажмите F5 (конфигурация в .vscode/launch.json).
Живые тесты против поднятого бекенда (по умолчанию пропускаются):
make run-dev
CODSTER_LIVE_BASE_URL=http://127.0.0.1:8080 npx vitest run src/test/liveMarket.test.ts # каталог умений
CODSTER_LIVE_BASE_URL=http://127.0.0.1:8080 npx vitest run src/test/liveQuota.test.ts # бесплатный лимит и 402
UI можно посмотреть без VS Code:
open scripts/preview.html # окно десктопа
open scripts/preview-web.html # панель VS Code
Обе страницы используют те же media/chat.js и media/chat.css с мок-хостом; панельная не подключает media/desktop.css.
Логотип и иконки
Исходники — codster-icon-white.svg (знак) и codster-logo-white.svg (знак с
надписью) в корне репозитория. Из них собрано всё остальное:
| Файл | Где используется |
|---|---|
src/chat/logo.ts |
знак и надпись инлайном в разметку чата (<symbol> + <use>), общие для webview и десктопа |
media/activity.svg |
иконка расширения в Activity Bar VS Code и в заголовке панели (монохром, currentColor, знак 18px по центру квадрата 24×24) |
build/icon.svg |
исходник иконки приложения: белая плитка и чёрный знак |
media/icon.svg |
исходник иконки расширения: тёмная плитка и белый знак |
media/icon.png |
иконка расширения для marketplace, 512×512, плитка во весь холст |
media/icon-128.png |
та же иконка в 128×128 — логотип паблишера в портале Marketplace, в VSIX не попадает |
build/icon.png |
иконка приложения для electron-builder, 1024×1024, плитка 84% холста по сетке macOS |
- В интерфейсе логотип наследует
currentColor, поэтому автоматически подстраивается под тему VS Code и десктопную панель. - Логотип собран из двух независимых символов: знак и надпись. На рейле десктопа знак 30px, надпись 18px (их размеры задаются отдельно в
media/desktop.css), в шапке настроек — только знак. Спрайт с надписью добавляется лишь на десктопной поверхности, в webview его нет. viewBoxкаждого символа — измеренный прямоугольник краски, а не догадка по числам в path: иначе знак съезжает внутри плитки. Тем же приёмом считается глиф для Activity Bar.make icons(илиnpm run icons) перерисовывает PNG изbuild/icon.svgиmedia/icon.svgи заново собираетmedia/activity.svg: размер и положение знака скрипт берёт из отрисованного глифа, а не из чисел в path, иначе знак уезжает из центра. Другого SVG-рендерера в зависимостях нет, поэтому работает Electron. PNG лежат в git, так что сборка без перегенерации тоже работает.- В режиме разработки (
npm run desktop:start) Electron показывает иконку Electron; чтобы Dock совпадал с релизом,desktop/main.tsподставляетbuild/icon.pngчерезapp.dock.setIcon.
Скриншоты
Изображения для README не рисуются руками: make screenshots собирает из настоящей разметки (renderChatHtml) две страницы — scripts/preview.html (окно десктопа) и scripts/preview-web.html (панель VS Code) — и снимает их через Electron. Мок-хост, который отвечает вместо расширения, лежит отдельно в scripts/preview-mock.js, поэтому страница превью не отстаёт от продукта.
Кадры панели — docs/web-chat.png, docs/web-models.png, docs/web-skills.png: они и стоят в шапке README, потому что Marketplace показывает расширение, а не десктоп-окно с рейлом. Остальные docs/*.png — десктоп-приложение. Скрипт кладёт копии в site/public/screens/, а на https://codster.app/screens/… они попадают только после make site-publish — без деплоя сайта картинки в README и на странице Marketplace будут битыми.
Десктоп-приложение
Ядро агента не зависит от VS Code, поэтому то же приложение собирается как отдельное окно на Electron. UI — тот же media/chat.js и media/chat.css, то есть ровно тот же вид, что в панели VS Code: те же шрифты, палитра, пузыри сообщений и бренд-акцент. Отдельный media/desktop.css лишь добавляет то, чего в панели нет: полосу рабочей папки с меню проектов и более широкую колонку чата под размер окна. Вместо acquireVsCodeApi() preload отдаёт window.codster с тем же контрактом.
npm run desktop:build # esbuild → dist-desktop/main.js + preload.js
npm run desktop:start # собрать и запустить окно
node scripts/run-desktop.mjs ~/project # открыть конкретную папку
node scripts/run-desktop.mjs ~/project --add-dir ~/shared-lib
Сборка установщиков
npm run desktop:dist:mac # release/Codster-<version>-mac-universal.dmg + .zip
npm run desktop:dist:win # release/Codster-<version>-win-x64-setup.exe
npm run desktop:dist # обе платформы за один прогон (нужен macOS)
macOS-сборка делается только на macOS, Windows-инсталлятор собирается и с macOS (Wine не нужен). По умолчанию подписей нет: на macOS ставится ad-hoc подпись (mac.identity: "-"), на Windows сертификат не используется, поэтому обе системы один раз спросят подтверждение при первом запуске.
- macOS: приложение блокируется Gatekeeper — «Системные настройки → Конфиденциальность и безопасность → Всё равно открыть», либо
xattr -dr com.apple.quarantine /Applications/Codster.app. На Apple Silicon без ad-hoc подписи приложение падает как «повреждённое», поэтомуidentity: "-"вelectron-builder.yml— значение по умолчанию. - Подпись Developer ID:
npx electron-builder --mac -c.mac.identity="Developer ID Application: … (TEAMID)"— вместо ad-hoc подставляется настоящая личность (сертификат должен лежать в keychain). Нотаризация управляется только переменными окружения: если естьAPPLE_ID,APPLE_APP_SPECIFIC_PASSWORDиAPPLE_TEAM_ID(илиAPPLE_API_KEY/APPLE_API_KEY_ID/APPLE_API_ISSUER),@electron/notarizeотправляет.appвnotarytoolи приклеивает тикет. Для ad-hoc сборки эти переменные выставлять нельзя:notarytoolотклонит неподписанное приложение и сборка упадёт. Без них в логе будетskipped macOS notarization. - Windows: SmartScreen → «Подробнее» → «Выполнить в любом случае». Инсталлятор ставится без прав администратора (
perMachine: false). - Упакованное приложение открывает папку из
--workspace <path>, позиционного аргумента,CODSTER_WORKSPACEили системного диалога «Добавить папку…»; дополнительные папки —--add-dir/CODSTER_ADD_DIRS.
Ридер файлов
В десктопе есть свой просмотрщик кода — отдельная колонка справа. Открывает и закрывает его иконка файла в правом верхнем углу окна (рядом с названием текущего чата); активное состояние подсвечено бренд-цветом.
- дерево открытой папки с ленивой подгрузкой вложенных;
node_modules,.git,distи прочие тяжёлые каталоги скрыты; - поиск по путям прямо в панели (Enter открывает первый результат);
- превью с номерами строк, языком и размером; строки больше 512 КБ читаются частично и помечаются «обрезано»;
- изображения (PNG, JPEG, GIF, WebP, SVG) показываются как картинки;
- HTML открывается в песочнице без скриптов (
sandbox,srcdoc), так что страница из проекта не достаёт до интерфейса чата; PDF — встроенным просмотрщиком Chromium; - Markdown (
md,mdx,markdown) рендерится тем же парсером, что и ответы агента: заголовки, списки, таблицы, цитаты, код с кнопкой «Копировать», ссылки открываются в системе; - DOCX и XLSX разбираются на хосте в HTML без внешних зависимостей: у Word — абзацы и таблицы, у Excel — по таблице на лист (до 300 строк × 60 колонок, до 12 листов). Форматирование, картинки и формулы не переносятся;
- остальные бинарники (
.doc,.xls,.pptxи т.п.) — пометкой вместо байтов; - кнопка «В чат» вставляет
@путьв сообщение, так файл уходит в контекст; - клик по файловой ссылке в ответе агента открывает файл в этой панели (с подсветкой нужной строки), а не во внешнем редакторе;
- «открыть» — сдвоенная кнопка: левая половина открывает в приложении по умолчанию (на ней его иконка), правая — меню со всеми приложениями, которые в системе объявили этот тип файла, сверху помечено «по умолчанию»; внизу меню — «Показать в папке», так что отдельной кнопки для этого нет. Список берётся у LaunchServices (
NSWorkspace), поэтому туда попадают только действительно поддерживающие формат программы, а не все установленные. В VS Code списка нет — остаётся одна кнопка с системным обработчиком; - пути за пределами открытых папок отклоняются на стороне движка.
Обе панели (папки слева и файлы справа) тянутся за вертикальный разделитель: курсор col-resize, при наведении проступает зелёная линия. Ширины запоминаются между запусками, двойной клик или Home возвращает ширину по умолчанию, стрелки двигают на 16px (Shift — на 32). Чат при этом не сжимается меньше 360px: на узком окне панели упираются в минимум.
В VS Code панель не нужна: там файлы открываются в настоящем редакторе, поэтому весь блок скрыт и работает только при surface-desktop.
Скриншоты
Скриншот можно вставить из буфера, перетащить в окно кнопкой с картинкой или просто сослаться на него путём:
@docs/chat.png @docs/onboarding.png посмотри на эти экраны и найди проблему
Такие ссылки не уходят в модель текстом (раньше они превращались в мусор из байтов): collectPromptContext читает файл и прикладывает его как image_url, показывая миниатюры в сообщении. Лимиты — 4 изображения на сообщение, 8 МБ на файл, поддерживаются PNG, JPEG, GIF, WebP. Если агент сам попытается прочитать картинку через read_file, он получит подсказку прикрепить её к сообщению.
Папки (рейл слева)
Окно устроено как харнес: слева рейл с папками на компьютере, справа — чат выбранной папки.
- Первый запуск без аргументов сразу рабочий: сохранять нечего и выбирать не из чего, поэтому Codster сам создаёт
Документы/Codster/Проект(Проект 2, … если имя занято) и открывает её — как это делают харнесы с папкой по умолчанию. О создании сообщает тост с путём, потому что папка появилась без диалога. Экран «Открыть папку / Создать папку» остаётся аварийным: он показывается, только если папку создать не удалось (нет прав на «Документы», битый путь), и пока папки нет, отправка сообщения заблокирована — агенту нечего читать. - Кнопка «Создать папку» (и пункт меню) не спрашивает имя: папка появляется там же, в
Документах/Codster, и сразу открывается. +в шапке рейла добавляет папку на диске; она сразу становится активной. Действия строки («новый чат в папке», «убрать папку», «удалить чат» корзиной) появляются при наведении на строку и не срабатывают, пока скрыты.- Раскрытая папка показывает свои чаты;
+на строке папки создаёт в ней новый чат,×— закрывает чат или убирает саму папку из списка (файлы на диске не трогаются). Последнюю папку убрать нельзя, кроме случая, когда её уже нет на диске: тогда окно возвращается к экрану выбора папки. - Клик по чату в другой папке сначала переключает папку, потом открывает диалог.
- Папку можно переключить и во время работы агента: у каждого чата свой хост, привязанный к его папке, поэтому запущенный turn продолжает писать туда, где начался, а не в открытую сейчас. Его вкладка остаётся в полосе с точкой «работает» и пунктирной рамкой, а имя папки в вкладке показывает, откуда он. Когда turn закончится, точка сменится зелёной меткой «готово» (она же на строке чата в рейле) и пропадёт, как только чат откроют.
- Активная папка подсвечена, у каждой папки виден счётчик чатов; пропавшая с диска папка показывается зачёркнутой.
- Агент всегда работает строго в папке своего чата: относительные пути резолвятся только в ней, инструменты не выпускают за её пределы (включая проверку симлинков).
- Список папок и активная папка сохраняются между запусками. Папки можно задать при запуске: позиционный аргумент,
--add-dir,--add-dir=,CODSTER_ADD_DIRS(через:) — они попадают в тот же рейл. База для автоматической и созданной папки —Документы/Codster;CODSTER_FOLDERS_DIRпереносит её (нужно для тестов и своих сборок).
Что подменяется на десктопе:
| В VS Code | На десктопе |
|---|---|
WorkspaceHost на API VS Code |
NodeWorkspaceHost на node:fs: корень проекта, glob-обход, tsc --noEmit для диагностики |
acquireVsCodeApi() |
window.codster (preload + IPC) |
workspaceState / globalState |
JSON в app.getPath("userData") |
SecretStorage |
safeStorage (Keychain / DPAPI / libsecret) |
QuickPick и openExternal |
системные диалоги Electron |
Настройки, чаты, список папок и расход сохраняются между запусками. Установщики собирает electron-builder по electron-builder.yml; подпись Developer ID, notarization и автообновление в конфигурацию не входят — для них нужны сертификаты Apple и настройка publish.
Вопросы и ответы
Codster — это бесплатный аналог Cursor или GitHub Copilot? По сценарию — Cursor: агент с инструментами, правками и терминалом. Начать можно бесплатно: при первом запуске включается пробный лимит без регистрации. Аккаунт даёт лимит больше, платные тарифы — ещё больше; расход и остаток видны в разделе «План и потребление».
Нужен ли API-ключ OpenAI, Anthropic или DeepSeek?
Нет. Ключ провайдера держит сервер Codster, поэтому в настройках расширения нечего вставлять. Свой ключ и свой сервер — это self-hosted сценарий: codster.cloudBaseUrl плюс CODSTER_UPSTREAM_BASE_URL / CODSTER_UPSTREAM_API_KEY на бэкенде.
Чем это отличается от автодополнения в Copilot?
Codster не дописывает строку, а выполняет задачу: читает файлы, ищет по коду, правит их через old_string → new_string, запускает тесты и терминал, показывает diff и умеет откатить весь ход одной кнопкой.
Работает ли в Cursor, VS Codium и на других редакторах?
В Cursor — да, ставится из .vsix. Требуется VS Code 1.90+ (или совместимый форк) на macOS, Windows или Linux; собственной сборки Code-OSS нет, расширение работает поверх редактора.
Можно ли обойтись без аккаунта? Да, на пробном режиме: чат, модели и правки доступны, но лимит меньше, а каталог умений и организации не работают. Аккаунт нужен, чтобы продолжить после лимита, синхронизировать историю и работать в команде.
Куда уходят данные?
Запросы идут на бэкенд Codster (https://api.codster.app) и дальше к OpenAI-совместимому апстриму; .env не попадает ни в поиск, ни в контекст. Политика — codster.app/privacy, условия — codster.app/terms.
Какие модели и как их переключать? «Хокку», «Танка», «Ода» и «Эпос» — разная глубина размышлений; по умолчанию «Ода». Переключение — чип в композере, набор моделей — галочками в настройках. Каталог приходит с сервера, поэтому новая модель не требует обновления расширения.
Можно ли поставить свои правила и запреты?
Да: .codster/rules.md, .cursor/rules/**/*.mdc, .cursorrules, AGENTS.md, .github/copilot-instructions.md и codster.customInstructions попадают в системный промпт. Опасные команды блокируются независимо от настроек.
Лицензия
MIT. См. файл LICENSE.
