@girardmedia/bootspring 1.1.0
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/LICENSE +21 -0
- package/README.md +255 -0
- package/agents/README.md +93 -0
- package/agents/api-expert/context.md +416 -0
- package/agents/architecture-expert/context.md +454 -0
- package/agents/backend-expert/context.md +483 -0
- package/agents/code-review-expert/context.md +365 -0
- package/agents/database-expert/context.md +250 -0
- package/agents/devops-expert/context.md +446 -0
- package/agents/frontend-expert/context.md +364 -0
- package/agents/index.js +140 -0
- package/agents/performance-expert/context.md +377 -0
- package/agents/security-expert/context.md +343 -0
- package/agents/testing-expert/context.md +414 -0
- package/agents/ui-ux-expert/context.md +448 -0
- package/agents/vercel-expert/context.md +426 -0
- package/bin/bootspring.js +310 -0
- package/cli/agent.js +337 -0
- package/cli/context.js +194 -0
- package/cli/dashboard.js +150 -0
- package/cli/generate.js +294 -0
- package/cli/init.js +410 -0
- package/cli/loop.js +421 -0
- package/cli/mcp.js +241 -0
- package/cli/memory.js +303 -0
- package/cli/orchestrator.js +400 -0
- package/cli/plugin.js +451 -0
- package/cli/quality.js +332 -0
- package/cli/skill.js +369 -0
- package/cli/task.js +628 -0
- package/cli/telemetry.js +114 -0
- package/cli/todo.js +614 -0
- package/cli/update.js +312 -0
- package/core/config.js +245 -0
- package/core/context.js +329 -0
- package/core/entitlements.js +209 -0
- package/core/index.js +43 -0
- package/core/policies.js +68 -0
- package/core/telemetry.js +247 -0
- package/core/utils.js +380 -0
- package/dashboard/server.js +818 -0
- package/docs/integrations/claude-code.md +42 -0
- package/docs/integrations/codex.md +42 -0
- package/docs/mcp-api-platform.md +102 -0
- package/generators/generate.js +598 -0
- package/generators/index.js +18 -0
- package/hooks/context-detector.js +177 -0
- package/hooks/index.js +35 -0
- package/hooks/prompt-enhancer.js +289 -0
- package/intelligence/git-memory.js +551 -0
- package/intelligence/index.js +59 -0
- package/intelligence/orchestrator.js +964 -0
- package/intelligence/prd.js +447 -0
- package/intelligence/recommendation-weights.json +18 -0
- package/intelligence/recommendations.js +234 -0
- package/mcp/capabilities.js +71 -0
- package/mcp/contracts/mcp-contract.v1.json +497 -0
- package/mcp/registry.js +213 -0
- package/mcp/response-formatter.js +462 -0
- package/mcp/server.js +99 -0
- package/mcp/tools/agent-tool.js +137 -0
- package/mcp/tools/capabilities-tool.js +54 -0
- package/mcp/tools/context-tool.js +49 -0
- package/mcp/tools/dashboard-tool.js +58 -0
- package/mcp/tools/generate-tool.js +46 -0
- package/mcp/tools/loop-tool.js +134 -0
- package/mcp/tools/memory-tool.js +180 -0
- package/mcp/tools/orchestrator-tool.js +232 -0
- package/mcp/tools/plugin-tool.js +76 -0
- package/mcp/tools/quality-tool.js +47 -0
- package/mcp/tools/skill-tool.js +233 -0
- package/mcp/tools/telemetry-tool.js +95 -0
- package/mcp/tools/todo-tool.js +133 -0
- package/package.json +98 -0
- package/plugins/index.js +141 -0
- package/quality/index.js +380 -0
- package/quality/lint-budgets.json +19 -0
- package/skills/index.js +787 -0
- package/skills/patterns/README.md +163 -0
- package/skills/patterns/api/route-handler.md +217 -0
- package/skills/patterns/api/server-action.md +249 -0
- package/skills/patterns/auth/clerk.md +132 -0
- package/skills/patterns/database/prisma.md +180 -0
- package/skills/patterns/payments/stripe.md +272 -0
- package/skills/patterns/security/validation.md +268 -0
- package/skills/patterns/testing/vitest.md +307 -0
- package/templates/bootspring.config.js +83 -0
- package/templates/mcp.json +9 -0
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
# Vercel Expert Agent
|
|
2
|
+
|
|
3
|
+
## Role
|
|
4
|
+
Specialized in Vercel platform deployment, Edge Functions, serverless configuration, environment management, and optimizing Next.js apps for Vercel hosting.
|
|
5
|
+
|
|
6
|
+
## Core Expertise
|
|
7
|
+
|
|
8
|
+
### Vercel Project Configuration
|
|
9
|
+
|
|
10
|
+
```json
|
|
11
|
+
// vercel.json
|
|
12
|
+
{
|
|
13
|
+
"framework": "nextjs",
|
|
14
|
+
"buildCommand": "npm run build",
|
|
15
|
+
"devCommand": "npm run dev",
|
|
16
|
+
"installCommand": "npm install",
|
|
17
|
+
"regions": ["iad1"],
|
|
18
|
+
"functions": {
|
|
19
|
+
"app/api/**/*.ts": {
|
|
20
|
+
"maxDuration": 30
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
"headers": [
|
|
24
|
+
{
|
|
25
|
+
"source": "/api/(.*)",
|
|
26
|
+
"headers": [
|
|
27
|
+
{ "key": "Access-Control-Allow-Origin", "value": "*" }
|
|
28
|
+
]
|
|
29
|
+
}
|
|
30
|
+
],
|
|
31
|
+
"redirects": [
|
|
32
|
+
{
|
|
33
|
+
"source": "/old-path",
|
|
34
|
+
"destination": "/new-path",
|
|
35
|
+
"permanent": true
|
|
36
|
+
}
|
|
37
|
+
],
|
|
38
|
+
"rewrites": [
|
|
39
|
+
{
|
|
40
|
+
"source": "/blog/:slug",
|
|
41
|
+
"destination": "/posts/:slug"
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Edge Functions
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
// app/api/edge/route.ts
|
|
51
|
+
export const runtime = 'edge';
|
|
52
|
+
|
|
53
|
+
export async function GET(request: Request) {
|
|
54
|
+
const { searchParams } = new URL(request.url);
|
|
55
|
+
const country = request.headers.get('x-vercel-ip-country') ?? 'US';
|
|
56
|
+
|
|
57
|
+
// Edge-compatible code only
|
|
58
|
+
// No Node.js APIs (fs, path, etc.)
|
|
59
|
+
// No Prisma (use Prisma Accelerate or Data Proxy)
|
|
60
|
+
|
|
61
|
+
return new Response(JSON.stringify({
|
|
62
|
+
message: 'Hello from the edge!',
|
|
63
|
+
country,
|
|
64
|
+
}), {
|
|
65
|
+
headers: { 'Content-Type': 'application/json' },
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Middleware (runs on Edge by default)
|
|
70
|
+
// middleware.ts
|
|
71
|
+
import { NextResponse } from 'next/server';
|
|
72
|
+
import type { NextRequest } from 'next/server';
|
|
73
|
+
|
|
74
|
+
export function middleware(request: NextRequest) {
|
|
75
|
+
const country = request.geo?.country ?? 'US';
|
|
76
|
+
const response = NextResponse.next();
|
|
77
|
+
|
|
78
|
+
// Add custom headers
|
|
79
|
+
response.headers.set('x-country', country);
|
|
80
|
+
|
|
81
|
+
// Geo-based redirect
|
|
82
|
+
if (country === 'DE' && !request.nextUrl.pathname.startsWith('/de')) {
|
|
83
|
+
return NextResponse.redirect(new URL('/de' + request.nextUrl.pathname, request.url));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return response;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const config = {
|
|
90
|
+
matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
|
|
91
|
+
};
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### Environment Variables
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# Vercel Environment Variables UI or CLI
|
|
98
|
+
|
|
99
|
+
# Production only
|
|
100
|
+
vercel env add STRIPE_SECRET_KEY production
|
|
101
|
+
|
|
102
|
+
# Preview and Production
|
|
103
|
+
vercel env add DATABASE_URL preview production
|
|
104
|
+
|
|
105
|
+
# All environments
|
|
106
|
+
vercel env add NEXT_PUBLIC_APP_URL
|
|
107
|
+
|
|
108
|
+
# Pull to local
|
|
109
|
+
vercel env pull .env.local
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
```typescript
|
|
113
|
+
// Validate at runtime
|
|
114
|
+
// lib/env.ts
|
|
115
|
+
import { z } from 'zod';
|
|
116
|
+
|
|
117
|
+
const envSchema = z.object({
|
|
118
|
+
// Server-only (no NEXT_PUBLIC_ prefix)
|
|
119
|
+
DATABASE_URL: z.string().url(),
|
|
120
|
+
CLERK_SECRET_KEY: z.string().min(1),
|
|
121
|
+
STRIPE_SECRET_KEY: z.string().min(1),
|
|
122
|
+
|
|
123
|
+
// Public (available in browser)
|
|
124
|
+
NEXT_PUBLIC_APP_URL: z.string().url(),
|
|
125
|
+
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1),
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
export const env = envSchema.parse({
|
|
129
|
+
DATABASE_URL: process.env.DATABASE_URL,
|
|
130
|
+
CLERK_SECRET_KEY: process.env.CLERK_SECRET_KEY,
|
|
131
|
+
STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY,
|
|
132
|
+
NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
|
|
133
|
+
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Serverless Function Configuration
|
|
138
|
+
|
|
139
|
+
```typescript
|
|
140
|
+
// app/api/long-running/route.ts
|
|
141
|
+
|
|
142
|
+
// Increase timeout (Pro plan: up to 300s)
|
|
143
|
+
export const maxDuration = 60; // seconds
|
|
144
|
+
|
|
145
|
+
// Force dynamic rendering
|
|
146
|
+
export const dynamic = 'force-dynamic';
|
|
147
|
+
|
|
148
|
+
// Specify region
|
|
149
|
+
export const preferredRegion = 'iad1';
|
|
150
|
+
|
|
151
|
+
// Or edge runtime
|
|
152
|
+
// export const runtime = 'edge';
|
|
153
|
+
|
|
154
|
+
export async function POST(request: Request) {
|
|
155
|
+
// Long-running operation
|
|
156
|
+
const result = await processLargeDataset();
|
|
157
|
+
return Response.json(result);
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Caching & ISR
|
|
162
|
+
|
|
163
|
+
```typescript
|
|
164
|
+
// app/posts/page.tsx
|
|
165
|
+
|
|
166
|
+
// Revalidate every hour
|
|
167
|
+
export const revalidate = 3600;
|
|
168
|
+
|
|
169
|
+
// Or use fetch with revalidation
|
|
170
|
+
async function getPosts() {
|
|
171
|
+
const res = await fetch('https://api.example.com/posts', {
|
|
172
|
+
next: { revalidate: 3600 }
|
|
173
|
+
});
|
|
174
|
+
return res.json();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// On-demand revalidation
|
|
178
|
+
// app/api/revalidate/route.ts
|
|
179
|
+
import { revalidatePath, revalidateTag } from 'next/cache';
|
|
180
|
+
import { NextRequest, NextResponse } from 'next/server';
|
|
181
|
+
|
|
182
|
+
export async function POST(request: NextRequest) {
|
|
183
|
+
const secret = request.headers.get('x-revalidate-secret');
|
|
184
|
+
|
|
185
|
+
if (secret !== process.env.REVALIDATE_SECRET) {
|
|
186
|
+
return NextResponse.json({ error: 'Invalid secret' }, { status: 401 });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const { path, tag } = await request.json();
|
|
190
|
+
|
|
191
|
+
if (path) {
|
|
192
|
+
revalidatePath(path);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
if (tag) {
|
|
196
|
+
revalidateTag(tag);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
return NextResponse.json({ revalidated: true, now: Date.now() });
|
|
200
|
+
}
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Vercel Blob Storage
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// app/api/upload/route.ts
|
|
207
|
+
import { put, del, list } from '@vercel/blob';
|
|
208
|
+
import { NextResponse } from 'next/server';
|
|
209
|
+
|
|
210
|
+
export async function POST(request: Request) {
|
|
211
|
+
const formData = await request.formData();
|
|
212
|
+
const file = formData.get('file') as File;
|
|
213
|
+
|
|
214
|
+
if (!file) {
|
|
215
|
+
return NextResponse.json({ error: 'No file provided' }, { status: 400 });
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Upload to Vercel Blob
|
|
219
|
+
const blob = await put(file.name, file, {
|
|
220
|
+
access: 'public',
|
|
221
|
+
addRandomSuffix: true, // Prevent overwrites
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
return NextResponse.json(blob);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export async function DELETE(request: Request) {
|
|
228
|
+
const { url } = await request.json();
|
|
229
|
+
|
|
230
|
+
await del(url);
|
|
231
|
+
|
|
232
|
+
return NextResponse.json({ deleted: true });
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// List blobs
|
|
236
|
+
export async function GET() {
|
|
237
|
+
const { blobs } = await list();
|
|
238
|
+
return NextResponse.json(blobs);
|
|
239
|
+
}
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Vercel KV (Redis)
|
|
243
|
+
|
|
244
|
+
```typescript
|
|
245
|
+
// lib/kv.ts
|
|
246
|
+
import { kv } from '@vercel/kv';
|
|
247
|
+
|
|
248
|
+
// Cache user sessions
|
|
249
|
+
export async function cacheSession(sessionId: string, data: SessionData) {
|
|
250
|
+
await kv.set(`session:${sessionId}`, JSON.stringify(data), {
|
|
251
|
+
ex: 60 * 60 * 24, // 24 hours
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
export async function getSession(sessionId: string) {
|
|
256
|
+
const data = await kv.get(`session:${sessionId}`);
|
|
257
|
+
return data ? JSON.parse(data as string) : null;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// Rate limiting
|
|
261
|
+
export async function checkRateLimit(ip: string, limit = 10, window = 60) {
|
|
262
|
+
const key = `ratelimit:${ip}`;
|
|
263
|
+
const count = await kv.incr(key);
|
|
264
|
+
|
|
265
|
+
if (count === 1) {
|
|
266
|
+
await kv.expire(key, window);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
allowed: count <= limit,
|
|
271
|
+
remaining: Math.max(0, limit - count),
|
|
272
|
+
reset: await kv.ttl(key),
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
### Vercel Postgres
|
|
278
|
+
|
|
279
|
+
```typescript
|
|
280
|
+
// lib/db.ts
|
|
281
|
+
import { sql } from '@vercel/postgres';
|
|
282
|
+
|
|
283
|
+
// Direct SQL queries
|
|
284
|
+
export async function getUsers() {
|
|
285
|
+
const { rows } = await sql`SELECT * FROM users LIMIT 10`;
|
|
286
|
+
return rows;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// With parameters (safe from SQL injection)
|
|
290
|
+
export async function getUserByEmail(email: string) {
|
|
291
|
+
const { rows } = await sql`SELECT * FROM users WHERE email = ${email}`;
|
|
292
|
+
return rows[0];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Or use with Prisma
|
|
296
|
+
// schema.prisma
|
|
297
|
+
datasource db {
|
|
298
|
+
provider = "postgresql"
|
|
299
|
+
url = env("POSTGRES_PRISMA_URL")
|
|
300
|
+
directUrl = env("POSTGRES_URL_NON_POOLING")
|
|
301
|
+
}
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
### Preview Deployments
|
|
305
|
+
|
|
306
|
+
```typescript
|
|
307
|
+
// Detect preview environment
|
|
308
|
+
const isPreview = process.env.VERCEL_ENV === 'preview';
|
|
309
|
+
const isProduction = process.env.VERCEL_ENV === 'production';
|
|
310
|
+
|
|
311
|
+
// Preview-specific behavior
|
|
312
|
+
if (isPreview) {
|
|
313
|
+
// Use test Stripe keys
|
|
314
|
+
// Show debug info
|
|
315
|
+
// Use staging database
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// Get deployment URL
|
|
319
|
+
const deploymentUrl = process.env.VERCEL_URL
|
|
320
|
+
? `https://${process.env.VERCEL_URL}`
|
|
321
|
+
: 'http://localhost:3000';
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### Cron Jobs
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
// vercel.json
|
|
328
|
+
{
|
|
329
|
+
"crons": [
|
|
330
|
+
{
|
|
331
|
+
"path": "/api/cron/cleanup",
|
|
332
|
+
"schedule": "0 0 * * *"
|
|
333
|
+
},
|
|
334
|
+
{
|
|
335
|
+
"path": "/api/cron/sync",
|
|
336
|
+
"schedule": "*/15 * * * *"
|
|
337
|
+
}
|
|
338
|
+
]
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// app/api/cron/cleanup/route.ts
|
|
342
|
+
export async function GET(request: Request) {
|
|
343
|
+
// Verify cron secret
|
|
344
|
+
const authHeader = request.headers.get('authorization');
|
|
345
|
+
if (authHeader !== `Bearer ${process.env.CRON_SECRET}`) {
|
|
346
|
+
return new Response('Unauthorized', { status: 401 });
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Run cleanup
|
|
350
|
+
await cleanupOldRecords();
|
|
351
|
+
|
|
352
|
+
return Response.json({ success: true });
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Analytics & Web Vitals
|
|
357
|
+
|
|
358
|
+
```typescript
|
|
359
|
+
// app/components/Analytics.tsx
|
|
360
|
+
import { Analytics } from '@vercel/analytics/react';
|
|
361
|
+
import { SpeedInsights } from '@vercel/speed-insights/next';
|
|
362
|
+
|
|
363
|
+
export function VercelIntegrations() {
|
|
364
|
+
return (
|
|
365
|
+
<>
|
|
366
|
+
<Analytics />
|
|
367
|
+
<SpeedInsights />
|
|
368
|
+
</>
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// app/layout.tsx
|
|
373
|
+
import { VercelIntegrations } from './components/Analytics';
|
|
374
|
+
|
|
375
|
+
export default function RootLayout({ children }) {
|
|
376
|
+
return (
|
|
377
|
+
<html>
|
|
378
|
+
<body>
|
|
379
|
+
{children}
|
|
380
|
+
<VercelIntegrations />
|
|
381
|
+
</body>
|
|
382
|
+
</html>
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
### Deployment Commands
|
|
388
|
+
|
|
389
|
+
```bash
|
|
390
|
+
# Deploy to preview
|
|
391
|
+
vercel
|
|
392
|
+
|
|
393
|
+
# Deploy to production
|
|
394
|
+
vercel --prod
|
|
395
|
+
|
|
396
|
+
# Link to existing project
|
|
397
|
+
vercel link
|
|
398
|
+
|
|
399
|
+
# Check deployment status
|
|
400
|
+
vercel ls
|
|
401
|
+
|
|
402
|
+
# View logs
|
|
403
|
+
vercel logs <deployment-url>
|
|
404
|
+
|
|
405
|
+
# Promote preview to production
|
|
406
|
+
vercel promote <deployment-url>
|
|
407
|
+
|
|
408
|
+
# Rollback
|
|
409
|
+
vercel rollback
|
|
410
|
+
```
|
|
411
|
+
|
|
412
|
+
## Vercel Checklist
|
|
413
|
+
|
|
414
|
+
- [ ] vercel.json configured correctly
|
|
415
|
+
- [ ] Environment variables set for all environments
|
|
416
|
+
- [ ] Functions have appropriate timeouts
|
|
417
|
+
- [ ] Edge functions for latency-sensitive routes
|
|
418
|
+
- [ ] Caching strategy defined
|
|
419
|
+
- [ ] Preview deployments tested
|
|
420
|
+
- [ ] Production domain configured
|
|
421
|
+
- [ ] Analytics enabled
|
|
422
|
+
- [ ] Error tracking configured
|
|
423
|
+
- [ ] Cron jobs scheduled if needed
|
|
424
|
+
|
|
425
|
+
## Trigger Keywords
|
|
426
|
+
vercel, deploy, deployment, edge, serverless, preview, production, env, environment, blob, kv, postgres, cron, analytics, domain, region, function, timeout, isr, revalidate
|