Skip to main content
Glama

Борт — персональная система управления проектами

Локальная сводка по всем проектам: деньги, дедлайны, задачи, чаты. Одна SQLite-база, два потребителя: человек — веб-интерфейс на http://localhost:8100, AI-ассистент — MCP-сервер. Числа в UI и у ассистента всегда совпадают: оба идут через один сервисный слой.

Старт

cd ~/bort
uv sync                                  # зависимости, venv на Python 3.13
uv run python scripts/migrate.py         # создать/обновить схему БД (идемпотентно)
uv run uvicorn bort.web.app:app --host 127.0.0.1 --port 8100   # вручную

Открыть http://localhost:8100. API-документация (Swagger): http://localhost:8100/docs. Команда ручного старта слушает 127.0.0.1. Развёрнутый на Mac mini сервис слушает 0.0.0.0:8100 для доступа через LAN/Tailscale; не пробрасывайте его в публичный интернет — в Борта пока нет авторизации.

Сводка: порядок, канбан и статусы

Проекты в «Открытых» можно расставить вручную: перетащите строку за ручку слева от названия, поставьте фокус на ручку и нажмите ↑/↓ (на тач-экранах для этого рядом есть кнопки ↑/↓). Порядок хранится в SQLite (projects.sort_order) и действует на всех устройствах; ссылка «Вернуть автосортировку» возвращает автоматический порядок «горящее сверху, потом по приоритету». Проекты, не затронутые перестановкой, остаются в автосортировке.

Переключатель «Список / Канбан» показывает ту же сводку досками по статусам проекта; статус меняется прямо в карточке, архив закрытых проектов остаётся свёрнутым. Ручная перестановка задаётся в виде «Список».

Стоимость проекта (сумма сделки) задаётся при создании и меняется в карточке проекта: кнопка «Изменить» рядом со «Стоимостью» в панели денег открывает поле суммы («150 000,50»; пустое значение — 0). После сохранения сразу пересчитываются остаток к оплате, процент оплаты и маржа.

Проект можно удалить: на его странице рядом с «← К сводке» есть кнопка «Удалить». Подтверждение предупреждает, что задачи, затраты, оплаты и связи проекта уйдут вместе с ним каскадом и безвозвратно; после удаления браузер возвращается на сводку, сохраняя её фильтры.

Помимо «Идеи», «В работе», «На паузе» и «Закрыт» есть статусы «На обсуждении», «На проверке» и «Ожидаем оплату» (работа сдана, деньги ещё не пришли — суммы уже входят в общие деньги). Проект «На обсуждении» ещё не взят в работу: он виден в списке и в счётчике проектов, но его стоимость, оплаты, затраты и маржа не входят в общие деньги, пока статус не сменится на рабочий. В панели портфеля такие проекты перечислены подписью «На обсуждении — не в деньгах».

Кнопки «+ Задача» в строке проекта и в блоке внимания нет: следующая задача показана текстом, а задачи ведутся в карточке проекта и через MCP.

Вкладка AI

Откройте http://localhost:8100/ai после применения миграций. Карточки идут сверху вниз: сначала подписки (Claude, GPT · Codex, Qwen Cloud · Token Plan, Z.AI · GLM Coding Plan), затем балансы и API (DeepSeek, OpenRouter). Каждая карточка обновляется своей кнопкой «Обновить», а кнопка «Обновить всё» над сеткой обновляет все провайдеры разом; сбой одного из них не мешает остальным (сбойная карточка покажет ошибку, прежние цифры сохранятся).

  • Claude — окна подписки 5 часов / 7 дней из GET /usage локального демона kraube serve (OAuth-подписка Pro/Max/Team, шлюз ai-gateway на mini). Служебный endpoint не делает платных обращений к Anthropic: демон кэширует лимиты из заголовков последних ответов (до первого запроса — 404, карточка честно об этом скажет). Нужен KRAUBE_SERVE_KEY (serve-ключ демона, не ключ Anthropic) в ~/.config/bort/env; адрес переопределяется BORT_KRAUBE_USAGE_URL (по умолчанию http://127.0.0.1:8787/usage). При статусе allowed_warning/rejected карточка подсвечивает сообщение.

  • DeepSeek — баланс по DEEPSEEK_API_KEY через официальный /user/balance.

  • Qwen Cloud · Token Plan — кредиты читает снимок консоли: ключ подписки остаток не отдаёт, поэтому скрипт scripts/pull_qwencloud_usage.py на MacBook открывает консоль Qwen Cloud в headless Chrome (копия залогиненного профиля), читает внутренние API и присылает снимок в ~/bort/data/qwencloud-usage.json на mini (0600). Карточка показывает остаток кредитов, план, срок и время сброса; снимок старше 3 часов помечается предупреждением. Кнопка «Обновить» (или «Обновить всё») на mini ставит заявку qwen-refresh.request: sync-агент MacBook забирает её в течение ~30 секунд, запускает сборщик и присылает свежий снимок, а карточка подтягивает его раз в минуту. Путь снимка переопределяет BORT_QWENCLOUD_FILE, путь заявки — BORT_QWEN_REQUEST_FILE, адрес mini — те же BORT_BEADS_SYNC_HOST/_KEY_ALIAS.

  • OpenRouter — баланс аккаунта по OPENROUTER_API_KEY (/credits) и расход этого ключа (/key). Разбивка по моделям (за 7 дней) и ключам работает только с management-ключом (OPENROUTER_MANAGEMENT_KEY, endpoint /activity) — обычный ключ её не отдаёт, карточка честно об этом пишет. С mini API OpenRouter отвечает 403 Access denied by security policy (Cloudflare режет egress-IP), поэтому баланс читает sync-агент MacBook из scripts/push_beads_snapshot.py и кладёт снимок ~/bort/data/openrouter-snapshot.json (путь переопределяет BORT_OPENROUTER_SNAPSHOT). Кнопка «Обновить» на mini создаёт заявку openrouter-refresh.request (путь — BORT_OPENROUTER_REQUEST_FILE), агент забирает её в течение ~30 секунд и обновляет снимок; без заявки агент обновляет его, когда снимок старше 5 минут. Устаревший (>3 часов) снимок помечается предупреждением.

  • Z.AI · GLM Coding Plan — остаток по ZAI_API_KEY (или Z_AI_API_KEY) с monitor-эндпоинта панели Z.AI /api/monitor/usage/quota/limit. Это endpoint панели, а не публичный API: формат может измениться. Переопределить URL: BORT_ZAI_QUOTA_URL.

  • GPT · Codex — лимиты подписки читаются из официального Codex app-server (account/rateLimits/read). На машине с Codex CLI (MacBook) они берутся напрямую, а на mini — из снимка scripts/pull_codex_limits.py (~/bort/data/codex-limits.json, путь переопределяет BORT_CODEX_LIMITS_FILE). Кнопка «Обновить» на mini создаёт заявку codex-refresh.request: sync-агент MacBook забирает её в течение ~30 секунд, перечитывает CLI и присылает свежий снимок; сам агент обновляет лимиты и без заявки, если снимок старше 15 минут. Карточка на mini подтягивает снимок раз в минуту, а устаревший (>3 часов) помечает предупреждением. Это независимая локальная авторизация: сверьте учётную запись Codex CLI с GPT-подключением OpenCode. OpenCode V2 не отдаёт остаток подписки напрямую; если CLI отсутствует и снимка нет, вкладка покажет «недоступно».

Ключи не хранятся в базе и репозитории: положите их в локальный файл ~/.config/bort/env (права 600, строки KEY=VALUE; путь переопределяет BORT_ENV_FILE). Файл читается при старте, реальное окружение имеет приоритет:

mkdir -p ~/.config/bort && chmod 700 ~/.config/bort
printf 'DEEPSEEK_API_KEY=...\nZAI_API_KEY=...\nOPENROUTER_API_KEY=sk-or-...\n' > ~/.config/bort/env
chmod 600 ~/.config/bort/env

Журнал получает только метаданные через POST /api/v1/ai/usage. Он не перехватывает внешние запросы автоматически: вызывающий клиент должен отправить событие после AI-запроса. Например:

{"provider":"deepseek","agent":"OpenCode","model":"deepseek-chat","input_tokens":1200,"output_tokens":250,"cost_microusd":3500,"external_request_id":"request-123"}

cost_microusd — стоимость в миллионных долях USD, которую сообщает клиент; Борт пока не вычисляет её по тарифам. Промпты и ответы API отклоняет. Повтор external_request_id в пределах провайдера не записывается (409). Все метрики во вкладке отражают только присланные события; балансы обновляются кнопкой «Обновить».

Вкладка «Задачи»

После применения миграций откройте /tasks (старый /beads перенаправляет сюда). «Борт» показывает проекты Beads: канбан по состояниям, переключаемый список, счётчики, поиск и фильтры по статусу, приоритету и типу. Проекты, у которых Beads не может прочитать задачи, не показываются и не предлагаются для привязки.

Задачи можно создавать и менять прямо на странице: «+ Новая задача», смена статуса и приоритета, «Закрыть»/«Вернуть». Запись выполняет bd на машине, где лежат проекты (MacBook), поэтому Борт ставит команды в очередь (beads_commands в SQLite) и показывает их до выполнения; на mini очередь разбирает sync-агент. Ошибки выполнения видны в блоке команд и снимаются кнопкой «Снять». Кнопка «Синхронизировать с MacBook» просит агента прислать свежий снимок, «Проверить снимок» просто перечитывает уже полученный файл. Пока команды ждут, страница сама обновляется (черновики форм при этом сохраняются).

Лишние проекты убираются из левого списка кнопкой «×» («Скрытые · N» — вернуть обратно). Это скрытие только в Борте: задачи и файлы на Mac не удаляются и не меняются.

Из «Сводки» раскройте «Ещё» у проекта и нажмите «Привязать к задачам». Выберите один доступный проект и сохраните связь; затем из той же строки можно открыть задачи и при необходимости изменить связь. Один проект Beads привязывается не более чем к одному проекту Борта. Привязку можно снять на странице задач. Только связь хранится в SQLite Борта, сами задачи остаются в Beads. Собственные задачи в карточке проекта Борта сохраняются отдельно.

На машине с bd по умолчанию просматриваются каталоги ~/Documents с .beads/config.yaml на глубине до двух папок, без обхода всего домашнего каталога и скрытых директорий. Иные корни — BORT_BEADS_ROOTS (через : на macOS); нестандартный путь к CLI — BORT_BEADS_BIN. Вызовы bd --readonly ограничены четырьмя одновременно и 12 секундами на команду; кеш в памяти — 2 минуты. Проекты с ошибкой чтения скрыты вместе с прежними данными.

На Mac mini, где нет bd и каталогов MacBook, страница читает снимок из MacBook ~/bort/data/beads-snapshot.json. Скрипт scripts/push_beads_snapshot.py на MacBook каждый тик забирает очередь команд mini (scripts/beads_remote_queue.py по SSH), выполняет их локальным bd, отмечает результаты и атомарно отправляет снимок (названия, ID, статусы, типы и приоритеты — без описаний и путей). Для ручного прогона задайте BORT_BEADS_SYNC_HOST=<ssh-адрес-mini> и выполните uv run python scripts/push_beads_snapshot.py. При доступе по альтернативному имени SSH можно задать BORT_BEADS_SYNC_HOST_KEY_ALIAS с уже проверенным именем ключа в known_hosts. Для регулярного обновления на MacBook используется LaunchAgent com.alluc.bort-beads-sync с интервалом 30 секунд; если он выключен или MacBook недоступен, вкладка пометит снимок старше 10 минут как устаревший, а команды останутся в очереди. Снимок хранится только локально на mini с правами 0600; при локальном запуске Борта без него используется сам bd.

Вкладка первоначально подгружается через htmx, чтобы не задерживать открытие страницы; без JavaScript доступна ссылка «Открыть без JavaScript». Сервер без аутентификации: не открывайте порт 8100 в публичный интернет. Привязка 127.0.0.1 из примера старта не переопределяет уже запущенный сервис на mini.

Вкладка «Работа»

Откройте /work — справочник постоянных проектов с ежемесячной платой: подписки, регулярные контракты, частичная занятость. Проекты заводятся вручную кнопкой «+ Проект»; поле «В месяц» принимает рубли («150 000») и хранится в копейках. Поля карточки: название, клиент, роль, формат (полная/частичная занятость, контракт, другое), ежемесячная плата, валюта, статус (активен/на паузе/завершён), дата начала, ссылка и заметки. Строку можно изменить, сменить статус прямо в списке и удалить.

Панель итогов главным числом показывает «Приходит в месяц» — сумму плат активных проектов в базовой валюте; рядом «В паузе» — потенциал, если вернуть паузы в работу, и счётчики по статусам. Проекты в других валютах в эти суммы не подмешиваются и перечислены отдельной строкой «Вне итогов», как и чужие валюты в «Сводке».

Раздел независим от денег «Сводки» и от Beads: у него своя таблица work_projects (миграция 009_work_projects) и свои агрегаты; проекты «Работы» не появляются в проектах, оплатах, затратах и марже, а задачи Beads на них не ссылаются. REST API и MCP-инструменты для «Работы» пока не сделаны — это возможный следующий шаг; интерфейс работает только через страницу /work.

Запуск на Mac mini

На mini веб-сервис управляется LaunchAgent com.alluc.bort-web, а не PM2:

launchctl print gui/$(id -u)/com.alluc.bort-web
launchctl kickstart -k gui/$(id -u)/com.alluc.bort-web  # после обновления кода

На MacBook проверить синхронизацию можно командой launchctl print gui/$(id -u)/com.alluc.bort-beads-sync; первый снимок и миграция 005_project_beads_links уже применены на mini. Запущенный сервис берёт БД и снимок из ~/bort/data/. Не запускайте второй процесс на порту 8100. Перед запуском новой версии кода на mini примените миграции (.venv/bin/python scripts/migrate.py): сводка использует поле ручного порядка из 006_project_order_statuses и без него не откроется, а вкладка «Работа» — таблицу из 009_work_projects.

Альтернативный запуск через PM2

cd ~/bort
pm2 start ecosystem.config.js     # процесс bort-web, логи в ~/bort/logs/
pm2 save                          # запомнить список процессов
pm2 startup                       # автозапуск при загрузке (если не используется LaunchAgent)
pm2 restart bort-web              # перезапуск
pm2 logs bort-web                 # смотреть логи

Если PM2 не установлен: npm i -g pm2. Без него сервис стартует вручную командой uv run uvicorn bort.web.app:app --host 127.0.0.1 --port 8100 из ~/bort.

Related MCP server: acumulus-mcp

Подключение MCP к Hermes

Добавить секцию mcp_servers в конфиг Hermes (~/.hermes/config.yaml; если секция уже есть — дописать ключ bort внутрь):

mcp_servers:
  bort:
    command: <ПУТЬ К РЕПО>/.venv/bin/python
    args: ["-m", "bort.mcp.server"]
    env:
      BORT_DB: <ПУТЬ К РЕПО>/data/bort.db
      BORT_TZ: Europe/Moscow

Транспорт — stdio: Hermes сам запускает процесс, открытых портов нет. Проверка вручную: echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"t","version":"0"}}}' | BORT_DB=~/bort/data/bort.db ~/bort/.venv/bin/python -m bort.mcp.server — в stdout придёт JSON-ответ с serverInfo.name == "bort". Ресурс bort://summary — markdown-сводка одним чтением.

Инструменты MCP

Инструмент

Что делает

bort_project_list

Список проектов (краткая форма), фильтры статус/приоритет/поиск

bort_project_get

Проект целиком: задачи, затраты, чаты, люди (по id или названию)

bort_project_create

Создать проект; сумма в рублях или копейках

bort_project_update

Обновить поля проекта (по id или названию)

bort_task_create

Создать задачу в проекте

bort_task_update

Обновить задачу (статус, приоритет, дедлайн…)

bort_task_close

Закрыть задачу: done или cancelled, проставляет closed_at

bort_expense_add

Добавить затрату; в ответе — новая маржа проекта

bort_summary

Сводка: агрегаты по деньгам + счётчики «просрочено»/«горит». Проекты «на обсуждении» в денежные агрегаты не входят, пока не взяты в работу

bort_project_summary

Сводка одного проекта: маржа, прогресс задач, разбивка по категориям

bort_chat_attach

Привязать чат к проекту (существующий или создать новый)

bort_chat_detach

Отвязать чат от проекта (чат остаётся в справочнике)

bort_chat_list

Чаты с участниками и привязками к проектам

bort_person_upsert

Создать/обновить человека, сразу привязать к проекту/чату с ролью

Все инструменты возвращают {"ok": true, ...} либо {"ok": false, "error": {code, message, details}}. Проект ищется по названию регистронезависимо; неоднозначность → conflict со списком кандидатов.

REST API

База: http://127.0.0.1:8100/api/v1. Полный список — в Swagger (/docs). Основное:

GET    /api/v1/summary?scope=open|active|all&q=   сводка с агрегатами
GET    /api/v1/projects                           список (status, priority, q, limit, offset, sort)
POST   /api/v1/projects                           создать (name*, deal_amount или deal_amount_minor)
GET    /api/v1/projects/{id}                      проект + задачи, затраты, чаты, люди
PATCH  /api/v1/projects/{id}                      частичное обновление
DELETE /api/v1/projects/{id}                      удалить (каскадом задачи и затраты)
POST   /api/v1/projects/reorder                   ручной порядок: {"ids": [...]}
POST   /api/v1/projects/reorder/reset             вернуть автосортировку
POST   /api/v1/projects/{id}/tasks                задача ·  POST /tasks/{id}/close — закрыть
POST   /api/v1/projects/{id}/expenses             затрата ·  PATCH/DELETE /expenses/{id}
GET    /api/v1/expense-categories                 категории ·  POST — добавить
GET/POST /api/v1/chats, /api/v1/people            справочники
POST   /api/v1/projects/{id}/chats                привязать чат ·  DELETE .../chats/{chat_id}
POST   /api/v1/projects/{id}/people               привязать человека ·  DELETE .../people/{person_id}
GET/POST /api/v1/work                              постоянные проекты ·  PATCH/DELETE /work/{id}
GET    /api/v1/health                             {status, db_path, schema_version, wal}
GET    /api/v1/meta/enums                         статусы/приоритеты с русскими подписями

Деньги: канон — целые копейки (*_minor); человекочитаемая строка («150 000,50») принимается в полях deal_amount/amount и отдаётся рядом с *_minor.

Бэкапы

~/bort/scripts/backup.sh

Копия через sqlite3 .backup (корректно при WAL) → ~/bort/backups/bort-YYYYMMDD-HHMMSS.db, хранятся последние 14 копий. Переменные: BORT_DB, BORT_BACKUP_DIR. Код возврата — 0/не-0. Запускается вручную или из cron — отдельного экрана настроек в интерфейсе нет.

Ежедневный бэкап в 21:00 — добавить в crontab (crontab -e, НЕ добавлено автоматически):

0 21 * * * ~/bort/scripts/backup.sh >> ~/bort/logs/backup.log 2>&1

Демо-данные

uv run python scripts/seed_demo.py          # 6 демо-проектов (если база пуста)
uv run python scripts/seed_demo.py --wipe   # убрать ВСЁ: проекты (с задачами и затратами), чаты, люди

Тесты и структура

uv run pytest tests/ -q

Схема БД: migrations/001_init.sql (деньги — INTEGER копейки; даты — YYYY-MM-DD без TZ; WAL; foreign_keys=ON на каждом соединении). Логика — в src/bort/services/, REST и MCP — тонкие транспорты над ней, поэтому числа в UI и у ассистента не расходятся.

Available Tools

14 tools
bort_chat_attachB

Привязать чат к проекту: существующий chat_id или создать новый (title, kind, tg_chat_id, tg_link)

Inputs: kind, note, title, chat_id, +4 more.

bort_chat_detachB

Отвязать чат от проекта (чат остаётся в справочнике)

Inputs: chat_id, project_id.

bort_chat_listC

Чаты с участниками и привязками к проектам; с project_id — только чаты проекта

Inputs: q, project_id, project_name.

bort_expense_addA

Добавить затрату в проект: amount в рублях («1 200,50») или amount_minor в копейках; spent_on по умолчанию сегодня (BORT_TZ), категория по умолчанию other; возвращает новую маржу проекта

Inputs: amount, comment, currency, spent_on, +4 more.

bort_person_upsertA

Создать или обновить человека (ищется по tg_username, затем по имени); можно сразу привязать к проекту и/или чату с ролью

Inputs: role, notes, full_name, tg_username, +3 more.

bort_project_createC

Создать проект. Сумма: deal_amount в рублях («150 000,50») или deal_amount_minor в копейках

Inputs: name (required), notes, status, currency, +4 more.

bort_project_getA

Проект целиком: задачи, затраты, чаты, люди. Поиск по project_id или точному названию (регистронезависимо)

Inputs: name, project_id.

bort_project_listB

Список проектов (краткая форма). Фильтры: status idea|active|paused|closed, priority 1..4 (1 — критичный), поиск q по названию

Inputs: q, limit, status, priority.

bort_project_summaryB

Сводка одного проекта: деньги, маржа, прогресс задач, разбивка затрат по категориям. По project_id или названию

Inputs: name, project_id.

bort_project_updateB

Обновить проект (project_id или name); передаются только изменяемые поля

Inputs: name, notes, status, currency, +7 more.

bort_summaryC

Сводка по проектам: агрегаты (деньги, счётчики просрочено/горит) и список с состоянием дедлайна. scope: open|active|all

Inputs: q, scope.

bort_task_closeB

Закрыть задачу: outcome 'done' (выполнено) или 'cancelled' (отменено); проставляет closed_at

Inputs: task_id (required), outcome.

bort_task_createC

Создать задачу в проекте (project_id или project_name)

Inputs: notes, title, status, deadline, +3 more.

bort_task_updateC

Обновить задачу по task_id (передаются только изменяемые поля)

Inputs: task_id (required), notes, title, status, +2 more.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    B3.2/5.0

    Scored across 14 tools

    Disambiguation4/5

    Each tool targets a distinct resource+action, and the project/task/chat/person prefixes make boundaries clear. The only fuzziness is between bort_summary (cross-project aggregates) and bort_project_summary (single project), plus mild overlap with bort_project_get, which also returns money and tasks, but the descriptions distinguish scopes adequately.

    Naming Consistency4/5

    All tools share the bort_ prefix and follow a resource_action pattern (project_list, task_create, chat_attach, expense_add), which is highly predictable. The lone global bort_summary lacking a resource prefix is a minor deviation from an otherwise consistent convention.

    Tool Count5/5

    14 tools is well within the ideal 3-15 range for a project-management server covering projects, tasks, chats, people, expenses, and summaries. Each tool earns its place without redundancy.

    Completeness3/5

    Core lifecycles (project create/update, task create/update/close, chat attach/detach/list, expense add) are covered, but there are notable gaps: no expense update/delete/list, no standalone person list/get, no project delete/archive, and tasks lack a standalone list or reopen. Agents can partly work around via bort_project_get, but the surface is not fully rounded.

    Maintenance

    ActivityActive
    ResponsivenessNo recent community issues

    Related MCP Connectors

    Related MCP Servers

    • A
      license
      Not graded
      quality
      C
      maintenance
      Enables task management with persistent SQLite storage, along with tools, resources, subscriptions, prompts, and completions over stdio or Streamable HTTP with per-user authentication.
      7 npm
      MIT
    • A
      license
      A
      quality
      B
      maintenance
      Enables AI assistants to read and modify an Acumulus administration through natural language over the official API, including invoices, picklists, and direct endpoint calls. Runs locally over stdio and supports test mode to validate writes without persisting them.
      4
      27 PyPI
      AGPL 3.0
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables coding agents such as Claude Code or Codex to run and stop the timer, log, correct, delete and restore time entries, read settings, projects, clients, tags, entries and reports, and preview invoices or create and edit invoice drafts. It communicates over stdin/stdout without opening a port, writes to the same audited database as the app, and leaves issuing or voiding invoices to the user in the app.
      MIT
    • A
      license
      Not graded
      quality
      B
      maintenance
      Enables an AI assistant to query Plaid-synced bank data stored locally in SQLite — balances, transactions, spending, cash flow, recurring charges, and liabilities — plus bounded read-only SQL and local CSV/JSON exports, all over local stdio with no hosted service or listening port.
      PolyForm Noncommercial 1.0.0