feat(cover): add /cover command and VERCEL_TOKEN placeholder (#199)

* feat(cover): add /cover command for branded cover generation

* chore(env): add VERCEL_TOKEN placeholder to .env.example

* docs(project-map): add cover command, ADR-088 and handoff for PR #199

---------

Co-authored-by: opencode-agent <agent@opencode.local>
This commit is contained in:
Sergey 2026-08-01 02:07:34 +03:00 committed by GitHub
parent 66f0afa58b
commit 87d6c4464b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 120 additions and 1 deletions

View file

@ -33,6 +33,9 @@ TELEGRAM_BOOSTY_CHAT_ID=your-boosty-chat-id
CLOUDFLARE_TUNNEL_TOKEN=
TUNNEL_DOMAIN=
# Vercel (optional — placeholder example, not currently wired to code)
VERCEL_TOKEN=
# Antidetect Browser MCP
# Local: http://localhost:8765/mcp
# Remote: http://<your-server-ip>:8765/mcp

View file

@ -0,0 +1,61 @@
---
description: Generate a branded 1024×1024 cover (Lime style) via draw-image
agent: build
---
Generate a repository cover image following the **hardcoded slaid098 Lime standard**.
Style is NEVER asked of the user and NEVER overridden — only content questions are asked.
## Hardcoded standard (НЕ спрашивается, НЕ переопределяется)
- `template` = `cover` (1024×1024, единственный доступный шаблон в `.opencode/draw-image/templates/cover.svg`).
- Palette = **Lime** from `.opencode/draw-image/brand.json`: base `#0a0a0a`, surface `#121212`, accent `#ccff00` (icons), fg `#ededed` (text), muted `#a1a1aa`.
- `title`: ровно ОДИН заголовок, **lowercase English**, без исключений. Авто-деривация из имени репо (см. правила ниже).
- `subtitle`: **ОТСУТСТВУЕТ**. Флаг `--subtitle` / параметр `subtitle` запрещён стандартом. Передача subtitle = ошибка команды.
- `out` = `assets/cover.png` (относительно корня репо). Глобальный путь для витрины slaid098.dev — без исключений.
## Slots
- `icon`**обязательный**. Lucide-иконка из `.opencode/draw-image/icons/lucide/` (2007 шт.) ИЛИ brand-logo из `.opencode/draw-image/brand-logos/`. Передаётся без расширения (напр. `mic`, не `mic.svg`).
- `sub-icon`**опциональный**. Brand-logo из `.opencode/draw-image/brand-logos/` для показа brand-принадлежности.
- `badge`**опциональный**. Путь к SVG-бейджу.
## Title derivation rules
- Имя репо → lowercase, дефисы/подчёркивания → пробелы: `opencode-voice-dictation``opencode voice dictation`.
- camelCase / UPPER → lowercase: `MyRepo``myrepo`.
- Числа сохраняются: `video-uniq-2``video uniq 2`.
- Если имя длиннее ~40 символов → спроси у пользователя сокращение.
## Flow
1. **Read repo** — прочитай `README.md`, манифест (`package.json` / `pyproject.toml` / иной), пробегись по `src/` (топ-уровень). Определи purpose репо.
2. **Infer** — выведи: `title` (по правилам выше), candidate `icon` (по purpose, напр. voice-dictation → `mic`), candidate `sub-icon` (по brand-принадлежности из README/манифеста).
3. **Ask user** — задай 2-3 вопроса ТОЛЬКО по контенту, который не удалось вывести: подтвердить/скорректировать `icon`, `sub-icon`, `title`. НЕ задавай вопросов по стилю — стиль захардкожен.
4. **Pre-check** — если `assets/cover.png` уже существует, спроси «перезаписать?» перед рендером.
5. **Ensure `assets/` dir** — если директории `assets/` нет, создай её автоматически.
6. **Render** — вызови tool `draw-image`:
```
draw-image({ template: "cover", title: "<lowercase english>", slots: "icon=<value>,sub-icon=<value>", out: "assets/cover.png" })
```
НЕ передавай `subtitle`. Если tool `draw-image` недоступен — остановись с ошибкой: «draw-image tool недоступен — невозможно сгенерировать кавер».
7. **Validate README** — проверь, что в `README.md` есть `![Cover](assets/cover.png)`. Если нет — добавь ссылку сразу после заголовка H1. Можно использовать `create-readme validate` или прямую проверку; если `create-readme validate` падает (README без delimiter tags) — предупреди, но кавер всё равно остаётся отрендеренным (кавер ≠ README).
8. **Done** — сообщи путь к готовому PNG.
## Extension mechanism (НЕ хардкод брендов)
Команда НЕ хардкодит конкретные бренды. **OpenCode упоминается ниже только как ПРИМЕР** — это не special-case в коде. Для любого бренда: если в `.opencode/draw-image/brand-logos/` есть SVG, его можно использовать как `sub-icon`. Расширение = добавление brand-logo SVG в `draw-image/brand-logos/`, а НЕ изменение этой команды.
> Пример: README указывает на принадлежность к OpenCode-ecosystem → предложи `sub-icon=opencode` (файл `brand-logos/opencode.svg`). Для не-OpenCode репо `sub-icon` пропускается.
## Граничные случаи
- **Нет README / нет манифеста** → purpose не выводится → спроси пользователя `icon` + `title` напрямую (без infer).
- **Имя репо camelCase / UPPER** → нормализуется в lowercase (`MyRepo``myrepo`).
- **Имя репо с числами** → сохраняются (`video-uniq-2``video uniq 2`).
- **Имя репо > ~40 символов** → спроси сокращение.
- **Пользователь хочет не-Lime кавер** → отказ: «команда /cover поддерживает только Lime-стиль. Кастомизация вне scope.»
- **Существующий `assets/cover.png`** → спроси «перезаписать?» перед рендером.
- **Lucide-иконка не найдена** → ошибка со списком похожих иконок (fuzzy match по имени в `icons/lucide/`).
- **`create-readme validate` падает** (README без delimiter tags) → предупреди, но кавер рендерится.
- **`draw-image` tool недоступен** → ясная ошибка, рендер невозможен.
- **`assets/` директория отсутствует** → создаётся автоматически.

View file

@ -0,0 +1,31 @@
# ADR-088: /cover command — hardcoded Lime standard + VERCEL_TOKEN placeholder (PR#199)
## Статус
Accepted (2026-07-31)
## Контекст
Генерация брендового кавера 1024×1024 для репозиториев slaid098 ранее требовала повторного объяснения Lime-стиля (палитра, lowercase-заголовок, отсутствие subtitle) в каждом новом чате — детерминированного способа одной командой не было (issue #198). `draw-image` tool (PR#133) и `repo-readme` skill (PR#158) уже формируют cover-pipeline, но стиль оставался на усмотрение агента/пользователя, что рисковало сломать единый вид витрины slaid098.dev кастомной палитрой или путём вывода.
Дополнительно: будущая интеграция с Vercel потребует токен `VERCEL_TOKEN`, но код ещё не готов — нужен placeholder в `.env.example`, чтобы переменную не забыть при подключении (доп. scope, одобренный пользователем в issue #198).
Рассматривались варианты:
- Сделать `/cover` параметризуемой командой (палитра/шаблон/subtitle — на выбор пользователя): ломает единый бренд-стандарт витрины, пользователь может вывести не-Lime кавер.
- Вынести стиль в отдельный skill `cover`: issue #198 явно выводит skill вне scope — команда должна быть самодостаточной.
- Не добавлять `VERCEL_TOKEN` пока код не готов: риск забыть переменную при будущей интеграции.
## Решение
1. **Команда `/cover`** (`.opencode/commands/cover.md`, frontmatter `agent: build`) — самодостаточная, без отдельного skill. Стиль **захардкожен** и НЕ спрашивается/НЕ переопределяется:
- `template` = `cover` (единственный шаблон в `draw-image/templates/cover.svg`).
- Палитра = Lime из `draw-image/brand.json` (base `#0a0a0a`, accent `#ccff00`, fg `#ededed`).
- `title`: ровно ОДИН заголовок, lowercase English, авто-деривация из имени репо (`opencode-voice-dictation``opencode voice dictation`).
- `subtitle`: **запрещён** стандартом — передача = ошибка команды.
- `out` = `assets/cover.png` (глобальный путь для витрины, без исключений).
2. **Слоты**: `icon` (обязательный — Lucide из `icons/lucide/` или brand-logo из `brand-logos/`), `sub-icon` (опц.), `badge` (опц.).
3. **Flow**: read repo → infer title/icon/sub-icon → ask user 2-3 content-вопроса (НЕ по стилю) → `draw-image` tool → validate README `![Cover](assets/cover.png)`.
4. **Extension-механизм**: команда НЕ хардкодит бренды — OpenCode упомянут только как пример. Расширение = добавление brand-logo SVG в `draw-image/brand-logos/`, а не изменение команды.
5. **`VERCEL_TOKEN` в `.env.example`** — optional-плейсхолдер `VERCEL_TOKEN=` после секции Cloudflare Tunnel, в стиле соседних optional-секций. В коде пока не задействован — placeholder-пример. Существующие записи и тесты `tests/test_permissions.py` не затронуты.
## Альтернативы
- **Параметризуемая команда (палитра/шаблон/subtitle на выбор)**: отвергнуто — ломает единый бренд-стандарт витрины slaid098.dev, пользователь может вывести не-Lime кавер или добавить subtitle. Захардкоженный стандарт гарантирует консистентность.
- **Отдельный skill `cover`**: отвергнуто — issue #198 явно выводит skill вне scope. Команда самодостаточна (frontmatter + инструкции в одном файле), skill избыточен.
- **Не добавлять `VERCEL_TOKEN` до готовности кода**: отвергнуто — placeholder фиксирует намерение и стиль (optional-секция), чтобы переменную не забыть при будущей интеграции. Добавление пустого плейсхолдера не ломает существующие тесты и записи.

View file

@ -0,0 +1,23 @@
---
pr: 199
title: "feat(cover): add /cover command and VERCEL_TOKEN placeholder"
---
## Что сделано
Добавил команду `/cover` для генерации брендового кавера 1024×1024 в захардкоженном Lime-стиле (issue #198) + optional-плейсхолдер `VERCEL_TOKEN` в `.env.example`. Структурное изменение: новый файл `.opencode/commands/cover.md`.
1. **`.opencode/commands/cover.md`** — новая команда `/cover` (frontmatter `agent: build`, description). Стиль захардкожен и НЕ спрашивается: `template=cover`, Lime-палитра из `draw-image/brand.json` (base `#0a0a0a`, accent `#ccff00`, fg `#ededed`), title — один заголовок lowercase English (авто-деривация из имени репо: `opencode-voice-dictation``opencode voice dictation`), subtitle запрещён стандартом, `out=assets/cover.png`. Слоты: `icon` (обязательный — Lucide из `icons/lucide/` или brand-logo из `brand-logos/`), `sub-icon` (опц.), `badge` (опц.). Flow: read repo → infer → ask 2-3 content-вопроса (НЕ по стилю) → `draw-image` tool → validate README `![Cover](assets/cover.png)`. Extension = добавление brand-logo SVG в `draw-image/brand-logos/`, а не изменение команды (OpenCode упомянут только как пример). Граничные случаи: нет README/манифеста, camelCase→lowercase, числа сохраняются, длинное имя (>~40 символов)→сокращение, не-Lime запрос→отказ, существующий cover.png→спросить перезапись, Lucide не найден→fuzzy-подсказки, `draw-image` недоступен→ошибка, `assets/` нет→автосоздание.
2. **`.env.example`** — добавлен optional-плейсхолдер `VERCEL_TOKEN=` после секции Cloudflare Tunnel, в стиле соседних optional-секций (комментарий «Vercel (optional — placeholder example, not currently wired to code)»). В коде пока не задействован — placeholder-пример. Существующие записи не изменены; тесты `tests/test_permissions.py` не затронуты.
3. Добавлен handoff + ADR-088.
## Почему
Детерминированный способ генерации кавера одной командой вместо повторного объяснения Lime-стиля в каждом новом чате. Стандарт захардкожен — пользователь не может сломать витрину slaid098.dev кастомной палитрой/путём. `VERCEL_TOKEN` добавлен как placeholder, чтобы не забыть про переменную при будущей интеграции (доп. scope, одобрено пользователем — тот же PR).
## Pending
— (после merge: при будущей Vercel-интеграции — заполнить `VERCEL_TOKEN` в `.env` и завести код, читающий переменную)
## Watch out
- **Скилла `cover` нет по спеке** — команда самодостаточна (вне scope issue #198). Не путать с `repo-readme` skill, который формирует cover-pipeline в своём workflow (PR#158) — `/cover` переиспользует тот же `draw-image` tool, но не зависит от skill.
- **`subtitle` запрещён стандартом** — передача `--subtitle` / параметра `subtitle` = ошибка команды. Это сознательное решение (ADR-088): продакшн `templates/cover.svg` уже упрощён до icon+title (PR#197), subtitle-слот удалён из продакшн-шаблона.
- **`VERCEL_TOKEN` — placeholder, не wired** — переменная объявлена в `.env.example`, но код её не читает. Тесты `tests/test_permissions.py` (`CONTEXT7_API_KEY`, `OPENCODE_SERVER_USERNAME`) не затронуты и проходят.
- **Extension-механизм** — команда НЕ хардкодит бренды. OpenCode упоминается в `cover.md` только как ПРИМЕР (`sub-icon=opencode`). Для любого бренда: добавить SVG в `draw-image/brand-logos/` — команда подхватит через `draw-image` slot resolver.

View file

@ -21,6 +21,7 @@ opencode-config/
│ │ └── reviewer.md # Code review subagent (verdict via `post-review` tool: APPROVE|REQUEST_CHANGES|NEEDS_DISCUSSION) — PR#46, PR#69
│ ├── commands/
│ │ ├── configure-opencode.md # /configure-opencode — edit opencode.json
│ │ ├── cover.md # /cover — branded 1024×1024 cover generation (hardcoded Lime standard, agent: build, draw-image tool; no subtitle, extension via brand-logos) — PR#199
│ │ ├── feature-spec.md # /feature-spec — SDD-style Q&A for feature planning (loads feature-spec skill) — PR#172
│ │ ├── repo-readme.md # /repo-readme — standardized README generation (frontmatter agent: build, loads repo-readme skill) — PR#158
│ │ ├── run-pipeline.md # /run-pipeline — 7-phase PR pipeline
@ -189,7 +190,7 @@ opencode-config/
├── docker-entrypoint.sh # Self-healing entrypoint shim: checks node_modules/sharp, runs npm ci if missing (continue-on-error), exec opencode "$@" — PR#144
├── .dockerignore # Excludes app_data/, .git, **/node_modules from docker build context — PR#144
│ # Memory deps install layers (PR#107): COPY .opencode/package.json → npm install --omit=dev (runtime: @vscode/ripgrep for memory-search.ts); COPY pyproject.toml uv.lock → uv sync --no-dev --frozen (runtime: httpx, numpy, tenacity for python -m src.memory)
├── .env.example # Placeholder-only env template (user copies to .env) — PR#24, PR#34 (TUNNEL_DOMAIN), PR#36 (OPENCODE_MEMORY_REMOTE/DIR), PR#106 (AI_PROVIDER_* removed, OPENCODE_MEMORY_REMOTE now optional), PR#118 (OPENCODE_SERVER_USERNAME), PR#174 (TELEGRAM_CHAT_ID)
├── .env.example # Placeholder-only env template (user copies to .env) — PR#24, PR#34 (TUNNEL_DOMAIN), PR#36 (OPENCODE_MEMORY_REMOTE/DIR), PR#106 (AI_PROVIDER_* removed, OPENCODE_MEMORY_REMOTE now optional), PR#118 (OPENCODE_SERVER_USERNAME), PR#174 (TELEGRAM_CHAT_ID), PR#199 (VERCEL_TOKEN placeholder, not wired)
├── app_data/
│ ├── opencode-memory/ # Persistent memory (separate git repo, gitignored) — PR#36
│ ├── workspaces/ # Agent working directory (.gitkeep)