diff --git a/.env.example b/.env.example index ccac05d..6c8ff0b 100644 --- a/.env.example +++ b/.env.example @@ -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://:8765/mcp diff --git a/.opencode/commands/cover.md b/.opencode/commands/cover.md new file mode 100644 index 0000000..c958539 --- /dev/null +++ b/.opencode/commands/cover.md @@ -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: "", slots: "icon=,sub-icon=", 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/` директория отсутствует** → создаётся автоматически. diff --git a/docs/decisions/088-pr-199-cover-command.md b/docs/decisions/088-pr-199-cover-command.md new file mode 100644 index 0000000..32d0b49 --- /dev/null +++ b/docs/decisions/088-pr-199-cover-command.md @@ -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-секция), чтобы переменную не забыть при будущей интеграции. Добавление пустого плейсхолдера не ломает существующие тесты и записи. diff --git a/docs/handoff/pr-199-cover-command.md b/docs/handoff/pr-199-cover-command.md new file mode 100644 index 0000000..1cabe0b --- /dev/null +++ b/docs/handoff/pr-199-cover-command.md @@ -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. diff --git a/docs/project-map/README.md b/docs/project-map/README.md index 20f3fe8..f0eeea2 100644 --- a/docs/project-map/README.md +++ b/docs/project-map/README.md @@ -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)