javascript

16 часов и 8600 строк: как Claude Code помог собрать персональный супер-апп

  • пятница, 23 января 2026 г. в 00:00:08
https://habr.com/ru/articles/987872/

16 часов и 8600 строк: как Claude Code помог собрать персональный супер-апп

Я решил собрать для себя приложение, которое объединит несколько AI-модулей в одном месте: фитнес-трекер с AI-тренером, новостной дайджест по AI/ML, дашборды для других проектов. Не SaaS для всех, а инструмент для себя. PWA, чтобы работало как нативное приложение на телефоне.

В итоге: 4 дня по 4 часа, 8600 строк кода, работающий продукт в production. Расскажу как это получилось.

Первый экран
Первый экран

Вместо десятка разных приложений и сервисов собрать всё нужное в одном месте. Под себя, без компромиссов.

Минус очевиден: ROI отрицательный в денежном выражении. Но экономит мне время.

Что решил собрать

Fitness Module: AI-тренер с готовыми планами тренировок. Не просто список упражнений, а агент, который понимает контекст: что я делал раньше, какие есть ограничения, как прогрессирую. С долгосрочной памятью, которая накапливает знания о пользователе.

Экран статистики
Экран статистики

AI News Module: ежедневный дайджест по AI/ML. Grok с доступом к поиску и Twitter собирает новости, GitHub trending, исследования. Можно обсудить любую новость в чате.

Экран еждневных новостей
Экран еждневных новостей

Dashboard Modules: статистика других проектов (генерация изображений, Telegram-боты).

Экран статистики бесплатного бота помощи
Экран статистики бесплатного бота помощи

Выбирал то, с чем комфортно работать самому Claude:

Backend: Python 3.12, FastAPI, SQLAlchemy 2.0 (async), PostgreSQL, Alembic для миграций

Frontend: Next.js с App Router, React 19, TypeScript, Tailwind CSS, Zustand для state management

AI: Claude Sonnet 4.5 для фитнес-агента (нужен большой контекст и качественные ответы), Grok 4.1 для новостей (встроенный поиск и доступ к Twitter), Zep Cloud для долгосрочной памяти

Инфраструктура: VPS, Nginx, systemd

Процесс разработки с Claude Code

Я работаю с Claude Code CLI с самого его появления, как продакт, проджет и архитектор, а пишет код. Не «напиши мне приложение», а итеративная работа: обсуждаем задачу, он реализует, я проверяю, корректируем.

Кастомные агенты

Для Helper я создал четыре специализированных агента:

.claude/agents/
├── helper-ai.md       # AI интеграция (Claude, Grok, Zep)
├── helper-backend.md  # Backend разработка
├── helper-frontend.md # Frontend разработка
└── helper-db.md       # База данных и миграции

Каждый агент знает контекст своей области: какие паттерны используем, где лежат файлы, какие есть ограничения. Пример агента для AI-интеграции:

---
name: helper-ai
description: AI integration for Helper PWA
tools: Read, Write, Edit, MultiEdit, Bash, Glob, Grep
---

# Helper AI Integration Agent

## Context
- Fitness Agent: Claude Sonnet 4.5 with MCP tools
- News Agent: Grok 4.1 with web_search/x_search
- Memory: Zep Cloud

## Patterns
- Agentic loop with max_turns protection
- SSE streaming for chat responses
- Graceful degradation for memory operations

Когда даю задачу типа «добавь новый tool для агента», Claude Code уже знает структуру проекта, паттерны и может сразу писать код в нужном стиле.

Compound Engineering Plugin

На поздних стадиях проекта начал использовать Compound Engineering Plugin для Claude Code. Идея плагина в том, что каждый цикл разработки накапливает знания: планы информируют будущие планы, ревью ловят больше проблем, паттерны документируются автоматически.

Каждая итерация должна делать следующую проще.

На практике это помогло уменьшить количество багов. Плагин включает агентов для code review, планирования и документирования паттернов. Можно запустить в автоматическом режиме и уйти за кофе.

У меня подписка за 200$, поэтому за токенами я особо не слежу.

Типичный workflow

  1. Планирование: Описываю что нужно сделать, Claude Code анализирует и предлагает план

  2. Исследование: Он изучает существующий код через агентов, понимает контекст

  3. Реализация: Пишет код с учётом паттернов проекта

  4. Проверка: Я смотрю результат, даю фидбек

  5. Деплой: Коммит, перезапуск сервисов

Большинство задач укладывается в 15-30 минут: от постановки до работающего кода.

День 1

День 1: Скелет приложения (2 часа)

  • Структура проекта (backend + frontend)

  • Базовый FastAPI с CORS и health checks

  • Next.js с App Router, настройка Tailwind

  • PostgreSQL, первые миграции

  • Nginx конфиг, SSL

  • systemd сервисы для backend и frontend

К концу дня: приложение открывается в браузере, показывает заглушку.

Fitness Module (4 часа)

  • Модели данных: планы тренировок, дни, упражнения, прогресс

  • API endpoints: CRUD для планов, отметка выполнения

  • Фронтенд: список планов, детали плана, текущая тренировка

  • Zustand store с персистенцией в localStorage

К концу дня: можно выбрать план, смотреть упражнения, отмечать выполненные дни.

AI Agent + Zep (4 часа)

  • Интеграция Claude API с streaming

  • Agentic loop с MCP tools

  • Интеграция Zep Cloud для памяти

  • Чат-интерфейс с SSE

  • UI для отображения tool calls

Тут возникла проблема.

Проблема с Zep

Я уже использовал Zep в другом проекте полгода назад. Взял код оттуда, адаптировал под новую структуру. В процессе понял, что у агента что-то не так и знает про меня меньше, чем было информации в диалогах.

Оказалось, Zep обновился. Методы, которые я использовал, частично уже не работали, частично изменились и расширились.

Что пришлось переделать:

Batch добавление сообщений: Раньше добавлял user message и assistant message отдельными вызовами. Теперь можно одним:

# Было: 2 API calls
await client.thread.add_message(user_msg)
await client.thread.add_message(assistant_msg)

# Стало: 1 API call
await client.thread.add_messages(
    messages=[user_msg, assistant_msg],
    return_context=True
)

Параллельное получение контекста: Раньше получал user context и делал semantic search последовательно. Теперь параллельно:

# Было: 200-400ms последовательно
context = await self.get_user_context(user_id)
search = await self.search_graph(user_id, query)

# Стало: 100-200ms параллельно
results = await asyncio.gather(
    self.get_user_context(user_id),
    self.search_graph(user_id, query)
)

Graceful degradation: Добавил обработку ошибок, чтобы проблемы с Zep не ломали весь чат:

try:
    context = await zep.get_context(user_id)
except Exception:
    context = ""  # Продолжаем без контекста

На переделку Zep-интеграции ушло около 2 часов. Урок: не копировать код из старых проектов без проверки актуальности зависимостей.

День 2: News Module + PWA (3 часа)

  • Интеграция Grok API с web_search и x_search tools

  • Новостной дайджест: категории, избранное

  • Service Worker: стратегии кэширования

  • PWA manifest, иконки, shortcuts

  • Тестирование на телефоне

К концу дня: приложение можно установить на телефон, оно работает offline для статики.

Архитектура

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

┌─────────────────────────────────────────────────────────┐
│                      Helper PWA                          │
│  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐        │
│  │   Fitness   │ │  AI News    │ │  Dashboards │        │
│  │   Module    │ │   Module    │ │   Module    │        │
│  └──────┬──────┘ └──────┬──────┘ └──────┬──────┘        │
│         │               │               │                │
│  ┌──────▼───────────────▼───────────────▼──────┐        │
│  │              Frontend (Next.js)              │        │
│  │         React + Zustand + Tailwind           │        │
│  └──────────────────────┬──────────────────────┘        │
│                         │ SSE / REST                     │
│  ┌──────────────────────▼──────────────────────┐        │
│  │              Backend (FastAPI)               │        │
│  │    ┌─────────────┐  ┌─────────────┐         │        │
│  │    │FitnessAgent │  │ NewsAgent   │         │        │
│  │    │  (Claude)   │  │   (Grok)    │         │        │
│  │    └──────┬──────┘  └──────┬──────┘         │        │
│  │           │                │                 │        │
│  │    ┌──────▼────────────────▼──────┐         │        │
│  │    │      Zep Cloud (Memory)      │         │        │
│  │    └──────────────────────────────┘         │        │
│  └─────────────────────────────────────────────┘        │
└─────────────────────────────────────────────────────────┘

Два разных подхода к AI-агентам

Фитнес-агент и новостной агент работают по-разному.

Фитнес-агент (Claude Opus 4.5) использует MCP tools. Это значит, что я определяю инструменты на своей стороне, а Claude решает когда их вызывать. 11 инструментов: получить план, отметить день выполненным, добавить заметку к упражнению, создать новый план и т.д. Изначально был Sonnet, но мы слишком много спорили, а в сообщениях было больше воды, поменял и не стал его терпеть.

Новостной агент (Grok 4.1 fast) использует server-side tools. Grok сам ходит в интернет чере�� web_search() и x_search(). Мне не нужно реализовывать web scraping, всё происходит на стороне xAI. Позже еще добавил инструмент для анализа трендов github.

┌───────────┬─────────────────┬────────────────────────────────────┐
│  Аспект   │ Фитнес (Claude) │           Новости (Grok)           │
├───────────┼─────────────────┼────────────────────────────────────┤
│ Модель    │ Sonnet 4.5      │ Grok 4.1                           │
├───────────┼─────────────────┼────────────────────────────────────┤
│ Tools     │ MCP (БД, планы) │ Server-side (web_search, x_search) │
├───────────┼─────────────────┼────────────────────────────────────┤
│ Память    │ Zep Cloud       │ Нет (stateless)                    │
├───────────┼─────────────────┼────────────────────────────────────┤
│ Streaming │ SSE             │ SSE                                │
└───────────┴─────────────────┴────────────────────────────────────┘

Agentic Loop

Оба агента работают по одному паттерну: получают сообщение, решают нужно ли вызвать tool, если да, вызывают и продолжают. Защита от бесконечных циклов через max_turns:

max_turns = 10
for turn in range(max_turns):
    response = await model.create(messages, tools)
    if response.stop_reason != "tool_use":
        break  # Агент закончил
    # Выполняем tools и продолжаем

Когда пользователь пишет «что у меня сегодня?», агент сам вызывает get_current_workout() и формирует ответ на основе результата.

Plan Navigator: оптимизация контекста

Проблема: план тренировок содержит 116 упражнений (4 недели × ~4 дня × ~7 упражнений). Передавать всё в каждый запрос дорого и избыточно.

Решение: Navigator генерирует компактную сводку (~500 символов):

## План тренировок (навигатор)

**Активный план:** Ятс для здоровья спины
Прогресс: Неделя 4/4, 87% (14/16 дней)

**Текущая тренировка** (⏳ следующая):
Неделя 4, День 3: Плечи + Шея
Упражнения (7): Жим гантелей сидя, Разводка...

**Последние тренировки:**
- ✓ Н4Д2: День Б (20.01)
- ✓ Н4Д1: День А (18.01)

**Статистика:** streak 5, completion: 92%

Агент может ответить на простые вопросы («какая тренировка сегодня?») без tool calls, экономя токены и время.

Память через Zep Cloud

LLM не помнит между сессиями. Пользователь говорит «у меня болит плечо», через неделю агент предлагает жим над головой.

Zep Cloud автоматически извлекает факты из разговоров, строит граф знаний, предоставляет semantic search.

async def get_full_context(self, user_id: str, current_query: str) -> str:
    # Параллельный fetch для скорости (50% latency reduction)
    tasks = [
        self.get_user_context(user_id),           # Summarization
        self.search_graph(user_id, current_query) # Semantic search
    ]
    results = await asyncio.gather(*tasks, return_exceptions=True)
    # Combine into context string...

Latency: 100-200ms благодаря параллельным запросам.

Streaming

Ответы агента стримятся через SSE. Пользователь видит как агент «думает»: появляется текст, потом индикатор вызова tool, потом результат, потом продолжение ответа.

Офлайн-режим

Иногда у меня может не быть интернета, а тренировку я хочу посмотреть.

Zustand persist + offline queue. Без IndexedDB, просто localStorage (~2KB на план при лимите 5MB).

interface OfflineAction {
  type: 'complete' | 'skip';
  dayId: string;
  reason?: string;
}

// В completeDay():
if (!navigator.onLine) {
  set((state) => ({
    offlineQueue: [...state.offlineQueue, { type: 'complete', dayId }],
    // Optimistic update
    currentDay: { ...state.currentDay, completed: true }
  }));
  return true; // Success — queued for sync
}

При появлении сети очередь автоматически синхронизируется:

window.addEventListener('online', async () => {
  const { offlineQueue, syncOfflineQueue } = get();
  if (offlineQueue.length > 0) {
    await syncOfflineQueue();
  }
});

UI показывает статус: «Офлайн», количество действий в очереди, «Синхронизация...»

PWA: Service Worker

Service Worker написан вручную, без Workbox или других библиотек. Три стратегии кэширования:

Stale While Revalidate для статики: отдаём из кэша мгновенно, обновляем в фоне.

Network First для API: сначала пробуем сеть, если не получилось, берём из кэша.

No caching для SSE: стриминговые endpoints пропускаем.

self.addEventListener('fetch', (event) => {
  const url = new URL(event.request.url);
  
  if (url.pathname.includes('/chat/stream')) {
    return; // SSE не кэшируем
  }
  
  if (url.pathname.startsWith('/api/')) {
    event.respondWith(networkFirst(event.request));
  } else {
    event.respondWith(staleWhileRevalidate(event.request));
  }
});

Что получилось

Статистика кода:

Компонент

Строки

Backend Python

~3000

Frontend TypeScript

~5100

Конфигурация

~500

Итого

~8600

Время:

  • 4 дня по 4 часа = 16 часов

  • Из них ~2 часа на переделку Zep-интеграции

Технические цифры:

  • 11 MCP tools для фитнес-агента

  • 116 упражнений в плане (4 недели × ~4 дня × ~7 упражнений)

  • ~500 символов в Plan Navigator (вместо полного плана)

  • ~2KB localStorage на план (лимит 5MB)

  • 100-200ms latency Zep (параллельный fetch)

Стоимость AI:

  • Claude Sonnet 4.5: $3/$15 per 1M tokens (input/output)

  • Grok 4.1 Fast: $0.20/$0.50 per 1M tokens

Grok существенно дешевле для задач где не нужен большой контекст и сложное рассуждение.

Немного выводов

Claude Code: Когда есть чёткое понимание архитектуры и требований задача, Claude Code может писать код быстро и качественно. Работа должна строится итерациями через фидбек.

Агенты в CLI: Специализированные агенты для разных частей проекта экономят время. Они не тратят основной контекст.

Compound Engineering Plugin: Помогает накапливать знания о проекте. Особенно полезно когда возвращаешься к коду через неделю.

Для внешних сервисов: Zep может упасть, API может тормозить. Если это не блокирует основной flow, приложение продолжает работать.

Plan Navigator вместо полного контекста: Компактная сводка ~500 символов вместо 116 упражнений. Агент отвечает на простые вопросы без tool calls, экономя токены.

Разные модели для разных задач: Claude для сложных задач с памятью, Grok для задач с поиском. Grok в 10 раз дешевле, и для новостного дайджеста этого достаточно.

Копирование кода из старых проектов без проверки: Зависимости обновляются, API меняются. Потратил 2 часа на то, что можно было избежать проверкой документации.

Всё не имеет смысла если это для личного проекта - мне хватает текущей архитектуры.

Заключение

Можно было проще.