opencode-skills-antigravity 0.0.3 → 0.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bundled-skills/astro/SKILL.md +359 -0
- package/bundled-skills/browser-extension-builder/SKILL.md +0 -12
- package/bundled-skills/devcontainer-setup/SKILL.md +3 -0
- package/bundled-skills/docs/contributors/skill-anatomy.md +24 -3
- package/bundled-skills/docs/users/faq.md +3 -0
- package/bundled-skills/goldrush-api/SKILL.md +109 -0
- package/bundled-skills/hono/SKILL.md +348 -0
- package/bundled-skills/openclaw-github-repo-commander/SKILL.md +95 -0
- package/bundled-skills/pydantic-ai/SKILL.md +350 -0
- package/bundled-skills/sveltekit/SKILL.md +286 -0
- package/dist/index.js +12 -1
- package/package.json +1 -1
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pydantic-ai
|
|
3
|
+
description: "Build production-ready AI agents with PydanticAI — type-safe tool use, structured outputs, dependency injection, and multi-model support."
|
|
4
|
+
category: ai-agents
|
|
5
|
+
risk: safe
|
|
6
|
+
source: community
|
|
7
|
+
date_added: "2026-03-18"
|
|
8
|
+
author: suhaibjanjua
|
|
9
|
+
tags: [pydantic-ai, ai-agents, llm, openai, anthropic, gemini, tool-use, structured-output, python]
|
|
10
|
+
tools: [claude, cursor, gemini]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# PydanticAI — Typed AI Agents in Python
|
|
14
|
+
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
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.
|
|
18
|
+
|
|
19
|
+
## When to Use This Skill
|
|
20
|
+
|
|
21
|
+
- Use when building Python AI agents that call tools and return structured data
|
|
22
|
+
- Use when you need validated, typed LLM outputs (not raw strings)
|
|
23
|
+
- Use when you want to write unit tests for agent logic without hitting a real LLM
|
|
24
|
+
- Use when switching between LLM providers without rewriting agent code
|
|
25
|
+
- Use when the user asks about `Agent`, `@agent.tool`, `RunContext`, `ModelRetry`, or `result_type`
|
|
26
|
+
|
|
27
|
+
## How It Works
|
|
28
|
+
|
|
29
|
+
### Step 1: Installation
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install pydantic-ai
|
|
33
|
+
|
|
34
|
+
# Install extras for specific providers
|
|
35
|
+
pip install 'pydantic-ai[openai]' # OpenAI / Azure OpenAI
|
|
36
|
+
pip install 'pydantic-ai[anthropic]' # Anthropic Claude
|
|
37
|
+
pip install 'pydantic-ai[gemini]' # Google Gemini
|
|
38
|
+
pip install 'pydantic-ai[groq]' # Groq
|
|
39
|
+
pip install 'pydantic-ai[vertexai]' # Google Vertex AI
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Step 2: A Minimal Agent
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from pydantic_ai import Agent
|
|
46
|
+
|
|
47
|
+
# Simple agent — returns a plain string
|
|
48
|
+
agent = Agent(
|
|
49
|
+
'anthropic:claude-sonnet-4-6',
|
|
50
|
+
system_prompt='You are a helpful assistant. Be concise.',
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
result = agent.run_sync('What is the capital of Japan?')
|
|
54
|
+
print(result.data) # "Tokyo"
|
|
55
|
+
print(result.usage()) # Usage(requests=1, request_tokens=..., response_tokens=...)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Step 3: Structured Output with Pydantic Models
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
from pydantic import BaseModel
|
|
62
|
+
from pydantic_ai import Agent
|
|
63
|
+
|
|
64
|
+
class MovieReview(BaseModel):
|
|
65
|
+
title: str
|
|
66
|
+
year: int
|
|
67
|
+
rating: float # 0.0 to 10.0
|
|
68
|
+
summary: str
|
|
69
|
+
recommended: bool
|
|
70
|
+
|
|
71
|
+
agent = Agent(
|
|
72
|
+
'openai:gpt-4o',
|
|
73
|
+
result_type=MovieReview,
|
|
74
|
+
system_prompt='You are a film critic. Return structured reviews.',
|
|
75
|
+
)
|
|
76
|
+
|
|
77
|
+
result = agent.run_sync('Review Inception (2010)')
|
|
78
|
+
review = result.data # Fully typed MovieReview instance
|
|
79
|
+
print(f"{review.title} ({review.year}): {review.rating}/10")
|
|
80
|
+
print(f"Recommended: {review.recommended}")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Step 4: Tool Use
|
|
84
|
+
|
|
85
|
+
Register tools with `@agent.tool` — the LLM can call them during a run:
|
|
86
|
+
|
|
87
|
+
```python
|
|
88
|
+
from pydantic_ai import Agent, RunContext
|
|
89
|
+
from pydantic import BaseModel
|
|
90
|
+
import httpx
|
|
91
|
+
|
|
92
|
+
class WeatherReport(BaseModel):
|
|
93
|
+
city: str
|
|
94
|
+
temperature_c: float
|
|
95
|
+
condition: str
|
|
96
|
+
|
|
97
|
+
weather_agent = Agent(
|
|
98
|
+
'anthropic:claude-sonnet-4-6',
|
|
99
|
+
result_type=WeatherReport,
|
|
100
|
+
system_prompt='Get current weather for the requested city.',
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
@weather_agent.tool
|
|
104
|
+
async def get_temperature(ctx: RunContext, city: str) -> dict:
|
|
105
|
+
"""Fetch the current temperature for a city from the weather API."""
|
|
106
|
+
async with httpx.AsyncClient() as client:
|
|
107
|
+
r = await client.get(f'https://wttr.in/{city}?format=j1')
|
|
108
|
+
data = r.json()
|
|
109
|
+
return {
|
|
110
|
+
'temp_c': float(data['current_condition'][0]['temp_C']),
|
|
111
|
+
'description': data['current_condition'][0]['weatherDesc'][0]['value'],
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
import asyncio
|
|
115
|
+
result = asyncio.run(weather_agent.run('What is the weather in Tokyo?'))
|
|
116
|
+
print(result.data)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Step 5: Dependency Injection
|
|
120
|
+
|
|
121
|
+
Inject services (database, HTTP clients, config) into agents for testability:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
from dataclasses import dataclass
|
|
125
|
+
from pydantic_ai import Agent, RunContext
|
|
126
|
+
from pydantic import BaseModel
|
|
127
|
+
|
|
128
|
+
@dataclass
|
|
129
|
+
class Deps:
|
|
130
|
+
db: Database
|
|
131
|
+
user_id: str
|
|
132
|
+
|
|
133
|
+
class SupportResponse(BaseModel):
|
|
134
|
+
message: str
|
|
135
|
+
escalate: bool
|
|
136
|
+
|
|
137
|
+
support_agent = Agent(
|
|
138
|
+
'openai:gpt-4o-mini',
|
|
139
|
+
deps_type=Deps,
|
|
140
|
+
result_type=SupportResponse,
|
|
141
|
+
system_prompt='You are a support agent. Use the tools to help customers.',
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
@support_agent.tool
|
|
145
|
+
async def get_order_history(ctx: RunContext[Deps]) -> list[dict]:
|
|
146
|
+
"""Fetch recent orders for the current user."""
|
|
147
|
+
return await ctx.deps.db.get_orders(ctx.deps.user_id, limit=5)
|
|
148
|
+
|
|
149
|
+
@support_agent.tool
|
|
150
|
+
async def create_refund(ctx: RunContext[Deps], order_id: str, reason: str) -> dict:
|
|
151
|
+
"""Initiate a refund for a specific order."""
|
|
152
|
+
return await ctx.deps.db.create_refund(order_id, reason, ctx.deps.user_id)
|
|
153
|
+
|
|
154
|
+
# Usage
|
|
155
|
+
async def handle_support(user_id: str, message: str):
|
|
156
|
+
deps = Deps(db=get_db(), user_id=user_id)
|
|
157
|
+
result = await support_agent.run(message, deps=deps)
|
|
158
|
+
return result.data
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Step 6: Testing with TestModel
|
|
162
|
+
|
|
163
|
+
Write unit tests without real LLM calls:
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
from pydantic_ai.models.test import TestModel
|
|
167
|
+
|
|
168
|
+
def test_support_agent_escalates():
|
|
169
|
+
with support_agent.override(model=TestModel()):
|
|
170
|
+
# TestModel returns a minimal valid response matching result_type
|
|
171
|
+
result = support_agent.run_sync(
|
|
172
|
+
'I want to cancel my account',
|
|
173
|
+
deps=Deps(db=FakeDb(), user_id='user-123'),
|
|
174
|
+
)
|
|
175
|
+
# Test the structure, not the LLM's exact words
|
|
176
|
+
assert isinstance(result.data, SupportResponse)
|
|
177
|
+
assert isinstance(result.data.escalate, bool)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**FunctionModel** for deterministic test responses:
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
from pydantic_ai.models.function import FunctionModel, ModelContext
|
|
184
|
+
|
|
185
|
+
def my_model(messages, info):
|
|
186
|
+
return ModelResponse(parts=[TextPart('Always this response')])
|
|
187
|
+
|
|
188
|
+
with agent.override(model=FunctionModel(my_model)):
|
|
189
|
+
result = agent.run_sync('anything')
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Step 7: Streaming Responses
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
import asyncio
|
|
196
|
+
from pydantic_ai import Agent
|
|
197
|
+
|
|
198
|
+
agent = Agent('anthropic:claude-sonnet-4-6')
|
|
199
|
+
|
|
200
|
+
async def stream_response():
|
|
201
|
+
async with agent.run_stream('Write a haiku about Python') as result:
|
|
202
|
+
async for chunk in result.stream_text():
|
|
203
|
+
print(chunk, end='', flush=True)
|
|
204
|
+
print() # newline
|
|
205
|
+
print(f"Total tokens: {result.usage()}")
|
|
206
|
+
|
|
207
|
+
asyncio.run(stream_response())
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
### Step 8: Multi-Turn Conversations
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
from pydantic_ai import Agent
|
|
214
|
+
from pydantic_ai.messages import ModelMessagesTypeAdapter
|
|
215
|
+
|
|
216
|
+
agent = Agent('openai:gpt-4o', system_prompt='You are a helpful assistant.')
|
|
217
|
+
|
|
218
|
+
# First turn
|
|
219
|
+
result1 = agent.run_sync('My name is Alice.')
|
|
220
|
+
history = result1.all_messages()
|
|
221
|
+
|
|
222
|
+
# Second turn — passes conversation history
|
|
223
|
+
result2 = agent.run_sync('What is my name?', message_history=history)
|
|
224
|
+
print(result2.data) # "Your name is Alice."
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Examples
|
|
228
|
+
|
|
229
|
+
### Example 1: Code Review Agent
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
from pydantic import BaseModel, Field
|
|
233
|
+
from pydantic_ai import Agent
|
|
234
|
+
from typing import Literal
|
|
235
|
+
|
|
236
|
+
class CodeReview(BaseModel):
|
|
237
|
+
quality: Literal['excellent', 'good', 'needs_work', 'poor']
|
|
238
|
+
issues: list[str] = Field(default_factory=list)
|
|
239
|
+
suggestions: list[str] = Field(default_factory=list)
|
|
240
|
+
approved: bool
|
|
241
|
+
|
|
242
|
+
code_review_agent = Agent(
|
|
243
|
+
'anthropic:claude-sonnet-4-6',
|
|
244
|
+
result_type=CodeReview,
|
|
245
|
+
system_prompt="""
|
|
246
|
+
You are a senior engineer performing code review.
|
|
247
|
+
Evaluate code quality, identify issues, and provide actionable suggestions.
|
|
248
|
+
Set approved=True only for good or excellent quality code with no security issues.
|
|
249
|
+
""",
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
def review_code(diff: str) -> CodeReview:
|
|
253
|
+
result = code_review_agent.run_sync(f"Review this code:\n\n{diff}")
|
|
254
|
+
return result.data
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
### Example 2: Agent with Retry Logic
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
from pydantic_ai import Agent, ModelRetry
|
|
261
|
+
from pydantic import BaseModel, field_validator
|
|
262
|
+
|
|
263
|
+
class StrictJson(BaseModel):
|
|
264
|
+
value: int
|
|
265
|
+
|
|
266
|
+
@field_validator('value')
|
|
267
|
+
def must_be_positive(cls, v):
|
|
268
|
+
if v <= 0:
|
|
269
|
+
raise ValueError('value must be positive')
|
|
270
|
+
return v
|
|
271
|
+
|
|
272
|
+
agent = Agent('openai:gpt-4o-mini', result_type=StrictJson)
|
|
273
|
+
|
|
274
|
+
@agent.result_validator
|
|
275
|
+
async def validate_result(ctx, result: StrictJson) -> StrictJson:
|
|
276
|
+
if result.value > 1000:
|
|
277
|
+
raise ModelRetry('Value must be under 1000. Try again with a smaller number.')
|
|
278
|
+
return result
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Example 3: Multi-Agent Pipeline
|
|
282
|
+
|
|
283
|
+
```python
|
|
284
|
+
from pydantic_ai import Agent
|
|
285
|
+
from pydantic import BaseModel
|
|
286
|
+
|
|
287
|
+
class ResearchSummary(BaseModel):
|
|
288
|
+
key_points: list[str]
|
|
289
|
+
conclusion: str
|
|
290
|
+
|
|
291
|
+
class BlogPost(BaseModel):
|
|
292
|
+
title: str
|
|
293
|
+
body: str
|
|
294
|
+
meta_description: str
|
|
295
|
+
|
|
296
|
+
researcher = Agent('openai:gpt-4o', result_type=ResearchSummary)
|
|
297
|
+
writer = Agent('anthropic:claude-sonnet-4-6', result_type=BlogPost)
|
|
298
|
+
|
|
299
|
+
async def research_and_write(topic: str) -> BlogPost:
|
|
300
|
+
# Stage 1: research
|
|
301
|
+
research = await researcher.run(f'Research the topic: {topic}')
|
|
302
|
+
|
|
303
|
+
# Stage 2: write based on research
|
|
304
|
+
post = await writer.run(
|
|
305
|
+
f'Write a blog post about: {topic}\n\nResearch:\n' +
|
|
306
|
+
'\n'.join(f'- {p}' for p in research.data.key_points) +
|
|
307
|
+
f'\n\nConclusion: {research.data.conclusion}'
|
|
308
|
+
)
|
|
309
|
+
return post.data
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
## Best Practices
|
|
313
|
+
|
|
314
|
+
- ✅ Always define `result_type` with a Pydantic model — avoid returning raw strings in production
|
|
315
|
+
- ✅ Use `deps_type` with a dataclass for dependency injection — makes agents testable
|
|
316
|
+
- ✅ Use `TestModel` in unit tests — never hit a real LLM in CI
|
|
317
|
+
- ✅ Add `@agent.result_validator` for business-logic checks beyond Pydantic validation
|
|
318
|
+
- ✅ Use `run_stream` for long outputs in user-facing applications to show progressive results
|
|
319
|
+
- ❌ Don't put secrets (API keys) in `Agent()` arguments — use environment variables
|
|
320
|
+
- ❌ 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`
|
|
321
|
+
- ❌ Don't catch `ValidationError` broadly — let PydanticAI retry with `ModelRetry` for recoverable LLM output errors
|
|
322
|
+
|
|
323
|
+
## Security & Safety Notes
|
|
324
|
+
|
|
325
|
+
- Set API keys via environment variables (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, etc.) — never hardcode them.
|
|
326
|
+
- Validate all tool inputs before passing to external systems — use Pydantic models or manual checks.
|
|
327
|
+
- Tools that mutate data (write to DB, send emails, call payment APIs) should require explicit user confirmation before the agent invokes them in production.
|
|
328
|
+
- Log `result.all_messages()` for audit trails when agents perform consequential actions.
|
|
329
|
+
- Set `retries=` limits on `Agent()` to prevent runaway loops on persistent validation failures.
|
|
330
|
+
|
|
331
|
+
## Common Pitfalls
|
|
332
|
+
|
|
333
|
+
- **Problem:** `ValidationError` on every LLM response — structured output never validates
|
|
334
|
+
**Solution:** Simplify `result_type` fields. Use `Optional` and `default` where appropriate. The model may struggle with overly strict schemas.
|
|
335
|
+
|
|
336
|
+
- **Problem:** Tool is never called by the LLM
|
|
337
|
+
**Solution:** Write a clear, specific docstring for the tool function — PydanticAI sends the docstring as the tool description to the LLM.
|
|
338
|
+
|
|
339
|
+
- **Problem:** `RunContext` dependency is `None` inside a tool
|
|
340
|
+
**Solution:** Pass `deps=` when calling `agent.run()` or `agent.run_sync()`. Dependencies are not set globally.
|
|
341
|
+
|
|
342
|
+
- **Problem:** `asyncio.run()` error when calling `agent.run()` inside FastAPI
|
|
343
|
+
**Solution:** Use `await agent.run()` directly in async FastAPI route handlers — don't wrap in `asyncio.run()`.
|
|
344
|
+
|
|
345
|
+
## Related Skills
|
|
346
|
+
|
|
347
|
+
- `@langchain-architecture` — Alternative Python AI framework (more flexible, less type-safe)
|
|
348
|
+
- `@llm-application-dev-ai-assistant` — General LLM application development patterns
|
|
349
|
+
- `@fastapi-templates` — Serving PydanticAI agents via FastAPI endpoints
|
|
350
|
+
- `@agent-orchestration-multi-agent-optimize` — Orchestrating multiple PydanticAI agents
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sveltekit
|
|
3
|
+
description: "Build full-stack web applications with SvelteKit — file-based routing, SSR, SSG, API routes, and form actions in one framework."
|
|
4
|
+
category: frontend
|
|
5
|
+
risk: safe
|
|
6
|
+
source: community
|
|
7
|
+
date_added: "2026-03-18"
|
|
8
|
+
author: suhaibjanjua
|
|
9
|
+
tags: [svelte, sveltekit, fullstack, ssr, ssg, typescript]
|
|
10
|
+
tools: [claude, cursor, gemini]
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# SvelteKit Full-Stack Development
|
|
14
|
+
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
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.
|
|
18
|
+
|
|
19
|
+
## When to Use This Skill
|
|
20
|
+
|
|
21
|
+
- Use when building a new full-stack web application with Svelte
|
|
22
|
+
- Use when you need SSR or SSG with fine-grained control per route
|
|
23
|
+
- Use when migrating a SPA to a framework with server capabilities
|
|
24
|
+
- Use when working on a project that needs file-based routing and collocated API endpoints
|
|
25
|
+
- Use when the user asks about `+page.svelte`, `+layout.svelte`, `load` functions, or form actions
|
|
26
|
+
|
|
27
|
+
## How It Works
|
|
28
|
+
|
|
29
|
+
### Step 1: Project Setup
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npm create svelte@latest my-app
|
|
33
|
+
cd my-app
|
|
34
|
+
npm install
|
|
35
|
+
npm run dev
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Choose **Skeleton project** + **TypeScript** + **ESLint/Prettier** when prompted.
|
|
39
|
+
|
|
40
|
+
Directory structure after scaffolding:
|
|
41
|
+
|
|
42
|
+
```
|
|
43
|
+
src/
|
|
44
|
+
routes/
|
|
45
|
+
+page.svelte ← Root page component
|
|
46
|
+
+layout.svelte ← Root layout (wraps all pages)
|
|
47
|
+
+error.svelte ← Error boundary
|
|
48
|
+
lib/
|
|
49
|
+
server/ ← Server-only code (never bundled to client)
|
|
50
|
+
components/ ← Shared components
|
|
51
|
+
app.html ← HTML shell
|
|
52
|
+
static/ ← Static assets
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Step 2: File-Based Routing
|
|
56
|
+
|
|
57
|
+
Every `+page.svelte` file in `src/routes/` maps directly to a URL:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
src/routes/+page.svelte → /
|
|
61
|
+
src/routes/about/+page.svelte → /about
|
|
62
|
+
src/routes/blog/[slug]/+page.svelte → /blog/:slug
|
|
63
|
+
src/routes/shop/[...path]/+page.svelte → /shop/* (catch-all)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
**Route groups** (no URL segment): wrap in `(group)/` folder.
|
|
67
|
+
**Private routes** (not accessible as URLs): prefix with `_` or `(group)`.
|
|
68
|
+
|
|
69
|
+
### Step 3: Loading Data with `load` Functions
|
|
70
|
+
|
|
71
|
+
Use a `+page.ts` (universal) or `+page.server.ts` (server-only) file alongside the page:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// src/routes/blog/[slug]/+page.server.ts
|
|
75
|
+
import { error } from '@sveltejs/kit';
|
|
76
|
+
import type { PageServerLoad } from './$types';
|
|
77
|
+
|
|
78
|
+
export const load: PageServerLoad = async ({ params, fetch }) => {
|
|
79
|
+
const post = await fetch(`/api/posts/${params.slug}`).then(r => r.json());
|
|
80
|
+
|
|
81
|
+
if (!post) {
|
|
82
|
+
error(404, 'Post not found');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return { post };
|
|
86
|
+
};
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
```svelte
|
|
90
|
+
<!-- src/routes/blog/[slug]/+page.svelte -->
|
|
91
|
+
<script lang="ts">
|
|
92
|
+
import type { PageData } from './$types';
|
|
93
|
+
export let data: PageData;
|
|
94
|
+
</script>
|
|
95
|
+
|
|
96
|
+
<h1>{data.post.title}</h1>
|
|
97
|
+
<article>{@html data.post.content}</article>
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### Step 4: API Routes (Server Endpoints)
|
|
101
|
+
|
|
102
|
+
Create `+server.ts` files for REST-style endpoints:
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
// src/routes/api/posts/+server.ts
|
|
106
|
+
import { json } from '@sveltejs/kit';
|
|
107
|
+
import type { RequestHandler } from './$types';
|
|
108
|
+
|
|
109
|
+
export const GET: RequestHandler = async ({ url }) => {
|
|
110
|
+
const limit = Number(url.searchParams.get('limit') ?? 10);
|
|
111
|
+
const posts = await db.post.findMany({ take: limit });
|
|
112
|
+
return json(posts);
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
export const POST: RequestHandler = async ({ request }) => {
|
|
116
|
+
const body = await request.json();
|
|
117
|
+
const post = await db.post.create({ data: body });
|
|
118
|
+
return json(post, { status: 201 });
|
|
119
|
+
};
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Step 5: Form Actions
|
|
123
|
+
|
|
124
|
+
Form actions are the SvelteKit-native way to handle mutations — no client-side fetch required:
|
|
125
|
+
|
|
126
|
+
```typescript
|
|
127
|
+
// src/routes/contact/+page.server.ts
|
|
128
|
+
import { fail, redirect } from '@sveltejs/kit';
|
|
129
|
+
import type { Actions } from './$types';
|
|
130
|
+
|
|
131
|
+
export const actions: Actions = {
|
|
132
|
+
default: async ({ request }) => {
|
|
133
|
+
const data = await request.formData();
|
|
134
|
+
const email = data.get('email');
|
|
135
|
+
|
|
136
|
+
if (!email) {
|
|
137
|
+
return fail(400, { email, missing: true });
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
await sendEmail(String(email));
|
|
141
|
+
redirect(303, '/thank-you');
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
```svelte
|
|
147
|
+
<!-- src/routes/contact/+page.svelte -->
|
|
148
|
+
<script lang="ts">
|
|
149
|
+
import { enhance } from '$app/forms';
|
|
150
|
+
import type { ActionData } from './$types';
|
|
151
|
+
export let form: ActionData;
|
|
152
|
+
</script>
|
|
153
|
+
|
|
154
|
+
<form method="POST" use:enhance>
|
|
155
|
+
<input name="email" type="email" />
|
|
156
|
+
{#if form?.missing}<p class="error">Email is required</p>{/if}
|
|
157
|
+
<button type="submit">Subscribe</button>
|
|
158
|
+
</form>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Step 6: Layouts and Nested Routes
|
|
162
|
+
|
|
163
|
+
```svelte
|
|
164
|
+
<!-- src/routes/+layout.svelte -->
|
|
165
|
+
<script lang="ts">
|
|
166
|
+
import type { LayoutData } from './$types';
|
|
167
|
+
export let data: LayoutData;
|
|
168
|
+
</script>
|
|
169
|
+
|
|
170
|
+
<nav>
|
|
171
|
+
<a href="/">Home</a>
|
|
172
|
+
<a href="/blog">Blog</a>
|
|
173
|
+
{#if data.user}
|
|
174
|
+
<a href="/dashboard">Dashboard</a>
|
|
175
|
+
{/if}
|
|
176
|
+
</nav>
|
|
177
|
+
|
|
178
|
+
<slot /> <!-- child page renders here -->
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
// src/routes/+layout.server.ts
|
|
183
|
+
import type { LayoutServerLoad } from './$types';
|
|
184
|
+
|
|
185
|
+
export const load: LayoutServerLoad = async ({ locals }) => {
|
|
186
|
+
return { user: locals.user ?? null };
|
|
187
|
+
};
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
### Step 7: Rendering Modes
|
|
191
|
+
|
|
192
|
+
Control per-route rendering with page options:
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
// src/routes/docs/+page.ts
|
|
196
|
+
export const prerender = true; // Static — generated at build time
|
|
197
|
+
export const ssr = true; // Default — rendered on server per request
|
|
198
|
+
export const csr = false; // Disable client-side hydration entirely
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Examples
|
|
202
|
+
|
|
203
|
+
### Example 1: Protected Dashboard Route
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// src/routes/dashboard/+layout.server.ts
|
|
207
|
+
import { redirect } from '@sveltejs/kit';
|
|
208
|
+
import type { LayoutServerLoad } from './$types';
|
|
209
|
+
|
|
210
|
+
export const load: LayoutServerLoad = async ({ locals }) => {
|
|
211
|
+
if (!locals.user) {
|
|
212
|
+
redirect(303, '/login');
|
|
213
|
+
}
|
|
214
|
+
return { user: locals.user };
|
|
215
|
+
};
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### Example 2: Hooks — Session Middleware
|
|
219
|
+
|
|
220
|
+
```typescript
|
|
221
|
+
// src/hooks.server.ts
|
|
222
|
+
import type { Handle } from '@sveltejs/kit';
|
|
223
|
+
import { verifyToken } from '$lib/server/auth';
|
|
224
|
+
|
|
225
|
+
export const handle: Handle = async ({ event, resolve }) => {
|
|
226
|
+
const token = event.cookies.get('session');
|
|
227
|
+
if (token) {
|
|
228
|
+
event.locals.user = await verifyToken(token);
|
|
229
|
+
}
|
|
230
|
+
return resolve(event);
|
|
231
|
+
};
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Example 3: Preloading and Invalidation
|
|
235
|
+
|
|
236
|
+
```svelte
|
|
237
|
+
<script lang="ts">
|
|
238
|
+
import { invalidateAll } from '$app/navigation';
|
|
239
|
+
|
|
240
|
+
async function refresh() {
|
|
241
|
+
await invalidateAll(); // re-runs all load functions on the page
|
|
242
|
+
}
|
|
243
|
+
</script>
|
|
244
|
+
|
|
245
|
+
<button on:click={refresh}>Refresh</button>
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
## Best Practices
|
|
249
|
+
|
|
250
|
+
- ✅ Use `+page.server.ts` for database/auth logic — it never ships to the client
|
|
251
|
+
- ✅ Use `$lib/server/` for shared server-only modules (DB client, auth helpers)
|
|
252
|
+
- ✅ Use form actions for mutations instead of client-side `fetch` — works without JS
|
|
253
|
+
- ✅ Type all `load` return values with generated `$types` (`PageData`, `LayoutData`)
|
|
254
|
+
- ✅ Use `event.locals` in hooks to pass server-side context to load functions
|
|
255
|
+
- ❌ Don't import server-only code in `+page.svelte` or `+layout.svelte` directly
|
|
256
|
+
- ❌ Don't store sensitive state in stores — use `locals` on the server
|
|
257
|
+
- ❌ Don't skip `use:enhance` on forms — without it, forms lose progressive enhancement
|
|
258
|
+
|
|
259
|
+
## Security & Safety Notes
|
|
260
|
+
|
|
261
|
+
- All code in `+page.server.ts`, `+server.ts`, and `$lib/server/` runs exclusively on the server — safe for DB queries, secrets, and session validation.
|
|
262
|
+
- Always validate and sanitize form data before database writes.
|
|
263
|
+
- Use `error(403)` or `redirect(303)` from `@sveltejs/kit` rather than returning raw error objects.
|
|
264
|
+
- Set `httpOnly: true` and `secure: true` on all auth cookies.
|
|
265
|
+
- CSRF protection is built-in for form actions — do not disable `checkOrigin` in production.
|
|
266
|
+
|
|
267
|
+
## Common Pitfalls
|
|
268
|
+
|
|
269
|
+
- **Problem:** `Cannot use import statement in a module` in `+page.server.ts`
|
|
270
|
+
**Solution:** The file must be `.ts` or `.js`, not `.svelte`. Server files and Svelte components are separate.
|
|
271
|
+
|
|
272
|
+
- **Problem:** Store value is `undefined` on first SSR render
|
|
273
|
+
**Solution:** Populate the store from the `load` function return value (`data` prop), not from client-side `onMount`.
|
|
274
|
+
|
|
275
|
+
- **Problem:** Form action does not redirect after submit
|
|
276
|
+
**Solution:** Use `redirect(303, '/path')` from `@sveltejs/kit`, not a plain `return`. 303 is required for POST redirects.
|
|
277
|
+
|
|
278
|
+
- **Problem:** `locals.user` is undefined inside a `+page.server.ts` load function
|
|
279
|
+
**Solution:** Set `event.locals.user` in `src/hooks.server.ts` before the `resolve()` call.
|
|
280
|
+
|
|
281
|
+
## Related Skills
|
|
282
|
+
|
|
283
|
+
- `@nextjs-app-router-patterns` — When you prefer React over Svelte for SSR/SSG
|
|
284
|
+
- `@trpc-fullstack` — Add end-to-end type safety to SvelteKit API routes
|
|
285
|
+
- `@auth-implementation-patterns` — Authentication patterns usable with SvelteKit hooks
|
|
286
|
+
- `@tailwind-patterns` — Styling SvelteKit apps with Tailwind CSS
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,18 @@ const AntigravityAutoUpdater = async (_ctx) => {
|
|
|
15
15
|
fs.cpSync(bundledSkillsPath, skillsPath, { recursive: true, force: true });
|
|
16
16
|
}
|
|
17
17
|
catch (error) {
|
|
18
|
-
|
|
18
|
+
setTimeout(async () => {
|
|
19
|
+
try {
|
|
20
|
+
const { exec } = await import("child_process");
|
|
21
|
+
const util = await import("util");
|
|
22
|
+
const execAsync = util.promisify(exec);
|
|
23
|
+
const fallbackPath = path.join(os.homedir(), ".config", "opencode", ".agents", "skills");
|
|
24
|
+
await execAsync(`npx --yes antigravity-awesome-skills --path "${fallbackPath}"`);
|
|
25
|
+
}
|
|
26
|
+
catch (e) {
|
|
27
|
+
// silently fail completely
|
|
28
|
+
}
|
|
29
|
+
}, 0);
|
|
19
30
|
}
|
|
20
31
|
return {};
|
|
21
32
|
};
|
package/package.json
CHANGED