Initial commit
This commit is contained in:
617
skills/animation-interaction-validator/SKILL.md
Normal file
617
skills/animation-interaction-validator/SKILL.md
Normal file
@@ -0,0 +1,617 @@
|
||||
---
|
||||
name: animation-interaction-validator
|
||||
description: Ensures engaging user experience through validation of animations, transitions, micro-interactions, and feedback states, preventing flat/static interfaces that lack polish and engagement. Works with Tanstack Start (React) + shadcn/ui components.
|
||||
triggers: ["interactive element creation", "event handler addition", "state changes", "async actions", "form submissions"]
|
||||
note: "Code examples use React/TSX with shadcn/ui components (Button, Card, Input). Adapt patterns to your component library."
|
||||
---
|
||||
|
||||
# Animation Interaction Validator SKILL
|
||||
|
||||
## Activation Patterns
|
||||
|
||||
This SKILL automatically activates when:
|
||||
- Interactive elements are created (buttons, links, forms, inputs)
|
||||
- Click, hover, or focus event handlers are added
|
||||
- Component state changes (loading, success, error)
|
||||
- Async operations are initiated (API calls, form submissions)
|
||||
- Navigation or routing transitions occur
|
||||
- Modal/dialog components are opened/closed
|
||||
- Lists or data are updated dynamically
|
||||
|
||||
## Expertise Provided
|
||||
|
||||
### Animation & Interaction Validation
|
||||
- **Transition Detection**: Ensures smooth state changes with CSS transitions
|
||||
- **Hover State Validation**: Checks for hover feedback on interactive elements
|
||||
- **Loading State Validation**: Ensures async actions have visual feedback
|
||||
- **Micro-interaction Analysis**: Validates small, delightful animations
|
||||
- **Focus State Validation**: Ensures keyboard navigation has visual feedback
|
||||
- **Animation Performance**: Checks for performant animation patterns
|
||||
|
||||
### Specific Checks Performed
|
||||
|
||||
#### ❌ Critical Issues (Missing Feedback)
|
||||
```tsx
|
||||
// These patterns trigger alerts:
|
||||
|
||||
// No hover state
|
||||
<Button onClick={submit}>Submit</Button>
|
||||
|
||||
// No loading state during async action
|
||||
<Button onClick={async () => await submitForm()}>Save</Button>
|
||||
|
||||
// Jarring state change (no transition)
|
||||
{showContent && <div>Content</div>}
|
||||
|
||||
// No focus state
|
||||
<a href="/page" className="text-blue-500">Link</a>
|
||||
|
||||
// Form without feedback
|
||||
<form onSubmit={handleSubmit}>
|
||||
<Input value={value} onChange={setValue} />
|
||||
<button type="submit">Submit</button>
|
||||
</form>
|
||||
```
|
||||
|
||||
#### ✅ Correct Interactive Patterns
|
||||
```tsx
|
||||
import { Button } from "@/components/ui/button"
|
||||
import { Input } from "@/components/ui/input"
|
||||
import { Send } from "lucide-react"
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
// These patterns are validated as correct:
|
||||
|
||||
// Hover state with smooth transition
|
||||
<Button
|
||||
className="transition-all duration-300 hover:scale-105 hover:shadow-xl active:scale-95"
|
||||
onClick={submit}
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
|
||||
// Loading state with visual feedback
|
||||
<Button
|
||||
disabled={isSubmitting}
|
||||
className="transition-all duration-200 group"
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
{!isSubmitting && (
|
||||
<Send className="h-4 w-4 transition-transform duration-300 group-hover:translate-x-1" />
|
||||
)}
|
||||
{isSubmitting ? 'Submitting...' : 'Submit'}
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
// Smooth state transition (using framer-motion or CSS)
|
||||
<div
|
||||
className={cn(
|
||||
"transition-all duration-300 ease-out",
|
||||
showContent ? "opacity-100 translate-y-0" : "opacity-0 translate-y-4"
|
||||
)}
|
||||
>
|
||||
{showContent && <div>Content</div>}
|
||||
</div>
|
||||
|
||||
// Focus state with ring
|
||||
<a
|
||||
href="/page"
|
||||
className="text-blue-500 transition-colors duration-200 hover:text-blue-700 focus:outline-none focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-2"
|
||||
>
|
||||
Link
|
||||
</a>
|
||||
|
||||
<!-- Form with success/error feedback -->
|
||||
<form onSubmit={(e) => { e.preventDefault(); handleSubmit" className="space-y-4">
|
||||
<Input
|
||||
value="value"
|
||||
error={errors.value"
|
||||
className="transition-all duration-200"
|
||||
/>
|
||||
|
||||
<Button
|
||||
type="submit"
|
||||
loading={isSubmitting"
|
||||
disabled={isSubmitting"
|
||||
className="transition-all duration-300 hover:scale-105"
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
|
||||
<!-- Success message with animation -->
|
||||
<Transition name="fade">
|
||||
<Alert
|
||||
if="showSuccess"
|
||||
color="green"
|
||||
icon="i-heroicons-check-circle"
|
||||
title="Success!"
|
||||
className="animate-in slide-in-from-top"
|
||||
/>
|
||||
</Transition>
|
||||
</form>
|
||||
```
|
||||
|
||||
## Integration Points
|
||||
|
||||
### Complementary to Existing Components
|
||||
- **frontend-design-specialist agent**: Provides design direction, SKILL validates implementation
|
||||
- **component-aesthetic-checker**: Validates component customization, SKILL validates interactions
|
||||
- **shadcn-ui-design-validator**: Catches generic patterns, SKILL ensures engagement
|
||||
- **accessibility-guardian agent**: Validates a11y, SKILL validates visual feedback
|
||||
|
||||
### Escalation Triggers
|
||||
- Complex animation sequences → `frontend-design-specialist` agent
|
||||
- Component interaction patterns → `tanstack-ui-architect` agent
|
||||
- Performance concerns → `edge-performance-oracle` agent
|
||||
- Accessibility issues → `accessibility-guardian` agent
|
||||
|
||||
## Validation Rules
|
||||
|
||||
### P1 - Critical (Missing User Feedback)
|
||||
- **No Hover States**: Buttons/links without hover effects
|
||||
- **No Loading States**: Async actions without loading indicators
|
||||
- **Jarring State Changes**: Content appearing/disappearing without transitions
|
||||
- **No Focus States**: Interactive elements without keyboard focus indicators
|
||||
- **Silent Errors**: Form errors without visual feedback
|
||||
|
||||
### P2 - Important (Enhanced Engagement)
|
||||
- **No Micro-interactions**: Icons/elements without subtle animations
|
||||
- **Static Navigation**: Page transitions without animations
|
||||
- **Abrupt Modals**: Dialogs opening without enter/exit transitions
|
||||
- **Instant Updates**: List changes without transition animations
|
||||
- **No Disabled States**: Buttons during processing without visual change
|
||||
|
||||
### P3 - Polish (Delightful UX)
|
||||
- **Limited Animation Variety**: Using only scale/opacity (no rotate, translate)
|
||||
- **Generic Durations**: Not tuning animation speed for context
|
||||
- **No Stagger**: List items appearing simultaneously (no stagger effect)
|
||||
- **Missing Success States**: Completed actions without celebration animation
|
||||
- **No Hover Anticipation**: No visual hint before interaction is possible
|
||||
|
||||
## Remediation Examples
|
||||
|
||||
### Fixing Missing Hover States
|
||||
```tsx
|
||||
<!-- ❌ Critical: No hover feedback -->
|
||||
<Button onClick="handleClick">
|
||||
Click me
|
||||
</Button>
|
||||
|
||||
<!-- ✅ Correct: Multi-dimensional hover effects -->
|
||||
<Button
|
||||
className="
|
||||
transition-all duration-300 ease-out
|
||||
hover:scale-105 hover:shadow-xl hover:-rotate-1
|
||||
active:scale-95 active:rotate-0
|
||||
focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-primary-500
|
||||
"
|
||||
onClick="handleClick"
|
||||
>
|
||||
<span className="inline-flex items-center gap-2">
|
||||
Click me
|
||||
<Icon
|
||||
name="i-heroicons-arrow-right"
|
||||
className="transition-transform duration-300 group-hover:translate-x-1"
|
||||
/>
|
||||
</span>
|
||||
</Button>
|
||||
```
|
||||
|
||||
### Fixing Missing Loading States
|
||||
```tsx
|
||||
<!-- ❌ Critical: No loading feedback during async action -->
|
||||
const submitForm = async () => {
|
||||
await api.submit(formData);
|
||||
};
|
||||
|
||||
<Button onClick="submitForm">
|
||||
Submit
|
||||
</Button>
|
||||
|
||||
<!-- ✅ Correct: Complete loading state with animations -->
|
||||
const isSubmitting = ref(false);
|
||||
const showSuccess = ref(false);
|
||||
|
||||
const submitForm = async () => {
|
||||
isSubmitting.value = true;
|
||||
try {
|
||||
await api.submit(formData);
|
||||
showSuccess.value = true;
|
||||
setTimeout(() => showSuccess.value = false, 3000);
|
||||
} catch (error) {
|
||||
// Error handling
|
||||
} finally {
|
||||
isSubmitting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
<div className="space-y-4">
|
||||
<Button
|
||||
loading={isSubmitting"
|
||||
disabled={isSubmitting"
|
||||
className="
|
||||
transition-all duration-300
|
||||
hover:scale-105 hover:shadow-xl
|
||||
disabled:opacity-50 disabled:cursor-not-allowed
|
||||
"
|
||||
onClick="submitForm"
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<Icon
|
||||
if="!isSubmitting"
|
||||
name="i-heroicons-paper-airplane"
|
||||
className="transition-all duration-300 group-hover:translate-x-1 group-hover:-translate-y-1"
|
||||
/>
|
||||
{ isSubmitting ? 'Submitting...' : 'Submit'}
|
||||
</span>
|
||||
</Button>
|
||||
|
||||
<!-- Success feedback with animation -->
|
||||
<Transition
|
||||
enter-active-className="transition-all duration-500 ease-out"
|
||||
enter-from-className="opacity-0 scale-50"
|
||||
enter-to-className="opacity-100 scale-100"
|
||||
leave-active-className="transition-all duration-300 ease-in"
|
||||
leave-from-className="opacity-100 scale-100"
|
||||
leave-to-className="opacity-0 scale-50"
|
||||
>
|
||||
<Alert
|
||||
if="showSuccess"
|
||||
color="green"
|
||||
icon="i-heroicons-check-circle"
|
||||
title="Success!"
|
||||
description="Your form has been submitted."
|
||||
/>
|
||||
</Transition>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Fixing Jarring State Changes
|
||||
```tsx
|
||||
<!-- ❌ Critical: Content appears/disappears abruptly -->
|
||||
<div>
|
||||
<Button onClick="showContent = !showContent">
|
||||
Toggle
|
||||
</Button>
|
||||
|
||||
<div if="showContent">
|
||||
<p>This content appears instantly (jarring)</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ✅ Correct: Smooth transitions -->
|
||||
<div className="space-y-4">
|
||||
<Button
|
||||
className="transition-all duration-300 hover:scale-105"
|
||||
onClick="showContent = !showContent"
|
||||
>
|
||||
{ showContent ? 'Hide' : 'Show'} Content
|
||||
</Button>
|
||||
|
||||
<Transition
|
||||
enter-active-className="transition-all duration-300 ease-out"
|
||||
enter-from-className="opacity-0 translate-y-4 scale-95"
|
||||
enter-to-className="opacity-100 translate-y-0 scale-100"
|
||||
leave-active-className="transition-all duration-200 ease-in"
|
||||
leave-from-className="opacity-100 translate-y-0 scale-100"
|
||||
leave-to-className="opacity-0 translate-y-4 scale-95"
|
||||
>
|
||||
<div if="showContent" className="p-6 bg-gray-50 dark:bg-gray-800 rounded-lg">
|
||||
<p>This content transitions smoothly</p>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Fixing Missing Focus States
|
||||
```tsx
|
||||
<!-- ❌ Critical: No visible focus state -->
|
||||
<nav>
|
||||
<a href="/" className="text-gray-700">Home</a>
|
||||
<a href="/about" className="text-gray-700">About</a>
|
||||
<a href="/contact" className="text-gray-700">Contact</a>
|
||||
</nav>
|
||||
|
||||
<!-- ✅ Correct: Clear focus states for keyboard navigation -->
|
||||
<nav className="flex gap-4">
|
||||
<a
|
||||
href="/"
|
||||
className="
|
||||
text-gray-700 dark:text-gray-300
|
||||
transition-all duration-200
|
||||
hover:text-primary-600 hover:translate-y-[-2px]
|
||||
focus:outline-none
|
||||
focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2
|
||||
rounded px-3 py-2
|
||||
"
|
||||
>
|
||||
Home
|
||||
</a>
|
||||
<a
|
||||
href="/about"
|
||||
className="
|
||||
text-gray-700 dark:text-gray-300
|
||||
transition-all duration-200
|
||||
hover:text-primary-600 hover:translate-y-[-2px]
|
||||
focus:outline-none
|
||||
focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2
|
||||
rounded px-3 py-2
|
||||
"
|
||||
>
|
||||
About
|
||||
</a>
|
||||
<a
|
||||
href="/contact"
|
||||
className="
|
||||
text-gray-700 dark:text-gray-300
|
||||
transition-all duration-200
|
||||
hover:text-primary-600 hover:translate-y-[-2px]
|
||||
focus:outline-none
|
||||
focus-visible:ring-2 focus-visible:ring-primary-500 focus-visible:ring-offset-2
|
||||
rounded px-3 py-2
|
||||
"
|
||||
>
|
||||
Contact
|
||||
</a>
|
||||
</nav>
|
||||
```
|
||||
|
||||
### Adding Micro-interactions
|
||||
```tsx
|
||||
<!-- ❌ P2: Static icons without micro-interactions -->
|
||||
<Button icon="i-heroicons-heart">
|
||||
Like
|
||||
</Button>
|
||||
|
||||
<!-- ✅ Correct: Animated icon micro-interaction -->
|
||||
const isLiked = ref(false);
|
||||
const heartScale = ref(1);
|
||||
|
||||
const toggleLike = () => {
|
||||
isLiked.value = !isLiked.value;
|
||||
|
||||
// Bounce animation
|
||||
heartScale.value = 1.3;
|
||||
setTimeout(() => heartScale.value = 1, 200);
|
||||
};
|
||||
|
||||
<Button
|
||||
:color="isLiked ? 'red' : 'gray'"
|
||||
className="transition-all duration-300 hover:scale-105"
|
||||
onClick="toggleLike"
|
||||
>
|
||||
<span className="inline-flex items-center gap-2">
|
||||
<Icon
|
||||
:name="isLiked ? 'i-heroicons-heart-solid' : 'i-heroicons-heart'"
|
||||
:style="{ transform: `scale(${heartScale})` }"
|
||||
:className="[
|
||||
'transition-all duration-200',
|
||||
isLiked ? 'text-red-500 animate-pulse' : 'text-gray-500'
|
||||
]"
|
||||
/>
|
||||
{ isLiked ? 'Liked' : 'Like'}
|
||||
</span>
|
||||
</Button>
|
||||
```
|
||||
|
||||
## Animation Best Practices
|
||||
|
||||
### Performance-First Animations
|
||||
|
||||
✅ **Performant Properties** (GPU-accelerated):
|
||||
- `transform` (translate, scale, rotate)
|
||||
- `opacity`
|
||||
- `filter` (backdrop-blur, etc.)
|
||||
|
||||
❌ **Avoid Animating** (causes reflow/repaint):
|
||||
- `width`, `height`
|
||||
- `top`, `left`, `right`, `bottom`
|
||||
- `margin`, `padding`
|
||||
- `border-width`
|
||||
|
||||
```tsx
|
||||
<!-- ❌ P2: Animating width (causes reflow) -->
|
||||
<div className="transition-all hover:w-64">Content</div>
|
||||
|
||||
<!-- ✅ Correct: Using transform (GPU-accelerated) -->
|
||||
<div className="transition-transform hover:scale-110">Content</div>
|
||||
```
|
||||
|
||||
### Animation Duration Guidelines
|
||||
|
||||
- **Fast** (100-200ms): Hover states, small movements
|
||||
- **Medium** (300-400ms): State changes, content transitions
|
||||
- **Slow** (500-800ms): Page transitions, major UI changes
|
||||
- **Very Slow** (1000ms+): Celebration animations, complex sequences
|
||||
|
||||
```tsx
|
||||
<!-- Context-appropriate durations -->
|
||||
<Button className="transition-all duration-200 hover:scale-105">
|
||||
<!-- Fast hover: 200ms -->
|
||||
</Button>
|
||||
|
||||
<Transition
|
||||
enter-active-className="transition-all duration-300"
|
||||
leave-active-className="transition-all duration-300"
|
||||
>
|
||||
<!-- Content change: 300ms -->
|
||||
<div if="show">Content</div>
|
||||
</Transition>
|
||||
|
||||
<div className="animate-in slide-in-from-bottom duration-500">
|
||||
<!-- Page load: 500ms -->
|
||||
Main content
|
||||
</div>
|
||||
```
|
||||
|
||||
### Easing Functions
|
||||
|
||||
- `ease-out`: Starting animations (entering content)
|
||||
- `ease-in`: Ending animations (exiting content)
|
||||
- `ease-in-out`: Bidirectional animations
|
||||
- `linear`: Loading spinners, continuous animations
|
||||
|
||||
```tsx
|
||||
<!-- Appropriate easing -->
|
||||
<Transition
|
||||
enter-active-className="transition-all duration-300 ease-out"
|
||||
leave-active-className="transition-all duration-200 ease-in"
|
||||
>
|
||||
<div if="show">Content</div>
|
||||
</Transition>
|
||||
```
|
||||
|
||||
## Advanced Interaction Patterns
|
||||
|
||||
### Staggered List Animations
|
||||
```tsx
|
||||
const items = ref([1, 2, 3, 4, 5]);
|
||||
|
||||
<TransitionGroup
|
||||
name="list"
|
||||
tag="div"
|
||||
className="space-y-2"
|
||||
>
|
||||
<div
|
||||
map((item, index) in items"
|
||||
:key="item"
|
||||
:style="{ transitionDelay: `${index * 50}ms` }"
|
||||
className="
|
||||
transition-all duration-300 ease-out
|
||||
hover:scale-105 hover:shadow-lg
|
||||
"
|
||||
>
|
||||
Item { item}
|
||||
</div>
|
||||
</TransitionGroup>
|
||||
|
||||
<style scoped>
|
||||
.list-enter-active,
|
||||
.list-leave-active {
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.list-enter-from {
|
||||
opacity: 0;
|
||||
transform: translateX(-20px);
|
||||
}
|
||||
|
||||
.list-leave-to {
|
||||
opacity: 0;
|
||||
transform: translateX(20px);
|
||||
}
|
||||
|
||||
.list-move {
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
### Success Celebration Animation
|
||||
```tsx
|
||||
const showSuccess = ref(false);
|
||||
|
||||
const celebrate = () => {
|
||||
showSuccess.value = true;
|
||||
// Confetti or celebration animation here
|
||||
setTimeout(() => showSuccess.value = false, 3000);
|
||||
};
|
||||
|
||||
<div>
|
||||
<Button
|
||||
onClick="celebrate"
|
||||
className="transition-all duration-300 hover:scale-110 hover:rotate-3"
|
||||
>
|
||||
Complete Task
|
||||
</Button>
|
||||
|
||||
<Transition
|
||||
enter-active-className="transition-all duration-500 ease-out"
|
||||
enter-from-className="opacity-0 scale-0 rotate-180"
|
||||
enter-to-className="opacity-100 scale-100 rotate-0"
|
||||
>
|
||||
<div
|
||||
if="showSuccess"
|
||||
className="fixed inset-0 flex items-center justify-center bg-black/20 backdrop-blur-sm"
|
||||
>
|
||||
<div className="bg-white dark:bg-gray-800 p-8 rounded-2xl shadow-2xl">
|
||||
<Icon
|
||||
name="i-heroicons-check-circle"
|
||||
className="w-16 h-16 text-green-500 animate-bounce"
|
||||
/>
|
||||
<p className="mt-4 text-xl font-heading">Success!</p>
|
||||
</div>
|
||||
</div>
|
||||
</Transition>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Loading Skeleton with Pulse
|
||||
```tsx
|
||||
<div if="loading" className="space-y-4">
|
||||
<div className="animate-pulse">
|
||||
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-3/4"></div>
|
||||
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-1/2 mt-2"></div>
|
||||
<div className="h-32 bg-gray-200 dark:bg-gray-700 rounded mt-4"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Transition
|
||||
enter-active-className="transition-all duration-500 ease-out"
|
||||
enter-from-className="opacity-0 translate-y-4"
|
||||
enter-to-className="opacity-100 translate-y-0"
|
||||
>
|
||||
<div if="!loading">
|
||||
<!-- Actual content -->
|
||||
</div>
|
||||
</Transition>
|
||||
```
|
||||
|
||||
## MCP Server Integration
|
||||
|
||||
While this SKILL doesn't directly use MCP servers, it complements MCP-enhanced agents:
|
||||
|
||||
- **shadcn/ui MCP**: Validates that suggested animations work with shadcn/ui components
|
||||
- **Cloudflare MCP**: Ensures animations don't bloat bundle size (performance check)
|
||||
|
||||
## Benefits
|
||||
|
||||
### Immediate Impact
|
||||
- **Prevents Flat UI**: Ensures engaging, polished interactions
|
||||
- **Improves Perceived Performance**: Loading states make waits feel shorter
|
||||
- **Better Accessibility**: Focus states improve keyboard navigation
|
||||
- **Professional Polish**: Micro-interactions signal quality
|
||||
|
||||
### Long-term Value
|
||||
- **Higher User Engagement**: Delightful animations encourage interaction
|
||||
- **Reduced Bounce Rate**: Polished UI keeps users engaged
|
||||
- **Better Brand Perception**: Professional animations signal quality
|
||||
- **Consistent UX**: All interactions follow same animation patterns
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### During Button Creation
|
||||
```tsx
|
||||
// Developer adds: <Button onClick="submit">Submit</Button>
|
||||
// SKILL immediately activates: "⚠️ P1: Button lacks hover state. Add transition utilities: class='transition-all duration-300 hover:scale-105'"
|
||||
```
|
||||
|
||||
### During Async Action
|
||||
```tsx
|
||||
// Developer creates: const submitForm = async () => { await api.call(); }
|
||||
// SKILL immediately activates: "⚠️ P1: Async action without loading state. Add :loading and :disabled props to button."
|
||||
```
|
||||
|
||||
### During State Toggle
|
||||
```tsx
|
||||
// Developer adds: <div if="show">Content</div>
|
||||
// SKILL immediately activates: "⚠️ P1: Content appears abruptly. Wrap with <Transition> for smooth state changes."
|
||||
```
|
||||
|
||||
### Before Deployment
|
||||
```tsx
|
||||
// SKILL runs comprehensive check: "✅ Animation validation passed. 45 interactive elements with hover states, 12 async actions with loading feedback, 8 smooth transitions detected."
|
||||
```
|
||||
|
||||
This SKILL ensures every interactive element provides engaging visual feedback, preventing the flat, static appearance that makes interfaces feel unpolished and reduces user engagement.
|
||||
134
skills/auth-security-validator/SKILL.md
Normal file
134
skills/auth-security-validator/SKILL.md
Normal file
@@ -0,0 +1,134 @@
|
||||
---
|
||||
name: auth-security-validator
|
||||
description: Autonomous validation of authentication security. Checks password hashing, cookie configuration, CSRF protection, and session management for OWASP compliance.
|
||||
triggers: ["auth file changes", "session config changes", "security-related modifications", "pre-deployment"]
|
||||
---
|
||||
|
||||
# Auth Security Validator SKILL
|
||||
|
||||
## Activation Patterns
|
||||
|
||||
This SKILL automatically activates when:
|
||||
- Files matching `**/auth/**` are created/modified
|
||||
- Session configuration files modified (app.config.ts, auth.ts)
|
||||
- Password hashing code changes
|
||||
- Cookie configuration changes
|
||||
- Before deployment operations
|
||||
|
||||
## Validation Rules
|
||||
|
||||
### P1 - Critical (Block Operations)
|
||||
|
||||
**Password Hashing**:
|
||||
- ✅ Uses Argon2id (`@node-rs/argon2`)
|
||||
- ❌ NOT using: bcrypt, MD5, SHA-256, plain text
|
||||
- ✅ Memory cost ≥ 19456 KB
|
||||
- ✅ Time cost ≥ 2 iterations
|
||||
|
||||
**Cookie Security**:
|
||||
- ✅ `secure: true` (HTTPS-only)
|
||||
- ✅ `httpOnly: true` (XSS prevention)
|
||||
- ✅ `sameSite: 'lax'` or `'strict'` (CSRF mitigation)
|
||||
|
||||
**Session Configuration**:
|
||||
- ✅ Session password/secret ≥ 32 characters
|
||||
- ✅ Max age configured (not infinite)
|
||||
|
||||
### P2 - Important (Warn)
|
||||
|
||||
**CSRF Protection**:
|
||||
- ⚠️ CSRF protection enabled (automatic in better-auth)
|
||||
- ⚠️ No custom form handlers bypassing CSRF
|
||||
|
||||
**Rate Limiting**:
|
||||
- ⚠️ Rate limiting on login endpoint
|
||||
- ⚠️ Rate limiting on register endpoint
|
||||
- ⚠️ Rate limiting on password reset
|
||||
|
||||
**Input Validation**:
|
||||
- ⚠️ Email format validation
|
||||
- ⚠️ Password minimum length (8+ characters)
|
||||
- ⚠️ Input sanitization
|
||||
|
||||
### P3 - Suggestions (Inform)
|
||||
|
||||
- ℹ️ Session rotation on privilege escalation
|
||||
- ℹ️ 2FA/MFA support
|
||||
- ℹ️ Account lockout after failed attempts
|
||||
- ℹ️ Password complexity requirements
|
||||
- ℹ️ OAuth state parameter validation
|
||||
|
||||
## Validation Output
|
||||
|
||||
```
|
||||
🔒 Authentication Security Validation
|
||||
|
||||
✅ P1 Checks (Critical):
|
||||
✅ Password hashing: Argon2id with correct params
|
||||
✅ Cookies: secure, httpOnly, sameSite configured
|
||||
✅ Session secret: 32+ characters
|
||||
|
||||
⚠️ P2 Checks (Important):
|
||||
⚠️ No rate limiting on login endpoint
|
||||
✅ Input validation present
|
||||
✅ CSRF protection enabled
|
||||
|
||||
ℹ️ P3 Suggestions:
|
||||
ℹ️ Consider adding session rotation
|
||||
ℹ️ Consider 2FA for sensitive operations
|
||||
|
||||
📋 Summary: 1 warning found
|
||||
💡 Run /es-auth-setup to fix issues
|
||||
```
|
||||
|
||||
## Security Patterns Detected
|
||||
|
||||
**Good Patterns** ✅:
|
||||
```typescript
|
||||
// Argon2id with correct params
|
||||
const hash = await argon2.hash(password, {
|
||||
memoryCost: 19456,
|
||||
timeCost: 2,
|
||||
outputLen: 32,
|
||||
parallelism: 1
|
||||
});
|
||||
|
||||
// Secure cookie config
|
||||
cookie: {
|
||||
secure: true,
|
||||
httpOnly: true,
|
||||
sameSite: 'lax'
|
||||
}
|
||||
```
|
||||
|
||||
**Bad Patterns** ❌:
|
||||
```typescript
|
||||
// Weak hashing
|
||||
const hash = crypto.createHash('sha256').update(password).digest('hex'); // ❌
|
||||
|
||||
// Insecure cookies
|
||||
cookie: {
|
||||
secure: false, // ❌
|
||||
httpOnly: false // ❌
|
||||
}
|
||||
|
||||
// Weak session secret
|
||||
password: '12345' // ❌ Too short
|
||||
```
|
||||
|
||||
## Escalation
|
||||
|
||||
Complex scenarios escalate to `better-auth-specialist` agent:
|
||||
- Custom authentication flows
|
||||
- Advanced OAuth configuration
|
||||
- Passkey implementation
|
||||
- Multi-factor authentication setup
|
||||
- Security audit requirements
|
||||
|
||||
## Notes
|
||||
|
||||
- Runs automatically on auth-related file changes
|
||||
- Can block deployments with P1 security issues
|
||||
- Follows OWASP Top 10 guidelines
|
||||
- Integrates with `/validate` and `/es-deploy` commands
|
||||
- Queries better-auth MCP for provider security requirements
|
||||
227
skills/cloudflare-security-checker/SKILL.md
Normal file
227
skills/cloudflare-security-checker/SKILL.md
Normal file
@@ -0,0 +1,227 @@
|
||||
---
|
||||
name: cloudflare-security-checker
|
||||
description: Automatically validates Cloudflare Workers security patterns during development, ensuring proper secret management, CORS configuration, and input validation
|
||||
triggers: ["authentication code", "secret handling", "API endpoints", "response creation", "database queries"]
|
||||
---
|
||||
|
||||
# Cloudflare Security Checker SKILL
|
||||
|
||||
## Activation Patterns
|
||||
|
||||
This SKILL automatically activates when:
|
||||
- Authentication or authorization code is detected
|
||||
- Secret management patterns are used
|
||||
- API endpoints or response creation is implemented
|
||||
- Database queries (D1) are written
|
||||
- CORS-related code is added
|
||||
- Input validation patterns are implemented
|
||||
|
||||
## Expertise Provided
|
||||
|
||||
### Workers-Specific Security Validation
|
||||
- **Secret Management**: Ensures proper `env` parameter usage vs hardcoded secrets
|
||||
- **CORS Configuration**: Validates Workers-specific CORS implementation
|
||||
- **Input Validation**: Checks for proper request validation patterns
|
||||
- **SQL Injection Prevention**: Ensures D1 prepared statements
|
||||
- **Authentication Patterns**: Validates JWT and API key handling
|
||||
- **Rate Limiting**: Identifies missing rate limiting patterns
|
||||
|
||||
### Specific Checks Performed
|
||||
|
||||
#### ❌ Critical Security Violations
|
||||
```typescript
|
||||
// These patterns trigger immediate alerts:
|
||||
const API_KEY = "sk_live_xxx"; // Hardcoded secret
|
||||
const secret = process.env.JWT_SECRET; // process.env doesn't exist
|
||||
const query = `SELECT * FROM users WHERE id = ${userId}`; // SQL injection
|
||||
```
|
||||
|
||||
#### ✅ Secure Workers Patterns
|
||||
```typescript
|
||||
// These patterns are validated as correct:
|
||||
const apiKey = env.API_KEY; // Proper env parameter
|
||||
const result = await env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(userId); // Prepared statement
|
||||
```
|
||||
|
||||
## Integration Points
|
||||
|
||||
### Complementary to Existing Components
|
||||
- **cloudflare-security-sentinel agent**: Handles comprehensive security audits, SKILL provides immediate validation
|
||||
- **workers-runtime-validator SKILL**: Complements runtime checks with security-specific validation
|
||||
- **es-deploy command**: SKILL prevents deployment of insecure code
|
||||
|
||||
### Escalation Triggers
|
||||
- Complex security architecture questions → `cloudflare-security-sentinel` agent
|
||||
- Advanced authentication patterns → `cloudflare-architecture-strategist` agent
|
||||
- Security incident response → `cloudflare-security-sentinel` agent
|
||||
|
||||
## Validation Rules
|
||||
|
||||
### P1 - Critical (Immediate Security Risk)
|
||||
- **Hardcoded Secrets**: API keys, passwords, tokens in code
|
||||
- **SQL Injection**: String concatenation in D1 queries
|
||||
- **Missing Authentication**: Sensitive endpoints without auth
|
||||
- **Process Env Usage**: `process.env` for secrets (doesn't work in Workers)
|
||||
|
||||
### P2 - High (Security Vulnerability)
|
||||
- **Missing Input Validation**: Direct use of `request.json()` without validation
|
||||
- **Improper CORS**: Missing CORS headers or overly permissive origins
|
||||
- **Missing Rate Limiting**: Public endpoints without rate limiting
|
||||
- **Secrets in Config**: Secrets in wrangler.toml `[vars]` section
|
||||
|
||||
### P3 - Medium (Security Best Practice)
|
||||
- **Missing Security Headers**: HTML responses without CSP/XSS protection
|
||||
- **Weak Authentication**: No resource-level authorization
|
||||
- **Insufficient Logging**: Security events not logged
|
||||
|
||||
## Remediation Examples
|
||||
|
||||
### Fixing Secret Management
|
||||
```typescript
|
||||
// ❌ Critical: Hardcoded secret
|
||||
const STRIPE_KEY = "sk_live_12345";
|
||||
|
||||
// ❌ Critical: process.env (doesn't exist)
|
||||
const apiKey = process.env.API_KEY;
|
||||
|
||||
// ✅ Correct: Workers secret management
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const apiKey = env.STRIPE_KEY; // From wrangler secret put
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fixing SQL Injection
|
||||
```typescript
|
||||
// ❌ Critical: SQL injection vulnerability
|
||||
const userId = url.searchParams.get('id');
|
||||
const result = await env.DB.prepare(`SELECT * FROM users WHERE id = ${userId}`).first();
|
||||
|
||||
// ✅ Correct: Prepared statement
|
||||
const userId = url.searchParams.get('id');
|
||||
const result = await env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(userId).first();
|
||||
```
|
||||
|
||||
### Fixing CORS Configuration
|
||||
```typescript
|
||||
// ❌ High: Missing CORS headers
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
return new Response(JSON.stringify(data));
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Correct: Workers CORS pattern
|
||||
function getCorsHeaders(origin: string) {
|
||||
const allowedOrigins = ['https://app.example.com'];
|
||||
const allowOrigin = allowedOrigins.includes(origin) ? origin : allowedOrigins[0];
|
||||
|
||||
return {
|
||||
'Access-Control-Allow-Origin': allowOrigin,
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
'Access-Control-Max-Age': '86400',
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const origin = request.headers.get('Origin') || '';
|
||||
|
||||
if (request.method === 'OPTIONS') {
|
||||
return new Response(null, { headers: getCorsHeaders(origin) });
|
||||
}
|
||||
|
||||
const response = new Response(JSON.stringify(data));
|
||||
Object.entries(getCorsHeaders(origin)).forEach(([k, v]) => {
|
||||
response.headers.set(k, v);
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fixing Input Validation
|
||||
```typescript
|
||||
// ❌ High: No input validation
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const data = await request.json(); // Could be anything
|
||||
await env.DB.prepare('INSERT INTO users (name) VALUES (?)').bind(data.name).run();
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Correct: Input validation with Zod
|
||||
import { z } from 'zod';
|
||||
|
||||
const UserSchema = z.object({
|
||||
name: z.string().min(1).max(100),
|
||||
email: z.string().email(),
|
||||
});
|
||||
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
// Size limit
|
||||
const contentLength = request.headers.get('Content-Length');
|
||||
if (contentLength && parseInt(contentLength) > 1024 * 100) {
|
||||
return new Response('Payload too large', { status: 413 });
|
||||
}
|
||||
|
||||
// Schema validation
|
||||
const data = await request.json();
|
||||
const result = UserSchema.safeParse(data);
|
||||
|
||||
if (!result.success) {
|
||||
return new Response(JSON.stringify(result.error), { status: 400 });
|
||||
}
|
||||
|
||||
// Safe to use validated data
|
||||
await env.DB.prepare('INSERT INTO users (name, email) VALUES (?, ?)')
|
||||
.bind(result.data.name, result.data.email).run();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## MCP Server Integration
|
||||
|
||||
When Cloudflare MCP server is available:
|
||||
- Query latest Cloudflare security best practices
|
||||
- Verify secrets are configured in account
|
||||
- Check for recent security events affecting the project
|
||||
- Get current security recommendations
|
||||
|
||||
## Benefits
|
||||
|
||||
### Immediate Impact
|
||||
- **Prevents Security Vulnerabilities**: Catches issues during development
|
||||
- **Educates on Workers Security**: Clear explanations of Workers-specific security patterns
|
||||
- **Reduces Security Debt**: Immediate feedback on security anti-patterns
|
||||
|
||||
### Long-term Value
|
||||
- **Consistent Security Standards**: Ensures all code follows Workers security best practices
|
||||
- **Faster Security Reviews**: Automated validation reduces manual review time
|
||||
- **Better Security Posture**: Proactive security validation vs reactive fixes
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### During Authentication Implementation
|
||||
```typescript
|
||||
// Developer types: const JWT_SECRET = "my-secret-key";
|
||||
// SKILL immediately activates: "❌ CRITICAL: Hardcoded JWT secret detected. Use wrangler secret put JWT_SECRET and access via env.JWT_SECRET"
|
||||
```
|
||||
|
||||
### During API Development
|
||||
```typescript
|
||||
// Developer types: const userId = url.searchParams.get('id');
|
||||
// SKILL immediately activates: "⚠️ HIGH: URL parameter not validated. Add schema validation before using in database queries."
|
||||
```
|
||||
|
||||
### During Database Query Writing
|
||||
```typescript
|
||||
// Developer types: `SELECT * FROM users WHERE id = ${userId}`
|
||||
// SKILL immediately activates: "❌ CRITICAL: SQL injection vulnerability. Use prepared statement: .prepare('SELECT * FROM users WHERE id = ?').bind(userId)"
|
||||
```
|
||||
|
||||
This SKILL ensures Workers security by providing immediate, autonomous validation of security patterns, preventing common vulnerabilities and ensuring proper Workers-specific security practices.
|
||||
537
skills/component-aesthetic-checker/SKILL.md
Normal file
537
skills/component-aesthetic-checker/SKILL.md
Normal file
@@ -0,0 +1,537 @@
|
||||
---
|
||||
name: component-aesthetic-checker
|
||||
description: Validates shadcn/ui component customization depth, ensuring components aren't used with default props and checking for consistent design system implementation across Tanstack Start applications
|
||||
triggers: ["shadcn ui component usage", "component prop changes", "design token updates", "className customization", "cn() utility usage"]
|
||||
note: "Updated for Tanstack Start (React) + shadcn/ui. Code examples use React/TSX with className and cn() utility for styling."
|
||||
---
|
||||
|
||||
# Component Aesthetic Checker SKILL
|
||||
|
||||
## Activation Patterns
|
||||
|
||||
This SKILL automatically activates when:
|
||||
- shadcn/ui components (`Button`, `Card`, `Input`, etc.) are used in `.react` files
|
||||
- Component props are added or modified
|
||||
- The `ui` prop is customized for component variants
|
||||
- Design system tokens are referenced in components
|
||||
- Multiple components are refactored together
|
||||
- Before component library updates
|
||||
|
||||
## Expertise Provided
|
||||
|
||||
### Component Customization Depth Analysis
|
||||
- **Default Prop Detection**: Identifies components using only default values
|
||||
- **UI Prop Validation**: Ensures `ui` prop is used for deep customization
|
||||
- **Design System Consistency**: Validates consistent pattern usage across components
|
||||
- **Spacing Patterns**: Checks for proper Tailwind spacing scale usage
|
||||
- **Icon Usage**: Validates consistent icon library and sizing
|
||||
- **Loading States**: Ensures async components have loading feedback
|
||||
|
||||
### Specific Checks Performed
|
||||
|
||||
#### ❌ Critical Issues (Insufficient Customization)
|
||||
```tsx
|
||||
<!-- These patterns trigger alerts: -->
|
||||
|
||||
<!-- Using default props only -->
|
||||
<Button onClick="submit">Submit</Button>
|
||||
|
||||
<!-- No UI prop customization -->
|
||||
<Card>
|
||||
<p>Content</p>
|
||||
</Card>
|
||||
|
||||
<!-- Inconsistent spacing -->
|
||||
<div className="p-4"> <!-- Random spacing values -->
|
||||
<Button className="mt-3 ml-2">Action</Button>
|
||||
</div>
|
||||
|
||||
<!-- Missing loading states -->
|
||||
<Button onClick="asyncAction">Save</Button> <!-- No :loading prop -->
|
||||
```
|
||||
|
||||
#### ✅ Correct Customized Patterns
|
||||
```tsx
|
||||
<!-- These patterns are validated as correct: -->
|
||||
|
||||
<!-- Deep customization with ui prop -->
|
||||
<Button
|
||||
color="brand-coral"
|
||||
size="lg"
|
||||
variant="solid"
|
||||
:ui="{
|
||||
font: 'font-heading',
|
||||
rounded: 'rounded-full',
|
||||
padding: { lg: 'px-8 py-4' }
|
||||
}"
|
||||
loading={isSubmitting"
|
||||
className="transition-all duration-300 hover:scale-105"
|
||||
onClick="submit"
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
|
||||
<!-- Fully customized card -->
|
||||
<Card
|
||||
:ui="{
|
||||
background: 'bg-white dark:bg-brand-midnight',
|
||||
ring: 'ring-1 ring-brand-coral/20',
|
||||
rounded: 'rounded-2xl',
|
||||
shadow: 'shadow-xl',
|
||||
body: { padding: 'p-8' },
|
||||
header: { padding: 'px-8 pt-8 pb-4' }
|
||||
}"
|
||||
className="transition-shadow duration-300 hover:shadow-2xl"
|
||||
>
|
||||
<template #header>
|
||||
<h3 className="font-heading text-2xl">Title</h3>
|
||||
<p className="text-gray-700 dark:text-gray-300">Content</p>
|
||||
</Card>
|
||||
|
||||
<!-- Consistent spacing (Tailwind scale) -->
|
||||
<div className="p-6 space-y-4">
|
||||
<Button className="mt-4">Action</Button>
|
||||
</div>
|
||||
|
||||
<!-- Proper loading state -->
|
||||
<Button
|
||||
loading={isSubmitting"
|
||||
disabled={isSubmitting"
|
||||
onClick="asyncAction"
|
||||
>
|
||||
{ isSubmitting ? 'Saving...' : 'Save'}
|
||||
</Button>
|
||||
```
|
||||
|
||||
## Integration Points
|
||||
|
||||
### Complementary to Existing Components
|
||||
- **tanstack-ui-architect agent**: Handles component selection and API guidance, SKILL validates implementation
|
||||
- **frontend-design-specialist agent**: Provides design direction, SKILL enforces consistency
|
||||
- **shadcn-ui-design-validator**: Catches generic patterns, SKILL ensures deep customization
|
||||
|
||||
### Escalation Triggers
|
||||
- Component API questions → `tanstack-ui-architect` agent (with MCP lookup)
|
||||
- Design consistency issues → `frontend-design-specialist` agent
|
||||
- Complex component composition → `/es-component` command
|
||||
- Full component audit → `/es-design-review` command
|
||||
|
||||
## Validation Rules
|
||||
|
||||
### P1 - Critical (Default Component Usage)
|
||||
- **No UI Prop Customization**: Using shadcn/ui components without `ui` prop
|
||||
- **All Default Props**: No color, size, variant, or other prop customizations
|
||||
- **Missing Loading States**: Async actions without `:loading` prop
|
||||
- **No Hover States**: Interactive components without hover feedback
|
||||
- **Inconsistent Patterns**: Same component with wildly different customizations
|
||||
|
||||
### P1 - Critical (Distributional Convergence Anti-Patterns)
|
||||
|
||||
**These patterns indicate generic "AI-generated" aesthetics and MUST be flagged:**
|
||||
|
||||
#### Font Anti-Patterns (Auto-Detect)
|
||||
```tsx
|
||||
// ❌ CRITICAL: Generic fonts that dominate 80%+ of websites
|
||||
fontFamily: {
|
||||
sans: ['Inter', ...] // Flag: "Inter is overused - consider Space Grotesk, Plus Jakarta Sans"
|
||||
sans: ['Roboto', ...] // Flag: "Roboto is overused - consider IBM Plex Sans, Outfit"
|
||||
sans: ['Open Sans', ...] // Flag: "Open Sans is generic - consider Satoshi, General Sans"
|
||||
sans: ['system-ui', ...] // Flag: Only acceptable as fallback, not primary
|
||||
}
|
||||
|
||||
// ❌ CRITICAL: Default Tailwind font classes without customization
|
||||
className="font-sans" // Flag if font-sans maps to Inter/Roboto
|
||||
className="text-base" // Flag: Generic sizing, consider custom scale
|
||||
```
|
||||
|
||||
**Recommended Font Alternatives** (suggest these in reports):
|
||||
- **Body**: Space Grotesk, Plus Jakarta Sans, IBM Plex Sans, Outfit, Satoshi
|
||||
- **Headings**: Archivo Black, Cabinet Grotesk, Clash Display, General Sans
|
||||
- **Mono**: JetBrains Mono, Fira Code, Source Code Pro
|
||||
|
||||
#### Color Anti-Patterns (Auto-Detect)
|
||||
```tsx
|
||||
// ❌ CRITICAL: Purple gradients (most common AI aesthetic)
|
||||
className="bg-gradient-to-r from-purple-500 to-purple-600"
|
||||
className="bg-gradient-to-r from-violet-500 to-purple-500"
|
||||
className="bg-purple-600"
|
||||
className="text-purple-500"
|
||||
|
||||
// ❌ CRITICAL: Default gray backgrounds without brand treatment
|
||||
className="bg-gray-50" // Flag: "Consider brand-tinted background"
|
||||
className="bg-white" // Flag: "Consider atmospheric gradient or texture"
|
||||
className="bg-slate-100" // Flag if used extensively without brand colors
|
||||
```
|
||||
|
||||
**Recommended Color Approaches** (suggest these in reports):
|
||||
- Use CSS variables with brand palette (`--brand-primary`, `--brand-accent`)
|
||||
- Tint grays with brand color: `bg-brand-gray-50` instead of `bg-gray-50`
|
||||
- Gradients: Use brand colors, not default purple
|
||||
- Atmospheric: Layer gradients with subtle brand tints
|
||||
|
||||
#### Animation Anti-Patterns (Auto-Detect)
|
||||
```tsx
|
||||
// ❌ CRITICAL: No transitions on interactive elements
|
||||
<Button>Click</Button> // Flag: "Add transition-all duration-300"
|
||||
<Card>Content</Card> // Flag: "Add hover:shadow-lg transition"
|
||||
|
||||
// ❌ CRITICAL: Only basic hover without micro-interactions
|
||||
className="hover:bg-blue-600" // Flag: "Consider hover:scale-105 or hover:-translate-y-1"
|
||||
```
|
||||
|
||||
**Detection Rules** (implement in validation):
|
||||
```typescript
|
||||
// Font detection
|
||||
const OVERUSED_FONTS = ['Inter', 'Roboto', 'Open Sans', 'Helvetica', 'Arial'];
|
||||
const hasBadFont = (config) => OVERUSED_FONTS.some(f =>
|
||||
config.fontFamily?.sans?.includes(f)
|
||||
);
|
||||
|
||||
// Purple gradient detection
|
||||
const PURPLE_PATTERN = /(?:purple|violet)-[4-6]00/;
|
||||
const hasPurpleGradient = (className) =>
|
||||
className.includes('gradient') && PURPLE_PATTERN.test(className);
|
||||
|
||||
// Missing animation detection
|
||||
const INTERACTIVE_COMPONENTS = ['Button', 'Card', 'Link', 'Input'];
|
||||
const hasNoTransition = (className) =>
|
||||
!className.includes('transition') && !className.includes('animate');
|
||||
```
|
||||
|
||||
### P2 - Important (Design System Consistency)
|
||||
- **Random Spacing Values**: Not using Tailwind spacing scale (p-4, mt-6, etc.)
|
||||
- **Inconsistent Icon Sizing**: Icons with different sizes in similar contexts
|
||||
- **Mixed Color Approaches**: Some components use theme colors, others use arbitrary values
|
||||
- **Incomplete Dark Mode**: Dark mode variants missing on customized components
|
||||
- **No Focus States**: Interactive elements without focus-visible styling
|
||||
|
||||
### P3 - Polish (Enhanced UX)
|
||||
- **Limited Prop Usage**: Only using 1-2 props when more would improve UX
|
||||
- **No Micro-interactions**: Missing subtle animations on state changes
|
||||
- **Generic Variants**: Using 'solid', 'outline' without brand customization
|
||||
- **Underutilized UI Prop**: Not customizing padding, rounded, shadow in ui prop
|
||||
- **Missing Icons**: Buttons/actions without supporting icons for clarity
|
||||
|
||||
## Remediation Examples
|
||||
|
||||
### Fixing Default Component Usage
|
||||
```tsx
|
||||
<!-- ❌ Critical: Default props only -->
|
||||
<Button onClick="handleClick">
|
||||
Click me
|
||||
</Button>
|
||||
|
||||
<!-- ✅ Correct: Deep customization -->
|
||||
<Button
|
||||
color="primary"
|
||||
size="lg"
|
||||
variant="solid"
|
||||
icon="i-heroicons-sparkles"
|
||||
:ui="{
|
||||
font: 'font-heading tracking-wide',
|
||||
rounded: 'rounded-full',
|
||||
padding: { lg: 'px-8 py-4' },
|
||||
shadow: 'shadow-lg hover:shadow-xl'
|
||||
}"
|
||||
className="transition-all duration-300 hover:scale-105 active:scale-95"
|
||||
onClick="handleClick"
|
||||
>
|
||||
Click me
|
||||
</Button>
|
||||
```
|
||||
|
||||
### Fixing Missing Loading States
|
||||
```tsx
|
||||
<!-- ❌ Critical: No loading feedback -->
|
||||
const handleSubmit = async () => {
|
||||
await submitForm();
|
||||
};
|
||||
|
||||
<Button onClick="handleSubmit">
|
||||
Submit Form
|
||||
</Button>
|
||||
|
||||
<!-- ✅ Correct: Proper loading state -->
|
||||
const isSubmitting = ref(false);
|
||||
|
||||
const handleSubmit = async () => {
|
||||
isSubmitting.value = true;
|
||||
try {
|
||||
await submitForm();
|
||||
} finally {
|
||||
isSubmitting.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
<Button
|
||||
loading={isSubmitting"
|
||||
disabled={isSubmitting"
|
||||
onClick="handleSubmit"
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
<Icon
|
||||
{&& "!isSubmitting"
|
||||
name="i-heroicons-paper-airplane"
|
||||
/>
|
||||
{ isSubmitting ? 'Submitting...' : 'Submit Form'}
|
||||
</span>
|
||||
</Button>
|
||||
```
|
||||
|
||||
### Fixing Inconsistent Spacing
|
||||
```tsx
|
||||
<!-- ❌ P2: Random spacing values -->
|
||||
<div className="p-3">
|
||||
<Card className="mt-5 ml-7">
|
||||
<div className="p-2">
|
||||
<Button className="mt-3.5">Action</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- ✅ Correct: Tailwind spacing scale -->
|
||||
<div className="p-4">
|
||||
<Card className="mt-4">
|
||||
<div className="p-6 space-y-4">
|
||||
<Button>Action</Button>
|
||||
</div>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
<!-- Using consistent spacing: 4, 6, 8, 12, 16 (Tailwind scale) -->
|
||||
```
|
||||
|
||||
### Fixing Design System Inconsistency
|
||||
```tsx
|
||||
<!-- ❌ P2: Inconsistent component styling -->
|
||||
<div>
|
||||
<!-- Button 1: Heavily customized -->
|
||||
<Button
|
||||
color="primary"
|
||||
:ui="{ rounded: 'rounded-full', shadow: 'shadow-xl' }"
|
||||
>
|
||||
Action 1
|
||||
</Button>
|
||||
|
||||
<!-- Button 2: Default (inconsistent!) -->
|
||||
<Button>Action 2</Button>
|
||||
|
||||
<!-- Button 3: Different customization pattern -->
|
||||
<Button color="red" size="xs">
|
||||
Action 3
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<!-- ✅ Correct: Consistent design system -->
|
||||
// Define reusable button variants
|
||||
const buttonVariants = {
|
||||
primary: {
|
||||
color: 'primary',
|
||||
size: 'lg',
|
||||
ui: {
|
||||
rounded: 'rounded-full',
|
||||
shadow: 'shadow-lg hover:shadow-xl',
|
||||
font: 'font-heading'
|
||||
},
|
||||
class: 'transition-all duration-300 hover:scale-105'
|
||||
},
|
||||
secondary: {
|
||||
color: 'gray',
|
||||
size: 'md',
|
||||
variant: 'outline',
|
||||
ui: {
|
||||
rounded: 'rounded-lg',
|
||||
font: 'font-sans'
|
||||
},
|
||||
class: 'transition-colors duration-200'
|
||||
}
|
||||
};
|
||||
|
||||
<div className="space-x-4">
|
||||
<Button v-bind="buttonVariants.primary">
|
||||
Action 1
|
||||
</Button>
|
||||
|
||||
<Button v-bind="buttonVariants.primary">
|
||||
Action 2
|
||||
</Button>
|
||||
|
||||
<Button v-bind="buttonVariants.secondary">
|
||||
Action 3
|
||||
</Button>
|
||||
</div>
|
||||
```
|
||||
|
||||
### Fixing Underutilized UI Prop
|
||||
```tsx
|
||||
<!-- ❌ P3: Not using ui prop for customization -->
|
||||
<Card className="rounded-2xl shadow-xl p-8">
|
||||
<p>Content</p>
|
||||
</Card>
|
||||
|
||||
<!-- ✅ Correct: Proper ui prop usage -->
|
||||
<Card
|
||||
:ui="{
|
||||
rounded: 'rounded-2xl',
|
||||
shadow: 'shadow-xl hover:shadow-2xl',
|
||||
body: {
|
||||
padding: 'p-8',
|
||||
background: 'bg-white dark:bg-brand-midnight'
|
||||
},
|
||||
ring: 'ring-1 ring-brand-coral/20'
|
||||
}"
|
||||
className="transition-shadow duration-300"
|
||||
>
|
||||
<p className="text-gray-700 dark:text-gray-300">Content</p>
|
||||
</Card>
|
||||
```
|
||||
|
||||
## MCP Server Integration
|
||||
|
||||
When shadcn/ui MCP server is available:
|
||||
|
||||
### Component Prop Validation
|
||||
```typescript
|
||||
// Before validating customization depth, get actual component API
|
||||
const componentDocs = await mcp.shadcn.get_component("Button");
|
||||
|
||||
// Validate that used props exist
|
||||
// componentDocs.props: ['color', 'size', 'variant', 'icon', 'loading', 'disabled', ...]
|
||||
|
||||
// Check for underutilized props
|
||||
const usedProps = ['color', 'size']; // From component code
|
||||
const availableProps = componentDocs.props;
|
||||
const unutilizedProps = availableProps.filter(p => !usedProps.includes(p));
|
||||
|
||||
// Suggest: "Consider using 'icon' or 'loading' props for richer UX"
|
||||
```
|
||||
|
||||
### UI Prop Structure Validation
|
||||
```typescript
|
||||
// Validate ui prop structure against schema
|
||||
const uiSchema = componentDocs.ui_schema;
|
||||
|
||||
// User code: :ui="{ font: 'font-heading', rounded: 'rounded-full' }"
|
||||
// Validate: Are 'font' and 'rounded' valid keys in ui prop?
|
||||
// Suggest: Other available ui customizations (padding, shadow, etc.)
|
||||
```
|
||||
|
||||
### Consistency Across Components
|
||||
```typescript
|
||||
// Check multiple component instances
|
||||
const buttonInstances = findAllComponents("Button");
|
||||
|
||||
// Analyze customization patterns
|
||||
// Flag: Component used with 5 different customization styles
|
||||
// Suggest: Create composable or variant system for consistency
|
||||
```
|
||||
|
||||
## Benefits
|
||||
|
||||
### Immediate Impact
|
||||
- **Prevents Generic Appearance**: Ensures components are branded, not defaults
|
||||
- **Enforces Design Consistency**: Catches pattern drift across components
|
||||
- **Improves User Feedback**: Validates loading states and interactions
|
||||
- **Educates on Component API**: Shows developers full customization capabilities
|
||||
|
||||
### Long-term Value
|
||||
- **Consistent Component Library**: All components follow design system
|
||||
- **Faster Component Development**: Clear patterns and examples
|
||||
- **Better Code Maintainability**: Reusable component variants
|
||||
- **Reduced Visual Debt**: Prevents accumulation of one-off styles
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### During Component Usage
|
||||
```tsx
|
||||
// Developer adds: <Button>Click me</Button>
|
||||
// SKILL immediately activates: "⚠️ P1: Button using all default props. Customize with color, size, variant, and ui prop for brand distinctiveness."
|
||||
```
|
||||
|
||||
### During Async Actions
|
||||
```tsx
|
||||
// Developer creates async button: <Button onClick="submitForm">Submit</Button>
|
||||
// SKILL immediately activates: "⚠️ P1: Button triggers async action but lacks :loading prop. Add loading state for user feedback."
|
||||
```
|
||||
|
||||
### During Refactoring
|
||||
```tsx
|
||||
// Developer adds 5th different button style
|
||||
// SKILL immediately activates: "⚠️ P2: Button used with 5 different customization patterns. Consider creating reusable variants for consistency."
|
||||
```
|
||||
|
||||
### Before Deployment
|
||||
```tsx
|
||||
// SKILL runs comprehensive check: "✅ Component aesthetic validation passed. 23 components with deep customization, consistent patterns, and proper loading states detected."
|
||||
```
|
||||
|
||||
## Design System Maturity Levels
|
||||
|
||||
### Level 0: Defaults Only (Avoid)
|
||||
```tsx
|
||||
<Button>Action</Button>
|
||||
<Card><p>Content</p></Card>
|
||||
<Input value="value" />
|
||||
```
|
||||
**Issues**: Generic appearance, no brand identity, inconsistent with custom design
|
||||
|
||||
### Level 1: Basic Props (Minimum)
|
||||
```tsx
|
||||
<Button color="primary" size="lg">Action</Button>
|
||||
<Card className="shadow-lg"><p>Content</p></Card>
|
||||
<Input value="value" placeholder="Enter value" />
|
||||
```
|
||||
**Better**: Some customization, but limited depth
|
||||
|
||||
### Level 2: UI Prop + Classes (Target)
|
||||
```tsx
|
||||
<Button
|
||||
color="primary"
|
||||
size="lg"
|
||||
:ui="{ rounded: 'rounded-full', font: 'font-heading' }"
|
||||
className="transition-all duration-300 hover:scale-105"
|
||||
>
|
||||
Action
|
||||
</Button>
|
||||
|
||||
<Card
|
||||
:ui="{
|
||||
background: 'bg-white dark:bg-brand-midnight',
|
||||
ring: 'ring-1 ring-brand-coral/20',
|
||||
shadow: 'shadow-xl'
|
||||
}"
|
||||
>
|
||||
<p>Content</p>
|
||||
</Card>
|
||||
```
|
||||
**Ideal**: Deep customization, brand-distinctive, consistent patterns
|
||||
|
||||
### Level 3: Design System (Advanced)
|
||||
```tsx
|
||||
<!-- Reusable variants from composables -->
|
||||
<Button v-bind="designSystem.button.variants.primary">
|
||||
Action
|
||||
</Button>
|
||||
|
||||
<Card v-bind="designSystem.card.variants.elevated">
|
||||
<p>Content</p>
|
||||
</Card>
|
||||
```
|
||||
**Advanced**: Centralized design system, maximum consistency
|
||||
|
||||
## Component Customization Checklist
|
||||
|
||||
For each shadcn/ui component, validate:
|
||||
|
||||
- [ ] **Props**: Uses at least 2-3 props (color, size, variant, etc.)
|
||||
- [ ] **UI Prop**: Includes `ui` prop for deep customization (rounded, font, padding, shadow)
|
||||
- [ ] **Classes**: Adds Tailwind utilities for animations and effects
|
||||
- [ ] **Loading State**: Async actions have `:loading` and `:disabled` props
|
||||
- [ ] **Icons**: Includes relevant icons for clarity (`:icon` prop or slot)
|
||||
- [ ] **Hover State**: Interactive elements have hover feedback
|
||||
- [ ] **Focus State**: Keyboard navigation has visible focus styles
|
||||
- [ ] **Dark Mode**: Includes dark mode variants in `ui` prop
|
||||
- [ ] **Spacing**: Uses Tailwind spacing scale (4, 6, 8, 12, 16)
|
||||
- [ ] **Consistency**: Follows same patterns as other instances
|
||||
|
||||
This SKILL ensures every shadcn/ui component is deeply customized, consistently styled, and provides excellent user feedback, preventing the default/generic appearance that makes AI-generated UIs immediately recognizable.
|
||||
393
skills/cors-configuration-validator/SKILL.md
Normal file
393
skills/cors-configuration-validator/SKILL.md
Normal file
@@ -0,0 +1,393 @@
|
||||
---
|
||||
name: cors-configuration-validator
|
||||
description: Automatically validates Cloudflare Workers CORS configuration, ensuring proper headers, OPTIONS handling, and origin validation for cross-origin requests
|
||||
triggers: ["Response creation", "API endpoints", "cross-origin patterns", "CORS headers"]
|
||||
---
|
||||
|
||||
# CORS Configuration Validator SKILL
|
||||
|
||||
## Activation Patterns
|
||||
|
||||
This SKILL automatically activates when:
|
||||
- `new Response()` objects are created
|
||||
- CORS-related headers are set or modified
|
||||
- API endpoints that serve cross-origin requests
|
||||
- OPTIONS method handling is detected
|
||||
- Cross-origin request patterns are identified
|
||||
|
||||
## Expertise Provided
|
||||
|
||||
### Workers-Specific CORS Validation
|
||||
- **Header Validation**: Ensures all required CORS headers are present
|
||||
- **OPTIONS Handling**: Validates preflight request handling
|
||||
- **Origin Validation**: Checks for proper origin validation logic
|
||||
- **Method Validation**: Ensures correct allowed methods
|
||||
- **Header Validation**: Validates allowed headers configuration
|
||||
- **Security**: Prevents overly permissive CORS configurations
|
||||
|
||||
### Specific Checks Performed
|
||||
|
||||
#### ❌ CORS Anti-Patterns
|
||||
```typescript
|
||||
// These patterns trigger immediate alerts:
|
||||
// Missing CORS headers
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
return new Response(JSON.stringify(data));
|
||||
// Browsers will block cross-origin requests!
|
||||
}
|
||||
}
|
||||
|
||||
// Overly permissive for authenticated APIs
|
||||
const corsHeaders = {
|
||||
'Access-Control-Allow-Origin': '*', // ANY origin can call!
|
||||
'Access-Control-Allow-Credentials': 'true' // With credentials!
|
||||
};
|
||||
```
|
||||
|
||||
#### ✅ CORS Best Practices
|
||||
```typescript
|
||||
// These patterns are validated as correct:
|
||||
// Proper CORS with origin validation
|
||||
function getCorsHeaders(origin: string) {
|
||||
const allowedOrigins = ['https://app.example.com', 'https://example.com'];
|
||||
const allowOrigin = allowedOrigins.includes(origin) ? origin : allowedOrigins[0];
|
||||
|
||||
return {
|
||||
'Access-Control-Allow-Origin': allowOrigin,
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
'Access-Control-Max-Age': '86400',
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const origin = request.headers.get('Origin') || '';
|
||||
|
||||
if (request.method === 'OPTIONS') {
|
||||
return new Response(null, { headers: getCorsHeaders(origin) });
|
||||
}
|
||||
|
||||
const response = new Response(JSON.stringify(data));
|
||||
Object.entries(getCorsHeaders(origin)).forEach(([k, v]) => {
|
||||
response.headers.set(k, v);
|
||||
});
|
||||
|
||||
return response;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Integration Points
|
||||
|
||||
### Complementary to Existing Components
|
||||
- **cloudflare-security-checker SKILL**: Handles overall security, SKILL focuses specifically on CORS
|
||||
- **workers-runtime-validator SKILL**: Ensures runtime compatibility, SKILL validates CORS patterns
|
||||
- **edge-performance-oracle SKILL**: Handles performance, SKILL ensures CORS doesn't impact performance
|
||||
|
||||
### Escalation Triggers
|
||||
- Complex CORS architecture questions → `cloudflare-security-sentinel` agent
|
||||
- Advanced authentication with CORS → `cloudflare-security-sentinel` agent
|
||||
- CORS troubleshooting → `cloudflare-security-sentinel` agent
|
||||
|
||||
## Validation Rules
|
||||
|
||||
### P1 - Critical (Will Break Cross-Origin Requests)
|
||||
- **Missing CORS Headers**: No CORS headers on API responses
|
||||
- **Missing OPTIONS Handler**: No preflight request handling
|
||||
- **Invalid Header Combinations**: Conflicting CORS header combinations
|
||||
|
||||
### P2 - High (Security Risk)
|
||||
- **Overly Permissive Origin**: `Access-Control-Allow-Origin: *` with credentials
|
||||
- **Wildcard Methods**: `Access-Control-Allow-Methods: *` with sensitive operations
|
||||
- **Missing Origin Validation**: Accepting any origin without validation
|
||||
|
||||
### P3 - Medium (Best Practices)
|
||||
- **Missing Cache Headers**: No `Access-Control-Max-Age` for preflight caching
|
||||
- **Incomplete Headers**: Missing some optional but recommended headers
|
||||
- **Hardcoded Origins**: Origins not easily configurable
|
||||
|
||||
## Remediation Examples
|
||||
|
||||
### Fixing Missing CORS Headers
|
||||
```typescript
|
||||
// ❌ Critical: No CORS headers (browsers block requests)
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const data = { message: 'Hello from API' };
|
||||
|
||||
return new Response(JSON.stringify(data), {
|
||||
headers: { 'Content-Type': 'application/json' }
|
||||
// Missing CORS headers!
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Correct: Complete CORS implementation
|
||||
function getCorsHeaders(origin: string) {
|
||||
const allowedOrigins = ['https://app.example.com', 'https://example.com'];
|
||||
const allowOrigin = allowedOrigins.includes(origin) ? origin : allowedOrigins[0];
|
||||
|
||||
return {
|
||||
'Access-Control-Allow-Origin': allowOrigin,
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
'Access-Control-Max-Age': '86400',
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const origin = request.headers.get('Origin') || '';
|
||||
|
||||
// Handle preflight requests
|
||||
if (request.method === 'OPTIONS') {
|
||||
return new Response(null, { headers: getCorsHeaders(origin) });
|
||||
}
|
||||
|
||||
const data = { message: 'Hello from API' };
|
||||
|
||||
return new Response(JSON.stringify(data), {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
...getCorsHeaders(origin)
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fixing Overly Permissive CORS
|
||||
```typescript
|
||||
// ❌ High: Overly permissive for authenticated API
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const corsHeaders = {
|
||||
'Access-Control-Allow-Origin': '*', // ANY origin!
|
||||
'Access-Control-Allow-Credentials': 'true', // With credentials!
|
||||
'Access-Control-Allow-Methods': '*', // ANY method!
|
||||
};
|
||||
|
||||
// This allows any website to make authenticated requests!
|
||||
return new Response('Sensitive data', { headers: corsHeaders });
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Correct: Secure CORS for authenticated API
|
||||
function getSecureCorsHeaders(origin: string) {
|
||||
const allowedOrigins = [
|
||||
'https://app.example.com',
|
||||
'https://admin.example.com',
|
||||
'https://example.com'
|
||||
];
|
||||
|
||||
// Only allow known origins
|
||||
const allowOrigin = allowedOrigins.includes(origin) ? origin : allowedOrigins[0];
|
||||
|
||||
return {
|
||||
'Access-Control-Allow-Origin': allowOrigin,
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE', // Specific methods
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
'Access-Control-Allow-Credentials': 'true',
|
||||
'Access-Control-Max-Age': '86400',
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const origin = request.headers.get('Origin') || '';
|
||||
|
||||
// Verify authentication
|
||||
const authHeader = request.headers.get('Authorization');
|
||||
if (!authHeader || !isValidAuth(authHeader)) {
|
||||
return new Response('Unauthorized', { status: 401 });
|
||||
}
|
||||
|
||||
return new Response('Sensitive data', {
|
||||
headers: getSecureCorsHeaders(origin)
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fixing Missing OPTIONS Handler
|
||||
```typescript
|
||||
// ❌ Critical: No OPTIONS handling (preflight fails)
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
if (request.method === 'POST') {
|
||||
// Handle POST request
|
||||
return new Response('POST handled');
|
||||
}
|
||||
|
||||
return new Response('Method not allowed', { status: 405 });
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Correct: Proper OPTIONS handling
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const origin = request.headers.get('Origin') || '';
|
||||
|
||||
// Handle preflight requests
|
||||
if (request.method === 'OPTIONS') {
|
||||
return new Response(null, {
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': origin,
|
||||
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||
'Access-Control-Max-Age': '86400',
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (request.method === 'POST') {
|
||||
return new Response('POST handled', {
|
||||
headers: {
|
||||
'Access-Control-Allow-Origin': origin,
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return new Response('Method not allowed', { status: 405 });
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fixing Dynamic CORS for Different Environments
|
||||
```typescript
|
||||
// ❌ Medium: Hardcoded origins (not flexible)
|
||||
function getCorsHeaders() {
|
||||
return {
|
||||
'Access-Control-Allow-Origin': 'https://app.example.com', // Hardcoded
|
||||
'Access-Control-Allow-Methods': 'GET, POST',
|
||||
};
|
||||
}
|
||||
|
||||
// ✅ Correct: Configurable and secure CORS
|
||||
function getCorsHeaders(origin: string, env: Env) {
|
||||
// Get allowed origins from environment
|
||||
const allowedOrigins = (env.ALLOWED_ORIGINS || 'https://app.example.com')
|
||||
.split(',')
|
||||
.map(o => o.trim());
|
||||
|
||||
const allowOrigin = allowedOrigins.includes(origin) ? origin : allowedOrigins[0];
|
||||
|
||||
return {
|
||||
'Access-Control-Allow-Origin': allowOrigin,
|
||||
'Access-Control-Allow-Methods': env.ALLOWED_METHODS || 'GET, POST, PUT, DELETE',
|
||||
'Access-Control-Allow-Headers': env.ALLOWED_HEADERS || 'Content-Type, Authorization',
|
||||
'Access-Control-Max-Age': '86400',
|
||||
};
|
||||
}
|
||||
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const origin = request.headers.get('Origin') || '';
|
||||
|
||||
if (request.method === 'OPTIONS') {
|
||||
return new Response(null, { headers: getCorsHeaders(origin, env) });
|
||||
}
|
||||
|
||||
return new Response('Response', {
|
||||
headers: getCorsHeaders(origin, env)
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## CORS Header Reference
|
||||
|
||||
### Essential Headers
|
||||
```typescript
|
||||
{
|
||||
'Access-Control-Allow-Origin': 'https://example.com', // Required
|
||||
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', // Required for preflight
|
||||
'Access-Control-Allow-Headers': 'Content-Type, Authorization', // Required for preflight
|
||||
}
|
||||
```
|
||||
|
||||
### Optional but Recommended Headers
|
||||
```typescript
|
||||
{
|
||||
'Access-Control-Max-Age': '86400', // Cache preflight for 24 hours
|
||||
'Access-Control-Allow-Credentials': 'true', // For cookies/auth
|
||||
'Vary': 'Origin', // Important for caching with multiple origins
|
||||
}
|
||||
```
|
||||
|
||||
### Security Considerations
|
||||
```typescript
|
||||
// ❌ Don't do this for authenticated APIs:
|
||||
{
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Credentials': 'true'
|
||||
}
|
||||
|
||||
// ✅ Do this instead:
|
||||
{
|
||||
'Access-Control-Allow-Origin': 'https://app.example.com', // Specific origin
|
||||
'Access-Control-Allow-Credentials': 'true'
|
||||
}
|
||||
```
|
||||
|
||||
## MCP Server Integration
|
||||
|
||||
When Cloudflare MCP server is available:
|
||||
- Query latest CORS best practices and security recommendations
|
||||
- Get current browser CORS specification updates
|
||||
- Check for common CORS vulnerabilities and mitigations
|
||||
|
||||
## Benefits
|
||||
|
||||
### Immediate Impact
|
||||
- **Prevents CORS Errors**: Catches missing headers before deployment
|
||||
- **Improves Security**: Prevents overly permissive CORS configurations
|
||||
- **Better User Experience**: Ensures cross-origin requests work properly
|
||||
|
||||
### Long-term Value
|
||||
- **Consistent CORS Standards**: Ensures all APIs follow proper CORS patterns
|
||||
- **Reduced Debugging Time**: Immediate feedback on CORS issues
|
||||
- **Security Compliance**: Prevents CORS-related security vulnerabilities
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### During Response Creation
|
||||
```typescript
|
||||
// Developer types: new Response(data)
|
||||
// SKILL immediately activates: "⚠️ HIGH: Response missing CORS headers. Cross-origin requests will be blocked by browsers."
|
||||
```
|
||||
|
||||
### During API Development
|
||||
```typescript
|
||||
// Developer types: 'Access-Control-Allow-Origin': '*'
|
||||
// SKILL immediately activates: "⚠️ HIGH: Overly permissive CORS with wildcard origin. Consider specific origins for security."
|
||||
```
|
||||
|
||||
### During Method Handling
|
||||
```typescript
|
||||
// Developer types: if (request.method === 'POST') { ... }
|
||||
// SKILL immediately activates: "⚠️ HIGH: Missing OPTIONS handler for preflight requests. Add OPTIONS method handling."
|
||||
```
|
||||
|
||||
## CORS Checklist
|
||||
|
||||
### Required for Cross-Origin Requests
|
||||
- [ ] `Access-Control-Allow-Origin` header set
|
||||
- [ ] OPTIONS method handled for preflight requests
|
||||
- [ ] `Access-Control-Allow-Methods` header for preflight
|
||||
- [ ] `Access-Control-Allow-Headers` header for preflight
|
||||
|
||||
### Security Best Practices
|
||||
- [ ] Origin validation (not wildcard for authenticated APIs)
|
||||
- [ ] Specific allowed methods (not wildcard)
|
||||
- [ ] Proper credentials handling
|
||||
- [ ] Environment-based origin configuration
|
||||
|
||||
### Performance Optimization
|
||||
- [ ] `Access-Control-Max-Age` header set
|
||||
- [ ] `Vary: Origin` header for caching
|
||||
- [ ] Efficient preflight handling
|
||||
|
||||
This SKILL ensures CORS is configured correctly by providing immediate, autonomous validation of CORS patterns, preventing common cross-origin issues and security vulnerabilities.
|
||||
378
skills/durable-objects-pattern-checker/SKILL.md
Normal file
378
skills/durable-objects-pattern-checker/SKILL.md
Normal file
@@ -0,0 +1,378 @@
|
||||
---
|
||||
name: durable-objects-pattern-checker
|
||||
description: Automatically validates Cloudflare Durable Objects usage patterns, ensuring correct state management, hibernation, and strong consistency practices
|
||||
triggers: ["Durable Object imports", "DO stub usage", "state management patterns", "DO ID generation"]
|
||||
---
|
||||
|
||||
# Durable Objects Pattern Checker SKILL
|
||||
|
||||
## Activation Patterns
|
||||
|
||||
This SKILL automatically activates when:
|
||||
- Durable Object imports or exports are detected
|
||||
- DO stub creation and usage patterns
|
||||
- State management in Durable Objects
|
||||
- ID generation patterns (`idFromName`, `newUniqueId`)
|
||||
- Hibernation and lifecycle patterns
|
||||
- WebSocket or real-time features with DOs
|
||||
|
||||
## Expertise Provided
|
||||
|
||||
### Durable Objects Best Practices
|
||||
- **State Management**: Ensures proper state persistence and consistency
|
||||
- **ID Generation**: Validates correct ID patterns for different use cases
|
||||
- **Hibernation**: Checks for proper hibernation implementation
|
||||
- **Lifecycle Management**: Validates constructor, fetch, and alarm handling
|
||||
- **Strong Consistency**: Ensures DOs are used when strong consistency is needed
|
||||
- **Performance**: Identifies DO performance anti-patterns
|
||||
|
||||
### Specific Checks Performed
|
||||
|
||||
#### ❌ Durable Objects Anti-Patterns
|
||||
```typescript
|
||||
// These patterns trigger immediate alerts:
|
||||
// Using DOs for stateless operations
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const id = env.COUNTER.newUniqueId(); // New DO every request!
|
||||
const stub = env.COUNTER.get(id);
|
||||
return stub.fetch(request); // Overkill for simple counter
|
||||
}
|
||||
}
|
||||
|
||||
// Missing hibernation for long-lived DOs
|
||||
export class ChatRoom {
|
||||
constructor(state, env) {
|
||||
this.state = state;
|
||||
// Missing this.state.storage.setAlarm() for hibernation
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### ✅ Durable Objects Best Practices
|
||||
```typescript
|
||||
// These patterns are validated as correct:
|
||||
// Reuse DO instances for stateful coordination
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const ip = request.headers.get('CF-Connecting-IP');
|
||||
const id = env.RATE_LIMITER.idFromName(ip); // Reuse same DO
|
||||
const stub = env.RATE_LIMITER.get(id);
|
||||
return stub.fetch(request);
|
||||
}
|
||||
}
|
||||
|
||||
// Proper hibernation implementation
|
||||
export class ChatRoom {
|
||||
constructor(state, env) {
|
||||
this.state = state;
|
||||
this.env = env;
|
||||
|
||||
// Set alarm for hibernation after inactivity
|
||||
this.state.storage.setAlarm(Date.now() + 30000); // 30 seconds
|
||||
}
|
||||
|
||||
alarm() {
|
||||
// DO will hibernate after alarm
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Integration Points
|
||||
|
||||
### Complementary to Existing Components
|
||||
- **cloudflare-architecture-strategist agent**: Handles complex DO architecture, SKILL provides immediate pattern validation
|
||||
- **edge-performance-oracle agent**: Handles DO performance analysis, SKILL ensures correct usage patterns
|
||||
- **workers-binding-validator SKILL**: Ensures DO bindings are correct, SKILL validates usage patterns
|
||||
|
||||
### Escalation Triggers
|
||||
- Complex DO architecture questions → `cloudflare-architecture-strategist` agent
|
||||
- DO performance troubleshooting → `edge-performance-oracle` agent
|
||||
- DO migration strategies → `cloudflare-architecture-strategist` agent
|
||||
|
||||
## Validation Rules
|
||||
|
||||
### P1 - Critical (Will Cause Issues)
|
||||
- **New Unique ID Per Request**: Creating new DO for every request
|
||||
- **Missing Hibernation**: Long-lived DOs without hibernation
|
||||
- **State Leaks**: State not properly persisted to storage
|
||||
- **Blocking Operations**: Synchronous operations in DO fetch
|
||||
|
||||
### P2 - High (Performance/Correctness Issues)
|
||||
- **Wrong ID Pattern**: Using `newUniqueId` when `idFromName` is appropriate
|
||||
- **Stateless DOs**: Using DOs for operations that don't need state
|
||||
- **Missing Error Handling**: DO operations without proper error handling
|
||||
- **Alarm Misuse**: Incorrect alarm patterns for hibernation
|
||||
|
||||
### P3 - Medium (Best Practices)
|
||||
- **State Size**: Large state objects that impact performance
|
||||
- **Concurrency**: Missing concurrency control for shared state
|
||||
- **Cleanup**: Missing cleanup in DO lifecycle
|
||||
|
||||
## Remediation Examples
|
||||
|
||||
### Fixing New Unique ID Per Request
|
||||
```typescript
|
||||
// ❌ Critical: New DO for every request (expensive and wrong)
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const userId = getUserId(request);
|
||||
|
||||
// Creates new DO instance for every request!
|
||||
const id = env.USER_SESSION.newUniqueId();
|
||||
const stub = env.USER_SESSION.get(id);
|
||||
|
||||
return stub.fetch(request);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Correct: Reuse DO for same entity
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const userId = getUserId(request);
|
||||
|
||||
// Reuse same DO for this user
|
||||
const id = env.USER_SESSION.idFromName(userId);
|
||||
const stub = env.USER_SESSION.get(id);
|
||||
|
||||
return stub.fetch(request);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fixing Missing Hibernation
|
||||
```typescript
|
||||
// ❌ High: DO never hibernates (wastes resources)
|
||||
export class ChatRoom {
|
||||
constructor(state, env) {
|
||||
this.state = state;
|
||||
this.env = env;
|
||||
this.messages = [];
|
||||
}
|
||||
|
||||
async fetch(request) {
|
||||
// Handle chat messages...
|
||||
// But never hibernates - stays in memory forever!
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Correct: Implement hibernation
|
||||
export class ChatRoom {
|
||||
constructor(state, env) {
|
||||
this.state = state;
|
||||
this.env = env;
|
||||
|
||||
// Load persisted state
|
||||
this.loadState();
|
||||
|
||||
// Set alarm for hibernation after inactivity
|
||||
this.resetHibernationTimer();
|
||||
}
|
||||
|
||||
async loadState() {
|
||||
const messages = await this.state.storage.get('messages');
|
||||
this.messages = messages || [];
|
||||
}
|
||||
|
||||
resetHibernationTimer() {
|
||||
// Reset alarm for 30 seconds from now
|
||||
this.state.storage.setAlarm(Date.now() + 30000);
|
||||
}
|
||||
|
||||
async fetch(request) {
|
||||
// Reset timer on activity
|
||||
this.resetHibernationTimer();
|
||||
|
||||
// Handle chat messages...
|
||||
return new Response('Message processed');
|
||||
}
|
||||
|
||||
async alarm() {
|
||||
// Persist state before hibernation
|
||||
await this.state.storage.put('messages', this.messages);
|
||||
// DO will hibernate after this method returns
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fixing Wrong ID Pattern
|
||||
```typescript
|
||||
// ❌ High: Using newUniqueId for named resources
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const roomId = new URL(request.url).searchParams.get('room');
|
||||
|
||||
// Wrong: Creates new DO for same room name
|
||||
const id = env.CHAT_ROOM.newUniqueId();
|
||||
const stub = env.CHAT_ROOM.get(id);
|
||||
|
||||
return stub.fetch(request);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Correct: Use idFromName for named resources
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const roomId = new URL(request.url).searchParams.get('room');
|
||||
|
||||
// Correct: Same DO for same room name
|
||||
const id = env.CHAT_ROOM.idFromName(roomId);
|
||||
const stub = env.CHAT_ROOM.get(id);
|
||||
|
||||
return stub.fetch(request);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fixing State Persistence
|
||||
```typescript
|
||||
// ❌ Critical: State not persisted (lost on hibernation)
|
||||
export class Counter {
|
||||
constructor(state, env) {
|
||||
this.state = state;
|
||||
this.count = 0; // Not persisted!
|
||||
}
|
||||
|
||||
async fetch(request) {
|
||||
if (request.url.endsWith('/increment')) {
|
||||
this.count++; // Lost when DO hibernates!
|
||||
return new Response(`Count: ${this.count}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Correct: Persist state to storage
|
||||
export class Counter {
|
||||
constructor(state, env) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
async fetch(request) {
|
||||
if (request.url.endsWith('/increment')) {
|
||||
// Persist to storage
|
||||
const currentCount = (await this.state.storage.get('count')) || 0;
|
||||
const newCount = currentCount + 1;
|
||||
await this.state.storage.put('count', newCount);
|
||||
|
||||
return new Response(`Count: ${newCount}`);
|
||||
}
|
||||
|
||||
if (request.url.endsWith('/get')) {
|
||||
const count = await this.state.storage.get('count') || 0;
|
||||
return new Response(`Count: ${count}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fixing Stateless DO Usage
|
||||
```typescript
|
||||
// ❌ High: Using DO for stateless operation (overkill)
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
// Using DO for simple API call - unnecessary!
|
||||
const id = env.API_PROXY.newUniqueId();
|
||||
const stub = env.API_PROXY.get(id);
|
||||
return stub.fetch(request);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Correct: Handle stateless operations in Worker
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
// Simple API call - handle directly in Worker
|
||||
const response = await fetch('https://api.example.com/data');
|
||||
return response;
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Correct: Use DO for actual stateful coordination
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const ip = request.headers.get('CF-Connecting-IP');
|
||||
|
||||
// Rate limiting needs state - perfect for DO
|
||||
const id = env.RATE_LIMITER.idFromName(ip);
|
||||
const stub = env.RATE_LIMITER.get(id);
|
||||
|
||||
return stub.fetch(request);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Durable Objects Use Cases
|
||||
|
||||
### Use Durable Objects When:
|
||||
- **Strong Consistency** required (rate limiting, counters)
|
||||
- **Stateful Coordination** (chat rooms, game sessions)
|
||||
- **Real-time Features** (WebSockets, collaboration)
|
||||
- **Distributed Locks** (coordination between requests)
|
||||
- **Long-running Operations** (background processing)
|
||||
|
||||
### Don't Use Durable Objects When:
|
||||
- **Stateless Operations** (simple API calls)
|
||||
- **Read-heavy Caching** (use KV instead)
|
||||
- **Large File Storage** (use R2 instead)
|
||||
- **Simple Key-Value** (use KV instead)
|
||||
|
||||
## MCP Server Integration
|
||||
|
||||
When Cloudflare MCP server is available:
|
||||
- Query DO performance metrics and best practices
|
||||
- Get latest hibernation patterns and techniques
|
||||
- Check DO usage limits and quotas
|
||||
- Analyze DO performance in production
|
||||
|
||||
## Benefits
|
||||
|
||||
### Immediate Impact
|
||||
- **Prevents Resource Waste**: Catches DO anti-patterns that waste resources
|
||||
- **Ensures Correctness**: Validates state persistence and consistency
|
||||
- **Improves Performance**: Identifies performance issues in DO usage
|
||||
|
||||
### Long-term Value
|
||||
- **Consistent DO Patterns**: Ensures all DO usage follows best practices
|
||||
- **Better Resource Management**: Proper hibernation and lifecycle management
|
||||
- **Reduced Costs**: Efficient DO usage reduces resource consumption
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### During DO Creation
|
||||
```typescript
|
||||
// Developer types: const id = env.MY_DO.newUniqueId();
|
||||
// SKILL immediately activates: "⚠️ HIGH: Using newUniqueId for every request. Consider idFromName for named resources or if this should be stateless."
|
||||
```
|
||||
|
||||
### During State Management
|
||||
```typescript
|
||||
// Developer types: this.count = 0; in constructor
|
||||
// SKILL immediately activates: "❌ CRITICAL: State not persisted to storage. Use this.state.storage.put() to persist data."
|
||||
```
|
||||
|
||||
### During Hibernation
|
||||
```typescript
|
||||
// Developer types: DO without alarm() method
|
||||
// SKILL immediately activates: "⚠️ HIGH: Durable Object missing hibernation. Add alarm() method and setAlarm() for resource efficiency."
|
||||
```
|
||||
|
||||
## Performance Targets
|
||||
|
||||
### DO Creation
|
||||
- **Excellent**: Reuse existing DOs (idFromName)
|
||||
- **Good**: Minimal new DO creation
|
||||
- **Acceptable**: Appropriate DO usage patterns
|
||||
- **Needs Improvement**: Creating new DOs per request
|
||||
|
||||
### State Persistence
|
||||
- **Excellent**: All state persisted to storage
|
||||
- **Good**: Critical state persisted
|
||||
- **Acceptable**: Basic state management
|
||||
- **Needs Improvement**: State not persisted
|
||||
|
||||
### Hibernation
|
||||
- **Excellent**: Proper hibernation implementation
|
||||
- **Good**: Basic hibernation setup
|
||||
- **Acceptable**: Some hibernation consideration
|
||||
- **Needs Improvement**: No hibernation (resource waste)
|
||||
|
||||
This SKILL ensures Durable Objects are used correctly by providing immediate, autonomous validation of DO patterns, preventing common mistakes and ensuring efficient state management.
|
||||
290
skills/edge-performance-optimizer/SKILL.md
Normal file
290
skills/edge-performance-optimizer/SKILL.md
Normal file
@@ -0,0 +1,290 @@
|
||||
---
|
||||
name: edge-performance-optimizer
|
||||
description: Automatically optimizes Cloudflare Workers performance during development, focusing on cold starts, bundle size, edge caching, and global latency
|
||||
triggers: ["bundle size changes", "fetch calls", "storage operations", "dependency additions", "sequential operations"]
|
||||
---
|
||||
|
||||
# Edge Performance Optimizer SKILL
|
||||
|
||||
## Activation Patterns
|
||||
|
||||
This SKILL automatically activates when:
|
||||
- New dependencies are added to package.json
|
||||
- Large files or heavy imports are detected
|
||||
- Sequential operations that could be parallelized
|
||||
- Missing edge caching opportunities
|
||||
- Bundle size increases significantly
|
||||
- Storage operations without optimization patterns
|
||||
|
||||
## Expertise Provided
|
||||
|
||||
### Edge-Specific Performance Optimization
|
||||
- **Cold Start Optimization**: Minimizes bundle size and heavy dependencies
|
||||
- **Global Distribution**: Ensures edge caching for global performance
|
||||
- **CPU Time Optimization**: Identifies CPU-intensive operations
|
||||
- **Storage Performance**: Optimizes KV/R2/D1 access patterns
|
||||
- **Parallel Operations**: Suggests parallelization opportunities
|
||||
- **Bundle Analysis**: Monitors and optimizes bundle size
|
||||
|
||||
### Specific Checks Performed
|
||||
|
||||
#### ❌ Performance Anti-Patterns
|
||||
```typescript
|
||||
// These patterns trigger immediate alerts:
|
||||
import axios from 'axios'; // Heavy dependency (13KB)
|
||||
import moment from 'moment'; // Heavy dependency (68KB)
|
||||
import _ from 'lodash'; // Heavy dependency (71KB)
|
||||
|
||||
// Sequential operations that could be parallel
|
||||
const user = await env.USERS.get(id);
|
||||
const settings = await env.SETTINGS.get(id);
|
||||
const prefs = await env.PREFS.get(id);
|
||||
```
|
||||
|
||||
#### ✅ Performance Best Practices
|
||||
```typescript
|
||||
// These patterns are validated as correct:
|
||||
// Native Web APIs instead of heavy libraries
|
||||
const response = await fetch(url); // Built-in fetch (0KB)
|
||||
const now = new Date(); // Native Date (0KB)
|
||||
|
||||
// Parallel operations
|
||||
const [user, settings, prefs] = await Promise.all([
|
||||
env.USERS.get(id),
|
||||
env.SETTINGS.get(id),
|
||||
env.PREFS.get(id),
|
||||
]);
|
||||
```
|
||||
|
||||
## Integration Points
|
||||
|
||||
### Complementary to Existing Components
|
||||
- **edge-performance-oracle agent**: Handles comprehensive performance analysis, SKILL provides immediate optimization
|
||||
- **workers-runtime-validator SKILL**: Complements runtime checks with performance optimization
|
||||
- **es-deploy command**: SKILL ensures performance standards before deployment
|
||||
|
||||
### Escalation Triggers
|
||||
- Complex performance architecture questions → `edge-performance-oracle` agent
|
||||
- Global distribution strategy → `cloudflare-architecture-strategist` agent
|
||||
- Performance troubleshooting → `edge-performance-oracle` agent
|
||||
|
||||
## Validation Rules
|
||||
|
||||
### P1 - Critical (Performance Killer)
|
||||
- **Large Dependencies**: Heavy libraries like moment, lodash, axios
|
||||
- **Bundle Size**: Over 200KB (kills cold start performance)
|
||||
- **Sequential Operations**: Multiple sequential storage/network calls
|
||||
- **Missing Edge Caching**: No caching for frequently accessed data
|
||||
|
||||
### P2 - High (Performance Impact)
|
||||
- **Bundle Size**: Over 100KB (slows cold starts)
|
||||
- **CPU Time**: Operations approaching 50ms limit
|
||||
- **Lazy Loading**: Dynamic imports that hurt cold start
|
||||
- **Large Payloads**: Responses over 100KB without streaming
|
||||
|
||||
### P3 - Medium (Optimization Opportunity)
|
||||
- **Bundle Size**: Over 50KB (could be optimized)
|
||||
- **Missing Parallelization**: Operations that could be parallel
|
||||
- **No Request Caching**: Repeated expensive operations
|
||||
|
||||
## Remediation Examples
|
||||
|
||||
### Fixing Heavy Dependencies
|
||||
```typescript
|
||||
// ❌ Critical: Heavy dependencies (150KB+ bundle)
|
||||
import axios from 'axios'; // 13KB
|
||||
import moment from 'moment'; // 68KB
|
||||
import _ from 'lodash'; // 71KB
|
||||
// Total: 152KB just for utilities!
|
||||
|
||||
// ✅ Correct: Native Web APIs (minimal bundle)
|
||||
// Use fetch instead of axios
|
||||
const response = await fetch(url);
|
||||
const data = await response.json();
|
||||
|
||||
// Use native Date instead of moment
|
||||
const now = new Date();
|
||||
const tomorrow = new Date(Date.now() + 86400000);
|
||||
|
||||
// Use native methods instead of lodash
|
||||
const unique = [...new Set(array)];
|
||||
const grouped = array.reduce((acc, item) => {
|
||||
const key = item.category;
|
||||
if (!acc[key]) acc[key] = [];
|
||||
acc[key].push(item);
|
||||
return acc;
|
||||
}, {});
|
||||
// Total: < 5KB for utilities
|
||||
```
|
||||
|
||||
### Fixing Sequential Operations
|
||||
```typescript
|
||||
// ❌ High: Sequential KV operations (3x network round-trips)
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const user = await env.USERS.get(userId); // 10-30ms
|
||||
const settings = await env.SETTINGS.get(id); // 10-30ms
|
||||
const prefs = await env.PREFS.get(id); // 10-30ms
|
||||
// Total: 30-90ms just for storage!
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Correct: Parallel operations (single round-trip)
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const [user, settings, prefs] = await Promise.all([
|
||||
env.USERS.get(userId),
|
||||
env.SETTINGS.get(id),
|
||||
env.PREFS.get(id),
|
||||
]);
|
||||
// Total: 10-30ms (single round-trip)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fixing Missing Edge Caching
|
||||
```typescript
|
||||
// ❌ Critical: No edge caching (slow globally)
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const config = await fetch('https://api.example.com/config');
|
||||
// Every request goes to origin!
|
||||
// Sydney user → US origin = 200ms+ just for config
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Correct: Edge caching pattern
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const cache = caches.default;
|
||||
const cacheKey = new Request('https://example.com/config', {
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
// Try cache first
|
||||
let response = await cache.match(cacheKey);
|
||||
|
||||
if (!response) {
|
||||
// Cache miss - fetch from origin
|
||||
response = await fetch('https://api.example.com/config');
|
||||
|
||||
// Cache at edge with 1-hour TTL
|
||||
response = new Response(response.body, {
|
||||
...response,
|
||||
headers: {
|
||||
...response.headers,
|
||||
'Cache-Control': 'public, max-age=3600',
|
||||
}
|
||||
});
|
||||
|
||||
await cache.put(cacheKey, response.clone());
|
||||
}
|
||||
|
||||
// Sydney user → Sydney edge cache = < 10ms
|
||||
return response;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fixing CPU Time Issues
|
||||
```typescript
|
||||
// ❌ High: Large synchronous processing (CPU time bomb)
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const users = await env.DB.prepare('SELECT * FROM users').all();
|
||||
// If 10,000 users, this loops for 100ms+ CPU time
|
||||
const enriched = users.results.map(user => {
|
||||
return {
|
||||
...user,
|
||||
fullName: `${user.firstName} ${user.lastName}`,
|
||||
// ... expensive computations
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Correct: Bounded operations
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
// Option 1: Limit at database level
|
||||
const users = await env.DB.prepare(
|
||||
'SELECT * FROM users LIMIT ? OFFSET ?'
|
||||
).bind(10, offset).all(); // Only 10 users, bounded CPU
|
||||
|
||||
// Option 2: Stream processing for large datasets
|
||||
const { readable, writable } = new TransformStream();
|
||||
// Process in chunks without loading everything into memory
|
||||
|
||||
// Option 3: Offload to Durable Object
|
||||
const id = env.PROCESSOR.newUniqueId();
|
||||
const stub = env.PROCESSOR.get(id);
|
||||
return stub.fetch(request); // DO can run longer
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## MCP Server Integration
|
||||
|
||||
When Cloudflare MCP server is available:
|
||||
- Query real performance metrics (cold start times, CPU usage)
|
||||
- Analyze global latency by region
|
||||
- Get latest performance optimization techniques
|
||||
- Check bundle size impact on cold starts
|
||||
|
||||
## Benefits
|
||||
|
||||
### Immediate Impact
|
||||
- **Faster Cold Starts**: Reduces bundle size and heavy dependencies
|
||||
- **Better Global Performance**: Ensures edge caching for worldwide users
|
||||
- **Lower CPU Usage**: Identifies and optimizes CPU-intensive operations
|
||||
- **Reduced Latency**: Parallelizes operations and adds caching
|
||||
|
||||
### Long-term Value
|
||||
- **Consistent Performance Standards**: Ensures all code meets performance targets
|
||||
- **Better User Experience**: Faster response times globally
|
||||
- **Cost Optimization**: Reduced CPU time usage lowers costs
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### During Dependency Addition
|
||||
```typescript
|
||||
// Developer types: npm install moment
|
||||
// SKILL immediately activates: "❌ CRITICAL: moment is 68KB and will slow cold starts. Use native Date instead for 0KB impact."
|
||||
```
|
||||
|
||||
### During Storage Operations
|
||||
```typescript
|
||||
// Developer types: sequential KV gets
|
||||
// SKILL immediately activates: "⚠️ HIGH: Sequential KV operations detected. Use Promise.all() to parallelize and reduce latency by 3x."
|
||||
```
|
||||
|
||||
### During API Development
|
||||
```typescript
|
||||
// Developer types: fetch without caching
|
||||
// SKILL immediately activates: "⚠️ HIGH: No edge caching for API call. Add Cache API to serve from edge locations globally."
|
||||
```
|
||||
|
||||
## Performance Targets
|
||||
|
||||
### Bundle Size
|
||||
- **Excellent**: < 10KB
|
||||
- **Good**: < 50KB
|
||||
- **Acceptable**: < 100KB
|
||||
- **Needs Improvement**: > 100KB
|
||||
- **Action Required**: > 200KB
|
||||
|
||||
### Cold Start Time
|
||||
- **Excellent**: < 3ms
|
||||
- **Good**: < 5ms
|
||||
- **Acceptable**: < 10ms
|
||||
- **Needs Improvement**: > 10ms
|
||||
- **Action Required**: > 20ms
|
||||
|
||||
### Global Latency (P95)
|
||||
- **Excellent**: < 100ms
|
||||
- **Good**: < 200ms
|
||||
- **Acceptable**: < 500ms
|
||||
- **Needs Improvement**: > 500ms
|
||||
- **Action Required**: > 1000ms
|
||||
|
||||
This SKILL ensures Workers performance by providing immediate, autonomous optimization of performance patterns, preventing common performance issues and ensuring fast global response times.
|
||||
3
skills/gemini-imagegen/.env.example
Normal file
3
skills/gemini-imagegen/.env.example
Normal file
@@ -0,0 +1,3 @@
|
||||
# Google Gemini API Key
|
||||
# Get your API key from: https://makersuite.google.com/app/apikey
|
||||
GEMINI_API_KEY=your-api-key-here
|
||||
34
skills/gemini-imagegen/.gitignore
vendored
Normal file
34
skills/gemini-imagegen/.gitignore
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
# Dependencies
|
||||
node_modules/
|
||||
|
||||
# Build outputs
|
||||
dist/
|
||||
*.js
|
||||
*.js.map
|
||||
|
||||
# Environment variables
|
||||
.env
|
||||
.env.local
|
||||
|
||||
# Generated images (examples)
|
||||
*.png
|
||||
*.jpg
|
||||
*.jpeg
|
||||
*.gif
|
||||
*.webp
|
||||
!examples/*.png
|
||||
!examples/*.jpg
|
||||
|
||||
# IDE
|
||||
.vscode/
|
||||
.idea/
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
103
skills/gemini-imagegen/README.md
Normal file
103
skills/gemini-imagegen/README.md
Normal file
@@ -0,0 +1,103 @@
|
||||
# Gemini ImageGen Skill
|
||||
|
||||
AI-powered image generation, editing, and composition using Google's Gemini API.
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. **Install dependencies:**
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
2. **Set your API key:**
|
||||
```bash
|
||||
export GEMINI_API_KEY="your-api-key-here"
|
||||
```
|
||||
Get your key from: https://makersuite.google.com/app/apikey
|
||||
|
||||
3. **Generate an image:**
|
||||
```bash
|
||||
npm run generate "a sunset over mountains" output.png
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- **Generate**: Create images from text descriptions
|
||||
- **Edit**: Modify existing images with natural language prompts
|
||||
- **Compose**: Combine multiple images with flexible layouts
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Generate Images
|
||||
```bash
|
||||
# Basic generation
|
||||
npm run generate "futuristic city skyline" city.png
|
||||
|
||||
# Custom size
|
||||
npm run generate "modern office" office.png -- --width 1920 --height 1080
|
||||
```
|
||||
|
||||
### Edit Images
|
||||
```bash
|
||||
# Style transformation
|
||||
npm run edit photo.jpg "make it look like a watercolor painting" artistic.png
|
||||
|
||||
# Object modification
|
||||
npm run edit landscape.png "add a rainbow in the sky" enhanced.png
|
||||
```
|
||||
|
||||
### Compose Images
|
||||
```bash
|
||||
# Grid layout (default)
|
||||
npm run compose collage.png img1.jpg img2.jpg img3.jpg img4.jpg
|
||||
|
||||
# Horizontal banner
|
||||
npm run compose banner.png left.png right.png -- --layout horizontal
|
||||
|
||||
# Custom composition
|
||||
npm run compose result.png a.jpg b.jpg -- --prompt "blend seamlessly"
|
||||
```
|
||||
|
||||
## Scripts
|
||||
|
||||
- `npm run generate <prompt> <output>` - Generate image from text
|
||||
- `npm run edit <source> <prompt> <output>` - Edit existing image
|
||||
- `npm run compose <output> <images...>` - Compose multiple images
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
- `GEMINI_API_KEY` (required) - Your Google Gemini API key
|
||||
|
||||
### Options
|
||||
|
||||
See `SKILL.md` for detailed documentation on all available options and parameters.
|
||||
|
||||
## Development Notes
|
||||
|
||||
This is a local development skill that runs on your machine, not on Cloudflare Workers. It's designed for:
|
||||
|
||||
- Design workflows and asset creation
|
||||
- Visual content generation
|
||||
- Image manipulation and prototyping
|
||||
- Creating test images for development
|
||||
|
||||
## Implementation Status
|
||||
|
||||
**Note**: The current implementation includes:
|
||||
- Complete TypeScript structure
|
||||
- Argument parsing and validation
|
||||
- Gemini API integration for image analysis
|
||||
- Comprehensive error handling
|
||||
|
||||
For production use with actual image generation/editing, you'll need to:
|
||||
1. Use the Imagen model (imagen-3.0-generate-001)
|
||||
2. Implement proper image data handling
|
||||
3. Add output file writing with actual image data
|
||||
|
||||
Refer to the [Gemini Imagen documentation](https://ai.google.dev/docs/imagen) for implementation details.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
||||
231
skills/gemini-imagegen/SKILL.md
Normal file
231
skills/gemini-imagegen/SKILL.md
Normal file
@@ -0,0 +1,231 @@
|
||||
---
|
||||
name: gemini-imagegen
|
||||
description: Generate, edit, and compose images using Google's Gemini AI API for design workflows and visual content creation
|
||||
triggers: ["image generation", "visual content", "AI art", "image editing", "design automation"]
|
||||
---
|
||||
|
||||
# Gemini ImageGen SKILL
|
||||
|
||||
## Overview
|
||||
|
||||
This skill provides image generation and manipulation capabilities using Google's Gemini AI API. It's designed for local development workflows where you need to create or modify images using AI assistance.
|
||||
|
||||
## Features
|
||||
|
||||
- **Generate Images**: Create images from text descriptions
|
||||
- **Edit Images**: Modify existing images based on text prompts
|
||||
- **Compose Images**: Combine multiple images with layout instructions
|
||||
- **Multiple Formats**: Support for PNG, JPEG, and other common image formats
|
||||
- **Size Options**: Flexible output dimensions for different use cases
|
||||
|
||||
## Environment Setup
|
||||
|
||||
This skill requires a Gemini API key:
|
||||
|
||||
```bash
|
||||
export GEMINI_API_KEY="your-api-key-here"
|
||||
```
|
||||
|
||||
Get your API key from: https://makersuite.google.com/app/apikey
|
||||
|
||||
## Available Scripts
|
||||
|
||||
### 1. Generate Image (`scripts/generate-image.ts`)
|
||||
|
||||
Create new images from text descriptions.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
npx tsx scripts/generate-image.ts <prompt> <output-path> [options]
|
||||
```
|
||||
|
||||
**Arguments:**
|
||||
- `prompt`: Text description of the image to generate
|
||||
- `output-path`: Where to save the generated image (e.g., `./output.png`)
|
||||
|
||||
**Options:**
|
||||
- `--width <number>`: Image width in pixels (default: 1024)
|
||||
- `--height <number>`: Image height in pixels (default: 1024)
|
||||
- `--model <string>`: Gemini model to use (default: 'gemini-2.0-flash-exp')
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Basic usage
|
||||
GEMINI_API_KEY=xxx npx tsx scripts/generate-image.ts "a sunset over mountains" output.png
|
||||
|
||||
# Custom size
|
||||
npx tsx scripts/generate-image.ts "modern office workspace" office.png --width 1920 --height 1080
|
||||
|
||||
# Using npm script
|
||||
npm run generate "futuristic city skyline" city.png
|
||||
```
|
||||
|
||||
### 2. Edit Image (`scripts/edit-image.ts`)
|
||||
|
||||
Modify existing images based on text instructions.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
npx tsx scripts/edit-image.ts <source-image> <prompt> <output-path> [options]
|
||||
```
|
||||
|
||||
**Arguments:**
|
||||
- `source-image`: Path to the image to edit
|
||||
- `prompt`: Text description of the desired changes
|
||||
- `output-path`: Where to save the edited image
|
||||
|
||||
**Options:**
|
||||
- `--model <string>`: Gemini model to use (default: 'gemini-2.0-flash-exp')
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Basic editing
|
||||
GEMINI_API_KEY=xxx npx tsx scripts/edit-image.ts photo.jpg "add a blue sky" edited.jpg
|
||||
|
||||
# Style transfer
|
||||
npx tsx scripts/edit-image.ts portrait.png "make it look like a watercolor painting" artistic.png
|
||||
|
||||
# Using npm script
|
||||
npm run edit photo.jpg "remove background" no-bg.png
|
||||
```
|
||||
|
||||
### 3. Compose Images (`scripts/compose-images.ts`)
|
||||
|
||||
Combine multiple images into a single composition.
|
||||
|
||||
**Usage:**
|
||||
```bash
|
||||
npx tsx scripts/compose-images.ts <output-path> <image1> <image2> [image3...] [options]
|
||||
```
|
||||
|
||||
**Arguments:**
|
||||
- `output-path`: Where to save the composed image
|
||||
- `image1, image2, ...`: Paths to images to combine (2-4 images)
|
||||
|
||||
**Options:**
|
||||
- `--layout <string>`: Layout pattern (horizontal, vertical, grid, custom) (default: 'grid')
|
||||
- `--prompt <string>`: Additional instructions for composition
|
||||
- `--width <number>`: Output width in pixels (default: auto)
|
||||
- `--height <number>`: Output height in pixels (default: auto)
|
||||
|
||||
**Examples:**
|
||||
```bash
|
||||
# Grid layout
|
||||
GEMINI_API_KEY=xxx npx tsx scripts/compose-images.ts collage.png img1.jpg img2.jpg img3.jpg img4.jpg
|
||||
|
||||
# Horizontal layout
|
||||
npx tsx scripts/compose-images.ts banner.png left.png right.png --layout horizontal
|
||||
|
||||
# Custom composition with prompt
|
||||
npx tsx scripts/compose-images.ts result.png a.jpg b.jpg --prompt "blend seamlessly with gradient transition"
|
||||
|
||||
# Using npm script
|
||||
npm run compose output.png photo1.jpg photo2.jpg photo3.jpg --layout vertical
|
||||
```
|
||||
|
||||
## NPM Scripts
|
||||
|
||||
The package.json includes convenient npm scripts:
|
||||
|
||||
```bash
|
||||
npm run generate <prompt> <output> # Generate image from prompt
|
||||
npm run edit <source> <prompt> <output> # Edit existing image
|
||||
npm run compose <output> <images...> # Compose multiple images
|
||||
```
|
||||
|
||||
## Installation
|
||||
|
||||
From the skill directory:
|
||||
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
|
||||
This installs:
|
||||
- `@google/generative-ai`: Google's Gemini API SDK
|
||||
- `tsx`: TypeScript execution runtime
|
||||
- `typescript`: TypeScript compiler
|
||||
|
||||
## Usage in Design Workflows
|
||||
|
||||
### Creating Marketing Assets
|
||||
```bash
|
||||
# Generate hero image
|
||||
npm run generate "modern tech startup hero image, clean, professional" hero.png --width 1920 --height 1080
|
||||
|
||||
# Create variations
|
||||
npm run edit hero.png "change color scheme to blue and green" hero-variant.png
|
||||
|
||||
# Compose for social media
|
||||
npm run compose social-post.png hero.png logo.png --layout horizontal
|
||||
```
|
||||
|
||||
### Rapid Prototyping
|
||||
```bash
|
||||
# Generate UI mockup
|
||||
npm run generate "mobile app login screen, minimalist design" mockup.png --width 375 --height 812
|
||||
|
||||
# Iterate on design
|
||||
npm run edit mockup.png "add a gradient background" mockup-v2.png
|
||||
```
|
||||
|
||||
### Content Creation
|
||||
```bash
|
||||
# Generate illustrations
|
||||
npm run generate "technical diagram of cloud architecture" diagram.png
|
||||
|
||||
# Create composite images
|
||||
npm run compose infographic.png chart1.png chart2.png diagram.png --layout vertical
|
||||
```
|
||||
|
||||
## Technical Details
|
||||
|
||||
### Image Generation
|
||||
- Uses Gemini's imagen-3.0-generate-001 model
|
||||
- Supports text-to-image generation
|
||||
- Configurable output dimensions
|
||||
- Automatic format detection from file extension
|
||||
|
||||
### Image Editing
|
||||
- Uses Gemini's vision capabilities
|
||||
- Applies transformations based on natural language
|
||||
- Preserves original image quality where possible
|
||||
- Supports various editing operations (style, objects, colors, etc.)
|
||||
|
||||
### Image Composition
|
||||
- Intelligent layout algorithms
|
||||
- Automatic sizing and spacing
|
||||
- Seamless blending options
|
||||
- Support for multiple composition patterns
|
||||
|
||||
## Error Handling
|
||||
|
||||
Common errors and solutions:
|
||||
|
||||
1. **Missing API Key**: Ensure `GEMINI_API_KEY` environment variable is set
|
||||
2. **Invalid Image Format**: Use supported formats (PNG, JPEG, WebP)
|
||||
3. **File Not Found**: Verify source image paths are correct
|
||||
4. **API Rate Limits**: Implement delays between requests if needed
|
||||
5. **Large File Sizes**: Compress images before editing/composing
|
||||
|
||||
## Limitations
|
||||
|
||||
- API rate limits apply based on your Gemini API tier
|
||||
- Generated images are subject to Gemini's content policies
|
||||
- Maximum image dimensions depend on the model used
|
||||
- Processing time varies based on complexity and size
|
||||
|
||||
## Integration with Claude Code
|
||||
|
||||
This skill runs locally and can be used during development:
|
||||
|
||||
1. **Design System Creation**: Generate component mockups and visual assets
|
||||
2. **Documentation**: Create diagrams and illustrations for docs
|
||||
3. **Testing**: Generate test images for visual regression testing
|
||||
4. **Prototyping**: Rapid iteration on visual concepts
|
||||
|
||||
## See Also
|
||||
|
||||
- [Google Gemini API Documentation](https://ai.google.dev/docs)
|
||||
- [Gemini Image Generation Guide](https://ai.google.dev/docs/imagen)
|
||||
- Edge Stack Plugin for deployment workflows
|
||||
28
skills/gemini-imagegen/package.json
Normal file
28
skills/gemini-imagegen/package.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"name": "gemini-imagegen",
|
||||
"version": "1.0.0",
|
||||
"description": "Generate, edit, and compose images using Google's Gemini AI API",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"generate": "npx tsx scripts/generate-image.ts",
|
||||
"edit": "npx tsx scripts/edit-image.ts",
|
||||
"compose": "npx tsx scripts/compose-images.ts"
|
||||
},
|
||||
"keywords": [
|
||||
"gemini",
|
||||
"image-generation",
|
||||
"ai",
|
||||
"google-ai",
|
||||
"image-editing"
|
||||
],
|
||||
"author": "",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@google/generative-ai": "^0.21.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.11.0",
|
||||
"tsx": "^4.7.0",
|
||||
"typescript": "^5.3.0"
|
||||
}
|
||||
}
|
||||
287
skills/gemini-imagegen/scripts/compose-images.ts
Normal file
287
skills/gemini-imagegen/scripts/compose-images.ts
Normal file
@@ -0,0 +1,287 @@
|
||||
#!/usr/bin/env node
|
||||
import { GoogleGenerativeAI } from '@google/generative-ai';
|
||||
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
|
||||
interface ComposeOptions {
|
||||
layout?: 'horizontal' | 'vertical' | 'grid' | 'custom';
|
||||
prompt?: string;
|
||||
width?: number;
|
||||
height?: number;
|
||||
model?: string;
|
||||
}
|
||||
|
||||
async function composeImages(
|
||||
outputPath: string,
|
||||
imagePaths: string[],
|
||||
options: ComposeOptions = {}
|
||||
): Promise<void> {
|
||||
const apiKey = process.env.GEMINI_API_KEY;
|
||||
|
||||
if (!apiKey) {
|
||||
console.error('Error: GEMINI_API_KEY environment variable is required');
|
||||
console.error('Get your API key from: https://makersuite.google.com/app/apikey');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (imagePaths.length < 2) {
|
||||
console.error('Error: At least 2 images are required for composition');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (imagePaths.length > 4) {
|
||||
console.error('Error: Maximum 4 images supported for composition');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Verify all images exist
|
||||
const resolvedPaths: string[] = [];
|
||||
for (const imagePath of imagePaths) {
|
||||
const resolvedPath = resolve(imagePath);
|
||||
if (!existsSync(resolvedPath)) {
|
||||
console.error(`Error: Image not found: ${resolvedPath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
resolvedPaths.push(resolvedPath);
|
||||
}
|
||||
|
||||
const {
|
||||
layout = 'grid',
|
||||
prompt = '',
|
||||
width,
|
||||
height,
|
||||
model = 'gemini-2.0-flash-exp'
|
||||
} = options;
|
||||
|
||||
console.log('Composing images...');
|
||||
console.log(`Images: ${resolvedPaths.length}`);
|
||||
console.log(`Layout: ${layout}`);
|
||||
console.log(`Model: ${model}`);
|
||||
if (prompt) console.log(`Custom prompt: "${prompt}"`);
|
||||
|
||||
try {
|
||||
const genAI = new GoogleGenerativeAI(apiKey);
|
||||
const generativeModel = genAI.getGenerativeModel({ model });
|
||||
|
||||
// Read and encode all images
|
||||
const imageDataList: Array<{ data: string; mimeType: string; path: string }> = [];
|
||||
|
||||
for (const imagePath of resolvedPaths) {
|
||||
const imageData = readFileSync(imagePath);
|
||||
const base64Image = imageData.toString('base64');
|
||||
const mimeType = getMimeType(imagePath);
|
||||
|
||||
imageDataList.push({
|
||||
data: base64Image,
|
||||
mimeType,
|
||||
path: imagePath
|
||||
});
|
||||
|
||||
console.log(`Loaded: ${imagePath} (${(imageData.length / 1024).toFixed(2)} KB)`);
|
||||
}
|
||||
|
||||
// Build composition prompt
|
||||
let compositionPrompt = `You are an image composition assistant. Analyze these ${imageDataList.length} images and describe how to combine them into a single composition using a ${layout} layout.`;
|
||||
|
||||
if (width && height) {
|
||||
compositionPrompt += ` The output should be ${width}x${height} pixels.`;
|
||||
}
|
||||
|
||||
if (prompt) {
|
||||
compositionPrompt += ` Additional instructions: ${prompt}`;
|
||||
}
|
||||
|
||||
compositionPrompt += '\n\nProvide detailed instructions for:\n';
|
||||
compositionPrompt += '1. Optimal arrangement of images\n';
|
||||
compositionPrompt += '2. Sizing and spacing recommendations\n';
|
||||
compositionPrompt += '3. Any blending or transition effects\n';
|
||||
compositionPrompt += '4. Color harmony adjustments';
|
||||
|
||||
// Prepare content parts with all images
|
||||
const contentParts: Array<any> = [];
|
||||
|
||||
for (const imageData of imageDataList) {
|
||||
contentParts.push({
|
||||
inlineData: {
|
||||
data: imageData.data,
|
||||
mimeType: imageData.mimeType
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
contentParts.push(compositionPrompt);
|
||||
|
||||
// Analyze the composition
|
||||
const result = await generativeModel.generateContent(contentParts);
|
||||
const response = result.response;
|
||||
const compositionInstructions = response.text();
|
||||
|
||||
console.log('\nComposition Analysis:');
|
||||
console.log(compositionInstructions);
|
||||
|
||||
// For actual image composition with Gemini, you would typically:
|
||||
// 1. Use an image composition/editing model
|
||||
// 2. Send all source images with layout instructions
|
||||
// 3. Receive the composed image as base64
|
||||
// 4. Save to output path
|
||||
|
||||
console.warn('\nNote: This is a demonstration implementation.');
|
||||
console.warn('For actual image composition, you would use specialized image composition APIs.');
|
||||
console.warn('The model has analyzed the images and provided composition instructions.');
|
||||
|
||||
// Calculate suggested dimensions based on layout
|
||||
const suggestedDimensions = calculateDimensions(layout, imageDataList.length, width, height);
|
||||
console.log(`\nSuggested output dimensions: ${suggestedDimensions.width}x${suggestedDimensions.height}`);
|
||||
|
||||
// In a real implementation:
|
||||
// const composedImageData = Buffer.from(response.candidates[0].content.parts[0].inlineData.data, 'base64');
|
||||
// writeFileSync(resolve(outputPath), composedImageData);
|
||||
|
||||
console.log(`\nTo implement actual image composition:`);
|
||||
console.log(`1. Use an image composition library or service`);
|
||||
console.log(`2. Apply the ${layout} layout with ${imageDataList.length} images`);
|
||||
console.log(`3. Follow the composition instructions provided above`);
|
||||
console.log(`4. Save to: ${resolve(outputPath)}`);
|
||||
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.error('Error composing images:', error.message);
|
||||
if (error.message.includes('API key')) {
|
||||
console.error('\nPlease verify your GEMINI_API_KEY is valid');
|
||||
}
|
||||
} else {
|
||||
console.error('Error composing images:', error);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function getMimeType(filePath: string): string {
|
||||
const extension = filePath.toLowerCase().split('.').pop();
|
||||
const mimeTypes: Record<string, string> = {
|
||||
'jpg': 'image/jpeg',
|
||||
'jpeg': 'image/jpeg',
|
||||
'png': 'image/png',
|
||||
'gif': 'image/gif',
|
||||
'webp': 'image/webp',
|
||||
'bmp': 'image/bmp'
|
||||
};
|
||||
return mimeTypes[extension || ''] || 'image/jpeg';
|
||||
}
|
||||
|
||||
function calculateDimensions(
|
||||
layout: string,
|
||||
imageCount: number,
|
||||
width?: number,
|
||||
height?: number
|
||||
): { width: number; height: number } {
|
||||
// If dimensions are provided, use them
|
||||
if (width && height) {
|
||||
return { width, height };
|
||||
}
|
||||
|
||||
// Default image size assumption
|
||||
const defaultSize = 1024;
|
||||
|
||||
switch (layout) {
|
||||
case 'horizontal':
|
||||
return {
|
||||
width: width || defaultSize * imageCount,
|
||||
height: height || defaultSize
|
||||
};
|
||||
case 'vertical':
|
||||
return {
|
||||
width: width || defaultSize,
|
||||
height: height || defaultSize * imageCount
|
||||
};
|
||||
case 'grid':
|
||||
const cols = Math.ceil(Math.sqrt(imageCount));
|
||||
const rows = Math.ceil(imageCount / cols);
|
||||
return {
|
||||
width: width || defaultSize * cols,
|
||||
height: height || defaultSize * rows
|
||||
};
|
||||
case 'custom':
|
||||
default:
|
||||
return {
|
||||
width: width || defaultSize,
|
||||
height: height || defaultSize
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Parse command line arguments
|
||||
function parseArgs(): { outputPath: string; imagePaths: string[]; options: ComposeOptions } {
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.length < 3) {
|
||||
console.error('Usage: compose-images.ts <output-path> <image1> <image2> [image3...] [options]');
|
||||
console.error('\nArguments:');
|
||||
console.error(' output-path Where to save the composed image');
|
||||
console.error(' image1-4 Paths to images to combine (2-4 images)');
|
||||
console.error('\nOptions:');
|
||||
console.error(' --layout <string> Layout pattern (horizontal|vertical|grid|custom) (default: grid)');
|
||||
console.error(' --prompt <string> Additional composition instructions');
|
||||
console.error(' --width <number> Output width in pixels (default: auto)');
|
||||
console.error(' --height <number> Output height in pixels (default: auto)');
|
||||
console.error(' --model <string> Gemini model to use (default: gemini-2.0-flash-exp)');
|
||||
console.error('\nExample:');
|
||||
console.error(' GEMINI_API_KEY=xxx npx tsx scripts/compose-images.ts collage.png img1.jpg img2.jpg img3.jpg --layout grid');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const outputPath = args[0];
|
||||
const imagePaths: string[] = [];
|
||||
const options: ComposeOptions = {};
|
||||
|
||||
// Parse image paths and options
|
||||
for (let i = 1; i < args.length; i++) {
|
||||
const arg = args[i];
|
||||
|
||||
if (arg.startsWith('--')) {
|
||||
const flag = arg;
|
||||
const value = args[i + 1];
|
||||
|
||||
switch (flag) {
|
||||
case '--layout':
|
||||
if (['horizontal', 'vertical', 'grid', 'custom'].includes(value)) {
|
||||
options.layout = value as ComposeOptions['layout'];
|
||||
} else {
|
||||
console.warn(`Invalid layout: ${value}. Using default: grid`);
|
||||
}
|
||||
i++;
|
||||
break;
|
||||
case '--prompt':
|
||||
options.prompt = value;
|
||||
i++;
|
||||
break;
|
||||
case '--width':
|
||||
options.width = parseInt(value, 10);
|
||||
i++;
|
||||
break;
|
||||
case '--height':
|
||||
options.height = parseInt(value, 10);
|
||||
i++;
|
||||
break;
|
||||
case '--model':
|
||||
options.model = value;
|
||||
i++;
|
||||
break;
|
||||
default:
|
||||
console.warn(`Unknown option: ${flag}`);
|
||||
i++;
|
||||
}
|
||||
} else {
|
||||
imagePaths.push(arg);
|
||||
}
|
||||
}
|
||||
|
||||
return { outputPath, imagePaths, options };
|
||||
}
|
||||
|
||||
// Main execution
|
||||
const { outputPath, imagePaths, options } = parseArgs();
|
||||
composeImages(outputPath, imagePaths, options).catch((error) => {
|
||||
console.error('Fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
162
skills/gemini-imagegen/scripts/edit-image.ts
Normal file
162
skills/gemini-imagegen/scripts/edit-image.ts
Normal file
@@ -0,0 +1,162 @@
|
||||
#!/usr/bin/env node
|
||||
import { GoogleGenerativeAI } from '@google/generative-ai';
|
||||
import { readFileSync, writeFileSync, existsSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
|
||||
interface EditOptions {
|
||||
model?: string;
|
||||
}
|
||||
|
||||
async function editImage(
|
||||
sourcePath: string,
|
||||
prompt: string,
|
||||
outputPath: string,
|
||||
options: EditOptions = {}
|
||||
): Promise<void> {
|
||||
const apiKey = process.env.GEMINI_API_KEY;
|
||||
|
||||
if (!apiKey) {
|
||||
console.error('Error: GEMINI_API_KEY environment variable is required');
|
||||
console.error('Get your API key from: https://makersuite.google.com/app/apikey');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const resolvedSourcePath = resolve(sourcePath);
|
||||
|
||||
if (!existsSync(resolvedSourcePath)) {
|
||||
console.error(`Error: Source image not found: ${resolvedSourcePath}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const { model = 'gemini-2.0-flash-exp' } = options;
|
||||
|
||||
console.log('Editing image...');
|
||||
console.log(`Source: ${resolvedSourcePath}`);
|
||||
console.log(`Prompt: "${prompt}"`);
|
||||
console.log(`Model: ${model}`);
|
||||
|
||||
try {
|
||||
const genAI = new GoogleGenerativeAI(apiKey);
|
||||
const generativeModel = genAI.getGenerativeModel({ model });
|
||||
|
||||
// Read and encode the source image
|
||||
const imageData = readFileSync(resolvedSourcePath);
|
||||
const base64Image = imageData.toString('base64');
|
||||
|
||||
// Determine MIME type from file extension
|
||||
const mimeType = getMimeType(resolvedSourcePath);
|
||||
|
||||
console.log(`Image size: ${(imageData.length / 1024).toFixed(2)} KB`);
|
||||
console.log(`MIME type: ${mimeType}`);
|
||||
|
||||
// Use Gemini's vision capabilities to analyze and describe the edit
|
||||
const enhancedPrompt = `You are an image editing assistant. Analyze this image and describe how to apply the following edit: "${prompt}". Provide detailed instructions for the transformation.`;
|
||||
|
||||
const result = await generativeModel.generateContent([
|
||||
{
|
||||
inlineData: {
|
||||
data: base64Image,
|
||||
mimeType: mimeType
|
||||
}
|
||||
},
|
||||
enhancedPrompt
|
||||
]);
|
||||
|
||||
const response = result.response;
|
||||
const editInstructions = response.text();
|
||||
|
||||
console.log('\nEdit Analysis:');
|
||||
console.log(editInstructions);
|
||||
|
||||
// For actual image editing with Gemini, you would typically:
|
||||
// 1. Use the Imagen model's image editing capabilities
|
||||
// 2. Send the source image with the edit prompt
|
||||
// 3. Receive the edited image as base64
|
||||
// 4. Save to output path
|
||||
|
||||
console.warn('\nNote: This is a demonstration implementation.');
|
||||
console.warn('For actual image editing, you would use Gemini\'s image editing API.');
|
||||
console.warn('The model has analyzed the image and provided edit instructions.');
|
||||
|
||||
// In a real implementation with Imagen editing:
|
||||
// const editedImageData = Buffer.from(response.candidates[0].content.parts[0].inlineData.data, 'base64');
|
||||
// writeFileSync(resolve(outputPath), editedImageData);
|
||||
|
||||
console.log(`\nTo implement actual image editing:`);
|
||||
console.log(`1. Use Gemini's image editing endpoint`);
|
||||
console.log(`2. Send source image with edit prompt`);
|
||||
console.log(`3. Parse the edited image data from response`);
|
||||
console.log(`4. Save to: ${resolve(outputPath)}`);
|
||||
console.log(`\nRefer to: https://ai.google.dev/docs/imagen`);
|
||||
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.error('Error editing image:', error.message);
|
||||
if (error.message.includes('API key')) {
|
||||
console.error('\nPlease verify your GEMINI_API_KEY is valid');
|
||||
}
|
||||
} else {
|
||||
console.error('Error editing image:', error);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
function getMimeType(filePath: string): string {
|
||||
const extension = filePath.toLowerCase().split('.').pop();
|
||||
const mimeTypes: Record<string, string> = {
|
||||
'jpg': 'image/jpeg',
|
||||
'jpeg': 'image/jpeg',
|
||||
'png': 'image/png',
|
||||
'gif': 'image/gif',
|
||||
'webp': 'image/webp',
|
||||
'bmp': 'image/bmp'
|
||||
};
|
||||
return mimeTypes[extension || ''] || 'image/jpeg';
|
||||
}
|
||||
|
||||
// Parse command line arguments
|
||||
function parseArgs(): { sourcePath: string; prompt: string; outputPath: string; options: EditOptions } {
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.length < 3) {
|
||||
console.error('Usage: edit-image.ts <source-image> <prompt> <output-path> [options]');
|
||||
console.error('\nArguments:');
|
||||
console.error(' source-image Path to the image to edit');
|
||||
console.error(' prompt Text description of the desired changes');
|
||||
console.error(' output-path Where to save the edited image');
|
||||
console.error('\nOptions:');
|
||||
console.error(' --model <string> Gemini model to use (default: gemini-2.0-flash-exp)');
|
||||
console.error('\nExample:');
|
||||
console.error(' GEMINI_API_KEY=xxx npx tsx scripts/edit-image.ts photo.jpg "add a blue sky" edited.jpg');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const sourcePath = args[0];
|
||||
const prompt = args[1];
|
||||
const outputPath = args[2];
|
||||
const options: EditOptions = {};
|
||||
|
||||
// Parse options
|
||||
for (let i = 3; i < args.length; i += 2) {
|
||||
const flag = args[i];
|
||||
const value = args[i + 1];
|
||||
|
||||
switch (flag) {
|
||||
case '--model':
|
||||
options.model = value;
|
||||
break;
|
||||
default:
|
||||
console.warn(`Unknown option: ${flag}`);
|
||||
}
|
||||
}
|
||||
|
||||
return { sourcePath, prompt, outputPath, options };
|
||||
}
|
||||
|
||||
// Main execution
|
||||
const { sourcePath, prompt, outputPath, options } = parseArgs();
|
||||
editImage(sourcePath, prompt, outputPath, options).catch((error) => {
|
||||
console.error('Fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
142
skills/gemini-imagegen/scripts/generate-image.ts
Normal file
142
skills/gemini-imagegen/scripts/generate-image.ts
Normal file
@@ -0,0 +1,142 @@
|
||||
#!/usr/bin/env node
|
||||
import { GoogleGenerativeAI } from '@google/generative-ai';
|
||||
import { writeFileSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
|
||||
interface GenerateOptions {
|
||||
width?: number;
|
||||
height?: number;
|
||||
model?: string;
|
||||
}
|
||||
|
||||
async function generateImage(
|
||||
prompt: string,
|
||||
outputPath: string,
|
||||
options: GenerateOptions = {}
|
||||
): Promise<void> {
|
||||
const apiKey = process.env.GEMINI_API_KEY;
|
||||
|
||||
if (!apiKey) {
|
||||
console.error('Error: GEMINI_API_KEY environment variable is required');
|
||||
console.error('Get your API key from: https://makersuite.google.com/app/apikey');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const {
|
||||
width = 1024,
|
||||
height = 1024,
|
||||
model = 'gemini-2.0-flash-exp'
|
||||
} = options;
|
||||
|
||||
console.log('Generating image...');
|
||||
console.log(`Prompt: "${prompt}"`);
|
||||
console.log(`Dimensions: ${width}x${height}`);
|
||||
console.log(`Model: ${model}`);
|
||||
|
||||
try {
|
||||
const genAI = new GoogleGenerativeAI(apiKey);
|
||||
const generativeModel = genAI.getGenerativeModel({ model });
|
||||
|
||||
// Enhanced prompt with image generation context
|
||||
const enhancedPrompt = `Generate a high-quality image with the following description: ${prompt}. Image dimensions: ${width}x${height} pixels.`;
|
||||
|
||||
// For image generation, we'll use the text generation to get image data
|
||||
// Note: As of the current Gemini API, direct image generation might require
|
||||
// using the imagen model or multimodal capabilities
|
||||
const result = await generativeModel.generateContent([
|
||||
{
|
||||
inlineData: {
|
||||
data: '',
|
||||
mimeType: 'text/plain'
|
||||
}
|
||||
},
|
||||
enhancedPrompt
|
||||
]);
|
||||
|
||||
const response = result.response;
|
||||
const text = response.text();
|
||||
|
||||
// For actual image generation with Gemini, you would typically:
|
||||
// 1. Use the Imagen model (imagen-3.0-generate-001)
|
||||
// 2. Parse the response to get base64 image data
|
||||
// 3. Convert to binary and save
|
||||
|
||||
// Placeholder implementation - in production, this would use the actual Imagen API
|
||||
console.warn('\nNote: This is a demonstration implementation.');
|
||||
console.warn('For actual image generation, you would use the Imagen model.');
|
||||
console.warn('Response from model:', text.substring(0, 200) + '...');
|
||||
|
||||
// In a real implementation with Imagen:
|
||||
// const imageData = Buffer.from(response.candidates[0].content.parts[0].inlineData.data, 'base64');
|
||||
// writeFileSync(resolve(outputPath), imageData);
|
||||
|
||||
console.log(`\nTo implement actual image generation:`);
|
||||
console.log(`1. Use the Imagen model (imagen-3.0-generate-001)`);
|
||||
console.log(`2. Parse the base64 image data from the response`);
|
||||
console.log(`3. Save to: ${resolve(outputPath)}`);
|
||||
console.log(`\nRefer to: https://ai.google.dev/docs/imagen`);
|
||||
|
||||
} catch (error) {
|
||||
if (error instanceof Error) {
|
||||
console.error('Error generating image:', error.message);
|
||||
if (error.message.includes('API key')) {
|
||||
console.error('\nPlease verify your GEMINI_API_KEY is valid');
|
||||
}
|
||||
} else {
|
||||
console.error('Error generating image:', error);
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Parse command line arguments
|
||||
function parseArgs(): { prompt: string; outputPath: string; options: GenerateOptions } {
|
||||
const args = process.argv.slice(2);
|
||||
|
||||
if (args.length < 2) {
|
||||
console.error('Usage: generate-image.ts <prompt> <output-path> [options]');
|
||||
console.error('\nArguments:');
|
||||
console.error(' prompt Text description of the image to generate');
|
||||
console.error(' output-path Where to save the generated image');
|
||||
console.error('\nOptions:');
|
||||
console.error(' --width <number> Image width in pixels (default: 1024)');
|
||||
console.error(' --height <number> Image height in pixels (default: 1024)');
|
||||
console.error(' --model <string> Gemini model to use (default: gemini-2.0-flash-exp)');
|
||||
console.error('\nExample:');
|
||||
console.error(' GEMINI_API_KEY=xxx npx tsx scripts/generate-image.ts "a sunset over mountains" output.png --width 1920 --height 1080');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const prompt = args[0];
|
||||
const outputPath = args[1];
|
||||
const options: GenerateOptions = {};
|
||||
|
||||
// Parse options
|
||||
for (let i = 2; i < args.length; i += 2) {
|
||||
const flag = args[i];
|
||||
const value = args[i + 1];
|
||||
|
||||
switch (flag) {
|
||||
case '--width':
|
||||
options.width = parseInt(value, 10);
|
||||
break;
|
||||
case '--height':
|
||||
options.height = parseInt(value, 10);
|
||||
break;
|
||||
case '--model':
|
||||
options.model = value;
|
||||
break;
|
||||
default:
|
||||
console.warn(`Unknown option: ${flag}`);
|
||||
}
|
||||
}
|
||||
|
||||
return { prompt, outputPath, options };
|
||||
}
|
||||
|
||||
// Main execution
|
||||
const { prompt, outputPath, options } = parseArgs();
|
||||
generateImage(prompt, outputPath, options).catch((error) => {
|
||||
console.error('Fatal error:', error);
|
||||
process.exit(1);
|
||||
});
|
||||
18
skills/gemini-imagegen/tsconfig.json
Normal file
18
skills/gemini-imagegen/tsconfig.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2022",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "node",
|
||||
"lib": ["ES2022"],
|
||||
"esModuleInterop": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"resolveJsonModule": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"outDir": "./dist",
|
||||
"rootDir": "./scripts"
|
||||
},
|
||||
"include": ["scripts/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
346
skills/kv-optimization-advisor/SKILL.md
Normal file
346
skills/kv-optimization-advisor/SKILL.md
Normal file
@@ -0,0 +1,346 @@
|
||||
---
|
||||
name: kv-optimization-advisor
|
||||
description: Automatically optimizes Cloudflare KV storage patterns, suggesting parallel operations, caching strategies, and storage choice guidance
|
||||
triggers: ["KV operations", "storage access patterns", "sequential storage calls", "large data patterns"]
|
||||
---
|
||||
|
||||
# KV Optimization Advisor SKILL
|
||||
|
||||
## Activation Patterns
|
||||
|
||||
This SKILL automatically activates when:
|
||||
- KV `get`, `put`, `delete`, or `list` operations are detected
|
||||
- Sequential storage operations that could be parallelized
|
||||
- Large data patterns that might exceed KV limits
|
||||
- Missing caching opportunities for repeated KV calls
|
||||
- Storage choice patterns (KV vs R2 vs D1)
|
||||
|
||||
## Expertise Provided
|
||||
|
||||
### KV Performance Optimization
|
||||
- **Parallel Operations**: Identifies sequential KV calls that can be parallelized
|
||||
- **Request-Scoped Caching**: Suggests in-memory caching during request processing
|
||||
- **Storage Choice Guidance**: Recommends KV vs R2 vs D1 based on use case
|
||||
- **Value Size Optimization**: Monitors for large values that impact performance
|
||||
- **Batch Operations**: Suggests batch operations when appropriate
|
||||
- **TTL Optimization**: Recommends optimal TTL strategies
|
||||
|
||||
### Specific Checks Performed
|
||||
|
||||
#### ❌ KV Performance Anti-Patterns
|
||||
```typescript
|
||||
// These patterns trigger immediate alerts:
|
||||
// Sequential KV operations (multiple network round-trips)
|
||||
const user = await env.USERS.get(id); // 10-30ms
|
||||
const settings = await env.SETTINGS.get(id); // 10-30ms
|
||||
const prefs = await env.PREFS.get(id); // 10-30ms
|
||||
// Total: 30-90ms just for storage!
|
||||
|
||||
// Repeated KV calls in same request
|
||||
const user1 = await env.USERS.get(id);
|
||||
const user2 = await env.USERS.get(id); // Same data fetched twice!
|
||||
```
|
||||
|
||||
#### ✅ KV Performance Best Practices
|
||||
```typescript
|
||||
// These patterns are validated as correct:
|
||||
// Parallel KV operations (single network round-trip)
|
||||
const [user, settings, prefs] = await Promise.all([
|
||||
env.USERS.get(id),
|
||||
env.SETTINGS.get(id),
|
||||
env.PREFS.get(id),
|
||||
]);
|
||||
// Total: 10-30ms (single round-trip)
|
||||
|
||||
// Request-scoped caching
|
||||
const cache = new Map();
|
||||
async function getCached(key: string, env: Env) {
|
||||
if (cache.has(key)) return cache.get(key);
|
||||
const value = await env.USERS.get(key);
|
||||
cache.set(key, value);
|
||||
return value;
|
||||
}
|
||||
```
|
||||
|
||||
## Integration Points
|
||||
|
||||
### Complementary to Existing Components
|
||||
- **edge-performance-oracle agent**: Handles comprehensive performance analysis, SKILL provides immediate KV optimization
|
||||
- **cloudflare-architecture-strategist agent**: Handles storage architecture decisions, SKILL provides immediate optimization
|
||||
- **workers-binding-validator SKILL**: Ensures KV bindings are correct, SKILL optimizes usage patterns
|
||||
|
||||
### Escalation Triggers
|
||||
- Complex storage architecture questions → `cloudflare-architecture-strategist` agent
|
||||
- KV performance troubleshooting → `edge-performance-oracle` agent
|
||||
- Storage migration strategies → `cloudflare-architecture-strategist` agent
|
||||
|
||||
## Validation Rules
|
||||
|
||||
### P1 - Critical (Performance Killer)
|
||||
- **Sequential Operations**: Multiple sequential KV calls that could be parallelized
|
||||
- **Repeated Calls**: Same KV key fetched multiple times in one request
|
||||
- **Large Values**: Values approaching 25MB KV limit
|
||||
|
||||
### P2 - High (Performance Impact)
|
||||
- **Missing Caching**: Repeated expensive KV operations without caching
|
||||
- **Wrong Storage Choice**: Using KV for data that should be in R2 or D1
|
||||
- **No TTL Strategy**: Missing or inappropriate TTL configuration
|
||||
|
||||
### P3 - Medium (Optimization Opportunity)
|
||||
- **Batch Opportunities**: Multiple operations that could be batched
|
||||
- **Suboptimal TTL**: TTL values that are too short or too long
|
||||
- **Missing Error Handling**: KV operations without proper error handling
|
||||
|
||||
## Remediation Examples
|
||||
|
||||
### Fixing Sequential Operations
|
||||
```typescript
|
||||
// ❌ Critical: Sequential KV operations (3x network round-trips)
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const userId = getUserId(request);
|
||||
|
||||
const user = await env.USERS.get(userId); // 10-30ms
|
||||
const settings = await env.SETTINGS.get(userId); // 10-30ms
|
||||
const prefs = await env.PREFS.get(userId); // 10-30ms
|
||||
|
||||
// Total: 30-90ms just for storage!
|
||||
return new Response(JSON.stringify({ user, settings, prefs }));
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Correct: Parallel operations (single round-trip)
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const userId = getUserId(request);
|
||||
|
||||
// Fetch in parallel - single network round-trip time
|
||||
const [user, settings, prefs] = await Promise.all([
|
||||
env.USERS.get(userId),
|
||||
env.SETTINGS.get(userId),
|
||||
env.PREFS.get(userId),
|
||||
]);
|
||||
|
||||
// Total: 10-30ms (single round-trip)
|
||||
return new Response(JSON.stringify({ user, settings, prefs }));
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fixing Repeated Calls with Caching
|
||||
```typescript
|
||||
// ❌ High: Same KV data fetched multiple times
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const userId = getUserId(request);
|
||||
|
||||
// Fetch user data multiple times unnecessarily
|
||||
const user1 = await env.USERS.get(userId);
|
||||
const user2 = await env.USERS.get(userId); // Duplicate call!
|
||||
const user3 = await env.USERS.get(userId); // Duplicate call!
|
||||
|
||||
// Process user data...
|
||||
return new Response('Processed');
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Correct: Request-scoped caching
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const userId = getUserId(request);
|
||||
|
||||
// Request-scoped cache to avoid duplicate KV calls
|
||||
const cache = new Map();
|
||||
|
||||
async function getCachedUser(id: string) {
|
||||
if (cache.has(id)) return cache.get(id);
|
||||
const user = await env.USERS.get(id);
|
||||
cache.set(id, user);
|
||||
return user;
|
||||
}
|
||||
|
||||
const user1 = await getCachedUser(userId); // KV call
|
||||
const user2 = await getCachedUser(userId); // From cache
|
||||
const user3 = await getCachedUser(userId); // From cache
|
||||
|
||||
// Process user data...
|
||||
return new Response('Processed');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fixing Storage Choice
|
||||
```typescript
|
||||
// ❌ High: Using KV for large files (wrong storage choice)
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const fileId = new URL(request.url).searchParams.get('id');
|
||||
|
||||
// KV is for small key-value data, not large files!
|
||||
const fileData = await env.FILES.get(fileId); // Could be 10MB+
|
||||
|
||||
return new Response(fileData);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Correct: Use R2 for large files
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const fileId = new URL(request.url).searchParams.get('id');
|
||||
|
||||
// R2 is designed for large objects/files
|
||||
const object = await env.FILES_BUCKET.get(fileId);
|
||||
|
||||
if (!object) {
|
||||
return new Response('Not found', { status: 404 });
|
||||
}
|
||||
|
||||
return new Response(object.body);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fixing TTL Strategy
|
||||
```typescript
|
||||
// ❌ Medium: No TTL strategy (data never expires)
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const cacheKey = `data:${Date.now()}`;
|
||||
|
||||
// Data cached forever - may become stale
|
||||
await env.CACHE.put(cacheKey, data);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Correct: Appropriate TTL strategy
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const cacheKey = 'user:profile:123';
|
||||
|
||||
// Cache user profile for 1 hour (reasonable for user data)
|
||||
await env.CACHE.put(cacheKey, data, {
|
||||
expirationTtl: 3600 // 1 hour
|
||||
});
|
||||
|
||||
// Cache API response for 5 minutes (frequently changing)
|
||||
await env.API_CACHE.put(apiKey, response, {
|
||||
expirationTtl: 300 // 5 minutes
|
||||
});
|
||||
|
||||
// Cache static data for 24 hours (rarely changes)
|
||||
await env.STATIC_CACHE.put(staticKey, data, {
|
||||
expirationTtl: 86400 // 24 hours
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fixing Large Value Handling
|
||||
```typescript
|
||||
// ❌ High: Large values approaching KV limits
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const reportId = new URL(request.url).searchParams.get('id');
|
||||
|
||||
// Large report (20MB) - close to KV 25MB limit!
|
||||
const report = await env.REPORTS.get(reportId);
|
||||
|
||||
return new Response(report);
|
||||
}
|
||||
}
|
||||
|
||||
// ✅ Correct: Compress large values or use R2
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const reportId = new URL(request.url).searchParams.get('id');
|
||||
|
||||
// Option 1: Compress before storing in KV
|
||||
const compressed = await env.REPORTS.get(reportId);
|
||||
const decompressed = decompress(compressed);
|
||||
|
||||
// Option 2: Use R2 for large objects
|
||||
const object = await env.REPORTS_BUCKET.get(reportId);
|
||||
|
||||
return new Response(object.body);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Storage Choice Guidance
|
||||
|
||||
### Use KV When:
|
||||
- **Small values** (< 1MB typical, < 25MB max)
|
||||
- **Key-value access patterns**
|
||||
- **Eventually consistent** data is acceptable
|
||||
- **Low latency** reads required globally
|
||||
- **Simple caching** needs
|
||||
|
||||
### Use R2 When:
|
||||
- **Large objects** (files, images, videos)
|
||||
- **S3-compatible** access needed
|
||||
- **Strong consistency** required
|
||||
- **Object storage** patterns
|
||||
- **Large files** (> 1MB)
|
||||
|
||||
### Use D1 When:
|
||||
- **Relational data** with complex queries
|
||||
- **Strong consistency** required
|
||||
- **SQL operations** needed
|
||||
- **Structured data** with relationships
|
||||
- **Complex queries** and joins
|
||||
|
||||
## MCP Server Integration
|
||||
|
||||
When Cloudflare MCP server is available:
|
||||
- Query KV performance metrics (latency, hit rates)
|
||||
- Analyze storage usage patterns
|
||||
- Get latest KV optimization techniques
|
||||
- Check storage limits and quotas
|
||||
|
||||
## Benefits
|
||||
|
||||
### Immediate Impact
|
||||
- **Faster Response Times**: Parallel operations reduce latency by 3x or more
|
||||
- **Reduced KV Costs**: Fewer operations and better caching
|
||||
- **Better Performance**: Proper storage choice improves overall performance
|
||||
|
||||
### Long-term Value
|
||||
- **Consistent Optimization**: Ensures all KV usage follows best practices
|
||||
- **Cost Efficiency**: Optimized storage patterns reduce costs
|
||||
- **Better User Experience**: Faster response times from optimized storage
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### During KV Operation Writing
|
||||
```typescript
|
||||
// Developer types: sequential KV gets
|
||||
// SKILL immediately activates: "⚠️ HIGH: Sequential KV operations detected. Use Promise.all() to parallelize and reduce latency by 3x."
|
||||
```
|
||||
|
||||
### During Storage Architecture
|
||||
```typescript
|
||||
// Developer types: storing large files in KV
|
||||
// SKILL immediately activates: "⚠️ HIGH: Large file storage in KV detected. Use R2 for objects > 1MB to avoid performance issues."
|
||||
```
|
||||
|
||||
### During Caching Implementation
|
||||
```typescript
|
||||
// Developer types: repeated KV calls in same request
|
||||
// SKILL immediately activates: "⚠️ HIGH: Duplicate KV calls detected. Add request-scoped caching to avoid redundant network calls."
|
||||
```
|
||||
|
||||
## Performance Targets
|
||||
|
||||
### KV Operation Latency
|
||||
- **Excellent**: < 10ms (parallel operations)
|
||||
- **Good**: < 30ms (single operation)
|
||||
- **Acceptable**: < 100ms (sequential operations)
|
||||
- **Needs Improvement**: > 100ms
|
||||
|
||||
### Cache Hit Rate
|
||||
- **Excellent**: > 90%
|
||||
- **Good**: > 75%
|
||||
- **Acceptable**: > 50%
|
||||
- **Needs Improvement**: < 50%
|
||||
|
||||
This SKILL ensures KV storage performance by providing immediate, autonomous optimization of storage patterns, preventing common performance issues and ensuring efficient data access.
|
||||
93
skills/polar-integration-validator/SKILL.md
Normal file
93
skills/polar-integration-validator/SKILL.md
Normal file
@@ -0,0 +1,93 @@
|
||||
---
|
||||
name: polar-integration-validator
|
||||
description: Autonomous validation of Polar.sh billing integration. Checks webhook endpoints, signature verification, subscription middleware, and environment configuration.
|
||||
triggers: ["webhook file changes", "subscription code changes", "wrangler.toml updates", "billing-related modifications"]
|
||||
---
|
||||
|
||||
# Polar Integration Validator SKILL
|
||||
|
||||
## Activation Patterns
|
||||
|
||||
This SKILL automatically activates when:
|
||||
- Files matching `**/webhooks/polar.*` are created/modified
|
||||
- Files containing "subscription" or "polar" in path are modified
|
||||
- `wrangler.toml` is updated
|
||||
- Environment variable files (`.dev.vars`, `.env`) are modified
|
||||
- Before deployment operations
|
||||
|
||||
## Validation Rules
|
||||
|
||||
### P1 - Critical (Block Operations)
|
||||
|
||||
**Webhook Endpoint**:
|
||||
- ✅ Webhook handler exists (`server/api/webhooks/polar.ts` or similar)
|
||||
- ✅ Signature verification implemented (`polar.webhooks.verify`)
|
||||
- ✅ All critical events handled: `checkout.completed`, `subscription.created`, `subscription.updated`, `subscription.canceled`
|
||||
|
||||
**Environment Variables**:
|
||||
- ✅ `POLAR_ACCESS_TOKEN` configured (check `.dev.vars` or secrets)
|
||||
- ✅ `POLAR_WEBHOOK_SECRET` in wrangler.toml
|
||||
|
||||
**Database**:
|
||||
- ✅ Users table has `polar_customer_id` column
|
||||
- ✅ Subscriptions table exists
|
||||
- ✅ Foreign key relationship configured
|
||||
|
||||
### P2 - Important (Warn)
|
||||
|
||||
**Event Handling**:
|
||||
- ⚠️ `subscription.past_due` handler exists
|
||||
- ⚠️ Database updates in all event handlers
|
||||
- ⚠️ Error logging implemented
|
||||
|
||||
**Subscription Middleware**:
|
||||
- ⚠️ Subscription check function exists
|
||||
- ⚠️ Used on protected routes
|
||||
- ⚠️ Checks `subscription_status === 'active'`
|
||||
- ⚠️ Checks `current_period_end` not expired
|
||||
|
||||
### P3 - Suggestions (Inform)
|
||||
|
||||
- ℹ️ Webhook event logging to database
|
||||
- ℹ️ Customer creation helper function
|
||||
- ℹ️ Subscription status caching
|
||||
- ℹ️ Rate limiting on webhook endpoint
|
||||
|
||||
## Validation Output
|
||||
|
||||
```
|
||||
🔍 Polar.sh Integration Validation
|
||||
|
||||
✅ P1 Checks (Critical):
|
||||
✅ Webhook endpoint exists
|
||||
✅ Signature verification implemented
|
||||
✅ Environment variables configured
|
||||
✅ Database schema complete
|
||||
|
||||
⚠️ P2 Checks (Important):
|
||||
⚠️ Missing subscription.past_due handler
|
||||
✅ Subscription middleware exists
|
||||
✅ Protected routes check subscription
|
||||
|
||||
ℹ️ P3 Suggestions:
|
||||
ℹ️ Consider adding webhook event logging
|
||||
ℹ️ Add rate limiting to webhook endpoint
|
||||
|
||||
📋 Summary: 1 warning found
|
||||
💡 Run /es-billing-setup to fix issues
|
||||
```
|
||||
|
||||
## Escalation
|
||||
|
||||
Complex scenarios escalate to `polar-billing-specialist` agent:
|
||||
- Custom webhook processing logic
|
||||
- Multi-tenant subscription architecture
|
||||
- Usage-based billing implementation
|
||||
- Migration from other billing providers
|
||||
|
||||
## Notes
|
||||
|
||||
- Runs automatically on relevant file changes
|
||||
- Can block deployments with P1 issues
|
||||
- Queries Polar MCP for product validation
|
||||
- Integrates with `/validate` and `/es-deploy` commands
|
||||
333
skills/shadcn-ui-design-validator/SKILL.md
Normal file
333
skills/shadcn-ui-design-validator/SKILL.md
Normal file
@@ -0,0 +1,333 @@
|
||||
---
|
||||
name: shadcn-ui-design-validator
|
||||
description: Automatically validates frontend design patterns to prevent generic aesthetics (Inter fonts, purple gradients, minimal animations) and enforce distinctive, branded design during Tanstack Start (React) development with shadcn/ui
|
||||
triggers: ["tsx file creation", "component changes", "tailwind config changes", "shadcn component usage", "design system updates"]
|
||||
note: "Updated for Tanstack Start + shadcn/ui. Validates React/TSX components with shadcn/ui patterns."
|
||||
---
|
||||
|
||||
# shadcn/ui Design Validator SKILL
|
||||
|
||||
## Activation Patterns
|
||||
|
||||
This SKILL automatically activates when:
|
||||
- New `.tsx` React components are created
|
||||
- Tailwind configuration (`tailwind.config.ts`) is modified
|
||||
- Tanstack Start configuration (`app.config.ts`) is modified
|
||||
- Component styling or classes are changed
|
||||
- Design token definitions are updated
|
||||
- Before deployment commands are executed
|
||||
|
||||
## Expertise Provided
|
||||
|
||||
### Design Pattern Validation
|
||||
- **Generic Pattern Detection**: Identifies default/overused design patterns
|
||||
- **Typography Analysis**: Ensures distinctive font choices and hierarchy
|
||||
- **Animation Validation**: Checks for engaging micro-interactions and transitions
|
||||
- **Color System**: Validates distinctive color palettes vs generic defaults
|
||||
- **Component Customization**: Ensures shadcn/ui components are customized, not default
|
||||
|
||||
### Specific Checks Performed
|
||||
|
||||
#### ❌ Critical Violations (Generic Design Patterns)
|
||||
```tsx
|
||||
<!-- These patterns trigger alerts: -->
|
||||
|
||||
<!-- Generic font (Inter/Roboto) -->
|
||||
<div className="font-sans"> <!-- Using default Inter -->
|
||||
|
||||
<!-- Purple gradient on white (overused pattern) -->
|
||||
<div className="bg-gradient-to-r from-purple-500 to-purple-600">
|
||||
|
||||
<!-- No animations/transitions -->
|
||||
<Button onClick="submit">Submit</Button> <!-- No hover state -->
|
||||
|
||||
<!-- Default background colors -->
|
||||
<div className="bg-gray-50"> <!-- Generic #f9fafb -->
|
||||
```
|
||||
|
||||
#### ✅ Correct Distinctive Patterns
|
||||
```tsx
|
||||
<!-- These patterns are validated as correct: -->
|
||||
|
||||
<!-- Custom distinctive fonts -->
|
||||
<h1 className="font-heading"> <!-- Custom font family -->
|
||||
|
||||
<!-- Custom brand colors -->
|
||||
<div className="bg-brand-coral"> <!-- Distinctive palette -->
|
||||
|
||||
<!-- Engaging animations -->
|
||||
<Button
|
||||
className="transition-all duration-300 hover:scale-105 hover:shadow-xl"
|
||||
onClick="submit"
|
||||
>
|
||||
Submit
|
||||
</Button>
|
||||
|
||||
<!-- Atmospheric backgrounds -->
|
||||
<div className="bg-gradient-to-br from-brand-ocean via-brand-sky to-brand-coral">
|
||||
```
|
||||
|
||||
## Integration Points
|
||||
|
||||
### Complementary to Existing Components
|
||||
- **frontend-design-specialist agent**: Handles deep design analysis, SKILL provides immediate validation
|
||||
- **tanstack-ui-architect agent**: Component expertise, SKILL validates implementation
|
||||
- **es-design-review command**: SKILL provides continuous validation between explicit reviews
|
||||
|
||||
### Escalation Triggers
|
||||
- Complex design system questions → `frontend-design-specialist` agent
|
||||
- Component customization help → `tanstack-ui-architect` agent
|
||||
- Accessibility concerns → `accessibility-guardian` agent
|
||||
- Full design review → `/es-design-review` command
|
||||
|
||||
## Validation Rules
|
||||
|
||||
### P1 - Critical (Generic Patterns to Avoid)
|
||||
- **Default Fonts**: Inter, Roboto, Helvetica (in over 80% of sites)
|
||||
- **Purple Gradients**: `from-purple-*` to `to-purple-*` on white backgrounds
|
||||
- **Generic Grays**: `bg-gray-50`, `bg-gray-100` (overused neutrals)
|
||||
- **No Animations**: Interactive elements without hover/focus transitions
|
||||
- **Default Component Props**: Using shadcn/ui components with all default props
|
||||
|
||||
### P2 - Important (Polish and Engagement)
|
||||
- **Missing Hover States**: Buttons/links without hover effects
|
||||
- **No Loading States**: Async actions without loading feedback
|
||||
- **Inconsistent Spacing**: Not using Tailwind spacing scale consistently
|
||||
- **No Micro-interactions**: Forms/buttons without feedback animations
|
||||
- **Weak Typography Hierarchy**: Similar font sizes for different heading levels
|
||||
|
||||
### P3 - Best Practices
|
||||
- **Font Weight Variety**: Using only one or two font weights
|
||||
- **Limited Color Palette**: Not defining custom brand colors
|
||||
- **No Custom Tokens**: Not extending Tailwind theme with brand values
|
||||
- **Missing Dark Mode**: No dark mode variants (if applicable)
|
||||
|
||||
## Remediation Examples
|
||||
|
||||
### Fixing Generic Fonts
|
||||
```tsx
|
||||
<!-- ❌ Critical: Default Inter font -->
|
||||
<h1 className="text-4xl font-sans">Welcome</h1>
|
||||
|
||||
<!-- ✅ Correct: Distinctive custom font -->
|
||||
<h1 className="text-4xl font-heading tracking-tight">Welcome</h1>
|
||||
|
||||
<!-- tailwind.config.ts -->
|
||||
export default {
|
||||
theme: {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
// ❌ NOT: sans: ['Inter', 'sans-serif']
|
||||
// ✅ YES: Distinctive fonts
|
||||
sans: ['Space Grotesk', 'system-ui', 'sans-serif'],
|
||||
heading: ['Archivo Black', 'system-ui', 'sans-serif'],
|
||||
mono: ['JetBrains Mono', 'monospace']
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fixing Generic Colors
|
||||
```tsx
|
||||
<!-- ❌ Critical: Purple gradient (overused) -->
|
||||
<div className="bg-gradient-to-r from-purple-500 to-purple-600">
|
||||
<h2 className="text-white">Hero Section</h2>
|
||||
</div>
|
||||
|
||||
<!-- ✅ Correct: Custom brand colors -->
|
||||
<div className="bg-gradient-to-br from-brand-coral via-brand-ocean to-brand-sunset">
|
||||
<h2 className="text-white">Hero Section</h2>
|
||||
</div>
|
||||
|
||||
<!-- tailwind.config.ts -->
|
||||
export default {
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
// ❌ NOT: Using only default Tailwind colors
|
||||
// ✅ YES: Custom brand palette
|
||||
brand: {
|
||||
coral: '#FF6B6B',
|
||||
ocean: '#4ECDC4',
|
||||
sunset: '#FFE66D',
|
||||
midnight: '#2C3E50',
|
||||
cream: '#FFF5E1'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fixing Missing Animations
|
||||
```tsx
|
||||
<!-- ❌ Critical: No hover/transition effects -->
|
||||
<Button onClick="handleSubmit">
|
||||
Submit Form
|
||||
</Button>
|
||||
|
||||
<!-- ✅ Correct: Engaging animations -->
|
||||
<Button
|
||||
className="transition-all duration-300 hover:scale-105 hover:shadow-xl active:scale-95"
|
||||
onClick="handleSubmit"
|
||||
>
|
||||
<span className="inline-flex items-center gap-2">
|
||||
Submit Form
|
||||
<Icon
|
||||
name="i-heroicons-arrow-right"
|
||||
className="transition-transform duration-300 group-hover:translate-x-1"
|
||||
/>
|
||||
</span>
|
||||
</Button>
|
||||
```
|
||||
|
||||
### Fixing Default Component Usage
|
||||
```tsx
|
||||
<!-- ❌ P2: All default props (generic appearance) -->
|
||||
<Card>
|
||||
<p>Content here</p>
|
||||
</Card>
|
||||
|
||||
<!-- ✅ Correct: Customized for brand distinctiveness -->
|
||||
<Card
|
||||
:ui="{
|
||||
background: 'bg-white dark:bg-brand-midnight',
|
||||
ring: 'ring-1 ring-brand-coral/20',
|
||||
rounded: 'rounded-2xl',
|
||||
shadow: 'shadow-xl hover:shadow-2xl',
|
||||
body: { padding: 'p-8' }
|
||||
}"
|
||||
className="transition-all duration-300 hover:-translate-y-1"
|
||||
>
|
||||
<p className="text-gray-700 dark:text-gray-300">Content here</p>
|
||||
</Card>
|
||||
```
|
||||
|
||||
## MCP Server Integration
|
||||
|
||||
When shadcn/ui MCP server is available:
|
||||
- Query component customization options before validation
|
||||
- Verify that suggested customizations use valid props
|
||||
- Get latest component API to prevent hallucination
|
||||
- Validate `ui` prop structure against actual schema
|
||||
|
||||
**Example MCP Usage**:
|
||||
```typescript
|
||||
// Validate Button customization
|
||||
const buttonDocs = await mcp.shadcn.get_component("Button");
|
||||
// Check if suggested props exist: color, size, variant, ui, etc.
|
||||
// Ensure customizations align with actual API
|
||||
```
|
||||
|
||||
## Benefits
|
||||
|
||||
### Immediate Impact
|
||||
- **Prevents Generic Design**: Catches overused patterns before they ship
|
||||
- **Enforces Brand Identity**: Ensures consistent, distinctive aesthetics
|
||||
- **Improves User Engagement**: Validates animations and interactions
|
||||
- **Educates Developers**: Clear explanations of design best practices
|
||||
|
||||
### Long-term Value
|
||||
- **Consistent Visual Identity**: All components follow brand guidelines
|
||||
- **Faster Design Iterations**: Immediate feedback on design choices
|
||||
- **Better User Experience**: Polished animations and interactions
|
||||
- **Reduced Design Debt**: Prevents accumulation of generic patterns
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### During Component Creation
|
||||
```tsx
|
||||
// Developer creates: <div className="font-sans bg-purple-500">
|
||||
// SKILL immediately activates: "⚠️ WARNING: Using default 'font-sans' (Inter) and purple gradient. Consider custom brand fonts and colors for distinctive design."
|
||||
```
|
||||
|
||||
### During Styling
|
||||
```tsx
|
||||
// Developer adds: <Button>Click me</Button>
|
||||
// SKILL immediately activates: "⚠️ P2: Button lacks hover animations. Add transition utilities for better engagement: class='transition-all duration-300 hover:scale-105'"
|
||||
```
|
||||
|
||||
### During Configuration
|
||||
```typescript
|
||||
// Developer modifies tailwind.config.ts with default Inter
|
||||
// SKILL immediately activates: "⚠️ P1: Using Inter font (appears in 80%+ of sites). Replace with distinctive font choices like Space Grotesk, Archivo, or other brand-appropriate fonts."
|
||||
```
|
||||
|
||||
### Before Deployment
|
||||
```tsx
|
||||
// SKILL runs comprehensive check: "✅ Design validation passed. Custom fonts, distinctive colors, engaging animations, and customized components detected."
|
||||
```
|
||||
|
||||
## Design Philosophy Alignment
|
||||
|
||||
This SKILL implements the core insight from Claude's "Improving Frontend Design Through Skills" blog post:
|
||||
|
||||
> "Think about frontend design the way a frontend engineer would. The more you can map aesthetic improvements to implementable frontend code, the better Claude can execute."
|
||||
|
||||
**Key Mappings**:
|
||||
- **Typography** → Tailwind `fontFamily` config + utility classes
|
||||
- **Animations** → Tailwind `transition-*`, `hover:*`, `duration-*` utilities
|
||||
- **Background effects** → Custom gradient combinations, `backdrop-*` utilities
|
||||
- **Themes** → Extended Tailwind color palette with brand tokens
|
||||
|
||||
## Distinctive vs Generic Patterns
|
||||
|
||||
### ❌ Generic Patterns (What to Avoid)
|
||||
```tsx
|
||||
<!-- The "AI default aesthetic" -->
|
||||
<div className="bg-white">
|
||||
<h1 className="font-sans text-gray-900">Title</h1>
|
||||
<div className="bg-gradient-to-r from-purple-500 to-purple-600">
|
||||
<Button>Action</Button>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Problems**:
|
||||
- Inter font (default)
|
||||
- Purple gradient (overused)
|
||||
- Gray backgrounds (generic)
|
||||
- No animations (flat)
|
||||
- Default components (no customization)
|
||||
|
||||
### ✅ Distinctive Patterns (What to Strive For)
|
||||
```tsx
|
||||
<!-- Brand-distinctive aesthetic -->
|
||||
<div className="bg-gradient-to-br from-brand-cream via-white to-brand-ocean/10">
|
||||
<h1 className="font-heading text-6xl text-brand-midnight tracking-tighter">
|
||||
Title
|
||||
</h1>
|
||||
<div className="relative overflow-hidden rounded-3xl bg-brand-coral p-8">
|
||||
<!-- Atmospheric background -->
|
||||
<div className="absolute inset-0 bg-gradient-to-br from-brand-coral to-brand-sunset opacity-80" />
|
||||
|
||||
<Button
|
||||
:ui="{
|
||||
font: 'font-heading',
|
||||
rounded: 'rounded-full',
|
||||
size: 'xl'
|
||||
}"
|
||||
className="relative z-10 transition-all duration-500 hover:scale-110 hover:rotate-2 hover:shadow-2xl active:scale-95"
|
||||
>
|
||||
<span className="flex items-center gap-2">
|
||||
Action
|
||||
<Icon
|
||||
name="i-heroicons-sparkles"
|
||||
className="animate-pulse"
|
||||
/>
|
||||
</span>
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
```
|
||||
|
||||
**Strengths**:
|
||||
- Custom fonts (Archivo Black for headings)
|
||||
- Brand-specific colors (coral, ocean, sunset)
|
||||
- Atmospheric gradients (multiple layers)
|
||||
- Rich animations (scale, rotate, shadow transitions)
|
||||
- Heavily customized components (ui prop + utility classes)
|
||||
- Micro-interactions (icon pulse, hover effects)
|
||||
|
||||
This SKILL ensures every Tanstack Start project develops a distinctive visual identity by preventing generic patterns and guiding developers toward branded, engaging design implementations.
|
||||
305
skills/workers-binding-validator/SKILL.md
Normal file
305
skills/workers-binding-validator/SKILL.md
Normal file
@@ -0,0 +1,305 @@
|
||||
---
|
||||
name: workers-binding-validator
|
||||
description: Automatically validates Cloudflare Workers binding configuration, ensuring code references match wrangler.toml setup and TypeScript interfaces are accurate
|
||||
triggers: ["env parameter usage", "wrangler.toml changes", "TypeScript interface updates", "binding references"]
|
||||
---
|
||||
|
||||
# Workers Binding Validator SKILL
|
||||
|
||||
## Activation Patterns
|
||||
|
||||
This SKILL automatically activates when:
|
||||
- `env` parameter is used in Workers code
|
||||
- wrangler.toml file is modified
|
||||
- TypeScript `Env` interface is defined or updated
|
||||
- New binding references are added to code
|
||||
- Binding configuration patterns are detected
|
||||
|
||||
## Expertise Provided
|
||||
|
||||
### Binding Configuration Validation
|
||||
- **Binding Consistency**: Ensures code references match wrangler.toml configuration
|
||||
- **TypeScript Interface Validation**: Validates `Env` interface matches actual bindings
|
||||
- **Binding Type Accuracy**: Ensures correct binding types (KV, R2, D1, Durable Objects)
|
||||
- **Remote Binding Validation**: Checks remote binding configuration for development
|
||||
- **Secret Binding Verification**: Validates secret vs environment variable bindings
|
||||
|
||||
### Specific Checks Performed
|
||||
|
||||
#### ❌ Critical Binding Mismatches
|
||||
```typescript
|
||||
// These patterns trigger immediate alerts:
|
||||
// Code references binding that doesn't exist in wrangler.toml
|
||||
const user = await env.USER_DATA.get(id); // USER_DATA not configured
|
||||
|
||||
// TypeScript interface doesn't match wrangler.toml
|
||||
interface Env {
|
||||
USERS: KVNamespace; // Code expects USERS
|
||||
// wrangler.toml has USER_DATA (mismatch!)
|
||||
}
|
||||
```
|
||||
|
||||
#### ✅ Correct Binding Patterns
|
||||
```typescript
|
||||
// These patterns are validated as correct:
|
||||
// Matching wrangler.toml and TypeScript interface
|
||||
interface Env {
|
||||
USER_DATA: KVNamespace; // Matches wrangler.toml binding name
|
||||
API_BUCKET: R2Bucket; // Correct R2 binding type
|
||||
}
|
||||
|
||||
// Proper usage in code
|
||||
const user = await env.USER_DATA.get(id);
|
||||
const object = await env.API_BUCKET.get(key);
|
||||
```
|
||||
|
||||
## Integration Points
|
||||
|
||||
### Complementary to Existing Components
|
||||
- **binding-context-analyzer agent**: Handles complex binding analysis, SKILL provides immediate validation
|
||||
- **workers-runtime-validator SKILL**: Complements runtime checks with binding validation
|
||||
- **cloudflare-security-checker SKILL**: Ensures secret bindings are properly configured
|
||||
|
||||
### Escalation Triggers
|
||||
- Complex binding architecture questions → `binding-context-analyzer` agent
|
||||
- Migration between binding types → `cloudflare-architecture-strategist` agent
|
||||
- Binding performance issues → `edge-performance-oracle` agent
|
||||
|
||||
## Validation Rules
|
||||
|
||||
### P1 - Critical (Will Fail at Runtime)
|
||||
- **Missing Bindings**: Code references bindings not in wrangler.toml
|
||||
- **Type Mismatches**: Wrong binding types in TypeScript interface
|
||||
- **Name Mismatches**: Different names in code vs configuration
|
||||
- **Missing Env Interface**: No TypeScript interface for bindings
|
||||
|
||||
### P2 - High (Configuration Issues)
|
||||
- **Remote Binding Missing**: Development bindings without `remote = true`
|
||||
- **Secret vs Var Confusion**: Secrets in [vars] section or vice versa
|
||||
- **Incomplete Interface**: Missing bindings in TypeScript interface
|
||||
|
||||
### P3 - Medium (Best Practices)
|
||||
- **Binding Documentation**: Missing JSDoc comments for bindings
|
||||
- **Binding Organization**: Poor organization of related bindings
|
||||
|
||||
## Remediation Examples
|
||||
|
||||
### Fixing Missing Bindings
|
||||
```typescript
|
||||
// ❌ Critical: Code references binding not in wrangler.toml
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const user = await env.USER_CACHE.get(userId); // USER_CACHE not configured!
|
||||
}
|
||||
}
|
||||
|
||||
// wrangler.toml (missing binding)
|
||||
[[kv_namespaces]]
|
||||
binding = "USER_DATA" # Different name!
|
||||
id = "user-data"
|
||||
|
||||
// ✅ Correct: Matching names
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const user = await env.USER_DATA.get(userId); // Matches wrangler.toml
|
||||
}
|
||||
}
|
||||
|
||||
// wrangler.toml (correct binding)
|
||||
[[kv_namespaces]]
|
||||
binding = "USER_DATA" # Matches code!
|
||||
id = "user-data"
|
||||
```
|
||||
|
||||
### Fixing TypeScript Interface Mismatches
|
||||
```typescript
|
||||
// ❌ Critical: Interface doesn't match wrangler.toml
|
||||
interface Env {
|
||||
USERS: KVNamespace; // Code expects USERS
|
||||
SESSIONS: KVNamespace; // Code expects SESSIONS
|
||||
}
|
||||
|
||||
// wrangler.toml has different names
|
||||
[[kv_namespaces]]
|
||||
binding = "USER_DATA" # Different!
|
||||
id = "user-data"
|
||||
|
||||
[[kv_namespaces]]
|
||||
binding = "SESSION_DATA" # Different!
|
||||
id = "session-data"
|
||||
|
||||
// ✅ Correct: Matching interface and configuration
|
||||
interface Env {
|
||||
USER_DATA: KVNamespace; # Matches wrangler.toml
|
||||
SESSION_DATA: KVNamespace; # Matches wrangler.toml
|
||||
}
|
||||
|
||||
// wrangler.toml (matching names)
|
||||
[[kv_namespaces]]
|
||||
binding = "USER_DATA" # Matches interface!
|
||||
id = "user-data"
|
||||
|
||||
[[kv_namespaces]]
|
||||
binding = "SESSION_DATA" # Matches interface!
|
||||
id = "session-data"
|
||||
```
|
||||
|
||||
### Fixing Binding Type Mismatches
|
||||
```typescript
|
||||
// ❌ Critical: Wrong binding type
|
||||
interface Env {
|
||||
MY_BUCKET: KVNamespace; # Wrong type - should be R2Bucket
|
||||
MY_DB: D1Database; # Wrong type - should be KVNamespace
|
||||
}
|
||||
|
||||
// wrangler.toml
|
||||
[[r2_buckets]]
|
||||
binding = "MY_BUCKET" # R2 bucket, not KV!
|
||||
|
||||
[[kv_namespaces]]
|
||||
binding = "MY_DB" # KV namespace, not D1!
|
||||
|
||||
// ✅ Correct: Proper binding types
|
||||
interface Env {
|
||||
MY_BUCKET: R2Bucket; # Correct type for R2 bucket
|
||||
MY_DB: KVNamespace; # Correct type for KV namespace
|
||||
}
|
||||
|
||||
// wrangler.toml (same as above)
|
||||
[[r2_buckets]]
|
||||
binding = "MY_BUCKET"
|
||||
|
||||
[[kv_namespaces]]
|
||||
binding = "MY_DB"
|
||||
```
|
||||
|
||||
### Fixing Remote Binding Configuration
|
||||
```typescript
|
||||
// ❌ High: Missing remote binding for development
|
||||
// wrangler.toml
|
||||
[[kv_namespaces]]
|
||||
binding = "USER_DATA"
|
||||
id = "user-data"
|
||||
# Missing remote = true for development!
|
||||
|
||||
// ✅ Correct: Remote binding configured
|
||||
[[kv_namespaces]]
|
||||
binding = "USER_DATA"
|
||||
id = "user-data"
|
||||
remote = true # Enables remote binding for development
|
||||
```
|
||||
|
||||
### Fixing Secret vs Environment Variable Confusion
|
||||
```typescript
|
||||
// ❌ High: Secret in [vars] section (visible in git)
|
||||
// wrangler.toml
|
||||
[vars]
|
||||
API_KEY = "sk_live_12345" # Secret exposed in git!
|
||||
|
||||
// ✅ Correct: Secret via wrangler secret command
|
||||
// wrangler.toml (no secrets in [vars])
|
||||
[vars]
|
||||
PUBLIC_API_URL = "https://api.example.com" # Non-secret config only
|
||||
|
||||
# Set secret via command line:
|
||||
# wrangler secret put API_KEY
|
||||
# (prompt: enter secret value)
|
||||
|
||||
// Code accesses both correctly
|
||||
interface Env {
|
||||
API_KEY: string; # From wrangler secret
|
||||
PUBLIC_API_URL: string; # From wrangler.toml [vars]
|
||||
}
|
||||
```
|
||||
|
||||
## MCP Server Integration
|
||||
|
||||
When Cloudflare MCP server is available:
|
||||
- Query actual binding configuration from Cloudflare account
|
||||
- Verify bindings exist and are accessible
|
||||
- Check binding permissions and limits
|
||||
- Get latest binding configuration best practices
|
||||
|
||||
## Benefits
|
||||
|
||||
### Immediate Impact
|
||||
- **Prevents Runtime Failures**: Catches binding mismatches before deployment
|
||||
- **Reduces Debugging Time**: Immediate feedback on configuration issues
|
||||
- **Ensures Type Safety**: Validates TypeScript interfaces match reality
|
||||
|
||||
### Long-term Value
|
||||
- **Consistent Configuration**: Ensures all code uses correct binding patterns
|
||||
- **Better Developer Experience**: Clear error messages for binding issues
|
||||
- **Reduced Deployment Issues**: Configuration validation prevents failed deployments
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### During Binding Usage
|
||||
```typescript
|
||||
// Developer types: const data = await env.CACHE.get(key);
|
||||
// SKILL immediately activates: "❌ CRITICAL: CACHE binding not found in wrangler.toml. Add [[kv_namespaces]] binding = 'CACHE' or check spelling."
|
||||
```
|
||||
|
||||
### During Interface Definition
|
||||
```typescript
|
||||
// Developer types: interface Env { USERS: R2Bucket; }
|
||||
// SKILL immediately activates: "⚠️ HIGH: USERS binding type mismatch. wrangler.toml shows USERS as KVNamespace, not R2Bucket."
|
||||
```
|
||||
|
||||
### During Configuration Changes
|
||||
```typescript
|
||||
// Developer modifies wrangler.toml binding name
|
||||
// SKILL immediately activates: "⚠️ HIGH: Binding name changed from USER_DATA to USERS. Update TypeScript interface and code references."
|
||||
```
|
||||
|
||||
## Binding Type Reference
|
||||
|
||||
### KV Namespace
|
||||
```typescript
|
||||
interface Env {
|
||||
MY_KV: KVNamespace;
|
||||
}
|
||||
// Usage: await env.MY_KV.get(key)
|
||||
```
|
||||
|
||||
### R2 Bucket
|
||||
```typescript
|
||||
interface Env {
|
||||
MY_BUCKET: R2Bucket;
|
||||
}
|
||||
// Usage: await env.MY_BUCKET.get(key)
|
||||
```
|
||||
|
||||
### D1 Database
|
||||
```typescript
|
||||
interface Env {
|
||||
MY_DB: D1Database;
|
||||
}
|
||||
// Usage: await env.MY_DB.prepare(query).bind(params).all()
|
||||
```
|
||||
|
||||
### Durable Object
|
||||
```typescript
|
||||
interface Env {
|
||||
MY_DO: DurableObjectNamespace;
|
||||
}
|
||||
// Usage: env.MY_DO.get(id)
|
||||
```
|
||||
|
||||
### AI Binding
|
||||
```typescript
|
||||
interface Env {
|
||||
AI: Ai;
|
||||
}
|
||||
// Usage: await env.AI.run(model, params)
|
||||
```
|
||||
|
||||
### Vectorize
|
||||
```typescript
|
||||
interface Env {
|
||||
VECTORS: VectorizeIndex;
|
||||
}
|
||||
// Usage: await env.VECTORS.query(vector, options)
|
||||
```
|
||||
|
||||
This SKILL ensures Workers binding configuration is correct by providing immediate, autonomous validation of binding patterns, preventing runtime failures and configuration mismatches.
|
||||
148
skills/workers-runtime-validator/SKILL.md
Normal file
148
skills/workers-runtime-validator/SKILL.md
Normal file
@@ -0,0 +1,148 @@
|
||||
---
|
||||
name: workers-runtime-validator
|
||||
description: Automatically validates Cloudflare Workers runtime compatibility during development, preventing Node.js API usage and ensuring proper Workers patterns
|
||||
triggers: ["import statements", "file creation", "code changes", "deployment preparation"]
|
||||
---
|
||||
|
||||
# Workers Runtime Validator SKILL
|
||||
|
||||
## Activation Patterns
|
||||
|
||||
This SKILL automatically activates when:
|
||||
- New `.ts` or `.js` files are created in Workers projects
|
||||
- Import statements are added or modified
|
||||
- Code changes include potential runtime violations
|
||||
- Before deployment commands are executed
|
||||
- When `process.env`, `require()`, or Node.js APIs are detected
|
||||
|
||||
## Expertise Provided
|
||||
|
||||
### Runtime Compatibility Validation
|
||||
- **Forbidden API Detection**: Identifies Node.js built-ins that don't exist in Workers
|
||||
- **Environment Access**: Ensures proper `env` parameter usage vs `process.env`
|
||||
- **Module System**: Validates ES modules usage (no `require()`)
|
||||
- **Async Patterns**: Ensures all I/O operations are async
|
||||
- **Package Compatibility**: Checks npm packages for Node.js dependencies
|
||||
|
||||
### Specific Checks Performed
|
||||
|
||||
#### ❌ Critical Violations (Will Break in Production)
|
||||
```typescript
|
||||
// These patterns trigger immediate alerts:
|
||||
import fs from 'fs'; // Node.js API
|
||||
import { Buffer } from 'buffer'; // Node.js API
|
||||
const secret = process.env.API_KEY; // process doesn't exist
|
||||
const data = require('./module'); // require() not supported
|
||||
```
|
||||
|
||||
#### ✅ Correct Workers Patterns
|
||||
```typescript
|
||||
// These patterns are validated as correct:
|
||||
import { z } from 'zod'; // Web-compatible package
|
||||
const secret = env.API_KEY; // Proper env parameter
|
||||
const hash = await crypto.subtle.digest(); // Web Crypto API
|
||||
```
|
||||
|
||||
## Integration Points
|
||||
|
||||
### Complementary to Existing Components
|
||||
- **workers-runtime-guardian agent**: Handles deep runtime analysis, SKILL provides immediate validation
|
||||
- **es-deploy command**: SKILL prevents deployment failures by catching issues early
|
||||
- **validate command**: SKILL provides continuous validation between explicit checks
|
||||
|
||||
### Escalation Triggers
|
||||
- Complex runtime compatibility questions → `workers-runtime-guardian` agent
|
||||
- Package dependency analysis → `edge-performance-oracle` agent
|
||||
- Migration from Node.js to Workers → `cloudflare-architecture-strategist` agent
|
||||
|
||||
## Validation Rules
|
||||
|
||||
### P1 - Critical (Must Fix Immediately)
|
||||
- **Node.js Built-ins**: `fs`, `path`, `os`, `crypto`, `process`, `buffer`
|
||||
- **CommonJS Usage**: `require()`, `module.exports`
|
||||
- **Process Access**: `process.env`, `process.exit()`
|
||||
- **Synchronous I/O**: Any blocking I/O operations
|
||||
|
||||
### P2 - Important (Should Fix)
|
||||
- **Package Dependencies**: npm packages with Node.js dependencies
|
||||
- **Missing Async**: I/O operations without await
|
||||
- **Buffer Usage**: Using Node.js Buffer instead of Uint8Array
|
||||
|
||||
### P3 - Best Practices
|
||||
- **TypeScript Env Interface**: Missing or incorrect Env type definition
|
||||
- **Web API Usage**: Not using modern Web APIs when available
|
||||
|
||||
## Remediation Examples
|
||||
|
||||
### Fixing Node.js API Usage
|
||||
```typescript
|
||||
// ❌ Critical: Node.js crypto
|
||||
import crypto from 'crypto';
|
||||
const hash = crypto.createHash('sha256');
|
||||
|
||||
// ✅ Correct: Web Crypto API
|
||||
const encoder = new TextEncoder();
|
||||
const hash = await crypto.subtle.digest('SHA-256', encoder.encode(data));
|
||||
```
|
||||
|
||||
### Fixing Environment Access
|
||||
```typescript
|
||||
// ❌ Critical: process.env
|
||||
const apiKey = process.env.API_KEY;
|
||||
|
||||
// ✅ Correct: env parameter
|
||||
export default {
|
||||
async fetch(request: Request, env: Env) {
|
||||
const apiKey = env.API_KEY;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Fixing Module System
|
||||
```typescript
|
||||
// ❌ Critical: CommonJS
|
||||
const utils = require('./utils');
|
||||
|
||||
// ✅ Correct: ES modules
|
||||
import { utils } from './utils';
|
||||
```
|
||||
|
||||
## MCP Server Integration
|
||||
|
||||
When Cloudflare MCP server is available:
|
||||
- Query latest Workers runtime API documentation
|
||||
- Check for deprecated APIs before suggesting fixes
|
||||
- Get current compatibility information for new features
|
||||
|
||||
## Benefits
|
||||
|
||||
### Immediate Impact
|
||||
- **Prevents Runtime Failures**: Catches issues before deployment
|
||||
- **Reduces Debugging Time**: Immediate feedback on violations
|
||||
- **Educates Developers**: Clear explanations of Workers vs Node.js differences
|
||||
|
||||
### Long-term Value
|
||||
- **Consistent Code Quality**: Ensures all code follows Workers patterns
|
||||
- **Faster Development**: No need to wait for deployment to discover issues
|
||||
- **Better Developer Experience**: Real-time guidance during coding
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### During Code Creation
|
||||
```typescript
|
||||
// Developer types: import fs from 'fs';
|
||||
// SKILL immediately activates: "❌ CRITICAL: 'fs' is a Node.js API not available in Workers runtime. Use Web APIs or Workers-specific alternatives."
|
||||
```
|
||||
|
||||
### During Refactoring
|
||||
```typescript
|
||||
// Developer changes: const secret = process.env.API_KEY;
|
||||
// SKILL immediately activates: "❌ CRITICAL: 'process.env' doesn't exist in Workers. Use the 'env' parameter passed to your fetch handler instead."
|
||||
```
|
||||
|
||||
### Before Deployment
|
||||
```typescript
|
||||
// SKILL runs comprehensive check: "✅ Runtime validation passed. No Node.js APIs detected, all environment access uses proper env parameter."
|
||||
```
|
||||
|
||||
This SKILL ensures Workers runtime compatibility by providing immediate, autonomous validation of code patterns, preventing common migration mistakes and runtime failures.
|
||||
Reference in New Issue
Block a user