Задача превратить skill или апи в автономный browser-based playground.
Пользователь говорит `Сделай плейграунд для [скилл], инструкция [ссылка]` —
агент сам разбирается в скриптах, задаёт вопросы и собирает интерактивный UI.
Installs into .claude/skills of the current project.
Are you the author of 2653 Playground Builder 083c0a65?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/tools-only-2653-playground-builder-083c0a65)
# Playground Builder — инструкция для агента
## Что это
Задача превратить skill или апи в автономный browser-based playground.
Пользователь говорит `Сделай плейграунд для [скилл], инструкция [ссылка]` —
агент сам разбирается в скриптах, задаёт вопросы и собирает интерактивный UI.
## Промпт пользователя
```
/playground Сделай playground для [название скилла]
```
Всё. Дальше работает агент.
---
## Инструкция для агента
### Шаг 1: Исследование скилла
Прочитай скрипты скилла.
Найди:
- Все API-эндпоинты (URL, метод, параметры, формат ответа)
- Все параметры командной строки (--prompt, --resolution и т.д.)
- Наличие API-ключей (config/.env)
- Формат входных данных (текст, файлы, URL)
- Формат выходных данных (изображение, текст, JSON, файл)
- Есть ли queue/polling (submit → poll status → get result)
### Шаг 2: Уточнение у пользователя
Используй AskUserQuestionTool чтобы задать вопросы.
Задавай только релевантные — не спрашивай про файлы если скилл текстовый.
Возможные вопросы:
- **Режим** — если скилл поддерживает несколько (generate/edit, analyze/export)
- **Язык UI** — русский или английский
- **Папка результатов** — куда сохранять
- **Итерации** — нужна ли возможность переиспользовать результат как вход
- **Дополнительные фичи** — рисование областей, шаблоны, пресеты
### Шаг 3: Сборка playground
Собери playground как набор файлов в указанной пользователем папке.
---
## Кейсы — что делать когда
### Кейс: В скрипте есть API-ключ
Если в скрипте есть загрузка из `.env` (паттерн `KEY=...`):
- Добавить в UI текстовое поле для вставки ключа
- Добавить file picker для загрузки .env файла
- Убрать `accept` атрибут (`.env` не имеет MIME-типа, пикер его скроет)
- В подсказке указать путь, в зависимости от того где находится скилл, например: `~/.claude/plugins/cache/[маркетплейс]/[скилл]/[версия]/skills/[скилл]/config`
- Добавить hint: "Cmd+Shift+G для перехода по пути"
- Парсинг: `text.match(/KEY_NAME\s*=\s*(.+)/)`
- Хранить ключ только в `state` (память вкладки), не в localStorage
### Кейс: API не поддерживает CORS
Практически все API не отдают `Access-Control-Allow-Origin` для localhost.
Даже `file://` и `http://localhost` будут заблокированы.
Создать рядом с HTML:
**server.py** — прокси на stdlib Python:
- `http.server.SimpleHTTPRequestHandler` для статики
- `do_GET/do_POST/do_PUT` — если путь `/api/*`, проксировать к реальному API
- `do_OPTIONS` — вернуть 200 + CORS headers для preflight
- Заголовки: пробрасывать `Authorization`, `Content-Type` только для POST/PUT
- GET запросы: НЕ отправлять Content-Type (некоторые API ответят 405)
- `urllib.error.HTTPError` — проксировать код и тело, а не глотать
- Логирование: `print(f"[proxy] {method} {target}")` для отладки
**start.sh**:
```sh
#!/bin/sh
PORT=8090
echo "Playground: http://localhost:$PORT/[имя].html"
open "http://localhost:$PORT/[имя].html"
cd "$(dirname "$0")" && uv run server.py $PORT
```
### Кейс: Presigned URL upload (двухшаговая загрузка)
Паттерн: POST initiate → получить `{upload_url, file_url}` → PUT файл на `upload_url`.
Presigned URL содержит query-параметры (`?signature=...&token=...`).
Проксирование:
- В HTML: `fetch('/api/put?url=' + encodeURIComponent(presignedUrl), {method: 'PUT', body: file})`
- В server.py: парсить через `urllib.parse.parse_qs`, НЕ через `split("?url=")`
```python
elif path.startswith('/api/put?'):
qs = urllib.parse.urlparse(path).query
target = urllib.parse.parse_qs(qs).get('url', [''])[0]
```
### Кейс: Queue-based API (длинный polling)
Паттерн: submit → получить request_id → poll status → get result.
**ВАЖНО**: URL для submit и для status/result могут отличаться.
Всегда проверять все URL в скрипте — не предполагать единый базовый путь.
В HTML:
- Прогресс-бар с анимацией
- Polling с разумным интервалом (2-5 сек)
- Таймаут
- Обработка всех статусов из скрипта (success, fail, in progress, queued и т.д.)
- При завершении — забрать результат (эндпоинт может отличаться от status)
В server.py: отдельные маршруты для submit и status, если URL различаются.
### Кейс: Скилл работает с файлами (изображения, документы)
- Drag & drop зона с превью миниатюр
- `<input type="file">` как fallback
- Для изображений: отображение на `<canvas>`
- Для canvas: возможность рисовать прямоугольные области (mousedown → mousemove → mouseup)
- Каждая область — текстовая заметка в sidebar
- Заметки включаются в промпт с координатами в процентах от размера изображения
Загрузка default-изображения (если файл рядом с HTML):
- Использовать `canvas.toBlob()` для конвертации в File, НЕ `fetch()`
- Обернуть в `try/catch` — из `file://` canvas будет tainted
### Кейс: Результат можно итерировать
Если результат того же типа что и вход (изображение → изображение):
- Сохранять URL/blob каждого результата в массив `state.history`
- Показывать миниатюры в sidebar
- Клик по миниатюре → загрузить как новый исходник
- Кнопка "Вернуть оригинал"
- После генерации: обновить `state.imageFile` из результата для следующей итерации
### Кейс: AI-обратная связь через Claude CLI
Если результат выигрывает от анализа или итерации с помощью AI:
- Добавить в server.py эндпоинт `/api/claude`
- Сервер вызывает `claude -p "prompt" --output-format json` через `subprocess.run()`
- Для сохранения контекста: `claude --resume SESSION_ID -p "prompt"`
- В HTML: кнопка "Спросить Claude" отправляет результат + вопрос на `/api/claude`
### Кейс: Язык интерфейса
Всегда спрашивать через AskUserQuestion. Не угадывать по языку промпта.
Если пользователь выбрал русский:
- Все элементы UI: заголовки, label, placeholder, hint, кнопки, статусы, ошибки
- Кнопки: "Запустить", "Скопировать", "Очистить все", "Скачать", "Вернуть оригинал"
- Статусы: "Загружаю...", "Генерация... (обычно 15-30 сек)", "Готово!", "Ошибка: ..."
---
## Архитектура playground
### Файлы
```
output-folder/
[имя]-playground.html — UI, единый файл, inline CSS + JS
server.py — прокси (если нужен)
start.sh — запуск (если нужен прокси)
```
### UI-требования
- Единый HTML-файл, inline CSS + JS
- Тёмная тема
- Кнопка запуска: `disabled` пока не заполнены обязательные поля
- Прогресс/статус: пользователь должен видеть что происходит
- Лейаут подбирать под задачу скилла (sidebar + preview, полноэкранный и т.д.)
---
## Ловушки (must read)
1. **file:// не работает**. `fetch()`, `canvas.toBlob()` для cross-origin,
чтение файлов по пути — всё заблокировано. Всегда через localhost.
2. **CORS блокирует даже localhost**. Прокси нужен почти всегда.
3. **`.env` не имеет MIME-типа**. Файловый пикер скроет его.
Убрать `accept` атрибут с `<input type="file">`.
4. **Presigned URL содержит `?`**. Нельзя парсить простым split.
5. **Submit и status на разных URL**. Не предполагать единый базовый путь.
6. **GET + Content-Type = 405**. Некоторые API отвергнут GET с Content-Type.
---
## Пример: как выглядит диалог
**Пользователь:**
```
/playground Сделай интерфейс для [название скилла]
```
**Агент** (изучив скрипты): задаёт вопросы через AskUserQuestionTool —
только те, что релевантны конкретному скиллу.
**Пользователь:** отвечает на вопросы.
**Агент:** Создаёт файлы, запускает, playground работает.