init
This commit is contained in:
171
.opencode/skills/stitch/SKILL.md
Normal file
171
.opencode/skills/stitch/SKILL.md
Normal file
@@ -0,0 +1,171 @@
|
||||
---
|
||||
name: ck:stitch
|
||||
description: "AI design generation with Google Stitch. Generate UI designs from text prompts, export Tailwind/HTML/DESIGN.md, orchestrate design-to-code pipeline. Use for rapid prototyping, UI generation, design exploration."
|
||||
license: MIT
|
||||
allowed-tools:
|
||||
- Bash
|
||||
- Read
|
||||
- Write
|
||||
- Edit
|
||||
argument-hint: "[design prompt or action]"
|
||||
metadata:
|
||||
author: claudekit
|
||||
version: "1.0.0"
|
||||
---
|
||||
|
||||
# Google Stitch — AI Design Generation
|
||||
|
||||
Generate high-fidelity UI designs from text prompts via Google Stitch. Export Tailwind/HTML, orchestrate design-to-code pipelines with existing UI skills.
|
||||
|
||||
**Free tier:** 400 credits/day + 15 redesign credits/day. Resets at midnight UTC.
|
||||
|
||||
## Setup
|
||||
|
||||
### 1. API Key
|
||||
|
||||
Get an API key at https://stitch.withgoogle.com → Settings → API Keys.
|
||||
|
||||
Add `STITCH_API_KEY=sk_...` to `~/.opencode/.env` (or `~/.opencode/skills/.env`).
|
||||
|
||||
Running `install.sh` auto-adds the placeholder if missing — just fill in the value.
|
||||
|
||||
### 2. Install SDK
|
||||
|
||||
```bash
|
||||
cd ~/.opencode/skills/stitch/scripts && npm install
|
||||
```
|
||||
|
||||
Or run `~/.opencode/skills/install.sh` which handles this automatically.
|
||||
|
||||
### 3. Optional
|
||||
|
||||
```bash
|
||||
# In ~/.opencode/.env
|
||||
STITCH_PROJECT_ID="my-project" # Default project (auto-creates "claudekit-default" if unset)
|
||||
STITCH_QUOTA_LIMIT="200" # Override daily limit
|
||||
```
|
||||
|
||||
### 4. MCP Server (optional)
|
||||
|
||||
Add to `~/.opencode/.mcp.json` for native design context in Claude Code:
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"stitch": {
|
||||
"command": "npx",
|
||||
"args": ["@_davideast/stitch-mcp", "proxy"],
|
||||
"env": { "STITCH_API_KEY": "${STITCH_API_KEY}" }
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
See `references/stitch-mcp-setup.md` for alternative options (gcloud, auto-installer).
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Check quota
|
||||
npx tsx scripts/stitch-quota.ts check
|
||||
|
||||
# Generate design
|
||||
npx tsx scripts/stitch-generate.ts "A checkout page with payment form and cart summary"
|
||||
|
||||
# Export as HTML + DESIGN.md
|
||||
npx tsx scripts/stitch-export.ts <screen-id> --format all --output ./stitch-exports/
|
||||
```
|
||||
|
||||
## Actions
|
||||
|
||||
### generate
|
||||
|
||||
Generate UI design from text prompt.
|
||||
|
||||
```bash
|
||||
npx tsx scripts/stitch-generate.ts "<prompt>" [--project <id>] [--device MOBILE|DESKTOP|TABLET] [--variants <count>]
|
||||
```
|
||||
|
||||
Returns: screen ID, preview image URL. With `--variants`: additional design alternatives.
|
||||
|
||||
### export
|
||||
|
||||
Export generated design as HTML/Tailwind, screenshot, or DESIGN.md.
|
||||
|
||||
```bash
|
||||
npx tsx scripts/stitch-export.ts <screen-id> [--format html|image|all] [--output <dir>]
|
||||
```
|
||||
|
||||
Outputs:
|
||||
- `design.html` — Semantic HTML with Tailwind CSS classes
|
||||
- `design.png` — Screenshot of the design
|
||||
- `DESIGN.md` — Agent-readable design spec (colors, typography, spacing, components)
|
||||
|
||||
### quota
|
||||
|
||||
Check and manage daily quota.
|
||||
|
||||
```bash
|
||||
npx tsx scripts/stitch-quota.ts check # Show remaining credits
|
||||
npx tsx scripts/stitch-quota.ts increment # Bump after generation
|
||||
npx tsx scripts/stitch-quota.ts reset # Force reset (auto-resets daily)
|
||||
```
|
||||
|
||||
### edit
|
||||
|
||||
Refine an existing design.
|
||||
|
||||
```typescript
|
||||
const editedScreen = await screen.edit("Make the header darker and add a search bar");
|
||||
```
|
||||
|
||||
## Orchestration Pipeline
|
||||
|
||||
### Design-to-Code Flow
|
||||
|
||||
1. **Check quota** — Run `stitch-quota.ts check`. If exhausted, suggest `ck:ui-ux-pro-max` fallback.
|
||||
2. **Generate** — Run `stitch-generate.ts` with user's design prompt
|
||||
3. **Review** — Show generated design image to user for feedback
|
||||
4. **Variants** (optional) — Generate alternatives if user wants exploration
|
||||
5. **Export** — Run `stitch-export.ts --format all` to get HTML + DESIGN.md
|
||||
6. **Implement** — Hand off exported artifacts to implementation skill:
|
||||
- `ck:frontend-design` — React/Vue/Svelte components from Tailwind export
|
||||
- `ck:ui-ux-pro-max` — Full page layouts with style guide integration
|
||||
- `ck:ui-styling` — Design token extraction from DESIGN.md
|
||||
7. **Track quota** — Run `stitch-quota.ts increment`
|
||||
|
||||
### Handoff Protocol
|
||||
|
||||
- Export creates `DESIGN.md` in project root or plan directory
|
||||
- Implementation skills detect `DESIGN.md` and use it as design spec
|
||||
- DESIGN.md takes precedence over text descriptions when present
|
||||
- If no DESIGN.md exists, skills fall back to normal text-based design flow
|
||||
|
||||
See `references/design-to-code-pipeline.md` for detailed patterns and examples.
|
||||
|
||||
## Quota Management
|
||||
|
||||
- 400 credits/day + 15 redesign/day, resets at midnight UTC
|
||||
- Local tracking via `~/.claudekit/.stitch-quota.json`
|
||||
- Warns when remaining credits < 20%
|
||||
- **Fallback:** When exhausted, use `ck:ui-ux-pro-max` for text-based design generation
|
||||
|
||||
See `references/quota-management.md` for strategies.
|
||||
|
||||
## Limitations
|
||||
|
||||
- **No React export** — HTML/Tailwind only; Claude converts to React/Vue components
|
||||
- **Non-responsive layouts** — Must add breakpoints manually during implementation
|
||||
- **No animations** — Static designs only; add micro-interactions in code
|
||||
- **Single-user** — No multiplayer/collaboration features
|
||||
- **Hard daily quota** — No paid tier to increase limits
|
||||
- **Generic output risk** — Combine with style guides for differentiation
|
||||
|
||||
## References
|
||||
|
||||
| Topic | File |
|
||||
|-------|------|
|
||||
| SDK API | `references/stitch-sdk-api.md` |
|
||||
| MCP Setup | `references/stitch-mcp-setup.md` |
|
||||
| Pipeline Patterns | `references/design-to-code-pipeline.md` |
|
||||
| Quota Strategy | `references/quota-management.md` |
|
||||
11
.opencode/skills/stitch/data/mcp-config-snippet.json
Normal file
11
.opencode/skills/stitch/data/mcp-config-snippet.json
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"stitch": {
|
||||
"command": "npx",
|
||||
"args": ["@_davideast/stitch-mcp", "proxy"],
|
||||
"env": {
|
||||
"STITCH_API_KEY": "${STITCH_API_KEY}"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
128
.opencode/skills/stitch/references/design-to-code-pipeline.md
Normal file
128
.opencode/skills/stitch/references/design-to-code-pipeline.md
Normal file
@@ -0,0 +1,128 @@
|
||||
# Design-to-Code Pipeline Patterns
|
||||
|
||||
Orchestration workflows chaining Stitch design generation with ClaudeKit implementation skills.
|
||||
|
||||
## Basic Pipeline
|
||||
|
||||
```
|
||||
Prompt -> Stitch Generate -> Export HTML -> Claude Implements Components
|
||||
```
|
||||
|
||||
### Steps
|
||||
|
||||
1. User describes desired UI in natural language
|
||||
2. Run `stitch-generate.ts` with the prompt
|
||||
3. Show preview image to user for approval
|
||||
4. Run `stitch-export.ts --format all` to get HTML + DESIGN.md
|
||||
5. Hand DESIGN.md to implementation skill
|
||||
6. Implementation skill reads DESIGN.md and codes components
|
||||
|
||||
### Example
|
||||
|
||||
```bash
|
||||
# 1. Generate
|
||||
npx tsx stitch-generate.ts "E-commerce product page with image gallery, price, reviews, add-to-cart button"
|
||||
|
||||
# 2. Export
|
||||
npx tsx stitch-export.ts <screen-id> --format all --output ./stitch-exports/
|
||||
|
||||
# 3. Implementation skill reads ./stitch-exports/DESIGN.md
|
||||
# 4. Activate ck:frontend-design to implement React components from the spec
|
||||
```
|
||||
|
||||
## Advanced Pipeline (with Variants)
|
||||
|
||||
```
|
||||
Prompt -> Generate -> Variants -> User Picks -> Export -> Implement
|
||||
```
|
||||
|
||||
### Steps
|
||||
|
||||
1. Generate base design
|
||||
2. Generate 2-3 variants with different aspects (color, layout)
|
||||
3. Show all variants to user
|
||||
4. User selects preferred variant
|
||||
5. Export selected variant
|
||||
6. Hand off to implementation
|
||||
|
||||
### Example
|
||||
|
||||
```bash
|
||||
# Generate with variants
|
||||
npx tsx stitch-generate.ts "SaaS pricing page" --variants 3
|
||||
|
||||
# User reviews images, picks variant 2
|
||||
npx tsx stitch-export.ts <variant-2-screen-id> --format all
|
||||
```
|
||||
|
||||
## DESIGN.md Specification
|
||||
|
||||
DESIGN.md is an agent-readable markdown file containing extracted design tokens.
|
||||
|
||||
### Structure
|
||||
|
||||
```markdown
|
||||
# Design System
|
||||
|
||||
## Colors
|
||||
- `bg-blue-600` (primary background)
|
||||
- `text-gray-900` (body text)
|
||||
- `border-gray-200` (dividers)
|
||||
|
||||
## Typography
|
||||
- `text-2xl font-bold` (headings)
|
||||
- `text-base font-normal` (body)
|
||||
|
||||
## Spacing
|
||||
- `p-4` (card padding)
|
||||
- `gap-6` (grid gaps)
|
||||
|
||||
## Components
|
||||
- `<nav>` (top navigation)
|
||||
- `<section>` (content sections)
|
||||
- `<form>` (user inputs)
|
||||
- `<button>` (actions)
|
||||
|
||||
## Notes
|
||||
- Generated by Google Stitch AI
|
||||
- Tailwind CSS utility classes
|
||||
```
|
||||
|
||||
### How Implementation Skills Consume It
|
||||
|
||||
1. Skill checks for `DESIGN.md` in project root or plan directory
|
||||
2. If found: reads tokens and uses them as constraints for implementation
|
||||
3. If not found: falls back to text-based design (existing behavior)
|
||||
4. DESIGN.md takes precedence over verbal design descriptions
|
||||
|
||||
## Skill Routing
|
||||
|
||||
| Design Need | Skill | What It Does |
|
||||
|-------------|-------|-------------|
|
||||
| React/Vue/Svelte components | `ck:frontend-design` | Converts Tailwind HTML to framework components |
|
||||
| Full page with style guide | `ck:ui-ux-pro-max` | Builds complete layouts with design system |
|
||||
| Design token extraction | `ck:ui-styling` | Extracts shadcn/Tailwind tokens from DESIGN.md |
|
||||
|
||||
## Handoff Protocol
|
||||
|
||||
1. `ck:stitch` exports artifacts to `./stitch-exports/` (or plan directory)
|
||||
2. Copy `DESIGN.md` to project root if implementation skill expects it there
|
||||
3. Activate target skill with instruction: "Implement from DESIGN.md in project root"
|
||||
4. Target skill reads DESIGN.md, applies tokens, generates code
|
||||
5. No modifications needed to existing skills — they read DESIGN.md opportunistically
|
||||
|
||||
## Quota-Aware Pipeline
|
||||
|
||||
Always check quota before generating:
|
||||
|
||||
```bash
|
||||
# Pre-check
|
||||
npx tsx stitch-quota.ts check
|
||||
|
||||
# If exhausted (exit code 2):
|
||||
# -> Skip Stitch, use ck:ui-ux-pro-max for text-based design instead
|
||||
|
||||
# If available:
|
||||
npx tsx stitch-generate.ts "<prompt>"
|
||||
npx tsx stitch-quota.ts increment
|
||||
```
|
||||
64
.opencode/skills/stitch/references/quota-management.md
Normal file
64
.opencode/skills/stitch/references/quota-management.md
Normal file
@@ -0,0 +1,64 @@
|
||||
# Quota Management
|
||||
|
||||
Google Stitch free tier quota tracking and conservation strategies.
|
||||
|
||||
## Limits
|
||||
|
||||
| Pool | Credits/Day | Reset |
|
||||
|------|-------------|-------|
|
||||
| Daily Credits (generate, variants) | 400 | Midnight UTC |
|
||||
| Redesign Credits (edit_screens) | 15 | Midnight UTC |
|
||||
|
||||
Each generation = 1 credit. Each variant = 1 credit. Each edit = 1 redesign credit (separate pool).
|
||||
|
||||
## Local Tracking
|
||||
|
||||
Stitch SDK has no programmatic quota check endpoint. ClaudeKit tracks locally:
|
||||
|
||||
**File:** `~/.claudekit/.stitch-quota.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"date": "2026-03-23",
|
||||
"count": 42,
|
||||
"limit": 400
|
||||
}
|
||||
```
|
||||
|
||||
**Auto-reset:** When `date` != today (UTC), count resets to 0.
|
||||
|
||||
**Override limit:** `export STITCH_QUOTA_LIMIT="300"` (if Google increases limits).
|
||||
|
||||
## Warning Thresholds
|
||||
|
||||
| Remaining | Action |
|
||||
|-----------|--------|
|
||||
| > 20% | Normal operation |
|
||||
| < 20% | `[!] Low quota` warning printed |
|
||||
| 0 | `[X] Exhausted` — exit code 2, suggest fallback |
|
||||
|
||||
## Conservation Tips
|
||||
|
||||
1. **Use variants instead of regenerating** — 3 variants = 3 credits vs regenerating 3 times = 3 credits, but variants are more purposeful
|
||||
2. **Use `screen.edit()` to refine** — Editing costs 1 credit but preserves context
|
||||
3. **Export early** — Don't regenerate just to see the design again; export HTML/image once
|
||||
4. **Batch planning** — Plan all designs for the day, generate in one session
|
||||
5. **Review prompts** — Better prompts = fewer regenerations
|
||||
|
||||
## Fallback Workflow
|
||||
|
||||
When quota is exhausted:
|
||||
|
||||
1. `stitch-quota.ts check` returns exit code 2
|
||||
2. Skill prints: "Daily quota exhausted. Use ck:ui-ux-pro-max as fallback."
|
||||
3. Activate `ck:ui-ux-pro-max` with the same design prompt
|
||||
4. `ui-ux-pro-max` generates text-based design spec (no external API needed)
|
||||
5. Proceed with implementation using text-based spec
|
||||
|
||||
## Drift Warning
|
||||
|
||||
Local tracking can drift if user generates designs outside ClaudeKit (via Stitch web UI or other tools). If you hit `RATE_LIMITED` error despite local tracker showing credits available:
|
||||
|
||||
1. Run `npx tsx stitch-quota.ts reset`
|
||||
2. Set count to match actual usage (or leave at 0 if unknown)
|
||||
3. Stitch API itself enforces the real limit — local tracker is advisory only
|
||||
95
.opencode/skills/stitch/references/stitch-mcp-setup.md
Normal file
95
.opencode/skills/stitch/references/stitch-mcp-setup.md
Normal file
@@ -0,0 +1,95 @@
|
||||
# Stitch MCP Server Setup
|
||||
|
||||
Three options for connecting Google Stitch as an MCP server with Claude Code.
|
||||
|
||||
## Option A: API Key (Recommended for Most Users)
|
||||
|
||||
Simplest setup. No Google Cloud dependency.
|
||||
|
||||
### 1. Get API Key
|
||||
|
||||
1. Sign in at https://stitch.withgoogle.com
|
||||
2. Go to Settings -> API Keys
|
||||
3. Click "Generate New Key"
|
||||
4. Copy `sk_...` key
|
||||
|
||||
### 2. Add to `.opencode/.mcp.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"stitch": {
|
||||
"command": "npx",
|
||||
"args": ["@_davideast/stitch-mcp", "proxy"],
|
||||
"env": {
|
||||
"STITCH_API_KEY": "sk_your_key_here"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Verify
|
||||
|
||||
Restart Claude Code. You should see Stitch tools available:
|
||||
- `create_project` — Create new design project
|
||||
- `generate_screen` — Generate UI from prompt
|
||||
- `export_html` — Export as HTML/Tailwind
|
||||
- `export_image` — Export screenshot
|
||||
|
||||
## Option B: Google Cloud (For GCP Users)
|
||||
|
||||
Uses gcloud credentials. Zero API key management.
|
||||
|
||||
### 1. Setup gcloud
|
||||
|
||||
```bash
|
||||
gcloud auth login
|
||||
gcloud config set project YOUR_PROJECT_ID
|
||||
gcloud beta services mcp enable stitch.googleapis.com
|
||||
```
|
||||
|
||||
### 2. Add to `.opencode/.mcp.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"mcpServers": {
|
||||
"stitch": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "stitch-mcp"],
|
||||
"env": {
|
||||
"GOOGLE_CLOUD_PROJECT": "your-gcp-project-id"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Option C: Auto-Installer (Interactive)
|
||||
|
||||
Guided wizard, auto-detects environment.
|
||||
|
||||
```bash
|
||||
npx stitch-mcp-auto
|
||||
# Opens browser at http://localhost:8086
|
||||
# Follow wizard to configure
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Symptom | Fix |
|
||||
|---------|-----|
|
||||
| "AUTH_FAILED" on startup | Verify API key or re-run `gcloud auth login` |
|
||||
| Tools not appearing | Restart Claude Code after config change |
|
||||
| Timeout on generation | Stitch is processing; wait 10-30s for complex designs |
|
||||
| "RATE_LIMITED" errors | Daily quota exceeded; wait until midnight UTC |
|
||||
|
||||
## MCP Config Location
|
||||
|
||||
| OS | Path |
|
||||
|----|------|
|
||||
| macOS | `~/Library/Application Support/Claude/claude_desktop_config.json` |
|
||||
| Windows | `%AppData%\Claude\claude_desktop_config.json` |
|
||||
| Linux | `~/.config/Claude/claude_desktop_config.json` |
|
||||
|
||||
For Claude Code CLI: use `.opencode/.mcp.json` in project root (preferred for ClaudeKit).
|
||||
136
.opencode/skills/stitch/references/stitch-sdk-api.md
Normal file
136
.opencode/skills/stitch/references/stitch-sdk-api.md
Normal file
@@ -0,0 +1,136 @@
|
||||
# Stitch SDK API Reference
|
||||
|
||||
Condensed reference for `@google/stitch-sdk`. Agent-optimized — covers common operations only.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npm install @google/stitch-sdk
|
||||
# Optional: Vercel AI SDK integration
|
||||
npm install @google/stitch-sdk ai
|
||||
```
|
||||
|
||||
## Authentication
|
||||
|
||||
```bash
|
||||
export STITCH_API_KEY="sk_..." # From https://stitch.withgoogle.com/settings/api
|
||||
```
|
||||
|
||||
SDK auto-reads `STITCH_API_KEY` from environment. No explicit config needed.
|
||||
|
||||
## Core API
|
||||
|
||||
### Stitch (root singleton)
|
||||
|
||||
```typescript
|
||||
import { stitch } from "@google/stitch-sdk";
|
||||
|
||||
const projects = await stitch.projects(); // List all projects
|
||||
const project = stitch.project("my-id"); // Get project handle (sync)
|
||||
```
|
||||
|
||||
### Project
|
||||
|
||||
```typescript
|
||||
const project = stitch.project("project-123"); // sync — returns handle
|
||||
|
||||
// Generate screen from prompt (positional params, uppercase device types)
|
||||
const screen = await project.generate(
|
||||
"Login page with email/password",
|
||||
"MOBILE" // optional: "MOBILE" | "DESKTOP" | "TABLET" | "AGNOSTIC"
|
||||
);
|
||||
|
||||
const screens = await project.screens(); // List all screens
|
||||
const screen = await project.getScreen(screenId); // Get specific screen
|
||||
```
|
||||
|
||||
### Screen
|
||||
|
||||
```typescript
|
||||
// Export
|
||||
const htmlUrl = await screen.getHtml(); // Returns download URL (HTML + Tailwind)
|
||||
const imageUrl = await screen.getImage(); // Returns download URL (PNG screenshot)
|
||||
|
||||
// Edit/refine (positional: prompt, deviceType?, modelId?)
|
||||
const edited = await screen.edit("Make colors darker, add search bar");
|
||||
|
||||
// Generate variants (positional: prompt, variantOptions, deviceType?, modelId?)
|
||||
const variants = await screen.variants("Different color schemes", {
|
||||
variantCount: 3, // 1-5 variants
|
||||
creativeRange: "medium", // "low" | "medium" | "high"
|
||||
aspects: ["COLOR_SCHEME"] // "COLOR_SCHEME" | "LAYOUT" | etc.
|
||||
});
|
||||
```
|
||||
|
||||
### StitchToolClient (low-level MCP access)
|
||||
|
||||
```typescript
|
||||
import { StitchToolClient } from "@google/stitch-sdk";
|
||||
|
||||
const client = new StitchToolClient({ apiKey: "sk_..." });
|
||||
const tools = await client.listTools();
|
||||
const result = await client.callTool("create_project", { title: "My App" });
|
||||
```
|
||||
|
||||
### Vercel AI SDK Integration
|
||||
|
||||
```typescript
|
||||
import { stitchTools } from "@google/stitch-sdk/ai";
|
||||
import { generateText } from "ai";
|
||||
|
||||
const { text } = await generateText({
|
||||
model: yourModel,
|
||||
tools: stitchTools(),
|
||||
prompt: "Create a modern dashboard"
|
||||
});
|
||||
```
|
||||
|
||||
## Type Definitions
|
||||
|
||||
```typescript
|
||||
type DeviceType = "DEVICE_TYPE_UNSPECIFIED" | "MOBILE" | "DESKTOP" | "TABLET" | "AGNOSTIC";
|
||||
type ModelId = "MODEL_ID_UNSPECIFIED" | "GEMINI_3_PRO" | "GEMINI_3_FLASH";
|
||||
|
||||
interface Stitch {
|
||||
projects(): Promise<Project[]>;
|
||||
createProject(title?: string): Promise<Project>;
|
||||
project(id: string): Project; // sync — returns handle, no API call
|
||||
}
|
||||
|
||||
interface Project {
|
||||
id: string;
|
||||
generate(prompt: string, deviceType?: DeviceType, modelId?: ModelId): Promise<Screen>;
|
||||
screens(): Promise<Screen[]>;
|
||||
getScreen(screenId: string): Promise<Screen>;
|
||||
}
|
||||
|
||||
interface Screen {
|
||||
id: string;
|
||||
getHtml(): Promise<string>; // Download URL
|
||||
getImage(): Promise<string>; // Download URL
|
||||
edit(prompt: string, deviceType?: DeviceType, modelId?: ModelId): Promise<Screen>;
|
||||
variants(prompt: string, variantOptions: any, deviceType?: DeviceType, modelId?: ModelId): Promise<Screen[]>;
|
||||
}
|
||||
|
||||
class StitchError extends Error {
|
||||
code: "AUTH_FAILED" | "NOT_FOUND" | "RATE_LIMITED" | string;
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
| Code | Meaning | Action |
|
||||
|------|---------|--------|
|
||||
| `AUTH_FAILED` | Bad/missing API key | Check `STITCH_API_KEY` env var |
|
||||
| `NOT_FOUND` | Screen/project doesn't exist | Verify ID |
|
||||
| `RATE_LIMITED` | Daily quota exceeded | Wait until midnight UTC or use fallback |
|
||||
|
||||
```typescript
|
||||
try {
|
||||
const screen = await project.generate(prompt);
|
||||
} catch (error) {
|
||||
if (error.code === "RATE_LIMITED") {
|
||||
console.error("Quota exceeded — use ck:ui-ux-pro-max fallback");
|
||||
}
|
||||
}
|
||||
```
|
||||
17
.opencode/skills/stitch/scripts/package.json
Normal file
17
.opencode/skills/stitch/scripts/package.json
Normal file
@@ -0,0 +1,17 @@
|
||||
{
|
||||
"name": "stitch-scripts",
|
||||
"version": "1.0.0",
|
||||
"type": "module",
|
||||
"description": "ClaudeKit Stitch skill SDK wrapper scripts",
|
||||
"scripts": {
|
||||
"generate": "tsx stitch-generate.ts",
|
||||
"export": "tsx stitch-export.ts",
|
||||
"quota": "tsx stitch-quota.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@google/stitch-sdk": ">=0.0.3 <1.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"tsx": "^4.0.0"
|
||||
}
|
||||
}
|
||||
173
.opencode/skills/stitch/scripts/stitch-export.ts
Normal file
173
.opencode/skills/stitch/scripts/stitch-export.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* stitch-export.ts — Export Stitch designs as HTML, image, or DESIGN.md.
|
||||
*
|
||||
* Usage:
|
||||
* npx tsx stitch-export.ts <screen-id> [--project <id>] [--format html|image|all] [--output <dir>]
|
||||
*
|
||||
* Env: STITCH_API_KEY (required), STITCH_PROJECT_ID (optional default)
|
||||
*/
|
||||
|
||||
import { stitch } from "@google/stitch-sdk";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
|
||||
// -- Argument parsing --
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
function getFlag(name: string): string | undefined {
|
||||
const idx = args.indexOf(`--${name}`);
|
||||
if (idx === -1 || idx + 1 >= args.length) return undefined;
|
||||
return args[idx + 1];
|
||||
}
|
||||
|
||||
// Extract positional args (skip flags and their values)
|
||||
function getPositionalArgs(): string[] {
|
||||
const positional: string[] = [];
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i].startsWith("--")) {
|
||||
i++; // skip flag value
|
||||
} else {
|
||||
positional.push(args[i]);
|
||||
}
|
||||
}
|
||||
return positional;
|
||||
}
|
||||
|
||||
const screenId = getPositionalArgs()[0];
|
||||
const projectId =
|
||||
getFlag("project") || process.env.STITCH_PROJECT_ID || "claudekit-default";
|
||||
const format = getFlag("format") || "all";
|
||||
const outputDir = getFlag("output") || "./stitch-exports";
|
||||
|
||||
if (!screenId) {
|
||||
console.error("Usage: npx tsx stitch-export.ts <screen-id> [--project <id>] [--format html|image|all] [--output <dir>]");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!process.env.STITCH_API_KEY) {
|
||||
console.error("[X] STITCH_API_KEY not set. Get one at https://stitch.withgoogle.com/settings/api");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// -- Helpers --
|
||||
|
||||
async function downloadFile(url: string, dest: string): Promise<void> {
|
||||
const response = await fetch(url);
|
||||
if (!response.ok) throw new Error(`Download failed: ${response.status} ${response.statusText}`);
|
||||
const buffer = Buffer.from(await response.arrayBuffer());
|
||||
fs.writeFileSync(dest, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a DESIGN.md from HTML content by extracting Tailwind classes,
|
||||
* color values, typography patterns, and component structure.
|
||||
*/
|
||||
function generateDesignMd(html: string): string {
|
||||
const lines: string[] = ["# Design System", "", "Auto-generated from Google Stitch export.", ""];
|
||||
|
||||
// Extract colors from Tailwind classes and inline styles
|
||||
const colorMatches = html.match(/(?:bg|text|border)-(?:\w+)-(\d+)/g) || [];
|
||||
const uniqueColors = [...new Set(colorMatches)].slice(0, 20);
|
||||
if (uniqueColors.length > 0) {
|
||||
lines.push("## Colors", "");
|
||||
uniqueColors.forEach((c) => lines.push(`- \`${c}\``));
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
// Extract typography classes
|
||||
const textMatches = html.match(/(?:text-(?:xs|sm|base|lg|xl|2xl|3xl|4xl|5xl)|font-(?:thin|light|normal|medium|semibold|bold|extrabold))/g) || [];
|
||||
const uniqueText = [...new Set(textMatches)].slice(0, 15);
|
||||
if (uniqueText.length > 0) {
|
||||
lines.push("## Typography", "");
|
||||
uniqueText.forEach((t) => lines.push(`- \`${t}\``));
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
// Extract spacing patterns
|
||||
const spacingMatches = html.match(/(?:p|m|gap|space)-(?:x|y)?-?\d+/g) || [];
|
||||
const uniqueSpacing = [...new Set(spacingMatches)].slice(0, 15);
|
||||
if (uniqueSpacing.length > 0) {
|
||||
lines.push("## Spacing", "");
|
||||
uniqueSpacing.forEach((s) => lines.push(`- \`${s}\``));
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
// Extract component-level structure from HTML tags
|
||||
const componentMatches = html.match(/<(section|nav|header|footer|main|aside|form|button|input|table|dialog)[^>]*>/gi) || [];
|
||||
const uniqueComponents = [...new Set(componentMatches.map((c) => c.match(/<(\w+)/)?.[1] || ""))].filter(Boolean);
|
||||
if (uniqueComponents.length > 0) {
|
||||
lines.push("## Components", "");
|
||||
uniqueComponents.forEach((c) => lines.push(`- \`<${c}>\``));
|
||||
lines.push("");
|
||||
}
|
||||
|
||||
lines.push("## Notes", "", "- Generated by Google Stitch AI", "- Tailwind CSS utility classes used throughout", "- Review and customize colors/typography for brand alignment");
|
||||
|
||||
return lines.join("\n");
|
||||
}
|
||||
|
||||
// -- Main --
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
|
||||
console.error(`[i] Exporting screen ${screenId} from project ${projectId}`);
|
||||
// Resolve project handle — for default project, find by title first
|
||||
let project;
|
||||
if (projectId === "claudekit-default") {
|
||||
const projects = await stitch.projects();
|
||||
const found = projects.find(p => p.data?.title === "claudekit-default");
|
||||
project = found || stitch.project(projectId);
|
||||
} else {
|
||||
project = stitch.project(projectId);
|
||||
}
|
||||
const screen = await project.getScreen(screenId!);
|
||||
|
||||
const exported: Record<string, string> = {};
|
||||
|
||||
// Export HTML
|
||||
if (format === "html" || format === "all") {
|
||||
const htmlUrl = await screen.getHtml();
|
||||
const htmlPath = path.join(outputDir, "design.html");
|
||||
await downloadFile(htmlUrl, htmlPath);
|
||||
exported.html = htmlPath;
|
||||
console.error(`[OK] HTML exported: ${htmlPath}`);
|
||||
|
||||
// Generate DESIGN.md from HTML
|
||||
if (format === "all") {
|
||||
const htmlContent = fs.readFileSync(htmlPath, "utf-8");
|
||||
const designMd = generateDesignMd(htmlContent);
|
||||
const designPath = path.join(outputDir, "DESIGN.md");
|
||||
fs.writeFileSync(designPath, designMd);
|
||||
exported.designMd = designPath;
|
||||
console.error(`[OK] DESIGN.md generated: ${designPath}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Export image
|
||||
if (format === "image" || format === "all") {
|
||||
const imageUrl = await screen.getImage();
|
||||
const imagePath = path.join(outputDir, "design.png");
|
||||
await downloadFile(imageUrl, imagePath);
|
||||
exported.image = imagePath;
|
||||
console.error(`[OK] Image exported: ${imagePath}`);
|
||||
}
|
||||
|
||||
// Output result JSON to stdout
|
||||
console.log(JSON.stringify({ screenId, projectId, format, exported }, null, 2));
|
||||
} catch (error: unknown) {
|
||||
const err = error as { code?: string; message?: string };
|
||||
if (err.code === "NOT_FOUND") {
|
||||
console.error(`[X] Screen "${screenId}" not found in project "${projectId}".`);
|
||||
} else if (err.code === "AUTH_FAILED") {
|
||||
console.error("[X] Authentication failed. Check STITCH_API_KEY env var.");
|
||||
} else {
|
||||
console.error(`[X] Export error: ${err.message || error}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
182
.opencode/skills/stitch/scripts/stitch-generate.ts
Normal file
182
.opencode/skills/stitch/scripts/stitch-generate.ts
Normal file
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* stitch-generate.ts — Generate UI designs from text prompts via Google Stitch SDK.
|
||||
*
|
||||
* Usage:
|
||||
* npx tsx stitch-generate.ts "<prompt>" [--project <id>] [--device mobile|desktop|tablet] [--variants <count>]
|
||||
*
|
||||
* Env: STITCH_API_KEY (required), STITCH_PROJECT_ID (optional default)
|
||||
*/
|
||||
|
||||
import { stitch } from "@google/stitch-sdk";
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
|
||||
// -- Quota helpers (inline to avoid cross-script import issues) --
|
||||
|
||||
const QUOTA_DIR = path.join(os.homedir(), ".claudekit");
|
||||
const QUOTA_FILE = path.join(QUOTA_DIR, ".stitch-quota.json");
|
||||
// Stitch free tier: 400 daily credits. No API to fetch real usage.
|
||||
const DEFAULT_LIMIT = parseInt(process.env.STITCH_QUOTA_LIMIT || "400", 10);
|
||||
|
||||
interface QuotaState { date: string; count: number; limit: number; }
|
||||
|
||||
function todayUTC(): string { return new Date().toISOString().slice(0, 10); }
|
||||
|
||||
function loadQuota(): QuotaState {
|
||||
try {
|
||||
if (fs.existsSync(QUOTA_FILE)) {
|
||||
const data = JSON.parse(fs.readFileSync(QUOTA_FILE, "utf-8"));
|
||||
if (data.date !== todayUTC()) return { date: todayUTC(), count: 0, limit: data.limit || DEFAULT_LIMIT };
|
||||
return data;
|
||||
}
|
||||
} catch { /* corrupted — start fresh */ }
|
||||
return { date: todayUTC(), count: 0, limit: DEFAULT_LIMIT };
|
||||
}
|
||||
|
||||
function saveQuota(state: QuotaState): void {
|
||||
fs.mkdirSync(QUOTA_DIR, { recursive: true });
|
||||
fs.writeFileSync(QUOTA_FILE, JSON.stringify(state, null, 2));
|
||||
}
|
||||
|
||||
// -- Argument parsing (minimal, no deps) --
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
function getFlag(name: string): string | undefined {
|
||||
const idx = args.indexOf(`--${name}`);
|
||||
if (idx === -1 || idx + 1 >= args.length) return undefined;
|
||||
return args[idx + 1];
|
||||
}
|
||||
|
||||
// Extract positional args (skip flags and their values)
|
||||
function getPositionalArgs(): string[] {
|
||||
const positional: string[] = [];
|
||||
for (let i = 0; i < args.length; i++) {
|
||||
if (args[i].startsWith("--")) {
|
||||
i++; // skip flag value
|
||||
} else {
|
||||
positional.push(args[i]);
|
||||
}
|
||||
}
|
||||
return positional;
|
||||
}
|
||||
|
||||
const prompt = getPositionalArgs()[0];
|
||||
const projectId =
|
||||
getFlag("project") || process.env.STITCH_PROJECT_ID || "claudekit-default";
|
||||
// SDK expects uppercase device types: MOBILE, DESKTOP, TABLET, AGNOSTIC
|
||||
const deviceFlag = getFlag("device");
|
||||
const DEVICE_MAP: Record<string, "MOBILE" | "DESKTOP" | "TABLET"> = {
|
||||
mobile: "MOBILE", desktop: "DESKTOP", tablet: "TABLET",
|
||||
};
|
||||
const deviceType = deviceFlag
|
||||
? DEVICE_MAP[deviceFlag.toLowerCase()] || (deviceFlag.toUpperCase() as "MOBILE" | "DESKTOP" | "TABLET")
|
||||
: undefined;
|
||||
const variantCount = getFlag("variants") ? parseInt(getFlag("variants")!, 10) : 0;
|
||||
|
||||
if (!prompt) {
|
||||
console.error("Usage: npx tsx stitch-generate.ts <prompt> [--project <id>] [--device mobile|desktop|tablet] [--variants <count>]");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!process.env.STITCH_API_KEY) {
|
||||
console.error("[X] STITCH_API_KEY not set. Get one at https://stitch.withgoogle.com/settings/api");
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// -- Main --
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
// Pre-check quota — 1 credit per generate, 1 per variant
|
||||
const creditsNeeded = 1 + variantCount;
|
||||
const quota = loadQuota();
|
||||
const remaining = quota.limit - quota.count;
|
||||
if (remaining < creditsNeeded) {
|
||||
console.error(`[X] Not enough credits: need ${creditsNeeded}, have ${remaining}/${quota.limit}.`);
|
||||
console.error("[i] Use ck:ui-ux-pro-max as fallback, or wait until midnight UTC.");
|
||||
process.exit(2);
|
||||
}
|
||||
console.error(`[i] Credits: ${remaining}/${quota.limit} remaining (this run costs ${creditsNeeded})`);
|
||||
console.error(`[i] Prompt: "${prompt}"`);
|
||||
|
||||
// Resolve project — use existing or create if "claudekit-default" doesn't exist
|
||||
const isDefaultProject = projectId === "claudekit-default";
|
||||
let resolvedProjectId = projectId;
|
||||
if (isDefaultProject) {
|
||||
const projects = await stitch.projects();
|
||||
const existing = projects.find(p => p.data?.title === "claudekit-default");
|
||||
if (existing) {
|
||||
resolvedProjectId = existing.id;
|
||||
console.error(`[i] Using project: ${resolvedProjectId}`);
|
||||
} else {
|
||||
console.error("[i] Creating default project 'claudekit-default'...");
|
||||
const created = await stitch.createProject("claudekit-default");
|
||||
resolvedProjectId = created.id;
|
||||
console.error(`[OK] Created project: ${resolvedProjectId}`);
|
||||
}
|
||||
} else {
|
||||
console.error(`[i] Using project: ${resolvedProjectId}`);
|
||||
}
|
||||
// Always use a fresh handle for generation
|
||||
const project = stitch.project(resolvedProjectId);
|
||||
|
||||
// SDK signature: generate(prompt, deviceType?, modelId?)
|
||||
const screen = await project.generate(prompt!, deviceType);
|
||||
|
||||
const imageUrl = await screen.getImage();
|
||||
|
||||
const result: Record<string, unknown> = {
|
||||
screenId: screen.id,
|
||||
projectId: resolvedProjectId,
|
||||
imageUrl,
|
||||
prompt,
|
||||
};
|
||||
|
||||
// Generate variants if requested
|
||||
if (variantCount > 0) {
|
||||
console.error(`[i] Generating ${variantCount} variant(s)...`);
|
||||
const variants = await screen.variants("Generate design variants", {
|
||||
variantCount,
|
||||
creativeRange: "medium",
|
||||
});
|
||||
|
||||
result.variants = await Promise.all(
|
||||
variants.map(async (v) => ({
|
||||
screenId: v.id,
|
||||
imageUrl: await v.getImage(),
|
||||
}))
|
||||
);
|
||||
}
|
||||
|
||||
// Auto-increment quota tracker
|
||||
const postQuota = loadQuota();
|
||||
postQuota.count += creditsNeeded;
|
||||
saveQuota(postQuota);
|
||||
const postRemaining = postQuota.limit - postQuota.count;
|
||||
console.error(`[OK] Quota updated: ${postQuota.count}/${postQuota.limit} used (${postRemaining} remaining)`);
|
||||
|
||||
result.creditsUsed = creditsNeeded;
|
||||
result.creditsRemaining = postRemaining;
|
||||
|
||||
// Output JSON to stdout (logs go to stderr)
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
} catch (error: unknown) {
|
||||
const err = error as { code?: string; message?: string };
|
||||
if (err.code === "RATE_LIMITED") {
|
||||
// Auto-sync local tracker — API is the source of truth
|
||||
const q = loadQuota();
|
||||
q.count = q.limit;
|
||||
saveQuota(q);
|
||||
console.error("[X] Daily quota exceeded (local tracker synced). Try tomorrow or use ck:ui-ux-pro-max.");
|
||||
} else if (err.code === "AUTH_FAILED") {
|
||||
console.error("[X] Authentication failed. Check STITCH_API_KEY env var.");
|
||||
} else {
|
||||
console.error(`[X] Stitch error: ${err.message || error}`);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
123
.opencode/skills/stitch/scripts/stitch-quota.ts
Normal file
123
.opencode/skills/stitch/scripts/stitch-quota.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* stitch-quota.ts — Local quota tracker for Google Stitch daily credits.
|
||||
*
|
||||
* Usage:
|
||||
* npx tsx stitch-quota.ts check # Show remaining credits
|
||||
* npx tsx stitch-quota.ts increment # Bump count after generation
|
||||
* npx tsx stitch-quota.ts reset # Force reset counter
|
||||
*
|
||||
* Tracks usage in ~/.claudekit/.stitch-quota.json.
|
||||
* Auto-resets when date changes (UTC midnight).
|
||||
*/
|
||||
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import os from "os";
|
||||
|
||||
// -- Config --
|
||||
|
||||
const QUOTA_DIR = path.join(os.homedir(), ".claudekit");
|
||||
const QUOTA_FILE = path.join(QUOTA_DIR, ".stitch-quota.json");
|
||||
// Stitch free tier: 400 daily credits (generate), 15 redesign credits (edit)
|
||||
// Source: stitch.withgoogle.com dashboard. No API to fetch real usage.
|
||||
const DEFAULT_LIMIT = parseInt(process.env.STITCH_QUOTA_LIMIT || "400", 10);
|
||||
const WARN_THRESHOLD = 0.2; // Warn when <20% remaining
|
||||
|
||||
interface QuotaState {
|
||||
date: string;
|
||||
count: number;
|
||||
limit: number;
|
||||
}
|
||||
|
||||
// -- Helpers --
|
||||
|
||||
function todayUTC(): string {
|
||||
return new Date().toISOString().slice(0, 10);
|
||||
}
|
||||
|
||||
function loadQuota(): QuotaState {
|
||||
try {
|
||||
if (fs.existsSync(QUOTA_FILE)) {
|
||||
const data = JSON.parse(fs.readFileSync(QUOTA_FILE, "utf-8"));
|
||||
// Auto-reset if date changed
|
||||
if (data.date !== todayUTC()) {
|
||||
return { date: todayUTC(), count: 0, limit: data.limit || DEFAULT_LIMIT };
|
||||
}
|
||||
return data;
|
||||
}
|
||||
} catch {
|
||||
// Corrupted file — start fresh
|
||||
}
|
||||
return { date: todayUTC(), count: 0, limit: DEFAULT_LIMIT };
|
||||
}
|
||||
|
||||
function saveQuota(state: QuotaState): void {
|
||||
fs.mkdirSync(QUOTA_DIR, { recursive: true });
|
||||
fs.writeFileSync(QUOTA_FILE, JSON.stringify(state, null, 2));
|
||||
}
|
||||
|
||||
// -- Commands --
|
||||
|
||||
function check(): void {
|
||||
const state = loadQuota();
|
||||
saveQuota(state); // Persist auto-reset if date changed
|
||||
const remaining = state.limit - state.count;
|
||||
const pct = remaining / state.limit;
|
||||
|
||||
console.log(JSON.stringify({
|
||||
date: state.date,
|
||||
used: state.count,
|
||||
remaining,
|
||||
limit: state.limit,
|
||||
percentRemaining: Math.round(pct * 100),
|
||||
}, null, 2));
|
||||
|
||||
if (remaining <= 0) {
|
||||
console.error("[X] Daily quota exhausted. Use ck:ui-ux-pro-max as fallback.");
|
||||
process.exit(2);
|
||||
} else if (pct < WARN_THRESHOLD) {
|
||||
console.error(`[!] Low quota: ${remaining}/${state.limit} credits remaining (${Math.round(pct * 100)}%)`);
|
||||
} else {
|
||||
console.error(`[OK] ${remaining}/${state.limit} credits remaining`);
|
||||
}
|
||||
}
|
||||
|
||||
function increment(): void {
|
||||
const state = loadQuota();
|
||||
state.count += 1;
|
||||
saveQuota(state);
|
||||
|
||||
const remaining = state.limit - state.count;
|
||||
console.error(`[OK] Quota updated: ${state.count}/${state.limit} used (${remaining} remaining)`);
|
||||
|
||||
if (remaining <= 0) {
|
||||
console.error("[!] Daily quota now exhausted.");
|
||||
} else if (remaining / state.limit < WARN_THRESHOLD) {
|
||||
console.error(`[!] Low quota warning: ${remaining} credits remaining`);
|
||||
}
|
||||
}
|
||||
|
||||
function reset(): void {
|
||||
const state: QuotaState = { date: todayUTC(), count: 0, limit: DEFAULT_LIMIT };
|
||||
saveQuota(state);
|
||||
console.error(`[OK] Quota reset: 0/${state.limit} used`);
|
||||
}
|
||||
|
||||
// -- Main --
|
||||
|
||||
const command = process.argv[2];
|
||||
|
||||
switch (command) {
|
||||
case "check":
|
||||
check();
|
||||
break;
|
||||
case "increment":
|
||||
increment();
|
||||
break;
|
||||
case "reset":
|
||||
reset();
|
||||
break;
|
||||
default:
|
||||
console.error("Usage: npx tsx stitch-quota.ts <check|increment|reset>");
|
||||
process.exit(1);
|
||||
}
|
||||
Reference in New Issue
Block a user