Skip to main content
Glama

wrmax-criativo

Пайплайн генерации и редактирования изображений WRMax. Claude Code — мозг; этот репозиторий — руки.

Код ничего не знает о маркетинге — он получает параметры и возвращает файл. Формат, ракурс и промпт решает Claude, который затем смотрит на сгенерированную работу и решает, принять её или переделать. Именно этот замкнутый цикл характеризует оркестрацию.

Код, комментарии и сообщения написаны на английском. Документация и общение с командой — на португальском.


Setup (5 минут)

npm install
export OPENAI_API_KEY="sua-chave"      # https://platform.openai.com/api-keys

Важно: подписка ChatGPT Pro или приложения Gemini не даёт доступ к API. Это отдельные платежи. Нужен ключ API с активным биллингом.

Движок B (ещё не реализован):

export IMAGE_PROVIDER=gemini
export GEMINI_API_KEY="sua-chave"

Related MCP server: MCP OpenAI Image Generation Server

Структура

У каждой папки одна ответственность, и ни один файл не совмещает две.

bin/                      entradas executáveis
  cli.js                    CLI
  mcp-server.js             servidor MCP (só escolhe o transporte)

src/
  bootstrap/              carga do .env e resolução de caminhos
  config/                 ÚNICO ponto que lê process.env; tabelas de modelo,
                          formato e qualidade
  brands/                 brand kit, compliance e montagem do prompt
  media/                  entrada, redução e saída de imagem (Drive, download,
                          arquivo local, preview, upload)
  providers/              motores de imagem, por registro
  core/                   regra de negócio: artwork-service, artifact-store,
                          delivery
  mcp/                    servidor MCP, tools e transportes
  http/                   app Express, middleware, rotas e views
  auth/                   OAuth com Google
  cli/                    args, ajuda e orquestração do CLI

test/                     node --test, sem chave e sem custo
scripts/                  smoke — gasta crédito ou precisa de rede viva
brand/                    um JSON por cliente
out/                      saída local (só com PERSIST_OUTPUT=true)

Центральная идея: src/core/artwork-service.js не знает, что такое MCP, и не знает, что такое CLI. Он получает простой запрос и возвращает простой результат. Content block форматирует src/mcp/tool-result.js; JSON в stdout пишет src/cli/run.js. Поэтому оба фронтенда используют один общий путь.

Все зависимости (config, artifact store, каталог бренда) внедряются, а не импортируются как синглтон — это позволяет тестировать маршрут, инструмент и сервис, не трогая окружение.


Использование

Сгенерировать с нуля:

node bin/cli.js --brand forno-paulista --format feed \
  --prompt "Studio product shot of a rustic pizza on a wooden board, steam rising"

Отредактировать реальное фото клиента (замена фона с сохранением товара):

node bin/cli.js --brand forno-paulista --format square \
  --ref fotos/produto.jpg \
  --prompt "Change only the background to a clean warm studio gradient. Keep the product, its label and the lighting on it exactly unchanged."

Дешёвый черновик перед тратами на финал:

node bin/cli.js --quality draft --prompt "..."

Всегда черновик перед финалом. Он стоит лишь малую часть и избавляет от дорогой переделки.


MCP-сервер

npm run mcp          # stdio — é o que o Claude Code fala
npm run mcp:http     # Streamable HTTP em :8787/mcp — é o que conector remoto exige

Доступные инструменты: list_brands, generate_image, edit_image.

На сервере нет инструмента, который перечислял бы, искал или просматривал изображения, и это намеренно: файл выбирает пользователь. Инструмент поиска превратил бы промпт, внедрённый в фото клиента, в сканирование окружения.

Детали транспорта, аутентификации и того, куда сохраняется полное разрешение, — в CLAUDE.md.


Как Claude Code использует CLI

Команда выводит JSON в stdout, а лог — в stderr. Это намеренно: Claude запускает команду, читает JSON, открывает PNG, оценивает и формирует следующий вызов. Без человека в каждой итерации.

{"ok":true,"file":"out/1755777.png","seconds":6.2,"aspectRatio":"4:5"}

Коды выхода: 0 успех · 1 техническая ошибка · 2 заблокировано комплаенсом — 2 нужен, чтобы хук различал эти два случая.


Комплаенс

В brand/*.json есть массив forbidden_terms. assertPromptAllowed() запускается до вызова и блокирует — экономит кредиты и, что важнее, не полагается на то, что модель выполнит инструкцию.

{
  "name": "Forno Paulista",
  "visual": {
    "style": "appetizing food photography, rustic warmth, artisanal",
    "colors": ["wood brown", "tomato red", "warm cream"],
    "lighting": "warm golden light, natural window light",
    "avoid": ["cold blue tones", "plastic-looking food"]
  },
  "forbidden_terms": [],
  "compliance_reason": ""
}

Бренд

Блокировка

cliente-medico

пациент, до/после, тело, результат процедуры — CFM 2.336/2023

Быстрая проверка гардрейла — без ключа и без затрат:

node bin/cli.js --brand cliente-medico --prompt "before and after of a patient"
# x BLOCKED by compliance rules for "Cliente médico (template CFM)"

Тесты

npm test          # 110 testes, sem chave de API, sem rede externa, sem custo

Покрытие: комплаенс, бренд-кит, config, artifact store, конвертация ссылок Drive, все режимы сбоя download, уменьшение размера, upload, таблицы размеров, весь поток OAuth целиком (с фейковым Google), обнаружение, которое выполняет claude.ai, и оба транспорта MCP из конца в конец.

Тесты, которые тратят кредиты или зависят от реальной сети, вынесены из набора в scripts/:

npm run probe            # ~US$ 0,005 — separa "chave ruim" de "pipeline ruim"
npm run smoke:drive      # ~US$ 0,01  — link do Drive de ponta a ponta
npm run smoke:edit       # ~US$ 0,02  — o modelo edita ou só regenera?
npm run smoke:stateless  # ~US$ 0,01  — não deixa um byte para trás

Переменные окружения

Переменная

По умолчанию

Назначение

OPENAI_API_KEY

—

Обязательна при провайдере openai

IMAGE_PROVIDER

openai

Меняет движок генерации изображений

MCP_TRANSPORT

stdio

stdio или http

PORT

8787

Порт режима HTTP

MCP_PATH

/mcp

Путь к эндпоинту MCP

MCP_TOKEN

—

Фиксированный Bearer (для скрипта и тестов; claude.ai его не принимает)

MCP_BASE_URL

—

Обязательна с OAuth: это issuer, и он должен быть фиксированным

GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET

—

Включают OAuth

MCP_EMAILS

—

Кто может авторизоваться. Наличие аккаунта Google — не разрешение

PERSIST_OUTPUT

false

Сохраняет полное разрешение в out/ (только локальная разработка)

ARTIFACT_TTL_MS

900000

Срок действия ссылки на скачивание

ARTIFACT_MAX_BYTES

134217728

Потолок памяти хранилища работ


Хостинг (EasyPanel или любой контейнерный хост)

Сервер намеренно хранит состояние в памяти — OAuth-клиенты, токены и хранилище работ — это Map(). Это требует одного живого процесса, и именно это исключает serverless-платформы: там POST /register попал бы в один инстанс, а GET /authorize — в другой, который не знает о клиенте. Вход в систему сбоил бы с перебоями, с симптомом, который не похож на причину.

Поэтому деплой — контейнер, и правило действует для любого хоста: только одна реплика. Чтобы масштабироваться дальше, сначала замените три хранилища в памяти на Redis.

Dockerfile в корне подходит для любой контейнерной платформы. Шаги ниже описаны для EasyPanel; на другом хосте меняется интерфейс, но не содержание.

Сначала домен

Google не принимает IP-адрес как OAuth-редирект и требует HTTPS. То есть домен — это обязательное условие, а не финальный штрих.

Укажите A-запись поддомена на IP сервера. У кого нет домена, можно использовать wildcard DNS — mcp.<ip-com-hifens>.sslip.io сам резолвится в IP, встроенный в имя, а Let's Encrypt выдаёт сертификат как обычно, если порт 80 открыт.

Сервис

  1. Создать сервис → App, с source из этого репозитория и веткой main.

  2. Build: Dockerfile, в корне.

  3. Environment:

    Переменная

    Значение

    PORT

    8787

    MCP_BASE_URL

    https://<seu-dominio> — без слэша в конце

    OPENAI_API_KEY

    ключ OpenAI

    GOOGLE_CLIENT_ID

    из OAuth-клиента (веб-приложение)

    GOOGLE_CLIENT_SECRET

    из того же клиента

    MCP_EMAILS

    кто может авторизоваться, через запятую

    MCP_TRANSPORT=http уже задан в Dockerfile — не определяйте.

  4. Domains: поддомен, указывающий на порт 8787, с включённым HTTPS.

  5. Deploy.

  6. Google Cloud Console → Учётные данные → ваш OAuth-клиент, добавьте авторизованный redirect — точно:

    https://<seu-dominio>/oauth/google/callback
  7. claude.ai → коннекторы: https://<seu-dominio>/mcp.

MCP_BASE_URL становится issuer OAuth и сравнивается посимвольно с тем, что обнаруживает клиент. Домен, отличный от настроенного, или лишний слэш приводят к сбою привязки без внятного сообщения.

Проверка

curl https://<seu-dominio>/health

Важное поле — "auth":"oauth". Если пришло "none", какая-то переменная Google не дошла — значит, сервер запустился открытым, принимая любые вызовы и расходуя ключ того, кто его хостит.


Заметки по API, которые экономят отладку

  • Буква K в image_size — заглавная. 2k отклоняется.

  • gpt-image-2 принимает любые WxH, кратные 16; модели поменьше принимают только три фиксированных размера. Для финального story/reels нужен gpt-image-2.

  • При редактировании изображение идёт перед текстом в массиве input.

  • У провайдера openai нет цепочек доработок: previous_interaction_id — из Interactions API Gemini. Чтобы скорректировать, отправьте изображение заново как референс.

  • Вход по URL отправляет собственный User-Agent: многие источники (среди них Wikimedia) возвращают 400/403 на запрос без опознаваемого UA.

  • Работа с текстом: сначала задайте copy, затем запросите изображение с этой copy.

Related MCP Connectors

Related MCP Servers