feat: add sveltekit, astro, hono, and pydantic-ai skills (#336)

This commit is contained in:
Suhaib Janjua
2026-03-18 16:32:48 +05:00
committed by GitHub
parent 1336e8d455
commit ae306ce015
4 changed files with 1343 additions and 0 deletions

359
skills/astro/SKILL.md Normal file
View File

@@ -0,0 +1,359 @@
---
name: astro
description: "Build content-focused websites with Astro — zero JS by default, islands architecture, multi-framework components, and Markdown/MDX support."
category: frontend
risk: safe
source: community
date_added: "2026-03-18"
author: suhaibjanjua
tags: [astro, ssg, ssr, islands, content, markdown, mdx, performance]
tools: [claude, cursor, gemini]
---
# Astro Web Framework
## Overview
Astro is a web framework designed for content-rich websites — blogs, docs, portfolios, marketing sites, and e-commerce. Its core innovation is the **Islands Architecture**: by default, Astro ships zero JavaScript to the browser. Interactive components are selectively hydrated as isolated "islands." Astro supports React, Vue, Svelte, Solid, and other UI frameworks simultaneously in the same project, letting you pick the right tool per component.
## When to Use This Skill
- Use when building a blog, documentation site, marketing page, or portfolio
- Use when performance and Core Web Vitals are the top priority
- Use when the project is content-heavy with Markdown or MDX files
- Use when you want SSG (static) output with optional SSR for dynamic routes
- Use when the user asks about `.astro` files, `Astro.props`, content collections, or `client:` directives
## How It Works
### Step 1: Project Setup
```bash
npm create astro@latest my-site
cd my-site
npm install
npm run dev
```
Add integrations as needed:
```bash
npx astro add tailwind # Tailwind CSS
npx astro add react # React component support
npx astro add mdx # MDX support
npx astro add sitemap # Auto sitemap.xml
npx astro add vercel # Vercel SSR adapter
```
Project structure:
```
src/
pages/ ← File-based routing (.astro, .md, .mdx)
layouts/ ← Reusable page shells
components/ ← UI components (.astro, .tsx, .vue, etc.)
content/ ← Type-safe content collections (Markdown/MDX)
styles/ ← Global CSS
public/ ← Static assets (copied as-is)
astro.config.mjs ← Framework config
```
### Step 2: Astro Component Syntax
`.astro` files have a code fence at the top (server-only) and a template below:
```astro
---
// src/components/Card.astro
// This block runs on the server ONLY — never in the browser
interface Props {
title: string;
href: string;
description: string;
}
const { title, href, description } = Astro.props;
---
<article class="card">
<h2><a href={href}>{title}</a></h2>
<p>{description}</p>
</article>
<style>
/* Scoped to this component automatically */
.card { border: 1px solid #eee; padding: 1rem; }
</style>
```
### Step 3: File-Based Pages and Routing
```
src/pages/index.astro → /
src/pages/about.astro → /about
src/pages/blog/[slug].astro → /blog/:slug (dynamic)
src/pages/blog/[...path].astro → /blog/* (catch-all)
```
Dynamic route with `getStaticPaths`:
```astro
---
// src/pages/blog/[slug].astro
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map(post => ({
params: { slug: post.slug },
props: { post },
}));
}
const { post } = Astro.props;
const { Content } = await post.render();
---
<h1>{post.data.title}</h1>
<Content />
```
### Step 4: Content Collections
Content collections give you type-safe access to Markdown and MDX files:
```typescript
// src/content/config.ts
import { z, defineCollection } from 'astro:content';
const blog = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
date: z.coerce.date(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
}),
});
export const collections = { blog };
```
```astro
---
// src/pages/blog/index.astro
import { getCollection } from 'astro:content';
const posts = (await getCollection('blog'))
.filter(p => !p.data.draft)
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
---
<ul>
{posts.map(post => (
<li>
<a href={`/blog/${post.slug}`}>{post.data.title}</a>
<time>{post.data.date.toLocaleDateString()}</time>
</li>
))}
</ul>
```
### Step 5: Islands — Selective Hydration
By default, UI framework components render to static HTML with no JS. Use `client:` directives to hydrate:
```astro
---
import Counter from '../components/Counter.tsx'; // React component
import VideoPlayer from '../components/VideoPlayer.svelte';
---
<!-- Static HTML — no JavaScript sent to browser -->
<Counter initialCount={0} />
<!-- Hydrate immediately on page load -->
<Counter initialCount={0} client:load />
<!-- Hydrate when the component scrolls into view -->
<VideoPlayer src="/demo.mp4" client:visible />
<!-- Hydrate only when browser is idle -->
<Analytics client:idle />
<!-- Hydrate only on a specific media query -->
<MobileMenu client:media="(max-width: 768px)" />
```
### Step 6: Layouts
```astro
---
// src/layouts/BaseLayout.astro
interface Props {
title: string;
description?: string;
}
const { title, description = 'My Astro Site' } = Astro.props;
---
<html lang="en">
<head>
<meta charset="utf-8" />
<title>{title}</title>
<meta name="description" content={description} />
</head>
<body>
<nav>...</nav>
<main>
<slot /> <!-- page content renders here -->
</main>
<footer>...</footer>
</body>
</html>
```
```astro
---
// src/pages/about.astro
import BaseLayout from '../layouts/BaseLayout.astro';
---
<BaseLayout title="About Us">
<h1>About Us</h1>
<p>Welcome to our company...</p>
</BaseLayout>
```
### Step 7: SSR Mode (On-Demand Rendering)
Enable SSR for dynamic pages by setting an adapter:
```javascript
// astro.config.mjs
import { defineConfig } from 'astro/config';
import vercel from '@astrojs/vercel/serverless';
export default defineConfig({
output: 'hybrid', // 'static' | 'server' | 'hybrid'
adapter: vercel(),
});
```
Opt individual pages into SSR with `export const prerender = false`.
## Examples
### Example 1: Blog with RSS Feed
```typescript
// src/pages/rss.xml.ts
import rss from '@astrojs/rss';
import { getCollection } from 'astro:content';
export async function GET(context) {
const posts = await getCollection('blog');
return rss({
title: 'My Blog',
description: 'Latest posts',
site: context.site,
items: posts.map(post => ({
title: post.data.title,
pubDate: post.data.date,
link: `/blog/${post.slug}/`,
})),
});
}
```
### Example 2: API Endpoint (SSR)
```typescript
// src/pages/api/subscribe.ts
import type { APIRoute } from 'astro';
export const POST: APIRoute = async ({ request }) => {
const { email } = await request.json();
if (!email) {
return new Response(JSON.stringify({ error: 'Email required' }), {
status: 400,
headers: { 'Content-Type': 'application/json' },
});
}
await addToNewsletter(email);
return new Response(JSON.stringify({ success: true }), { status: 200 });
};
```
### Example 3: React Component as Island
```tsx
// src/components/SearchBox.tsx
import { useState } from 'react';
export default function SearchBox() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
async function search(e: React.FormEvent) {
e.preventDefault();
const data = await fetch(`/api/search?q=${query}`).then(r => r.json());
setResults(data);
}
return (
<form onSubmit={search}>
<input value={query} onChange={e => setQuery(e.target.value)} />
<button type="submit">Search</button>
<ul>{results.map(r => <li key={r.id}>{r.title}</li>)}</ul>
</form>
);
}
```
```astro
---
import SearchBox from '../components/SearchBox.tsx';
---
<!-- Hydrated immediately — this island is interactive -->
<SearchBox client:load />
```
## Best Practices
- ✅ Keep most components as static `.astro` files — only hydrate what must be interactive
- ✅ Use content collections for all Markdown/MDX content — you get type safety and auto-validation
- ✅ Prefer `client:visible` over `client:load` for below-the-fold components to reduce initial JS
- ✅ Use `import.meta.env` for environment variables — prefix public vars with `PUBLIC_`
- ✅ Add `<ViewTransitions />` from `astro:transitions` for smooth page navigation without a full SPA
- ❌ Don't use `client:load` on every component — this defeats Astro's performance advantage
- ❌ Don't put secrets in `.astro` frontmatter that gets used in client-facing templates
- ❌ Don't skip `getStaticPaths` for dynamic routes in static mode — builds will fail
## Security & Safety Notes
- Frontmatter code in `.astro` files runs server-side only and is never exposed to the browser.
- Use `import.meta.env.PUBLIC_*` only for non-sensitive values. Private env vars (no `PUBLIC_` prefix) are never sent to the client.
- When using SSR mode, validate all `Astro.request` inputs before database queries or API calls.
- Sanitize any user-supplied content before rendering with `set:html` — it bypasses auto-escaping.
## Common Pitfalls
- **Problem:** JavaScript from a React/Vue component doesn't run in the browser
**Solution:** Add a `client:` directive (`client:load`, `client:visible`, etc.) — without it, components render as static HTML only.
- **Problem:** `getStaticPaths` data is stale after content updates during dev
**Solution:** Astro's dev server watches content files — restart if changes to `content/config.ts` are not reflected.
- **Problem:** `Astro.props` type is `any` — no autocomplete
**Solution:** Define a `Props` interface or type in the frontmatter and Astro will infer it automatically.
- **Problem:** CSS from a `.astro` component bleeds into other components
**Solution:** Styles in `.astro` `<style>` tags are automatically scoped. Use `:global()` only when intentionally targeting children.
## Related Skills
- `@sveltekit` — When you need a full-stack framework with reactive UI (vs Astro's content focus)
- `@nextjs-app-router-patterns` — When you need a React-first full-stack framework
- `@tailwind-patterns` — Styling Astro sites with Tailwind CSS
- `@progressive-web-app` — Adding PWA capabilities to an Astro site

348
skills/hono/SKILL.md Normal file
View File

@@ -0,0 +1,348 @@
---
name: hono
description: "Build ultra-fast web APIs and full-stack apps with Hono — runs on Cloudflare Workers, Deno, Bun, Node.js, and any WinterCG-compatible runtime."
category: backend
risk: safe
source: community
date_added: "2026-03-18"
author: suhaibjanjua
tags: [hono, edge, cloudflare-workers, bun, deno, api, typescript, web-standards]
tools: [claude, cursor, gemini]
---
# Hono Web Framework
## Overview
Hono (炎, "flame" in Japanese) is a small, ultrafast web framework built on Web Standards (`Request`/`Response`/`fetch`). It runs anywhere: Cloudflare Workers, Deno Deploy, Bun, Node.js, AWS Lambda, and any WinterCG-compatible runtime — with the same code. Hono's router is one of the fastest available, and its middleware system, built-in JSX support, and RPC client make it a strong choice for edge APIs, BFFs, and lightweight full-stack apps.
## When to Use This Skill
- Use when building a REST or RPC API for edge deployment (Cloudflare Workers, Deno Deploy)
- Use when you need a minimal but type-safe server framework for Bun or Node.js
- Use when building a Backend for Frontend (BFF) layer with low latency requirements
- Use when migrating from Express but wanting better TypeScript support and edge compatibility
- Use when the user asks about Hono routing, middleware, `c.req`, `c.json`, or `hc()` RPC client
## How It Works
### Step 1: Project Setup
**Cloudflare Workers (recommended for edge):**
```bash
npm create hono@latest my-api
# Select: cloudflare-workers
cd my-api
npm install
npm run dev # Wrangler local dev
npm run deploy # Deploy to Cloudflare
```
**Bun / Node.js:**
```bash
mkdir my-api && cd my-api
bun init
bun add hono
```
```typescript
// src/index.ts (Bun)
import { Hono } from 'hono';
const app = new Hono();
app.get('/', c => c.text('Hello Hono!'));
export default {
port: 3000,
fetch: app.fetch,
};
```
### Step 2: Routing
```typescript
import { Hono } from 'hono';
const app = new Hono();
// Basic methods
app.get('/posts', c => c.json({ posts: [] }));
app.post('/posts', c => c.json({ created: true }, 201));
app.put('/posts/:id', c => c.json({ updated: true }));
app.delete('/posts/:id', c => c.json({ deleted: true }));
// Route params and query strings
app.get('/posts/:id', async c => {
const id = c.req.param('id');
const format = c.req.query('format') ?? 'json';
return c.json({ id, format });
});
// Wildcard
app.get('/static/*', c => c.text('static file'));
export default app;
```
**Chained routing:**
```typescript
app
.get('/users', listUsers)
.post('/users', createUser)
.get('/users/:id', getUser)
.patch('/users/:id', updateUser)
.delete('/users/:id', deleteUser);
```
### Step 3: Middleware
Hono middleware works exactly like `fetch` interceptors — before and after handlers:
```typescript
import { Hono } from 'hono';
import { logger } from 'hono/logger';
import { cors } from 'hono/cors';
import { bearerAuth } from 'hono/bearer-auth';
const app = new Hono();
// Built-in middleware
app.use('*', logger());
app.use('/api/*', cors({ origin: 'https://myapp.com' }));
app.use('/api/admin/*', bearerAuth({ token: process.env.API_TOKEN! }));
// Custom middleware
app.use('*', async (c, next) => {
c.set('requestId', crypto.randomUUID());
await next();
c.header('X-Request-Id', c.get('requestId'));
});
```
**Available built-in middleware:** `logger`, `cors`, `csrf`, `etag`, `cache`, `basicAuth`, `bearerAuth`, `jwt`, `compress`, `bodyLimit`, `timeout`, `prettyJSON`, `secureHeaders`.
### Step 4: Request and Response Helpers
```typescript
app.post('/submit', async c => {
// Parse body
const body = await c.req.json<{ name: string; email: string }>();
const form = await c.req.formData();
const text = await c.req.text();
// Headers and cookies
const auth = c.req.header('authorization');
const token = getCookie(c, 'session');
// Responses
return c.json({ ok: true }); // JSON
return c.text('hello'); // plain text
return c.html('<h1>Hello</h1>'); // HTML
return c.redirect('/dashboard', 302); // redirect
return new Response(stream, { status: 200 }); // raw Response
});
```
### Step 5: Zod Validator Middleware
```typescript
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
const createPostSchema = z.object({
title: z.string().min(1).max(200),
body: z.string().min(1),
tags: z.array(z.string()).default([]),
});
app.post(
'/posts',
zValidator('json', createPostSchema),
async c => {
const data = c.req.valid('json'); // fully typed
const post = await db.post.create({ data });
return c.json(post, 201);
}
);
```
### Step 6: Route Groups and App Composition
```typescript
// src/routes/posts.ts
import { Hono } from 'hono';
const posts = new Hono();
posts.get('/', async c => { /* list posts */ });
posts.post('/', async c => { /* create post */ });
posts.get('/:id', async c => { /* get post */ });
export default posts;
```
```typescript
// src/index.ts
import { Hono } from 'hono';
import posts from './routes/posts';
import users from './routes/users';
const app = new Hono().basePath('/api');
app.route('/posts', posts);
app.route('/users', users);
export default app;
```
### Step 7: RPC Client (End-to-End Type Safety)
Hono's RPC mode exports route types that the `hc` client consumes — similar to tRPC but using fetch conventions:
```typescript
// server: src/routes/posts.ts
import { Hono } from 'hono';
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
const posts = new Hono()
.get('/', c => c.json({ posts: [{ id: '1', title: 'Hello' }] }))
.post(
'/',
zValidator('json', z.object({ title: z.string() })),
async c => {
const { title } = c.req.valid('json');
return c.json({ id: '2', title }, 201);
}
);
export default posts;
export type PostsType = typeof posts;
```
```typescript
// client: src/client.ts
import { hc } from 'hono/client';
import type { PostsType } from '../server/routes/posts';
const client = hc<PostsType>('/api/posts');
// Fully typed — autocomplete on routes, params, and responses
const { posts } = await client.$get().json();
const newPost = await client.$post({ json: { title: 'New Post' } }).json();
```
## Examples
### Example 1: JWT Auth Middleware
```typescript
import { Hono } from 'hono';
import { jwt, sign } from 'hono/jwt';
const app = new Hono();
const SECRET = process.env.JWT_SECRET!;
app.post('/login', async c => {
const { email, password } = await c.req.json();
const user = await validateUser(email, password);
if (!user) return c.json({ error: 'Invalid credentials' }, 401);
const token = await sign({ sub: user.id, exp: Math.floor(Date.now() / 1000) + 3600 }, SECRET);
return c.json({ token });
});
app.use('/api/*', jwt({ secret: SECRET }));
app.get('/api/me', async c => {
const payload = c.get('jwtPayload');
const user = await getUserById(payload.sub);
return c.json(user);
});
export default app;
```
### Example 2: Cloudflare Workers with D1 Database
```typescript
// src/index.ts
import { Hono } from 'hono';
type Bindings = {
DB: D1Database;
API_TOKEN: string;
};
const app = new Hono<{ Bindings: Bindings }>();
app.get('/users', async c => {
const { results } = await c.env.DB.prepare('SELECT * FROM users LIMIT 50').all();
return c.json(results);
});
app.post('/users', async c => {
const { name, email } = await c.req.json();
await c.env.DB.prepare('INSERT INTO users (name, email) VALUES (?, ?)')
.bind(name, email)
.run();
return c.json({ created: true }, 201);
});
export default app;
```
### Example 3: Streaming Response
```typescript
import { stream, streamText } from 'hono/streaming';
app.get('/stream', c =>
streamText(c, async stream => {
for (const chunk of ['Hello', ' ', 'World']) {
await stream.write(chunk);
await stream.sleep(100);
}
})
);
```
## Best Practices
- ✅ Use route groups (sub-apps) to keep handlers in separate files — `app.route('/users', usersRouter)`
- ✅ Use `zValidator` for all request body, query, and param validation
- ✅ Type Cloudflare Workers bindings with the `Bindings` generic: `new Hono<{ Bindings: Env }>()`
- ✅ Use the RPC client (`hc`) when your frontend and backend share the same repo
- ✅ Prefer returning `c.json()`/`c.text()` over `new Response()` for cleaner code
- ❌ Don't use Node.js-specific APIs (`fs`, `path`, `process`) if you want edge portability
- ❌ Don't add heavy dependencies — Hono's value is its tiny footprint on edge runtimes
- ❌ Don't skip middleware typing — use generics (`Variables`, `Bindings`) to keep `c.get()` type-safe
## Security & Safety Notes
- Always validate input with `zValidator` before using data from requests.
- Use Hono's built-in `csrf` middleware on mutation endpoints when serving HTML/forms.
- For Cloudflare Workers, store secrets in `wrangler.toml` `[vars]` (non-secret) or `wrangler secret put` (secret) — never hardcode them in source.
- When using `bearerAuth` or `jwt`, ensure tokens are validated server-side — do not trust client-provided user IDs.
- Rate-limit sensitive endpoints (auth, password reset) with Cloudflare Rate Limiting or a custom middleware.
## Common Pitfalls
- **Problem:** Handler returns `undefined` — response is empty
**Solution:** Always `return` a response from handlers: `return c.json(...)` not just `c.json(...)`.
- **Problem:** Middleware runs after the response is sent
**Solution:** Call `await next()` before post-response logic; Hono runs code after `next()` as the response travels back up the chain.
- **Problem:** `c.env` is undefined on Node.js
**Solution:** Cloudflare `env` bindings only exist in Workers. Use `process.env` on Node.js.
- **Problem:** Route not matching — gets a 404
**Solution:** Check that `app.route('/prefix', subRouter)` uses the same prefix your client calls. Sub-routers should **not** repeat the prefix in their own routes.
## Related Skills
- `@cloudflare-workers-expert` — Deep dive into Cloudflare Workers platform specifics
- `@trpc-fullstack` — Alternative RPC approach for TypeScript full-stack apps
- `@zod-validation-expert` — Detailed Zod schema patterns used with `@hono/zod-validator`
- `@nodejs-backend-patterns` — When you need a Node.js-specific backend (not edge)

350
skills/pydantic-ai/SKILL.md Normal file
View File

@@ -0,0 +1,350 @@
---
name: pydantic-ai
description: "Build production-ready AI agents with PydanticAI — type-safe tool use, structured outputs, dependency injection, and multi-model support."
category: ai-agents
risk: safe
source: community
date_added: "2026-03-18"
author: suhaibjanjua
tags: [pydantic-ai, ai-agents, llm, openai, anthropic, gemini, tool-use, structured-output, python]
tools: [claude, cursor, gemini]
---
# PydanticAI — Typed AI Agents in Python
## Overview
PydanticAI is a Python agent framework from the Pydantic team that brings the same type-safety and validation guarantees as Pydantic to LLM-based applications. It supports structured outputs (validated with Pydantic models), dependency injection for testability, streamed responses, multi-turn conversations, and tool use — across OpenAI, Anthropic, Google Gemini, Groq, Mistral, and Ollama. Use this skill when building production AI agents, chatbots, or LLM pipelines where correctness and testability matter.
## When to Use This Skill
- Use when building Python AI agents that call tools and return structured data
- Use when you need validated, typed LLM outputs (not raw strings)
- Use when you want to write unit tests for agent logic without hitting a real LLM
- Use when switching between LLM providers without rewriting agent code
- Use when the user asks about `Agent`, `@agent.tool`, `RunContext`, `ModelRetry`, or `result_type`
## How It Works
### Step 1: Installation
```bash
pip install pydantic-ai
# Install extras for specific providers
pip install 'pydantic-ai[openai]' # OpenAI / Azure OpenAI
pip install 'pydantic-ai[anthropic]' # Anthropic Claude
pip install 'pydantic-ai[gemini]' # Google Gemini
pip install 'pydantic-ai[groq]' # Groq
pip install 'pydantic-ai[vertexai]' # Google Vertex AI
```
### Step 2: A Minimal Agent
```python
from pydantic_ai import Agent
# Simple agent — returns a plain string
agent = Agent(
'anthropic:claude-sonnet-4-6',
system_prompt='You are a helpful assistant. Be concise.',
)
result = agent.run_sync('What is the capital of Japan?')
print(result.data) # "Tokyo"
print(result.usage()) # Usage(requests=1, request_tokens=..., response_tokens=...)
```
### Step 3: Structured Output with Pydantic Models
```python
from pydantic import BaseModel
from pydantic_ai import Agent
class MovieReview(BaseModel):
title: str
year: int
rating: float # 0.0 to 10.0
summary: str
recommended: bool
agent = Agent(
'openai:gpt-4o',
result_type=MovieReview,
system_prompt='You are a film critic. Return structured reviews.',
)
result = agent.run_sync('Review Inception (2010)')
review = result.data # Fully typed MovieReview instance
print(f"{review.title} ({review.year}): {review.rating}/10")
print(f"Recommended: {review.recommended}")
```
### Step 4: Tool Use
Register tools with `@agent.tool` — the LLM can call them during a run:
```python
from pydantic_ai import Agent, RunContext
from pydantic import BaseModel
import httpx
class WeatherReport(BaseModel):
city: str
temperature_c: float
condition: str
weather_agent = Agent(
'anthropic:claude-sonnet-4-6',
result_type=WeatherReport,
system_prompt='Get current weather for the requested city.',
)
@weather_agent.tool
async def get_temperature(ctx: RunContext, city: str) -> dict:
"""Fetch the current temperature for a city from the weather API."""
async with httpx.AsyncClient() as client:
r = await client.get(f'https://wttr.in/{city}?format=j1')
data = r.json()
return {
'temp_c': float(data['current_condition'][0]['temp_C']),
'description': data['current_condition'][0]['weatherDesc'][0]['value'],
}
import asyncio
result = asyncio.run(weather_agent.run('What is the weather in Tokyo?'))
print(result.data)
```
### Step 5: Dependency Injection
Inject services (database, HTTP clients, config) into agents for testability:
```python
from dataclasses import dataclass
from pydantic_ai import Agent, RunContext
from pydantic import BaseModel
@dataclass
class Deps:
db: Database
user_id: str
class SupportResponse(BaseModel):
message: str
escalate: bool
support_agent = Agent(
'openai:gpt-4o-mini',
deps_type=Deps,
result_type=SupportResponse,
system_prompt='You are a support agent. Use the tools to help customers.',
)
@support_agent.tool
async def get_order_history(ctx: RunContext[Deps]) -> list[dict]:
"""Fetch recent orders for the current user."""
return await ctx.deps.db.get_orders(ctx.deps.user_id, limit=5)
@support_agent.tool
async def create_refund(ctx: RunContext[Deps], order_id: str, reason: str) -> dict:
"""Initiate a refund for a specific order."""
return await ctx.deps.db.create_refund(order_id, reason, ctx.deps.user_id)
# Usage
async def handle_support(user_id: str, message: str):
deps = Deps(db=get_db(), user_id=user_id)
result = await support_agent.run(message, deps=deps)
return result.data
```
### Step 6: Testing with TestModel
Write unit tests without real LLM calls:
```python
from pydantic_ai.models.test import TestModel
def test_support_agent_escalates():
with support_agent.override(model=TestModel()):
# TestModel returns a minimal valid response matching result_type
result = support_agent.run_sync(
'I want to cancel my account',
deps=Deps(db=FakeDb(), user_id='user-123'),
)
# Test the structure, not the LLM's exact words
assert isinstance(result.data, SupportResponse)
assert isinstance(result.data.escalate, bool)
```
**FunctionModel** for deterministic test responses:
```python
from pydantic_ai.models.function import FunctionModel, ModelContext
def my_model(messages, info):
return ModelResponse(parts=[TextPart('Always this response')])
with agent.override(model=FunctionModel(my_model)):
result = agent.run_sync('anything')
```
### Step 7: Streaming Responses
```python
import asyncio
from pydantic_ai import Agent
agent = Agent('anthropic:claude-sonnet-4-6')
async def stream_response():
async with agent.run_stream('Write a haiku about Python') as result:
async for chunk in result.stream_text():
print(chunk, end='', flush=True)
print() # newline
print(f"Total tokens: {result.usage()}")
asyncio.run(stream_response())
```
### Step 8: Multi-Turn Conversations
```python
from pydantic_ai import Agent
from pydantic_ai.messages import ModelMessagesTypeAdapter
agent = Agent('openai:gpt-4o', system_prompt='You are a helpful assistant.')
# First turn
result1 = agent.run_sync('My name is Alice.')
history = result1.all_messages()
# Second turn — passes conversation history
result2 = agent.run_sync('What is my name?', message_history=history)
print(result2.data) # "Your name is Alice."
```
## Examples
### Example 1: Code Review Agent
```python
from pydantic import BaseModel, Field
from pydantic_ai import Agent
from typing import Literal
class CodeReview(BaseModel):
quality: Literal['excellent', 'good', 'needs_work', 'poor']
issues: list[str] = Field(default_factory=list)
suggestions: list[str] = Field(default_factory=list)
approved: bool
code_review_agent = Agent(
'anthropic:claude-sonnet-4-6',
result_type=CodeReview,
system_prompt="""
You are a senior engineer performing code review.
Evaluate code quality, identify issues, and provide actionable suggestions.
Set approved=True only for good or excellent quality code with no security issues.
""",
)
def review_code(diff: str) -> CodeReview:
result = code_review_agent.run_sync(f"Review this code:\n\n{diff}")
return result.data
```
### Example 2: Agent with Retry Logic
```python
from pydantic_ai import Agent, ModelRetry
from pydantic import BaseModel, field_validator
class StrictJson(BaseModel):
value: int
@field_validator('value')
def must_be_positive(cls, v):
if v <= 0:
raise ValueError('value must be positive')
return v
agent = Agent('openai:gpt-4o-mini', result_type=StrictJson)
@agent.result_validator
async def validate_result(ctx, result: StrictJson) -> StrictJson:
if result.value > 1000:
raise ModelRetry('Value must be under 1000. Try again with a smaller number.')
return result
```
### Example 3: Multi-Agent Pipeline
```python
from pydantic_ai import Agent
from pydantic import BaseModel
class ResearchSummary(BaseModel):
key_points: list[str]
conclusion: str
class BlogPost(BaseModel):
title: str
body: str
meta_description: str
researcher = Agent('openai:gpt-4o', result_type=ResearchSummary)
writer = Agent('anthropic:claude-sonnet-4-6', result_type=BlogPost)
async def research_and_write(topic: str) -> BlogPost:
# Stage 1: research
research = await researcher.run(f'Research the topic: {topic}')
# Stage 2: write based on research
post = await writer.run(
f'Write a blog post about: {topic}\n\nResearch:\n' +
'\n'.join(f'- {p}' for p in research.data.key_points) +
f'\n\nConclusion: {research.data.conclusion}'
)
return post.data
```
## Best Practices
- ✅ Always define `result_type` with a Pydantic model — avoid returning raw strings in production
- ✅ Use `deps_type` with a dataclass for dependency injection — makes agents testable
- ✅ Use `TestModel` in unit tests — never hit a real LLM in CI
- ✅ Add `@agent.result_validator` for business-logic checks beyond Pydantic validation
- ✅ Use `run_stream` for long outputs in user-facing applications to show progressive results
- ❌ Don't put secrets (API keys) in `Agent()` arguments — use environment variables
- ❌ Don't share a single `Agent` instance across async tasks if deps differ — create per-request instances or use `agent.run()` with per-call `deps`
- ❌ Don't catch `ValidationError` broadly — let PydanticAI retry with `ModelRetry` for recoverable LLM output errors
## Security & Safety Notes
- Set API keys via environment variables (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, etc.) — never hardcode them.
- Validate all tool inputs before passing to external systems — use Pydantic models or manual checks.
- Tools that mutate data (write to DB, send emails, call payment APIs) should require explicit user confirmation before the agent invokes them in production.
- Log `result.all_messages()` for audit trails when agents perform consequential actions.
- Set `retries=` limits on `Agent()` to prevent runaway loops on persistent validation failures.
## Common Pitfalls
- **Problem:** `ValidationError` on every LLM response — structured output never validates
**Solution:** Simplify `result_type` fields. Use `Optional` and `default` where appropriate. The model may struggle with overly strict schemas.
- **Problem:** Tool is never called by the LLM
**Solution:** Write a clear, specific docstring for the tool function — PydanticAI sends the docstring as the tool description to the LLM.
- **Problem:** `RunContext` dependency is `None` inside a tool
**Solution:** Pass `deps=` when calling `agent.run()` or `agent.run_sync()`. Dependencies are not set globally.
- **Problem:** `asyncio.run()` error when calling `agent.run()` inside FastAPI
**Solution:** Use `await agent.run()` directly in async FastAPI route handlers — don't wrap in `asyncio.run()`.
## Related Skills
- `@langchain-architecture` — Alternative Python AI framework (more flexible, less type-safe)
- `@llm-application-dev-ai-assistant` — General LLM application development patterns
- `@fastapi-templates` — Serving PydanticAI agents via FastAPI endpoints
- `@agent-orchestration-multi-agent-optimize` — Orchestrating multiple PydanticAI agents

286
skills/sveltekit/SKILL.md Normal file
View File

@@ -0,0 +1,286 @@
---
name: sveltekit
description: "Build full-stack web applications with SvelteKit — file-based routing, SSR, SSG, API routes, and form actions in one framework."
category: frontend
risk: safe
source: community
date_added: "2026-03-18"
author: suhaibjanjua
tags: [svelte, sveltekit, fullstack, ssr, ssg, typescript]
tools: [claude, cursor, gemini]
---
# SvelteKit Full-Stack Development
## Overview
SvelteKit is the official full-stack framework built on top of Svelte. It provides file-based routing, server-side rendering (SSR), static site generation (SSG), API routes, and progressive form actions — all with Svelte's compile-time reactivity model that ships zero runtime overhead to the browser. Use this skill when building fast, modern web apps where both DX and performance matter.
## When to Use This Skill
- Use when building a new full-stack web application with Svelte
- Use when you need SSR or SSG with fine-grained control per route
- Use when migrating a SPA to a framework with server capabilities
- Use when working on a project that needs file-based routing and collocated API endpoints
- Use when the user asks about `+page.svelte`, `+layout.svelte`, `load` functions, or form actions
## How It Works
### Step 1: Project Setup
```bash
npm create svelte@latest my-app
cd my-app
npm install
npm run dev
```
Choose **Skeleton project** + **TypeScript** + **ESLint/Prettier** when prompted.
Directory structure after scaffolding:
```
src/
routes/
+page.svelte ← Root page component
+layout.svelte ← Root layout (wraps all pages)
+error.svelte ← Error boundary
lib/
server/ ← Server-only code (never bundled to client)
components/ ← Shared components
app.html ← HTML shell
static/ ← Static assets
```
### Step 2: File-Based Routing
Every `+page.svelte` file in `src/routes/` maps directly to a URL:
```
src/routes/+page.svelte → /
src/routes/about/+page.svelte → /about
src/routes/blog/[slug]/+page.svelte → /blog/:slug
src/routes/shop/[...path]/+page.svelte → /shop/* (catch-all)
```
**Route groups** (no URL segment): wrap in `(group)/` folder.
**Private routes** (not accessible as URLs): prefix with `_` or `(group)`.
### Step 3: Loading Data with `load` Functions
Use a `+page.ts` (universal) or `+page.server.ts` (server-only) file alongside the page:
```typescript
// src/routes/blog/[slug]/+page.server.ts
import { error } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load: PageServerLoad = async ({ params, fetch }) => {
const post = await fetch(`/api/posts/${params.slug}`).then(r => r.json());
if (!post) {
error(404, 'Post not found');
}
return { post };
};
```
```svelte
<!-- src/routes/blog/[slug]/+page.svelte -->
<script lang="ts">
import type { PageData } from './$types';
export let data: PageData;
</script>
<h1>{data.post.title}</h1>
<article>{@html data.post.content}</article>
```
### Step 4: API Routes (Server Endpoints)
Create `+server.ts` files for REST-style endpoints:
```typescript
// src/routes/api/posts/+server.ts
import { json } from '@sveltejs/kit';
import type { RequestHandler } from './$types';
export const GET: RequestHandler = async ({ url }) => {
const limit = Number(url.searchParams.get('limit') ?? 10);
const posts = await db.post.findMany({ take: limit });
return json(posts);
};
export const POST: RequestHandler = async ({ request }) => {
const body = await request.json();
const post = await db.post.create({ data: body });
return json(post, { status: 201 });
};
```
### Step 5: Form Actions
Form actions are the SvelteKit-native way to handle mutations — no client-side fetch required:
```typescript
// src/routes/contact/+page.server.ts
import { fail, redirect } from '@sveltejs/kit';
import type { Actions } from './$types';
export const actions: Actions = {
default: async ({ request }) => {
const data = await request.formData();
const email = data.get('email');
if (!email) {
return fail(400, { email, missing: true });
}
await sendEmail(String(email));
redirect(303, '/thank-you');
}
};
```
```svelte
<!-- src/routes/contact/+page.svelte -->
<script lang="ts">
import { enhance } from '$app/forms';
import type { ActionData } from './$types';
export let form: ActionData;
</script>
<form method="POST" use:enhance>
<input name="email" type="email" />
{#if form?.missing}<p class="error">Email is required</p>{/if}
<button type="submit">Subscribe</button>
</form>
```
### Step 6: Layouts and Nested Routes
```svelte
<!-- src/routes/+layout.svelte -->
<script lang="ts">
import type { LayoutData } from './$types';
export let data: LayoutData;
</script>
<nav>
<a href="/">Home</a>
<a href="/blog">Blog</a>
{#if data.user}
<a href="/dashboard">Dashboard</a>
{/if}
</nav>
<slot /> <!-- child page renders here -->
```
```typescript
// src/routes/+layout.server.ts
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ locals }) => {
return { user: locals.user ?? null };
};
```
### Step 7: Rendering Modes
Control per-route rendering with page options:
```typescript
// src/routes/docs/+page.ts
export const prerender = true; // Static — generated at build time
export const ssr = true; // Default — rendered on server per request
export const csr = false; // Disable client-side hydration entirely
```
## Examples
### Example 1: Protected Dashboard Route
```typescript
// src/routes/dashboard/+layout.server.ts
import { redirect } from '@sveltejs/kit';
import type { LayoutServerLoad } from './$types';
export const load: LayoutServerLoad = async ({ locals }) => {
if (!locals.user) {
redirect(303, '/login');
}
return { user: locals.user };
};
```
### Example 2: Hooks — Session Middleware
```typescript
// src/hooks.server.ts
import type { Handle } from '@sveltejs/kit';
import { verifyToken } from '$lib/server/auth';
export const handle: Handle = async ({ event, resolve }) => {
const token = event.cookies.get('session');
if (token) {
event.locals.user = await verifyToken(token);
}
return resolve(event);
};
```
### Example 3: Preloading and Invalidation
```svelte
<script lang="ts">
import { invalidateAll } from '$app/navigation';
async function refresh() {
await invalidateAll(); // re-runs all load functions on the page
}
</script>
<button on:click={refresh}>Refresh</button>
```
## Best Practices
- ✅ Use `+page.server.ts` for database/auth logic — it never ships to the client
- ✅ Use `$lib/server/` for shared server-only modules (DB client, auth helpers)
- ✅ Use form actions for mutations instead of client-side `fetch` — works without JS
- ✅ Type all `load` return values with generated `$types` (`PageData`, `LayoutData`)
- ✅ Use `event.locals` in hooks to pass server-side context to load functions
- ❌ Don't import server-only code in `+page.svelte` or `+layout.svelte` directly
- ❌ Don't store sensitive state in stores — use `locals` on the server
- ❌ Don't skip `use:enhance` on forms — without it, forms lose progressive enhancement
## Security & Safety Notes
- All code in `+page.server.ts`, `+server.ts`, and `$lib/server/` runs exclusively on the server — safe for DB queries, secrets, and session validation.
- Always validate and sanitize form data before database writes.
- Use `error(403)` or `redirect(303)` from `@sveltejs/kit` rather than returning raw error objects.
- Set `httpOnly: true` and `secure: true` on all auth cookies.
- CSRF protection is built-in for form actions — do not disable `checkOrigin` in production.
## Common Pitfalls
- **Problem:** `Cannot use import statement in a module` in `+page.server.ts`
**Solution:** The file must be `.ts` or `.js`, not `.svelte`. Server files and Svelte components are separate.
- **Problem:** Store value is `undefined` on first SSR render
**Solution:** Populate the store from the `load` function return value (`data` prop), not from client-side `onMount`.
- **Problem:** Form action does not redirect after submit
**Solution:** Use `redirect(303, '/path')` from `@sveltejs/kit`, not a plain `return`. 303 is required for POST redirects.
- **Problem:** `locals.user` is undefined inside a `+page.server.ts` load function
**Solution:** Set `event.locals.user` in `src/hooks.server.ts` before the `resolve()` call.
## Related Skills
- `@nextjs-app-router-patterns` — When you prefer React over Svelte for SSR/SSG
- `@trpc-fullstack` — Add end-to-end type safety to SvelteKit API routes
- `@auth-implementation-patterns` — Authentication patterns usable with SvelteKit hooks
- `@tailwind-patterns` — Styling SvelteKit apps with Tailwind CSS