← back to ClawCoder
initial snapshot — gitify all builds (CLAUDE.md rule 2026-05-06)
218fc5f8dca0fdcb78ad221f7ae9b90e3ece4d8a · 2026-05-06 10:20:41 -0700 · Steve
Files touched
A .gitignoreA .mcp.jsonA AGENTS.mdA CLAWCODER-CLAUDE.mdA docs/superpowers/plans/2026-03-23-clawcoder-implementation.mdA docs/superpowers/specs/2026-03-23-clawcoder-profiles-analyzer-design.mdA ecosystem.config.cjsA eslint.config.mjsA next.config.tsA package-lock.jsonA package.jsonA postcss.config.mjsA prisma.config.tsA prisma/migrations/20260323005809_init/migration.sqlA prisma/migrations/migration_lock.tomlA prisma/schema.prismaA public/favicon.svgA public/file.svgA public/globe.svgA public/next.svgA public/vercel.svgA public/window.svgA src/app/analyzer/page.tsxA src/app/api/analyze/route.tsA src/app/api/auth/me/route.tsA src/app/api/auth/route.tsA src/app/api/files/[id]/route.tsA src/app/api/files/route.tsA src/app/api/generate/route.tsA src/app/api/marketplace/route.tsA src/app/api/profiles/route.tsA src/app/api/suggestions/route.tsA src/app/builder/page.tsxA src/app/favicon.icoA src/app/globals.cssA src/app/layout.tsxA src/app/login/page.tsxA src/app/marketplace/page.tsxA src/app/mass-update/page.tsxA src/app/my-files/page.tsxA src/app/onboarding/page.tsxA src/app/page.tsxA src/components/nav-wrapper.tsxA src/components/nav.tsxA src/components/onboarding/question-screen.tsxA src/components/onboarding/result-screen.tsxA src/components/onboarding/review-screen.tsxA src/components/ui/back-next-bar.tsxA src/components/ui/diff-viewer.tsxA src/components/ui/grade-badge.tsxA src/components/ui/pill-button.tsxA src/components/ui/progress-indicator.tsxA src/components/ui/radar-chart.tsxA src/components/user-badge.tsxA src/lib/analyzer/applier.tsA src/lib/analyzer/index.tsA src/lib/analyzer/matcher.tsA src/lib/analyzer/rules.tsA src/lib/analyzer/scorer.tsA src/lib/analyzer/suggester.tsA src/lib/analyzer/types.tsA src/lib/auth.tsA src/lib/db.tsA src/lib/generator/bundle.tsA src/lib/generator/claude-md.tsA src/lib/generator/hooks.tsA src/lib/generator/index.tsA src/lib/generator/rules.tsA src/lib/marketplace/data.tsA src/lib/onboarding/questions.tsA src/lib/onboarding/resolver.tsA src/lib/profiles/data.tsA src/lib/profiles/index.tsA src/lib/profiles/templates.tsA src/lib/profiles/types.tsA src/lib/store.tsA src/middleware.tsA src/tests/lib/generator.test.tsA src/tests/lib/profiles.test.tsA src/tests/lib/scorer.test.tsA src/tests/lib/suggester.test.tsA src/tests/setup.tsA tsconfig.jsonA vitest.config.ts
Diff
commit 218fc5f8dca0fdcb78ad221f7ae9b90e3ece4d8a
Author: Steve <steve@designerwallcoverings.com>
Date: Wed May 6 10:20:41 2026 -0700
initial snapshot — gitify all builds (CLAUDE.md rule 2026-05-06)
---
.gitignore | 41 +
.mcp.json | 12 +
AGENTS.md | 5 +
CLAWCODER-CLAUDE.md | 1 +
.../plans/2026-03-23-clawcoder-implementation.md | 1609 ++++
...026-03-23-clawcoder-profiles-analyzer-design.md | 1377 +++
ecosystem.config.cjs | 12 +
eslint.config.mjs | 18 +
next.config.ts | 7 +
package-lock.json | 9393 ++++++++++++++++++++
package.json | 43 +
postcss.config.mjs | 7 +
prisma.config.ts | 14 +
.../migrations/20260323005809_init/migration.sql | 59 +
prisma/migrations/migration_lock.toml | 3 +
prisma/schema.prisma | 54 +
public/favicon.svg | 4 +
public/file.svg | 1 +
public/globe.svg | 1 +
public/next.svg | 1 +
public/vercel.svg | 1 +
public/window.svg | 1 +
src/app/analyzer/page.tsx | 565 ++
src/app/api/analyze/route.ts | 69 +
src/app/api/auth/me/route.ts | 16 +
src/app/api/auth/route.ts | 35 +
src/app/api/files/[id]/route.ts | 81 +
src/app/api/files/route.ts | 98 +
src/app/api/generate/route.ts | 23 +
src/app/api/marketplace/route.ts | 39 +
src/app/api/profiles/route.ts | 33 +
src/app/api/suggestions/route.ts | 27 +
src/app/builder/page.tsx | 584 ++
src/app/favicon.ico | Bin 0 -> 25931 bytes
src/app/globals.css | 7 +
src/app/layout.tsx | 30 +
src/app/login/page.tsx | 144 +
src/app/marketplace/page.tsx | 394 +
src/app/mass-update/page.tsx | 448 +
src/app/my-files/page.tsx | 520 ++
src/app/onboarding/page.tsx | 219 +
src/app/page.tsx | 212 +
src/components/nav-wrapper.tsx | 7 +
src/components/nav.tsx | 117 +
src/components/onboarding/question-screen.tsx | 186 +
src/components/onboarding/result-screen.tsx | 258 +
src/components/onboarding/review-screen.tsx | 103 +
src/components/ui/back-next-bar.tsx | 46 +
src/components/ui/diff-viewer.tsx | 29 +
src/components/ui/grade-badge.tsx | 36 +
src/components/ui/pill-button.tsx | 40 +
src/components/ui/progress-indicator.tsx | 29 +
src/components/ui/radar-chart.tsx | 137 +
src/components/user-badge.tsx | 47 +
src/lib/analyzer/applier.ts | 39 +
src/lib/analyzer/index.ts | 6 +
src/lib/analyzer/matcher.ts | 60 +
src/lib/analyzer/rules.ts | 517 ++
src/lib/analyzer/scorer.ts | 68 +
src/lib/analyzer/suggester.ts | 215 +
src/lib/analyzer/types.ts | 36 +
src/lib/auth.ts | 67 +
src/lib/db.ts | 13 +
src/lib/generator/bundle.ts | 51 +
src/lib/generator/claude-md.ts | 21 +
src/lib/generator/hooks.ts | 57 +
src/lib/generator/index.ts | 4 +
src/lib/generator/rules.ts | 18 +
src/lib/marketplace/data.ts | 843 ++
src/lib/onboarding/questions.ts | 222 +
src/lib/onboarding/resolver.ts | 87 +
src/lib/profiles/data.ts | 2035 +++++
src/lib/profiles/index.ts | 3 +
src/lib/profiles/templates.ts | 8 +
src/lib/profiles/types.ts | 79 +
src/lib/store.ts | 125 +
src/middleware.ts | 53 +
src/tests/lib/generator.test.ts | 167 +
src/tests/lib/profiles.test.ts | 140 +
src/tests/lib/scorer.test.ts | 79 +
src/tests/lib/suggester.test.ts | 47 +
src/tests/setup.ts | 1 +
tsconfig.json | 34 +
vitest.config.ts | 15 +
84 files changed, 22353 insertions(+)
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..78fea9a
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,41 @@
+# dependencies
+/node_modules
+/.pnp
+.pnp.*
+.yarn/*
+!.yarn/patches
+!.yarn/plugins
+!.yarn/releases
+!.yarn/versions
+
+# testing
+/coverage
+
+# next.js
+/.next/
+/out/
+
+# production
+/build
+
+# misc
+*.pem
+
+# debug
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.pnpm-debug.log*
+
+# env files
+.env
+.env*.local
+
+# vercel
+.vercel
+
+# typescript
+*.tsbuildinfo
+next-env.d.ts
+
+/src/generated/prisma
diff --git a/.mcp.json b/.mcp.json
new file mode 100644
index 0000000..fa2eb13
--- /dev/null
+++ b/.mcp.json
@@ -0,0 +1,12 @@
+{
+ "mcpServers": {
+ "context7": {
+ "command": "npx",
+ "args": ["-y", "@upstash/context7-mcp"]
+ },
+ "postgres": {
+ "command": "npx",
+ "args": ["-y", "@modelcontextprotocol/server-postgres", "postgresql://dw_admin:DW2024SecurePass@127.0.0.1:5432/clawcoder"]
+ }
+ }
+}
diff --git a/AGENTS.md b/AGENTS.md
new file mode 100644
index 0000000..8bd0e39
--- /dev/null
+++ b/AGENTS.md
@@ -0,0 +1,5 @@
+<!-- BEGIN:nextjs-agent-rules -->
+# This is NOT the Next.js you know
+
+This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` before writing any code. Heed deprecation notices.
+<!-- END:nextjs-agent-rules -->
diff --git a/CLAWCODER-CLAUDE.md b/CLAWCODER-CLAUDE.md
new file mode 100644
index 0000000..43c994c
--- /dev/null
+++ b/CLAWCODER-CLAUDE.md
@@ -0,0 +1 @@
+@AGENTS.md
diff --git a/docs/superpowers/plans/2026-03-23-clawcoder-implementation.md b/docs/superpowers/plans/2026-03-23-clawcoder-implementation.md
new file mode 100644
index 0000000..de9a5de
--- /dev/null
+++ b/docs/superpowers/plans/2026-03-23-clawcoder-implementation.md
@@ -0,0 +1,1609 @@
+# ClawCoder Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** Build a Next.js web app that generates, analyzes, and optimizes CLAUDE.md configurations using 15 age+skill profiles and a 12-criteria scoring engine with mass-update support.
+
+**Architecture:** Next.js 15 App Router with server actions for API, PostgreSQL for persistence, Zustand for client state. Profile data lives as typed JSON seed files. The analyzer runs server-side with Claude API structured outputs for intelligent suggestions. The UI uses Tailwind CSS + Framer Motion with accessibility-first defaults.
+
+**Tech Stack:** Next.js 15, TypeScript, Tailwind CSS, Framer Motion, PostgreSQL, Zustand, Claude API (@anthropic-ai/sdk), NextAuth.js
+
+**Spec:** `docs/superpowers/specs/2026-03-23-clawcoder-profiles-analyzer-design.md`
+
+---
+
+## File Structure
+
+```
+/root/Projects/ClawCoder/
+├── package.json
+├── tsconfig.json
+├── tailwind.config.ts
+├── next.config.ts
+├── .env.local # DB URL, API keys (gitignored)
+├── prisma/
+│ ├── schema.prisma # DB schema (profiles, analyses, users)
+│ └── seed.ts # Seed 15 profiles into DB
+├── src/
+│ ├── app/
+│ │ ├── layout.tsx # Root layout + theme provider
+│ │ ├── page.tsx # Landing / profile selector
+│ │ ├── onboarding/
+│ │ │ └── page.tsx # Full-screen wizard
+│ │ ├── builder/
+│ │ │ └── page.tsx # Builder with rail + preview
+│ │ ├── analyzer/
+│ │ │ └── page.tsx # Upload + analyze + suggestions
+│ │ ├── mass-update/
+│ │ │ └── page.tsx # Table view + bulk actions
+│ │ └── api/
+│ │ ├── profiles/route.ts # GET profiles, GET profile by slug
+│ │ ├── analyze/route.ts # POST analyze CLAUDE.md content
+│ │ ├── generate/route.ts # POST generate from profile + answers
+│ │ └── suggestions/route.ts # POST apply selected suggestions
+│ ├── lib/
+│ │ ├── profiles/
+│ │ │ ├── types.ts # Profile TypeScript types
+│ │ │ ├── data.ts # 15 profile definitions (source of truth)
+│ │ │ ├── matcher.ts # Detect profile from existing CLAUDE.md
+│ │ │ └── templates.ts # Mustache templates per profile
+│ │ ├── analyzer/
+│ │ │ ├── types.ts # Analysis types (criteria, scores, suggestions)
+│ │ │ ├── scorer.ts # 12-criteria scoring engine
+│ │ │ ├── rules.ts # Individual scoring rule implementations
+│ │ │ ├── suggester.ts # Generate actionable suggestions
+│ │ │ └── applier.ts # Apply suggestions to content
+│ │ ├── generator/
+│ │ │ ├── claude-md.ts # Generate CLAUDE.md from profile + vars
+│ │ │ ├── rules.ts # Generate .claude/rules/*.md files
+│ │ │ ├── hooks.ts # Generate .claude/settings.json hooks
+│ │ │ └── bundle.ts # Bundle all artifacts into ZIP
+│ │ ├── db.ts # Prisma client singleton
+│ │ └── store.ts # Zustand store (client state)
+│ ├── components/
+│ │ ├── ui/
+│ │ │ ├── pill-button.tsx # Large pill-shaped answer button
+│ │ │ ├── back-next-bar.tsx # XL sticky nav bar
+│ │ │ ├── progress-indicator.tsx # Step N of M
+│ │ │ ├── radar-chart.tsx # 12-axis scoring visualization
+│ │ │ ├── diff-viewer.tsx # Side-by-side diff
+│ │ │ └── grade-badge.tsx # Letter grade (A+ through F)
+│ │ ├── onboarding/
+│ │ │ ├── question-screen.tsx # Full-screen question layout
+│ │ │ └── pill-grid.tsx # Grid of pill answer options
+│ │ ├── builder/
+│ │ │ ├── rail.tsx # Left collapsible rail
+│ │ │ ├── preview-pane.tsx # Live CLAUDE.md preview
+│ │ │ └── section-editor.tsx # Edit individual sections
+│ │ ├── analyzer/
+│ │ │ ├── upload-zone.tsx # Drag-drop + paste area
+│ │ │ ├── report-card.tsx # Pros/cons/suggestions display
+│ │ │ ├── suggestion-card.tsx # Single suggestion with checkbox
+│ │ │ └── suggestion-list.tsx # Selectable list of suggestions
+│ │ └── mass-update/
+│ │ ├── record-table.tsx # Table with multi-select
+│ │ └── bulk-actions-bar.tsx # Floating bulk actions
+│ └── tests/
+│ ├── lib/
+│ │ ├── scorer.test.ts # Scoring engine tests
+│ │ ├── rules.test.ts # Individual rule tests
+│ │ ├── matcher.test.ts # Profile matcher tests
+│ │ ├── applier.test.ts # Suggestion applier tests
+│ │ └── generator.test.ts # CLAUDE.md generator tests
+│ └── components/
+│ ├── pill-button.test.tsx # UI component tests
+│ └── upload-zone.test.tsx # Upload component tests
+```
+
+---
+
+## Phase 1: Foundation — Profile Data + Template Engine
+
+### Task 1: Initialize Next.js Project
+
+**Files:**
+- Create: `package.json`, `tsconfig.json`, `tailwind.config.ts`, `next.config.ts`, `.env.local`, `.gitignore`
+
+- [ ] **Step 1: Scaffold Next.js with TypeScript + Tailwind**
+
+```bash
+cd /root/Projects/ClawCoder
+npx create-next-app@latest . --typescript --tailwind --eslint --app --src-dir --no-import-alias
+```
+
+- [ ] **Step 2: Install dependencies**
+
+```bash
+npm install @anthropic-ai/sdk zustand framer-motion prisma @prisma/client mustache
+npm install -D @types/mustache vitest @testing-library/react @testing-library/jest-dom jsdom
+```
+
+- [ ] **Step 3: Configure .env.local**
+
+```bash
+cat > .env.local << 'EOF'
+DATABASE_URL="postgresql://dw_admin:DW2024SecurePass@127.0.0.1:5432/clawcoder"
+ANTHROPIC_API_KEY="placeholder"
+NEXTAUTH_SECRET="clawcoder-dev-secret-change-in-prod"
+EOF
+```
+
+- [ ] **Step 4: Configure vitest**
+
+Create `vitest.config.ts`:
+```typescript
+import { defineConfig } from 'vitest/config'
+import path from 'path'
+
+export default defineConfig({
+ test: {
+ environment: 'jsdom',
+ globals: true,
+ setupFiles: ['./src/tests/setup.ts'],
+ },
+ resolve: {
+ alias: {
+ '@': path.resolve(__dirname, './src'),
+ },
+ },
+})
+```
+
+Create `src/tests/setup.ts`:
+```typescript
+import '@testing-library/jest-dom'
+```
+
+- [ ] **Step 5: Add test script to package.json**
+
+Add to scripts: `"test": "vitest run", "test:watch": "vitest"`
+
+- [ ] **Step 6: Verify setup builds**
+
+```bash
+npm run build
+```
+Expected: Build succeeds
+
+- [ ] **Step 7: Commit**
+
+```bash
+git init && git add -A && git commit -m "feat: initialize Next.js project with TypeScript, Tailwind, Prisma, Vitest"
+```
+
+---
+
+### Task 2: Define Profile Types + Data
+
+**Files:**
+- Create: `src/lib/profiles/types.ts`
+- Create: `src/lib/profiles/data.ts`
+- Test: `src/tests/lib/profiles.test.ts`
+
+- [ ] **Step 1: Write the failing test**
+
+Create `src/tests/lib/profiles.test.ts`:
+```typescript
+import { describe, it, expect } from 'vitest'
+import { PROFILES, getProfile, getProfilesByAge, getProfilesBySkill } from '@/lib/profiles/data'
+import type { Profile, AgeGroup, SkillLevel } from '@/lib/profiles/types'
+
+describe('Profile Data', () => {
+ it('has exactly 15 profiles', () => {
+ expect(PROFILES).toHaveLength(15)
+ })
+
+ it('covers all 5 age groups x 3 skill levels', () => {
+ const ageGroups: AgeGroup[] = ['teen', 'young_adult', 'professional', 'pre_senior', 'senior']
+ const skillLevels: SkillLevel[] = ['beginner', 'intermediate', 'expert']
+ for (const age of ageGroups) {
+ for (const skill of skillLevels) {
+ const match = PROFILES.find(p => p.ageGroup === age && p.skillLevel === skill)
+ expect(match, `Missing profile for ${age}/${skill}`).toBeDefined()
+ }
+ }
+ })
+
+ it('each profile has required fields', () => {
+ for (const p of PROFILES) {
+ expect(p.slug).toBeTruthy()
+ expect(p.displayName).toBeTruthy()
+ expect(p.ui.fontSize).toBeGreaterThanOrEqual(14)
+ expect(p.ui.touchTargetMin).toBeGreaterThanOrEqual(40)
+ expect(p.template.slug).toBeTruthy()
+ expect(p.template.content).toContain('{{project_name}}')
+ expect(p.skillBundle).toBeInstanceOf(Array)
+ expect(p.mcpServers).toBeInstanceOf(Array)
+ expect(p.learningPath).toBeInstanceOf(Array)
+ expect(p.learningPath.length).toBeGreaterThan(0)
+ }
+ })
+
+ it('senior profiles have larger fonts than teen profiles', () => {
+ const seniorBeginner = getProfile('senior-beginner')!
+ const teenBeginner = getProfile('teen-beginner')!
+ expect(seniorBeginner.ui.fontSize).toBeGreaterThan(teenBeginner.ui.fontSize)
+ })
+
+ it('beginner profiles have fewer MCP servers than expert', () => {
+ const teenBeg = getProfile('teen-beginner')!
+ const teenExp = getProfile('teen-expert')!
+ expect(teenBeg.mcpServers.length).toBeLessThan(teenExp.mcpServers.length)
+ })
+
+ it('getProfilesByAge returns 3 profiles per age group', () => {
+ expect(getProfilesByAge('teen')).toHaveLength(3)
+ expect(getProfilesByAge('senior')).toHaveLength(3)
+ })
+
+ it('getProfilesBySkill returns 5 profiles per skill level', () => {
+ expect(getProfilesBySkill('beginner')).toHaveLength(5)
+ expect(getProfilesBySkill('expert')).toHaveLength(5)
+ })
+})
+```
+
+- [ ] **Step 2: Run test to verify it fails**
+
+```bash
+npx vitest run src/tests/lib/profiles.test.ts
+```
+Expected: FAIL — modules not found
+
+- [ ] **Step 3: Create types**
+
+Create `src/lib/profiles/types.ts`:
+```typescript
+export type AgeGroup = 'teen' | 'young_adult' | 'professional' | 'pre_senior' | 'senior'
+export type SkillLevel = 'beginner' | 'intermediate' | 'expert'
+export type MotionLevel = 'minimal' | 'reduced' | 'normal' | 'full'
+export type ContrastLevel = 'standard' | 'enhanced' | 'high'
+export type LayoutDensity = 'compact' | 'comfortable' | 'spacious'
+export type ThemeMode = 'light' | 'dark' | 'auto'
+
+export interface UIConfig {
+ fontSize: number
+ fontScale: number
+ motionLevel: MotionLevel
+ contrast: ContrastLevel
+ touchTargetMin: number
+ layoutDensity: LayoutDensity
+ theme: ThemeMode
+ reducedMotion?: boolean
+ hapticFeedback?: boolean
+}
+
+export interface TemplateConfig {
+ slug: string
+ verbosity: 'minimal' | 'low' | 'medium' | 'medium-high' | 'high' | 'maximum'
+ safety: 'standard' | 'high' | 'maximum' | 'policy-driven'
+ targetLines: [number, number]
+ content: string
+}
+
+export interface LearningResource {
+ title: string
+ url: string
+ type: 'video' | 'course' | 'article' | 'repo' | 'docs'
+ level: SkillLevel
+}
+
+export interface SkillRef {
+ name: string
+ description: string
+}
+
+export interface MCPServerRef {
+ name: string
+ package: string
+ installCmd: string
+ requiresKey: boolean
+ trustLevel: 'official' | 'verified' | 'community'
+}
+
+export interface SubagentRef {
+ name: string
+ model: 'haiku' | 'sonnet' | 'opus' | 'inherit'
+ tools: string[]
+}
+
+export interface HookConfig {
+ PreToolUse?: HookRule[]
+ PostToolUse?: HookRule[]
+ Stop?: HookRule[]
+ SessionStart?: HookRule[]
+ PreCompact?: HookRule[]
+}
+
+export interface HookRule {
+ matcher: string
+ hooks: { type: 'command'; command: string }[]
+}
+
+export interface Profile {
+ slug: string
+ ageGroup: AgeGroup
+ skillLevel: SkillLevel
+ displayName: string
+ persona: string
+ ui: UIConfig
+ template: TemplateConfig
+ skillBundle: SkillRef[]
+ mcpServers: MCPServerRef[]
+ hooks: HookConfig
+ subagents: SubagentRef[]
+ learningPath: LearningResource[]
+ rulesFiles: { path: string; content: string }[]
+}
+```
+
+- [ ] **Step 4: Create profile data (all 15 profiles)**
+
+Create `src/lib/profiles/data.ts` with all 15 profiles from the spec. Each profile is a typed `Profile` object containing the full configuration from the design spec (UI config, template content, skill bundle, MCP servers, hooks, subagents, learning path).
+
+This file will be ~800-1000 lines. It is the single source of truth for all profile data.
+
+Export functions:
+```typescript
+export const PROFILES: Profile[] = [/* all 15 */]
+export function getProfile(slug: string): Profile | undefined
+export function getProfilesByAge(age: AgeGroup): Profile[]
+export function getProfilesBySkill(skill: SkillLevel): Profile[]
+```
+
+- [ ] **Step 5: Run tests to verify they pass**
+
+```bash
+npx vitest run src/tests/lib/profiles.test.ts
+```
+Expected: ALL PASS
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add src/lib/profiles/ src/tests/lib/profiles.test.ts
+git commit -m "feat: define 15 user profiles with types and data"
+```
+
+---
+
+### Task 3: CLAUDE.md Template Engine
+
+**Files:**
+- Create: `src/lib/profiles/templates.ts`
+- Create: `src/lib/generator/claude-md.ts`
+- Create: `src/lib/generator/rules.ts`
+- Create: `src/lib/generator/hooks.ts`
+- Create: `src/lib/generator/bundle.ts`
+- Test: `src/tests/lib/generator.test.ts`
+
+- [ ] **Step 1: Write the failing test**
+
+Create `src/tests/lib/generator.test.ts`:
+```typescript
+import { describe, it, expect } from 'vitest'
+import { generateClaudeMd } from '@/lib/generator/claude-md'
+import { generateRules } from '@/lib/generator/rules'
+import { generateHooksConfig } from '@/lib/generator/hooks'
+import { getProfile } from '@/lib/profiles/data'
+
+const vars = {
+ project_name: 'My App',
+ project_description: 'A simple todo app',
+ language: 'TypeScript',
+ stack: 'Next.js + TypeScript',
+ install_cmd: 'npm install',
+ dev_cmd: 'npm run dev',
+ test_cmd: 'npm test',
+ run_cmd: 'npm start',
+ lint_cmd: 'npm run lint',
+ build_cmd: 'npm run build',
+ indent_style: '2 spaces',
+ naming_convention: 'camelCase',
+}
+
+describe('CLAUDE.md Generator', () => {
+ it('generates valid markdown from teen-beginner profile', () => {
+ const profile = getProfile('teen-beginner')!
+ const result = generateClaudeMd(profile, vars)
+ expect(result).toContain('# CLAUDE.md')
+ expect(result).toContain('My App')
+ expect(result).toContain('npm test')
+ expect(result).not.toContain('{{')
+ })
+
+ it('generates shorter output for expert profiles', () => {
+ const beginner = generateClaudeMd(getProfile('teen-beginner')!, vars)
+ const expert = generateClaudeMd(getProfile('teen-expert')!, vars)
+ expect(expert.split('\n').length).toBeLessThanOrEqual(beginner.split('\n').length)
+ })
+
+ it('generates rules files for intermediate+ profiles', () => {
+ const profile = getProfile('young-adult-intermediate')!
+ const rules = generateRules(profile, vars)
+ expect(rules.length).toBeGreaterThan(0)
+ expect(rules[0].path).toMatch(/^\.claude\/rules\//)
+ expect(rules[0].content).toBeTruthy()
+ })
+
+ it('generates hooks config for profiles with hooks', () => {
+ const profile = getProfile('teen-beginner')!
+ const hooks = generateHooksConfig(profile)
+ expect(hooks.PreToolUse).toBeDefined()
+ expect(hooks.PreToolUse!.length).toBeGreaterThan(0)
+ })
+
+ it('expert profiles generate more rules files than beginner', () => {
+ const beg = generateRules(getProfile('teen-beginner')!, vars)
+ const exp = generateRules(getProfile('professional-expert')!, vars)
+ expect(exp.length).toBeGreaterThanOrEqual(beg.length)
+ })
+})
+```
+
+- [ ] **Step 2: Run test to verify it fails**
+
+```bash
+npx vitest run src/tests/lib/generator.test.ts
+```
+Expected: FAIL — modules not found
+
+- [ ] **Step 3: Implement template rendering**
+
+Create `src/lib/profiles/templates.ts`:
+```typescript
+import Mustache from 'mustache'
+
+// Disable HTML escaping for markdown output
+Mustache.escape = (text: string) => text
+
+export function renderTemplate(template: string, vars: Record<string, string>): string {
+ return Mustache.render(template, vars)
+}
+```
+
+- [ ] **Step 4: Implement CLAUDE.md generator**
+
+Create `src/lib/generator/claude-md.ts`:
+```typescript
+import type { Profile } from '@/lib/profiles/types'
+import { renderTemplate } from '@/lib/profiles/templates'
+
+export function generateClaudeMd(
+ profile: Profile,
+ vars: Record<string, string>
+): string {
+ return renderTemplate(profile.template.content, vars)
+}
+```
+
+- [ ] **Step 5: Implement rules generator**
+
+Create `src/lib/generator/rules.ts`:
+```typescript
+import type { Profile } from '@/lib/profiles/types'
+import { renderTemplate } from '@/lib/profiles/templates'
+
+export interface GeneratedRule {
+ path: string
+ content: string
+}
+
+export function generateRules(
+ profile: Profile,
+ vars: Record<string, string>
+): GeneratedRule[] {
+ return profile.rulesFiles.map(rule => ({
+ path: rule.path,
+ content: renderTemplate(rule.content, vars),
+ }))
+}
+```
+
+- [ ] **Step 6: Implement hooks generator**
+
+Create `src/lib/generator/hooks.ts`:
+```typescript
+import type { Profile, HookConfig } from '@/lib/profiles/types'
+
+export function generateHooksConfig(profile: Profile): HookConfig {
+ return { ...profile.hooks }
+}
+
+export function generateSettingsJson(profile: Profile): string {
+ return JSON.stringify({ hooks: profile.hooks }, null, 2)
+}
+```
+
+- [ ] **Step 7: Implement bundle generator**
+
+Create `src/lib/generator/bundle.ts`:
+```typescript
+import type { Profile } from '@/lib/profiles/types'
+import { generateClaudeMd } from './claude-md'
+import { generateRules } from './rules'
+import { generateHooksConfig } from './hooks'
+
+export interface Bundle {
+ claudeMd: string
+ rules: { path: string; content: string }[]
+ settingsJson: string
+ profile: {
+ slug: string
+ skillBundle: string[]
+ mcpServers: string[]
+ subagents: string[]
+ learningPath: { title: string; url: string }[]
+ }
+}
+
+export function generateBundle(
+ profile: Profile,
+ vars: Record<string, string>
+): Bundle {
+ return {
+ claudeMd: generateClaudeMd(profile, vars),
+ rules: generateRules(profile, vars),
+ settingsJson: JSON.stringify({ hooks: generateHooksConfig(profile) }, null, 2),
+ profile: {
+ slug: profile.slug,
+ skillBundle: profile.skillBundle.map(s => s.name),
+ mcpServers: profile.mcpServers.map(m => m.name),
+ subagents: profile.subagents.map(a => a.name),
+ learningPath: profile.learningPath.map(l => ({ title: l.title, url: l.url })),
+ },
+ }
+}
+```
+
+- [ ] **Step 8: Run tests to verify they pass**
+
+```bash
+npx vitest run src/tests/lib/generator.test.ts
+```
+Expected: ALL PASS
+
+- [ ] **Step 9: Commit**
+
+```bash
+git add src/lib/generator/ src/lib/profiles/templates.ts src/tests/lib/generator.test.ts
+git commit -m "feat: template engine for CLAUDE.md, rules, hooks, and bundle generation"
+```
+
+---
+
+## Phase 2: CLAUDE.md Analyzer — Scoring Engine
+
+### Task 4: Scoring Engine — 12 Criteria
+
+**Files:**
+- Create: `src/lib/analyzer/types.ts`
+- Create: `src/lib/analyzer/rules.ts`
+- Create: `src/lib/analyzer/scorer.ts`
+- Test: `src/tests/lib/scorer.test.ts`
+
+- [ ] **Step 1: Write the failing test**
+
+Create `src/tests/lib/scorer.test.ts`:
+```typescript
+import { describe, it, expect } from 'vitest'
+import { analyzeClaudeMd } from '@/lib/analyzer/scorer'
+import type { AnalysisResult } from '@/lib/analyzer/types'
+
+const goodClaudeMd = `# CLAUDE.md
+
+## Workflow
+- Make small, reviewable changes.
+- Run tests after behavior changes.
+
+## Safety
+- Don't modify .env or credentials.
+- Don't force push.
+
+## Commands
+- Install: npm install
+- Dev: npm run dev
+- Test: npm test
+- Lint: npm run lint
+
+## Code style
+Follow .claude/rules/code-style.md
+
+## Architecture
+@docs/ARCHITECTURE.md
+`
+
+const badClaudeMd = `# My Project
+This is my project. It does stuff.
+I like coding.
+` + 'x\n'.repeat(250) // 250+ lines of junk
+
+const secretsClaudeMd = `# CLAUDE.md
+API_KEY=sk-abc123def456
+password = hunter2
+`
+
+describe('CLAUDE.md Scorer', () => {
+ it('scores a well-structured CLAUDE.md highly', () => {
+ const result = analyzeClaudeMd(goodClaudeMd)
+ expect(result.overallScore).toBeGreaterThan(60)
+ expect(result.letterGrade).toMatch(/^[ABC]/)
+ })
+
+ it('scores a bloated CLAUDE.md poorly on line count', () => {
+ const result = analyzeClaudeMd(badClaudeMd)
+ const lineCountCrit = result.criteria.find(c => c.id === 'line-count')!
+ expect(lineCountCrit.score).toBe(0)
+ })
+
+ it('detects secrets in content', () => {
+ const result = analyzeClaudeMd(secretsClaudeMd)
+ const secretsCrit = result.criteria.find(c => c.id === 'no-secrets')!
+ expect(secretsCrit.score).toBe(0)
+ expect(secretsCrit.findings.length).toBeGreaterThan(0)
+ })
+
+ it('returns pros, cons, and suggestions', () => {
+ const result = analyzeClaudeMd(goodClaudeMd)
+ expect(result.pros.length).toBeGreaterThan(0)
+ expect(result.suggestions).toBeInstanceOf(Array)
+ })
+
+ it('detects modular imports', () => {
+ const result = analyzeClaudeMd(goodClaudeMd)
+ const modularCrit = result.criteria.find(c => c.id === 'modular-imports')!
+ expect(modularCrit.score).toBeGreaterThan(0)
+ })
+
+ it('detects missing build commands', () => {
+ const minimal = '# CLAUDE.md\n\nHello world\n'
+ const result = analyzeClaudeMd(minimal)
+ const cmdCrit = result.criteria.find(c => c.id === 'build-commands')!
+ expect(cmdCrit.score).toBe(0)
+ })
+})
+```
+
+- [ ] **Step 2: Run test to verify it fails**
+
+```bash
+npx vitest run src/tests/lib/scorer.test.ts
+```
+Expected: FAIL
+
+- [ ] **Step 3: Create analyzer types**
+
+Create `src/lib/analyzer/types.ts`:
+```typescript
+export interface CriterionResult {
+ id: string
+ name: string
+ weight: number
+ maxScore: number
+ score: number
+ findings: Finding[]
+}
+
+export interface Finding {
+ severity: 'critical' | 'warning' | 'info'
+ message: string
+ line?: number
+}
+
+export interface Suggestion {
+ id: string
+ criterion: string
+ description: string
+ before: string
+ after: string
+ priority: 'high' | 'medium' | 'low'
+}
+
+export interface AnalysisResult {
+ overallScore: number
+ maxScore: number
+ percentage: number
+ letterGrade: string
+ criteria: CriterionResult[]
+ pros: string[]
+ cons: string[]
+ suggestions: Suggestion[]
+ detectedProfileSlug?: string
+ lineCount: number
+}
+```
+
+- [ ] **Step 4: Implement individual scoring rules**
+
+Create `src/lib/analyzer/rules.ts` with 12 rule functions:
+```typescript
+import type { CriterionResult, Finding } from './types'
+
+export function scoreLineCount(content: string): CriterionResult {
+ const lines = content.split('\n').length
+ const maxLines = 200
+ const score = lines <= maxLines ? 15 : lines <= 300 ? 8 : 0
+ const findings: Finding[] = []
+ if (lines > maxLines) {
+ findings.push({
+ severity: lines > 300 ? 'critical' : 'warning',
+ message: `CLAUDE.md is ${lines} lines (target: <${maxLines}). Consider modularizing into .claude/rules/.`,
+ })
+ }
+ return { id: 'line-count', name: 'Line Count', weight: 15, maxScore: 15, score, findings }
+}
+
+export function scoreStructure(content: string): CriterionResult {
+ const headers = (content.match(/^#{1,3}\s+/gm) || []).length
+ const score = headers >= 3 ? 10 : headers >= 1 ? 5 : 0
+ const findings: Finding[] = []
+ if (headers < 3) {
+ findings.push({ severity: 'warning', message: `Only ${headers} section headers found. Use ## headers to organize content.` })
+ }
+ return { id: 'structure', name: 'Structure', weight: 10, maxScore: 10, score, findings }
+}
+
+export function scoreSpecificity(content: string): CriterionResult {
+ const cmdPatterns = /(?:npm|yarn|pnpm|pip|cargo|go|make|pytest|jest|vitest)\s+\w+/g
+ const commands = (content.match(cmdPatterns) || []).length
+ const score = commands >= 3 ? 12 : commands >= 1 ? 6 : 0
+ const findings: Finding[] = []
+ if (commands < 3) {
+ findings.push({ severity: 'warning', message: 'Include specific commands (e.g., "npm test" not just "run tests").' })
+ }
+ return { id: 'specificity', name: 'Specificity', weight: 12, maxScore: 12, score, findings }
+}
+
+export function scoreSafetyRules(content: string): CriterionResult {
+ const lower = content.toLowerCase()
+ const safetyKeywords = ['safety', 'don\'t delete', 'don\'t modify', 'never', 'protected', 'credential', 'secret']
+ const found = safetyKeywords.filter(k => lower.includes(k)).length
+ const score = found >= 3 ? 12 : found >= 1 ? 6 : 0
+ const findings: Finding[] = []
+ if (found === 0) {
+ findings.push({ severity: 'critical', message: 'No safety rules found. Add a ## Safety section.' })
+ }
+ return { id: 'safety-rules', name: 'Safety Rules', weight: 12, maxScore: 12, score, findings }
+}
+
+export function scoreBuildCommands(content: string): CriterionResult {
+ const cmdTypes = ['install', 'dev', 'test', 'lint', 'build', 'format']
+ const found = cmdTypes.filter(c => content.toLowerCase().includes(c + ':') || content.toLowerCase().includes(c + ' :')).length
+ const score = found >= 3 ? 8 : found >= 1 ? 4 : 0
+ const findings: Finding[] = []
+ if (found < 3) {
+ const missing = cmdTypes.filter(c => !content.toLowerCase().includes(c + ':') && !content.toLowerCase().includes(c + ' :'))
+ findings.push({ severity: 'warning', message: `Missing command definitions for: ${missing.join(', ')}` })
+ }
+ return { id: 'build-commands', name: 'Build Commands', weight: 8, maxScore: 8, score, findings }
+}
+
+export function scoreCodeStyle(content: string): CriterionResult {
+ const lower = content.toLowerCase()
+ const hasStyle = lower.includes('style') || lower.includes('indent') || lower.includes('naming') || lower.includes('linter') || lower.includes('.claude/rules/')
+ const score = hasStyle ? 5 : 0
+ const findings: Finding[] = []
+ if (!hasStyle) {
+ findings.push({ severity: 'info', message: 'No code style guidance found. Consider defining or delegating to a linter.' })
+ }
+ return { id: 'code-style', name: 'Code Style', weight: 5, maxScore: 5, score, findings }
+}
+
+export function scoreModularImports(content: string): CriterionResult {
+ const hasImports = content.includes('@docs/') || content.includes('@~/')
+ const hasRulesRef = content.includes('.claude/rules/')
+ const score = hasImports && hasRulesRef ? 10 : hasImports || hasRulesRef ? 5 : 0
+ const findings: Finding[] = []
+ if (!hasImports && !hasRulesRef) {
+ findings.push({ severity: 'warning', message: 'No modular imports or .claude/rules/ references. Consider splitting into modular files.' })
+ }
+ return { id: 'modular-imports', name: 'Modular Imports', weight: 10, maxScore: 10, score, findings }
+}
+
+export function scoreNoSecrets(content: string): CriterionResult {
+ const secretPatterns = [
+ /(?:api[_-]?key|token|secret|password|credential)\s*[:=]\s*\S+/gi,
+ /sk-[a-zA-Z0-9]{20,}/g,
+ /ghp_[a-zA-Z0-9]{36}/g,
+ /shpat_[a-zA-Z0-9]{32}/g,
+ /(?:Bearer|Basic)\s+[a-zA-Z0-9+/=]{20,}/g,
+ ]
+ const findings: Finding[] = []
+ const lines = content.split('\n')
+ for (let i = 0; i < lines.length; i++) {
+ for (const pattern of secretPatterns) {
+ pattern.lastIndex = 0
+ if (pattern.test(lines[i])) {
+ findings.push({ severity: 'critical', message: `Potential secret detected`, line: i + 1 })
+ }
+ }
+ }
+ const score = findings.length === 0 ? 10 : 0
+ return { id: 'no-secrets', name: 'No Secrets', weight: 10, maxScore: 10, score, findings }
+}
+
+export function scoreNoBloat(content: string): CriterionResult {
+ const lines = content.split('\n')
+ const emptyRuns = lines.reduce((acc, line, i) => {
+ if (line.trim() === '' && i > 0 && lines[i - 1].trim() === '') return acc + 1
+ return acc
+ }, 0)
+ const score = emptyRuns <= 3 ? 8 : emptyRuns <= 10 ? 4 : 0
+ const findings: Finding[] = []
+ if (emptyRuns > 3) {
+ findings.push({ severity: 'info', message: `${emptyRuns} consecutive empty line pairs found. Clean up whitespace.` })
+ }
+ return { id: 'no-bloat', name: 'No Bloat', weight: 8, maxScore: 8, score, findings }
+}
+
+export function scoreSkillsSubagents(content: string): CriterionResult {
+ const lower = content.toLowerCase()
+ const hasSkills = lower.includes('skill') || lower.includes('/skill')
+ const hasAgents = lower.includes('subagent') || lower.includes('.claude/agents')
+ const score = hasSkills || hasAgents ? 3 : 0
+ return { id: 'skills-subagents', name: 'Skills/Subagents', weight: 3, maxScore: 3, score, findings: [] }
+}
+
+export function scoreHookEnforcement(content: string): CriterionResult {
+ const lower = content.toLowerCase()
+ const hasHooks = lower.includes('hook') || lower.includes('.claude/settings')
+ const score = hasHooks ? 3 : 0
+ return { id: 'hook-enforcement', name: 'Hook Enforcement', weight: 3, maxScore: 3, score, findings: [] }
+}
+
+export function scoreMcpConfig(content: string): CriterionResult {
+ const lower = content.toLowerCase()
+ const hasMcp = lower.includes('mcp') || lower.includes('.mcp.json')
+ const score = hasMcp ? 4 : 0
+ return { id: 'mcp-config', name: 'MCP Configuration', weight: 4, maxScore: 4, score, findings: [] }
+}
+```
+
+- [ ] **Step 5: Implement scorer orchestrator**
+
+Create `src/lib/analyzer/scorer.ts`:
+```typescript
+import type { AnalysisResult } from './types'
+import * as rules from './rules'
+
+const allRules = [
+ rules.scoreLineCount,
+ rules.scoreStructure,
+ rules.scoreSpecificity,
+ rules.scoreSafetyRules,
+ rules.scoreBuildCommands,
+ rules.scoreCodeStyle,
+ rules.scoreModularImports,
+ rules.scoreNoSecrets,
+ rules.scoreNoBloat,
+ rules.scoreSkillsSubagents,
+ rules.scoreHookEnforcement,
+ rules.scoreMcpConfig,
+]
+
+function letterGrade(pct: number): string {
+ if (pct >= 95) return 'A+'
+ if (pct >= 90) return 'A'
+ if (pct >= 85) return 'A-'
+ if (pct >= 80) return 'B+'
+ if (pct >= 75) return 'B'
+ if (pct >= 70) return 'B-'
+ if (pct >= 65) return 'C+'
+ if (pct >= 60) return 'C'
+ if (pct >= 55) return 'C-'
+ if (pct >= 50) return 'D+'
+ if (pct >= 45) return 'D'
+ if (pct >= 40) return 'D-'
+ return 'F'
+}
+
+export function analyzeClaudeMd(content: string): AnalysisResult {
+ const criteria = allRules.map(rule => rule(content))
+ const maxScore = criteria.reduce((sum, c) => sum + c.maxScore, 0)
+ const totalScore = criteria.reduce((sum, c) => sum + c.score, 0)
+ const percentage = Math.round((totalScore / maxScore) * 100)
+
+ const pros = criteria
+ .filter(c => c.score === c.maxScore)
+ .map(c => `${c.name}: Looks good`)
+
+ const cons = criteria
+ .filter(c => c.score === 0 && c.findings.length > 0)
+ .map(c => `${c.name}: ${c.findings[0].message}`)
+
+ return {
+ overallScore: totalScore,
+ maxScore,
+ percentage,
+ letterGrade: letterGrade(percentage),
+ criteria,
+ pros,
+ cons,
+ suggestions: [], // populated by suggester.ts in Task 5
+ lineCount: content.split('\n').length,
+ }
+}
+```
+
+- [ ] **Step 6: Run tests**
+
+```bash
+npx vitest run src/tests/lib/scorer.test.ts
+```
+Expected: ALL PASS
+
+- [ ] **Step 7: Commit**
+
+```bash
+git add src/lib/analyzer/ src/tests/lib/scorer.test.ts
+git commit -m "feat: 12-criteria CLAUDE.md scoring engine with secret detection"
+```
+
+---
+
+### Task 5: Suggestion Engine + Applier
+
+**Files:**
+- Create: `src/lib/analyzer/suggester.ts`
+- Create: `src/lib/analyzer/applier.ts`
+- Create: `src/lib/analyzer/matcher.ts`
+- Test: `src/tests/lib/applier.test.ts`
+
+- [ ] **Step 1: Write the failing test**
+
+Create `src/tests/lib/applier.test.ts`:
+```typescript
+import { describe, it, expect } from 'vitest'
+import { generateSuggestions } from '@/lib/analyzer/suggester'
+import { applySuggestions } from '@/lib/analyzer/applier'
+import { analyzeClaudeMd } from '@/lib/analyzer/scorer'
+
+const minimalMd = `# CLAUDE.md
+
+## My project
+This is a simple app.
+`
+
+describe('Suggestion Engine', () => {
+ it('generates suggestions for a minimal CLAUDE.md', () => {
+ const analysis = analyzeClaudeMd(minimalMd)
+ const suggestions = generateSuggestions(minimalMd, analysis)
+ expect(suggestions.length).toBeGreaterThan(0)
+ expect(suggestions[0].description).toBeTruthy()
+ expect(suggestions[0].after).toBeTruthy()
+ })
+})
+
+describe('Suggestion Applier', () => {
+ it('applies suggestions to content', () => {
+ const analysis = analyzeClaudeMd(minimalMd)
+ const suggestions = generateSuggestions(minimalMd, analysis)
+ const safetyIdx = suggestions.findIndex(s => s.criterion === 'safety-rules')
+ if (safetyIdx >= 0) {
+ const result = applySuggestions(minimalMd, [suggestions[safetyIdx]])
+ expect(result).toContain('Safety')
+ expect(result.length).toBeGreaterThan(minimalMd.length)
+ }
+ })
+
+ it('applies multiple suggestions without conflicts', () => {
+ const analysis = analyzeClaudeMd(minimalMd)
+ const suggestions = generateSuggestions(minimalMd, analysis)
+ const result = applySuggestions(minimalMd, suggestions.slice(0, 3))
+ expect(result).toBeTruthy()
+ expect(result).toContain('# CLAUDE.md')
+ })
+})
+```
+
+- [ ] **Step 2: Run test to verify it fails**
+
+```bash
+npx vitest run src/tests/lib/applier.test.ts
+```
+Expected: FAIL
+
+- [ ] **Step 3: Implement suggester**
+
+Create `src/lib/analyzer/suggester.ts` — generates actionable suggestions based on analysis results. Each suggestion has `before` (current state) and `after` (proposed change) text, plus description and priority.
+
+Key suggestions:
+- Missing safety section → insert `## Safety` block
+- Missing commands → insert `## Commands` block with placeholders
+- Over line limit → suggest splitting into .claude/rules/
+- Secrets detected → suggest removing and using env vars
+- No modular imports → suggest adding .claude/rules/ references
+
+- [ ] **Step 4: Implement applier**
+
+Create `src/lib/analyzer/applier.ts` — takes content + selected suggestions, applies them in order (append-based for new sections, replace-based for modifications), returns updated content.
+
+- [ ] **Step 5: Implement profile matcher**
+
+Create `src/lib/analyzer/matcher.ts` — analyzes existing CLAUDE.md content and returns the closest matching profile slug by scoring similarity against each profile's template patterns.
+
+- [ ] **Step 6: Run tests**
+
+```bash
+npx vitest run src/tests/lib/applier.test.ts
+```
+Expected: ALL PASS
+
+- [ ] **Step 7: Commit**
+
+```bash
+git add src/lib/analyzer/suggester.ts src/lib/analyzer/applier.ts src/lib/analyzer/matcher.ts src/tests/lib/applier.test.ts
+git commit -m "feat: suggestion engine and applier for CLAUDE.md optimization"
+```
+
+---
+
+## Phase 3: API Routes + Database
+
+### Task 6: Prisma Schema + Database Setup
+
+**Files:**
+- Create: `prisma/schema.prisma`
+- Create: `prisma/seed.ts`
+- Create: `src/lib/db.ts`
+
+- [ ] **Step 1: Create Prisma schema**
+
+Create `prisma/schema.prisma`:
+```prisma
+generator client {
+ provider = "prisma-client-js"
+}
+
+datasource db {
+ provider = "postgresql"
+ url = env("DATABASE_URL")
+}
+
+model User {
+ id String @id @default(uuid())
+ email String? @unique
+ tier String @default("free")
+ createdAt DateTime @default(now()) @map("created_at")
+ analyses Analysis[]
+
+ @@map("users")
+}
+
+model Analysis {
+ id String @id @default(uuid())
+ userId String? @map("user_id")
+ user User? @relation(fields: [userId], references: [id])
+ inputContent String @map("input_content")
+ inputSource String? @map("input_source")
+ overallScore Float? @map("overall_score")
+ letterGrade String? @map("letter_grade")
+ criteriaScores Json @map("criteria_scores")
+ pros Json @default("[]")
+ cons Json @default("[]")
+ suggestions Json @default("[]")
+ detectedProfileSlug String? @map("detected_profile_slug")
+ createdAt DateTime @default(now()) @map("created_at")
+ appliedSuggestions AppliedSuggestion[]
+
+ @@map("analyses")
+}
+
+model AppliedSuggestion {
+ id String @id @default(uuid())
+ analysisId String @map("analysis_id")
+ analysis Analysis @relation(fields: [analysisId], references: [id])
+ suggestionIndex Int @map("suggestion_index")
+ appliedAt DateTime @default(now()) @map("applied_at")
+ resultContent String? @map("result_content")
+
+ @@map("applied_suggestions")
+}
+```
+
+- [ ] **Step 2: Create database and run migration**
+
+```bash
+createdb -U dw_admin clawcoder 2>/dev/null || true
+npx prisma migrate dev --name init
+```
+
+- [ ] **Step 3: Create Prisma client singleton**
+
+Create `src/lib/db.ts`:
+```typescript
+import { PrismaClient } from '@prisma/client'
+
+const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }
+
+export const prisma = globalForPrisma.prisma || new PrismaClient()
+
+if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
+```
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add prisma/ src/lib/db.ts
+git commit -m "feat: Prisma schema with users, analyses, applied_suggestions tables"
+```
+
+---
+
+### Task 7: API Routes
+
+**Files:**
+- Create: `src/app/api/profiles/route.ts`
+- Create: `src/app/api/analyze/route.ts`
+- Create: `src/app/api/generate/route.ts`
+- Create: `src/app/api/suggestions/route.ts`
+
+- [ ] **Step 1: Profiles API**
+
+Create `src/app/api/profiles/route.ts`:
+```typescript
+import { NextResponse } from 'next/server'
+import { PROFILES, getProfile, getProfilesByAge, getProfilesBySkill } from '@/lib/profiles/data'
+
+export async function GET(request: Request) {
+ const { searchParams } = new URL(request.url)
+ const slug = searchParams.get('slug')
+ const age = searchParams.get('age')
+ const skill = searchParams.get('skill')
+
+ if (slug) {
+ const profile = getProfile(slug)
+ return profile
+ ? NextResponse.json(profile)
+ : NextResponse.json({ error: 'Profile not found' }, { status: 404 })
+ }
+ if (age) return NextResponse.json(getProfilesByAge(age as any))
+ if (skill) return NextResponse.json(getProfilesBySkill(skill as any))
+ return NextResponse.json(PROFILES)
+}
+```
+
+- [ ] **Step 2: Analyze API**
+
+Create `src/app/api/analyze/route.ts`:
+```typescript
+import { NextResponse } from 'next/server'
+import { analyzeClaudeMd } from '@/lib/analyzer/scorer'
+import { generateSuggestions } from '@/lib/analyzer/suggester'
+import { detectProfile } from '@/lib/analyzer/matcher'
+import { prisma } from '@/lib/db'
+
+export async function POST(request: Request) {
+ const { content, source } = await request.json()
+ if (!content || typeof content !== 'string') {
+ return NextResponse.json({ error: 'content is required' }, { status: 400 })
+ }
+
+ const analysis = analyzeClaudeMd(content)
+ const suggestions = generateSuggestions(content, analysis)
+ const detectedProfile = detectProfile(content)
+
+ const saved = await prisma.analysis.create({
+ data: {
+ inputContent: content,
+ inputSource: source || 'paste',
+ overallScore: analysis.percentage,
+ letterGrade: analysis.letterGrade,
+ criteriaScores: analysis.criteria as any,
+ pros: analysis.pros as any,
+ cons: analysis.cons as any,
+ suggestions: suggestions as any,
+ detectedProfileSlug: detectedProfile,
+ },
+ })
+
+ return NextResponse.json({
+ id: saved.id,
+ ...analysis,
+ suggestions,
+ detectedProfileSlug: detectedProfile,
+ })
+}
+```
+
+- [ ] **Step 3: Generate API**
+
+Create `src/app/api/generate/route.ts`:
+```typescript
+import { NextResponse } from 'next/server'
+import { getProfile } from '@/lib/profiles/data'
+import { generateBundle } from '@/lib/generator/bundle'
+
+export async function POST(request: Request) {
+ const { profileSlug, vars } = await request.json()
+ const profile = getProfile(profileSlug)
+ if (!profile) {
+ return NextResponse.json({ error: 'Profile not found' }, { status: 404 })
+ }
+ const bundle = generateBundle(profile, vars || {})
+ return NextResponse.json(bundle)
+}
+```
+
+- [ ] **Step 4: Suggestions apply API**
+
+Create `src/app/api/suggestions/route.ts`:
+```typescript
+import { NextResponse } from 'next/server'
+import { applySuggestions } from '@/lib/analyzer/applier'
+import type { Suggestion } from '@/lib/analyzer/types'
+
+export async function POST(request: Request) {
+ const { content, selectedSuggestions } = await request.json()
+ if (!content || !selectedSuggestions) {
+ return NextResponse.json({ error: 'content and selectedSuggestions required' }, { status: 400 })
+ }
+ const result = applySuggestions(content, selectedSuggestions as Suggestion[])
+ return NextResponse.json({ updatedContent: result })
+}
+```
+
+- [ ] **Step 5: Verify APIs work**
+
+```bash
+npm run build
+npm run dev &
+sleep 3
+curl -s http://localhost:3000/api/profiles | head -100
+curl -s http://localhost:3000/api/profiles?slug=teen-beginner | head -50
+kill %1
+```
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add src/app/api/
+git commit -m "feat: API routes for profiles, analyze, generate, and suggestions"
+```
+
+---
+
+## Phase 4: UI Components
+
+### Task 8: Design System Primitives
+
+**Files:**
+- Create: `src/components/ui/pill-button.tsx`
+- Create: `src/components/ui/back-next-bar.tsx`
+- Create: `src/components/ui/progress-indicator.tsx`
+- Create: `src/components/ui/grade-badge.tsx`
+- Create: `src/components/ui/diff-viewer.tsx`
+- Create: `src/components/ui/radar-chart.tsx`
+
+- [ ] **Step 1: Implement pill-button**
+
+Accessible, large touch-target button with press animation. Respects `reducedMotion`. Min height 56px, min width 140px, full border-radius (pill shape).
+
+- [ ] **Step 2: Implement back-next-bar**
+
+Sticky bottom bar with Back/Next buttons. Min button height 56px. Always visible. Shows step count.
+
+- [ ] **Step 3: Implement progress-indicator**
+
+"Step N of M" text + thin progress bar. Accessible with aria-valuenow.
+
+- [ ] **Step 4: Implement grade-badge**
+
+Letter grade display (A+ through F) with color coding. Green for A/B, yellow for C/D, red for F.
+
+- [ ] **Step 5: Implement diff-viewer**
+
+Side-by-side or unified diff view for before/after comparison. Uses simple line-by-line diffing with green (added) and red (removed) highlighting.
+
+- [ ] **Step 6: Implement radar-chart**
+
+12-axis SVG radar chart showing scores for each criterion. Responsive, accessible with aria-label.
+
+- [ ] **Step 7: Commit**
+
+```bash
+git add src/components/ui/
+git commit -m "feat: design system primitives — pill button, nav bar, grade badge, diff viewer, radar chart"
+```
+
+---
+
+### Task 9: Profile Selector + Onboarding Page
+
+**Files:**
+- Create: `src/app/page.tsx` (landing/profile selector)
+- Create: `src/components/onboarding/question-screen.tsx`
+- Create: `src/components/onboarding/pill-grid.tsx`
+- Create: `src/app/onboarding/page.tsx`
+- Create: `src/lib/store.ts` (Zustand store)
+
+- [ ] **Step 1: Create Zustand store**
+
+Create `src/lib/store.ts`:
+```typescript
+import { create } from 'zustand'
+import type { Profile } from '@/lib/profiles/types'
+import type { AnalysisResult, Suggestion } from '@/lib/analyzer/types'
+
+interface ClawCoderStore {
+ selectedProfile: Profile | null
+ setProfile: (p: Profile) => void
+ onboardingAnswers: Record<string, string>
+ setAnswer: (key: string, value: string) => void
+ currentStep: number
+ setStep: (n: number) => void
+ analysisResult: AnalysisResult | null
+ setAnalysis: (a: AnalysisResult) => void
+ selectedSuggestions: Set<string>
+ toggleSuggestion: (id: string) => void
+ selectAllSuggestions: (suggestions: Suggestion[]) => void
+ clearSuggestions: () => void
+ generatedContent: string
+ setGeneratedContent: (c: string) => void
+}
+
+export const useStore = create<ClawCoderStore>((set) => ({
+ selectedProfile: null,
+ setProfile: (p) => set({ selectedProfile: p }),
+ onboardingAnswers: {},
+ setAnswer: (key, value) => set((s) => ({ onboardingAnswers: { ...s.onboardingAnswers, [key]: value } })),
+ currentStep: 0,
+ setStep: (n) => set({ currentStep: n }),
+ analysisResult: null,
+ setAnalysis: (a) => set({ analysisResult: a }),
+ selectedSuggestions: new Set(),
+ toggleSuggestion: (id) => set((s) => {
+ const next = new Set(s.selectedSuggestions)
+ next.has(id) ? next.delete(id) : next.add(id)
+ return { selectedSuggestions: next }
+ }),
+ selectAllSuggestions: (suggestions) => set({ selectedSuggestions: new Set(suggestions.map(s => s.id)) }),
+ clearSuggestions: () => set({ selectedSuggestions: new Set() }),
+ generatedContent: '',
+ setGeneratedContent: (c) => set({ generatedContent: c }),
+}))
+```
+
+- [ ] **Step 2: Implement landing page with profile selector**
+
+Grid of 15 profile cards organized by age group (rows) and skill level (columns). Each card shows: display name, persona description, key stats (font size, # skills, # MCP servers). Clicking a card navigates to `/onboarding?profile=slug`.
+
+- [ ] **Step 3: Implement question-screen component**
+
+Full-screen layout: top section (step label + question), middle (pill grid), bottom (back/next bar + progress). Animates between questions with Framer Motion (cross-fade, respects reduced motion).
+
+- [ ] **Step 4: Implement pill-grid component**
+
+Responsive grid of PillButton components. 2-4 columns depending on screen width. Each pill represents an answer option.
+
+- [ ] **Step 5: Implement onboarding page**
+
+9-step wizard from the spec: Profile, Comfort, Risk, Style, Tools, Workflow, Updates, Motion, Finish. Each step renders a QuestionScreen with appropriate pill options. Final step triggers generation.
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add src/app/page.tsx src/app/onboarding/ src/components/onboarding/ src/lib/store.ts
+git commit -m "feat: profile selector landing page and onboarding wizard"
+```
+
+---
+
+### Task 10: Analyzer Page — Upload + Report + Suggestions
+
+**Files:**
+- Create: `src/components/analyzer/upload-zone.tsx`
+- Create: `src/components/analyzer/report-card.tsx`
+- Create: `src/components/analyzer/suggestion-card.tsx`
+- Create: `src/components/analyzer/suggestion-list.tsx`
+- Create: `src/app/analyzer/page.tsx`
+
+- [ ] **Step 1: Implement upload-zone**
+
+Drag-and-drop zone + paste textarea. Accepts .md files and raw text. Sends content to `/api/analyze` on submit.
+
+- [ ] **Step 2: Implement report-card**
+
+Displays: letter grade (GradeBadge), percentage, radar chart (12 criteria), expandable pros (green), cons (red) sections.
+
+- [ ] **Step 3: Implement suggestion-card**
+
+Single suggestion with: checkbox, description, priority badge, expandable before/after diff preview (DiffViewer).
+
+- [ ] **Step 4: Implement suggestion-list**
+
+List of SuggestionCards with "Select All" / "Deselect All" controls. "Preview Changes" button shows unified diff. "Apply Selected" button calls `/api/suggestions`.
+
+- [ ] **Step 5: Wire up analyzer page**
+
+Full page flow: upload → loading → report + suggestions → apply → download/copy result. Includes profile detection: "Your config matches `young-adult-intermediate`" with upgrade CTA.
+
+- [ ] **Step 6: Commit**
+
+```bash
+git add src/components/analyzer/ src/app/analyzer/
+git commit -m "feat: CLAUDE.md analyzer page with upload, scoring, and suggestion workflow"
+```
+
+---
+
+### Task 11: Mass Update Page
+
+**Files:**
+- Create: `src/components/mass-update/record-table.tsx`
+- Create: `src/components/mass-update/bulk-actions-bar.tsx`
+- Create: `src/app/mass-update/page.tsx`
+
+- [ ] **Step 1: Implement record-table**
+
+Table with columns: checkbox, file name, type (claude_md/rule/skill/hook), status, score, last modified. Sortable columns. Multi-select via checkboxes. "Select All" header checkbox.
+
+- [ ] **Step 2: Implement bulk-actions-bar**
+
+Floating bar that appears when items are selected. Shows count ("3 selected"). Buttons: "Analyze Selected", "Apply All Suggestions", "Export Bundle (.zip)", "Delete Selected" (with confirm modal).
+
+- [ ] **Step 3: Wire up mass-update page**
+
+Page shows all user's generated artifacts in a table. Supports: analyze multiple files at once, apply suggestions in batch, export as downloadable .zip bundle.
+
+- [ ] **Step 4: Commit**
+
+```bash
+git add src/components/mass-update/ src/app/mass-update/
+git commit -m "feat: mass update page with record table and bulk actions"
+```
+
+---
+
+### Task 12: Builder Page — Rail + Preview
+
+**Files:**
+- Create: `src/components/builder/rail.tsx`
+- Create: `src/components/builder/preview-pane.tsx`
+- Create: `src/components/builder/section-editor.tsx`
+- Create: `src/app/builder/page.tsx`
+
+- [ ] **Step 1: Implement collapsible rail**
+
+Left sidebar with collapsible sections: Profile Summary, CLAUDE.md, Rules, Skills, Subagents, Hooks, MCP Servers, Learning Path. Chevron toggle. Animate height (respect reduced motion).
+
+- [ ] **Step 2: Implement preview-pane**
+
+Live markdown preview of generated CLAUDE.md. Syntax highlighted. Shows effective output including resolved imports. "Copy" and "Download" buttons.
+
+- [ ] **Step 3: Implement section-editor**
+
+Inline editor for individual sections of the CLAUDE.md. Textarea with live preview. "Reset to template" button.
+
+- [ ] **Step 4: Wire up builder page**
+
+Two-panel layout: rail (left, collapsible on mobile) + preview (right, full-width on mobile). Editing a section in the rail updates the preview in real-time.
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add src/components/builder/ src/app/builder/
+git commit -m "feat: builder page with collapsible rail and live CLAUDE.md preview"
+```
+
+---
+
+## Phase 5: Integration + Polish
+
+### Task 13: Root Layout + Navigation + Theme
+
+**Files:**
+- Modify: `src/app/layout.tsx`
+- Create: `src/components/nav.tsx`
+
+- [ ] **Step 1: Implement root layout**
+
+Apply profile-driven theme variables (font size, contrast, motion). Persist theme in localStorage. Add `prefers-reduced-motion` media query support.
+
+- [ ] **Step 2: Implement navigation**
+
+Top nav bar with links: Home, Onboarding, Builder, Analyzer, Mass Update. Highlights active route. Responsive (hamburger on mobile).
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add src/app/layout.tsx src/components/nav.tsx
+git commit -m "feat: root layout with theme system and navigation"
+```
+
+---
+
+### Task 14: End-to-End Integration Test
+
+**Files:**
+- Create: `src/tests/e2e/flow.test.ts`
+
+- [ ] **Step 1: Write integration test**
+
+Test the full flow:
+1. Select a profile → verify correct data loaded
+2. Complete onboarding → verify bundle generated
+3. Upload a CLAUDE.md → verify analysis returned with correct scoring
+4. Apply suggestions → verify content updated
+5. Verify profile detection matches expected
+
+- [ ] **Step 2: Run all tests**
+
+```bash
+npx vitest run
+```
+Expected: ALL PASS
+
+- [ ] **Step 3: Commit**
+
+```bash
+git add src/tests/
+git commit -m "test: end-to-end integration test for full ClawCoder flow"
+```
+
+---
+
+### Task 15: PM2 Deployment + Firewall
+
+**Files:**
+- Create: `ecosystem.config.cjs`
+
+- [ ] **Step 1: Build for production**
+
+```bash
+cd /root/Projects/ClawCoder && npm run build
+```
+
+- [ ] **Step 2: Create PM2 config**
+
+Create `ecosystem.config.cjs`:
+```javascript
+module.exports = {
+ apps: [{
+ name: 'clawcoder',
+ script: 'node_modules/.bin/next',
+ args: 'start -p 7350',
+ cwd: '/root/Projects/ClawCoder',
+ env: {
+ NODE_ENV: 'production',
+ PORT: 7350,
+ },
+ }],
+}
+```
+
+- [ ] **Step 3: Start with PM2 and open firewall**
+
+```bash
+pm2 start ecosystem.config.cjs
+sudo ufw allow from 76.33.146.135 to any port 7350 proto tcp
+```
+
+- [ ] **Step 4: Verify service is running**
+
+```bash
+curl -s http://45.61.58.125:7350 | head -20
+pm2 status clawcoder
+```
+
+- [ ] **Step 5: Commit**
+
+```bash
+git add ecosystem.config.cjs
+git commit -m "feat: PM2 deployment config on port 7350"
+```
+
+---
+
+## Summary
+
+| Phase | Tasks | Description |
+|-------|-------|-------------|
+| **1: Foundation** | Tasks 1-3 | Project scaffold, 15 profiles, template engine |
+| **2: Analyzer** | Tasks 4-5 | 12-criteria scorer, suggestion engine, applier |
+| **3: API + DB** | Tasks 6-7 | Prisma schema, 4 API routes |
+| **4: UI** | Tasks 8-12 | Design system, onboarding, analyzer, mass update, builder |
+| **5: Polish** | Tasks 13-15 | Layout, navigation, E2E tests, PM2 deployment |
+
+**Total**: 15 tasks, each with 4-8 bite-sized steps.
+**Port**: 7350 (IP-locked to Steve's IP)
diff --git a/docs/superpowers/specs/2026-03-23-clawcoder-profiles-analyzer-design.md b/docs/superpowers/specs/2026-03-23-clawcoder-profiles-analyzer-design.md
new file mode 100644
index 0000000..57243f2
--- /dev/null
+++ b/docs/superpowers/specs/2026-03-23-clawcoder-profiles-analyzer-design.md
@@ -0,0 +1,1377 @@
+# ClawCoder — Profile System + CLAUDE.md Analyzer Design Spec
+
+**Date**: 2026-03-23
+**Status**: Draft
+**Approach**: B (Profile + Analyzer + Marketplace)
+
+---
+
+## 1. Executive Summary
+
+ClawCoder is a cross-platform app that helps users generate, analyze, and maintain high-quality CLAUDE.md configurations. This spec covers two core features:
+
+1. **Profile System** — 15 profiles (5 age groups x 3 skill levels) that pre-configure the entire app experience: UI, templates, skill bundles, MCP servers, hooks, and learning paths.
+2. **CLAUDE.md Analyzer** — Upload an existing CLAUDE.md, get scored against 12 best-practice criteria, see pros/cons/suggestions, select suggestions to apply, and mass-update records.
+
+---
+
+## 2. Profile System Architecture
+
+### 2.1 Profile Matrix: 5 Age Groups x 3 Skill Levels
+
+Each profile is a JSON configuration object that controls:
+- `ui` — font size, motion preference, contrast, touch target size, layout density
+- `template` — CLAUDE.md template variant, verbosity level, safety strictness
+- `skills` — pre-selected skill bundles from marketplace
+- `mcpServers` — recommended MCP servers (trust-gated)
+- `hooks` — default hook configurations
+- `subagents` — recommended subagent bundles
+- `learningPath` — ordered list of video/doc resources
+- `onboarding` — question flow customization (pace, language level, examples)
+
+### 2.2 The 15 Profiles
+
+---
+
+#### Profile 1: Teen Beginner (age 13-17, skill: beginner)
+
+**Slug**: `teen-beginner`
+**Persona**: First-time coder, learning programming in school or self-taught. Needs encouragement, safety rails, and simple language.
+
+**UI Configuration**:
+```json
+{
+ "fontSize": 16,
+ "fontScale": 1.0,
+ "motionLevel": "normal",
+ "contrast": "standard",
+ "touchTargetMin": 44,
+ "layoutDensity": "comfortable",
+ "theme": "light"
+}
+```
+
+**CLAUDE.md Template**: `starter-safe`
+- Verbosity: high (explain every step)
+- Safety: maximum (no destructive commands, no secret handling, ask before any file change)
+- Style: encouraging, uses simple terms
+- Target lines: 40-60
+
+**Template Content**:
+```markdown
+# CLAUDE.md — My Project
+
+## How to help me
+- Explain what you're doing in simple steps.
+- If something might break, warn me first and ask permission.
+- Show me what changed after each step.
+- If I seem confused, offer a simpler explanation.
+
+## Safety rules
+- Never delete files without asking.
+- Never run commands that could break things.
+- If you're not sure about something, ask me first.
+- Keep my passwords and private info safe — never put them in code files.
+
+## My project
+- Name: {{project_name}}
+- What it does: {{project_description}}
+- Language: {{language}}
+
+## Commands
+- Run it: {{run_cmd}}
+- Test it: {{test_cmd}}
+```
+
+**Skill Bundle**:
+- `senior-explain` (simple language explanations)
+- `git-commit-helper` (safe commit guidance)
+
+**MCP Servers**:
+- `filesystem` (read-only, project dir only)
+- `memory` (knowledge graph for learning)
+
+**Hooks**:
+```json
+{
+ "PreToolUse": [{
+ "matcher": "Bash",
+ "hooks": [{
+ "type": "command",
+ "command": "echo 'Checking for dangerous commands...' && echo $TOOL_INPUT | grep -qE 'rm -rf|sudo|chmod 777|format|mkfs' && exit 2 || exit 0"
+ }]
+ }]
+}
+```
+
+**Learning Path**:
+1. [Sabrina's Ultimate Claude Code Tutorial](https://www.sabrina.dev/p/claude-code-full-course-for-beginners) (90 min, beginner-friendly)
+2. [Claude Code Official Docs — Best Practices](https://code.claude.com/docs/en/best-practices)
+
+**Subagents**: None (too complex for this level)
+
+---
+
+#### Profile 2: Teen Intermediate (age 13-17, skill: intermediate)
+
+**Slug**: `teen-intermediate`
+**Persona**: Coding student with project experience. Comfortable with Git, wants to level up with skills and automation.
+
+**UI Configuration**:
+```json
+{
+ "fontSize": 15,
+ "fontScale": 1.0,
+ "motionLevel": "normal",
+ "contrast": "standard",
+ "touchTargetMin": 44,
+ "layoutDensity": "comfortable",
+ "theme": "light"
+}
+```
+
+**CLAUDE.md Template**: `student-project`
+- Verbosity: medium (explain key decisions)
+- Safety: high (guard destructive ops, but allow standard dev workflow)
+- Style: clear, educational
+- Target lines: 60-80
+
+**Template Content**:
+```markdown
+# CLAUDE.md — {{project_name}}
+
+## Working style
+- Explain key decisions briefly.
+- Run tests after changes.
+- Use small, focused commits.
+- If a change could break existing features, flag it.
+
+## Safety
+- Don't modify .env or credential files.
+- Don't run destructive commands without asking.
+- Prefer safe exploration (read first, then suggest changes).
+
+## Project
+- Name: {{project_name}}
+- Stack: {{stack}}
+- Description: {{project_description}}
+
+## Commands
+- Install: {{install_cmd}}
+- Dev: {{dev_cmd}}
+- Test: {{test_cmd}}
+- Lint: {{lint_cmd}}
+
+## Code style
+- {{indent_style}}
+- {{naming_convention}}
+```
+
+**Skill Bundle**:
+- `git-commit-helper`
+- `update-claude-md` (self-improving instructions)
+- `webapp-testing` (if web project)
+
+**MCP Servers**:
+- `filesystem` (project directory)
+- `github` (learn collaboration)
+- `context7` (prevent hallucinated APIs)
+
+**Hooks**:
+```json
+{
+ "PostToolUse": [{
+ "matcher": "Write|Edit",
+ "hooks": [{
+ "type": "command",
+ "command": "echo 'File changed — consider running tests'"
+ }]
+ }]
+}
+```
+
+**Learning Path**:
+1. [Sabrina's Tutorial](https://www.sabrina.dev/p/claude-code-full-course-for-beginners)
+2. [CLAUDE.md Best Practices — Builder.io](https://www.builder.io/blog/claude-md-guide)
+3. [Skills vs Commands vs Subagents](https://www.youngleaders.tech/p/claude-skills-commands-subagents-plugins)
+
+**Subagents**:
+- `code-reviewer` (Haiku, read-only, reviews before commit)
+
+---
+
+#### Profile 3: Teen Expert (age 13-17, skill: expert)
+
+**Slug**: `teen-expert`
+**Persona**: Advanced young developer. Competitive programming, open source contributor, or building real apps. Wants full power.
+
+**UI Configuration**:
+```json
+{
+ "fontSize": 14,
+ "fontScale": 1.0,
+ "motionLevel": "full",
+ "contrast": "standard",
+ "touchTargetMin": 44,
+ "layoutDensity": "compact",
+ "theme": "dark"
+}
+```
+
+**CLAUDE.md Template**: `power-user`
+- Verbosity: low (just do it, explain only if asked)
+- Safety: standard (guard secrets, allow normal dev ops)
+- Style: concise, technical
+- Target lines: 50-70
+
+**Template Content**:
+```markdown
+# CLAUDE.md
+
+## Rules
+- Keep changes minimal and focused.
+- Run {{test_cmd}} after behavior changes.
+- Commit with descriptive messages.
+- Don't touch .env, credentials, or CI config without asking.
+
+## Stack
+- {{stack}}
+- {{framework_details}}
+
+## Commands
+- Install: {{install_cmd}}
+- Dev: {{dev_cmd}}
+- Test: {{test_cmd}}
+- Build: {{build_cmd}}
+- Lint: {{lint_cmd}}
+
+## Architecture
+@docs/ARCHITECTURE.md
+
+## Style
+- {{indent_style}}
+- {{naming_convention}}
+- {{import_style}}
+```
+
+**Skill Bundle**:
+- `update-claude-md`
+- `git-commit-helper`
+- `compound-engineering` (plan-work-review-compound loop)
+
+**MCP Servers**:
+- `filesystem`, `github`, `context7`
+- `21st-dev-magic` (UI generation)
+- `brave-search` (web research)
+
+**Hooks**:
+```json
+{
+ "PreToolUse": [{
+ "matcher": "Bash",
+ "hooks": [{
+ "type": "command",
+ "command": "echo $TOOL_INPUT | grep -qE 'rm -rf /|sudo rm|format|mkfs' && exit 2 || exit 0"
+ }]
+ }],
+ "PostToolUse": [{
+ "matcher": "Write|Edit",
+ "hooks": [{
+ "type": "command",
+ "command": "npx prettier --write $TOOL_INPUT_PATH 2>/dev/null; exit 0"
+ }]
+ }]
+}
+```
+
+**Learning Path**:
+1. [Agent Skills with Anthropic — DeepLearning.AI](https://learn.deeplearning.ai/courses/agent-skills-with-anthropic)
+2. [Frontend Masters Pro AI Setup](https://frontendmasters.com/courses/pro-ai/)
+3. [FlorianBruniaux Ultimate Guide](https://github.com/FlorianBruniaux/claude-code-ultimate-guide)
+
+**Subagents**:
+- `code-reviewer` (Sonnet, comprehensive review)
+- `test-engineer` (write tests before implementation)
+
+---
+
+#### Profile 4: Young Adult Beginner (age 18-30, skill: beginner)
+
+**Slug**: `young-adult-beginner`
+**Persona**: Career starter, bootcamp grad, or hobbyist. May know one language/framework. Needs clear guidance without condescension.
+
+**UI Configuration**:
+```json
+{
+ "fontSize": 16,
+ "fontScale": 1.0,
+ "motionLevel": "normal",
+ "contrast": "standard",
+ "touchTargetMin": 44,
+ "layoutDensity": "comfortable",
+ "theme": "light"
+}
+```
+
+**CLAUDE.md Template**: `guided-starter`
+- Verbosity: high (explain reasoning, show alternatives)
+- Safety: high
+- Style: supportive, practical
+- Target lines: 50-70
+
+**Template Content**:
+```markdown
+# CLAUDE.md — {{project_name}}
+
+## How we work
+- Explain your reasoning for each change.
+- If there are multiple approaches, briefly mention the tradeoff.
+- After finishing, summarize what changed and how to undo it.
+- Ask one clear question if you're unsure, rather than guessing.
+
+## Safety
+- Don't edit .env, secrets, or config files unless I say so.
+- Don't run destructive commands (rm -rf, drop tables, force push).
+- Prefer read-only exploration first.
+
+## Project
+- Name: {{project_name}}
+- What it does: {{project_description}}
+- Stack: {{stack}}
+
+## Commands
+- Install: {{install_cmd}}
+- Dev: {{dev_cmd}}
+- Test: {{test_cmd}}
+
+## Style
+- {{indent_style}}
+- {{naming_convention}}
+```
+
+**Skill Bundle**:
+- `senior-explain` (clear explanations)
+- `git-commit-helper`
+- `update-claude-md`
+
+**MCP Servers**:
+- `filesystem` (project dir, read-only initially)
+- `context7` (prevent hallucinated APIs)
+- `memory` (persistent learning)
+
+**Hooks**: Same destructive-command guard as teen-beginner
+
+**Learning Path**:
+1. [Sabrina's 90-min Tutorial](https://www.sabrina.dev/p/claude-code-full-course-for-beginners)
+2. [Official Best Practices](https://code.claude.com/docs/en/best-practices)
+3. [CLAUDE.md Guide — Builder.io](https://www.builder.io/blog/claude-md-guide)
+
+**Subagents**: None initially
+
+---
+
+#### Profile 5: Young Adult Intermediate (age 18-30, skill: intermediate)
+
+**Slug**: `young-adult-intermediate`
+**Persona**: Working developer (1-5 years). Uses Git daily, knows at least one framework well. Wants to optimize workflow.
+
+**UI Configuration**:
+```json
+{
+ "fontSize": 15,
+ "fontScale": 1.0,
+ "motionLevel": "normal",
+ "contrast": "standard",
+ "touchTargetMin": 44,
+ "layoutDensity": "comfortable",
+ "theme": "auto"
+}
+```
+
+**CLAUDE.md Template**: `working-dev`
+- Verbosity: medium
+- Safety: standard
+- Style: professional, practical
+- Target lines: 60-90
+
+**Template Content**:
+```markdown
+# CLAUDE.md
+
+## Workflow
+- Make small, reviewable changes.
+- Create or update tests when behavior changes.
+- Keep changes within the requested scope.
+- Follow existing patterns in the codebase.
+
+## Safety
+- Don't modify secrets, credentials, or CI/CD config without asking.
+- Don't force push or reset hard.
+- Validate changes work before reporting success.
+
+## Project
+- Stack: {{stack}}
+- Architecture: @docs/ARCHITECTURE.md
+
+## Commands
+- Install: {{install_cmd}}
+- Dev: {{dev_cmd}}
+- Unit tests: {{unit_test_cmd}}
+- E2E tests: {{e2e_test_cmd}}
+- Lint: {{lint_cmd}}
+- Format: {{format_cmd}}
+
+## Code style
+Follow .claude/rules/code-style.md
+```
+
+**Rules (.claude/rules/)**:
+- `code-style.md` — indent, TypeScript strict, naming conventions
+- `testing.md` — test requirements, coverage expectations
+- `security.md` — no secrets in logs, validate external input
+
+**Skill Bundle**:
+- `update-claude-md`
+- `compound-engineering`
+- `git-commit-helper`
+- `simplify` (code review for quality)
+
+**MCP Servers**:
+- `filesystem`, `github`, `context7`
+- `playwright` (testing)
+- `brave-search`
+
+**Hooks**:
+```json
+{
+ "PreToolUse": [{
+ "matcher": "Bash",
+ "hooks": [{ "type": "command", "command": "echo $TOOL_INPUT | grep -qE 'rm -rf /|--force|--hard|DROP TABLE|TRUNCATE' && exit 2 || exit 0" }]
+ }],
+ "PostToolUse": [{
+ "matcher": "Write|Edit",
+ "hooks": [{ "type": "command", "command": "npx prettier --write $TOOL_INPUT_PATH 2>/dev/null; exit 0" }]
+ }]
+}
+```
+
+**Learning Path**:
+1. [Frontend Masters Pro AI Setup](https://frontendmasters.com/courses/pro-ai/)
+2. [Agent Skills — DeepLearning.AI](https://learn.deeplearning.ai/courses/agent-skills-with-anthropic)
+3. [Blake Crosley's Production Hooks](https://blakecrosley.com/blog/claude-code-hooks-tutorial)
+4. [ChrisWiles/claude-code-showcase](https://github.com/ChrisWiles/claude-code-showcase)
+
+**Subagents**:
+- `code-reviewer` (Sonnet)
+- `test-engineer` (write test plans)
+
+---
+
+#### Profile 6: Young Adult Expert (age 18-30, skill: expert)
+
+**Slug**: `young-adult-expert`
+**Persona**: Senior dev or tech lead (3-8 years). Builds architectures, mentors juniors, deploys to production. Wants full control and automation.
+
+**UI Configuration**:
+```json
+{
+ "fontSize": 14,
+ "fontScale": 1.0,
+ "motionLevel": "full",
+ "contrast": "standard",
+ "touchTargetMin": 40,
+ "layoutDensity": "compact",
+ "theme": "dark"
+}
+```
+
+**CLAUDE.md Template**: `full-control`
+- Verbosity: low (terse, no trailing summaries)
+- Safety: targeted (protect specific paths, allow everything else)
+- Style: direct, technical
+- Target lines: 40-60 (with heavy modular imports)
+
+**Template Content**:
+```markdown
+# CLAUDE.md
+
+## Rules
+- Follow .claude/rules/*.md
+- Use skills for repeatable workflows.
+- Hooks enforce non-negotiable steps.
+
+## Commands
+- Install: {{install_cmd}}
+- Dev: {{dev_cmd}}
+- Test: {{test_cmd}}
+- Build: {{build_cmd}}
+- Deploy: {{deploy_cmd}}
+
+## Architecture
+@docs/ARCHITECTURE.md
+
+## API conventions
+@docs/API_CONVENTIONS.md
+
+## Protected paths
+- {{protected_paths}}
+- Never change: {{immutable_files}}
+```
+
+**Rules (.claude/rules/)**:
+- `code-style.md`, `testing.md`, `security.md`
+- `deployment.md` — deploy procedures, rollback steps
+- `api-conventions.md` — endpoint patterns, versioning
+
+**Skill Bundle**:
+- `compound-engineering`
+- `update-claude-md`
+- `simplify`
+- `prd` (product requirements documents)
+- `build-feature` (autonomous task loop)
+
+**MCP Servers**:
+- `filesystem`, `github`, `context7`, `brave-search`
+- `21st-dev-magic` (UI generation)
+- `playwright` (testing)
+- `postgres-mcp` (database)
+- `notion` (project management)
+- `firecrawl` (web scraping)
+
+**Hooks**:
+```json
+{
+ "PreToolUse": [{
+ "matcher": "Bash",
+ "hooks": [{ "type": "command", "command": "echo $TOOL_INPUT | grep -qE 'rm -rf /|format|mkfs' && exit 2 || exit 0" }]
+ }],
+ "PostToolUse": [
+ { "matcher": "Write|Edit", "hooks": [{ "type": "command", "command": "npx prettier --write $TOOL_INPUT_PATH 2>/dev/null; exit 0" }] },
+ { "matcher": "Bash", "hooks": [{ "type": "command", "command": "echo $TOOL_INPUT | grep -q 'test' && echo 'Tests executed' || exit 0" }] }
+ ],
+ "Stop": [{
+ "matcher": "",
+ "hooks": [{ "type": "command", "command": "echo 'Session ended — consider updating CLAUDE.md'" }]
+ }]
+}
+```
+
+**Learning Path**:
+1. [Agent Skills — DeepLearning.AI](https://learn.deeplearning.ai/courses/agent-skills-with-anthropic)
+2. [MCP: Build Rich-Context AI Apps](https://learn.deeplearning.ai/courses/mcp-build-rich-context-ai-apps-with-anthropic)
+3. [alirezarezvani/claude-skills](https://github.com/alirezarezvani/claude-skills) (self-improving agent pattern)
+4. [Code w/ Claude Conference](https://anthropic.com/events/code-with-claude-2025)
+
+**Subagents**:
+- `code-reviewer` (Sonnet, comprehensive)
+- `architect-reviewer` (SOLID, layering)
+- `security-auditor` (OWASP, auth)
+- `test-engineer` (TDD workflow)
+- `performance-engineer` (profiling, optimization)
+
+---
+
+#### Profile 7: Professional Beginner (age 31-50, skill: beginner)
+
+**Slug**: `professional-beginner`
+**Persona**: Career switcher, product manager learning to code, or domain expert new to dev tools. Values clarity over speed.
+
+**UI Configuration**:
+```json
+{
+ "fontSize": 17,
+ "fontScale": 1.05,
+ "motionLevel": "reduced",
+ "contrast": "enhanced",
+ "touchTargetMin": 48,
+ "layoutDensity": "comfortable",
+ "theme": "light"
+}
+```
+
+**CLAUDE.md Template**: `clear-guidance`
+- Verbosity: high (explain reasoning, give context)
+- Safety: maximum
+- Style: professional, patient
+- Target lines: 50-70
+
+**Template Content**:
+```markdown
+# CLAUDE.md — {{project_name}}
+
+## How to help me
+- Use clear, jargon-free language when possible.
+- Explain technical terms when you first use them.
+- Walk me through each step before doing it.
+- After each change, tell me: what changed, why, and how to undo it.
+- If I make a mistake, help me fix it without judgment.
+
+## Safety (very important)
+- Never delete or overwrite files without my explicit permission.
+- Never run commands that could damage my computer or data.
+- Don't access my email, messages, or personal files.
+- Keep all passwords and sensitive data out of code files.
+- If you're unsure about something, always ask first.
+
+## My project
+- Name: {{project_name}}
+- What it does: {{project_description}}
+- Language: {{language}}
+
+## Commands (copy and paste these exactly)
+- To start: {{run_cmd}}
+- To test: {{test_cmd}}
+```
+
+**Skill Bundle**:
+- `senior-explain` (accessible language)
+- `git-commit-helper`
+
+**MCP Servers**:
+- `filesystem` (project dir, read-only)
+- `memory` (track learning progress)
+
+**Hooks**: Maximum destructive-command blocking
+
+**Learning Path**:
+1. [Sabrina's 90-min Tutorial](https://www.sabrina.dev/p/claude-code-full-course-for-beginners) (no coding required)
+2. [Official Best Practices](https://code.claude.com/docs/en/best-practices)
+
+**Subagents**: None
+
+---
+
+#### Profile 8: Professional Intermediate (age 31-50, skill: intermediate)
+
+**Slug**: `professional-intermediate`
+**Persona**: Team lead, engineering manager, or experienced IC. Manages projects, reviews PRs, cares about team standards.
+
+**UI Configuration**:
+```json
+{
+ "fontSize": 16,
+ "fontScale": 1.0,
+ "motionLevel": "normal",
+ "contrast": "standard",
+ "touchTargetMin": 44,
+ "layoutDensity": "comfortable",
+ "theme": "auto"
+}
+```
+
+**CLAUDE.md Template**: `team-lead`
+- Verbosity: medium
+- Safety: standard with team protections
+- Style: structured, process-oriented
+- Target lines: 70-100 (with .claude/rules/)
+
+**Template Content**:
+```markdown
+# CLAUDE.md
+
+## Team workflow
+- Follow .claude/rules/ for code style, testing, and security standards.
+- All changes should be reviewable — small commits, clear messages.
+- Update tests when behavior changes.
+- Don't modify shared config, CI/CD, or infrastructure without approval.
+
+## Project
+- Stack: {{stack}}
+- Team size: {{team_size}}
+- Architecture: @docs/ARCHITECTURE.md
+
+## Commands
+- Install: {{install_cmd}}
+- Dev: {{dev_cmd}}
+- Test: {{test_cmd}}
+- Lint: {{lint_cmd}}
+- Build: {{build_cmd}}
+
+## Review standards
+- @docs/REVIEW_CHECKLIST.md
+
+## Protected paths
+- .github/workflows/ — CI/CD changes need team review
+- {{protected_paths}}
+```
+
+**Rules (.claude/rules/)**:
+- `code-style.md`, `testing.md`, `security.md`
+- `review-standards.md` — PR review checklist
+
+**Skill Bundle**:
+- `compound-engineering`
+- `update-claude-md`
+- `simplify`
+- `prd` (requirements docs)
+- `code-review` (automated reviews)
+
+**MCP Servers**:
+- `filesystem`, `github`, `context7`
+- `notion` (project management)
+- `slack` (team communication)
+- `brave-search`
+
+**Hooks**: Auto-format + destructive guard + post-edit test reminder
+
+**Learning Path**:
+1. [Frontend Masters Pro AI Setup](https://frontendmasters.com/courses/pro-ai/)
+2. [Agent Skills — DeepLearning.AI](https://learn.deeplearning.ai/courses/agent-skills-with-anthropic)
+3. [ChrisWiles Showcase (integration patterns)](https://github.com/ChrisWiles/claude-code-showcase)
+4. [DataCamp Hooks Tutorial](https://www.datacamp.com/tutorial/claude-code-hooks)
+
+**Subagents**:
+- `code-reviewer` (Sonnet)
+- `architect-reviewer`
+- `test-engineer`
+
+---
+
+#### Profile 9: Professional Expert (age 31-50, skill: expert)
+
+**Slug**: `professional-expert`
+**Persona**: Staff/principal engineer, CTO, or architect. Designs systems, enforces policy, manages infrastructure. Wants full automation and compliance.
+
+**UI Configuration**:
+```json
+{
+ "fontSize": 14,
+ "fontScale": 1.0,
+ "motionLevel": "full",
+ "contrast": "standard",
+ "touchTargetMin": 40,
+ "layoutDensity": "compact",
+ "theme": "dark"
+}
+```
+
+**CLAUDE.md Template**: `enterprise-architect`
+- Verbosity: minimal (just do it)
+- Safety: policy-driven (hooks enforce rules, not instructions)
+- Style: terse, technical, standards-referenced
+- Target lines: 30-50 (heavy modular imports + hooks do enforcement)
+
+**Template Content**:
+```markdown
+# CLAUDE.md
+
+## Principles
+- Hooks enforce all non-negotiable rules (see .claude/settings.json).
+- .claude/rules/ contains all standards — no duplication here.
+- Skills handle repeatable workflows.
+- Subagents handle domain-specific reviews.
+
+## Commands
+- Install: {{install_cmd}}
+- Dev: {{dev_cmd}}
+- Test: {{test_cmd}}
+- Build: {{build_cmd}}
+- Deploy: {{deploy_cmd}}
+- Infra: {{infra_cmd}}
+
+## Architecture
+@docs/ARCHITECTURE.md
+
+## Private overlays (local only)
+@~/.claude/private/{{project_slug}}/PREFERENCES.md
+```
+
+**Rules (.claude/rules/)**:
+- `code-style.md`, `testing.md`, `security.md`, `deployment.md`, `api-conventions.md`
+- `compliance.md` — regulatory requirements
+- `incident-response.md` — runbook references
+
+**Skill Bundle**:
+- `compound-engineering`, `build-feature`, `simplify`, `prd`
+- `update-claude-md` (self-improving)
+- `refactor-agent` (code health)
+
+**MCP Servers**: Full suite — filesystem, github, context7, playwright, postgres, firecrawl, notion, slack, brave-search, 21st-dev-magic, datadog, e2b, perplexity
+
+**Hooks**: Full lifecycle — PreToolUse (security gate), PostToolUse (format + test), Stop (session summary), SessionStart (load context), PreCompact (save critical state)
+
+**Learning Path**:
+1. [MCP: Build Rich-Context AI Apps](https://learn.deeplearning.ai/courses/mcp-build-rich-context-ai-apps-with-anthropic)
+2. [Code w/ Claude Conference](https://anthropic.com/events/code-with-claude-2025)
+3. [alirezarezvani Self-Improving Agent](https://github.com/alirezarezvani/claude-skills)
+4. [disler/hooks-mastery (all 13 events)](https://github.com/disler/claude-code-hooks-mastery)
+
+**Subagents**: Full fleet — code-reviewer, architect-reviewer, security-auditor, test-engineer, performance-engineer, debugger, deployment-engineer
+
+---
+
+#### Profile 10: Pre-Senior Beginner (age 51-65, skill: beginner)
+
+**Slug**: `pre-senior-beginner`
+**Persona**: Late-career professional exploring tech, or non-technical executive wanting to understand AI tools. Larger text, explicit undo instructions, patient pace.
+
+**UI Configuration**:
+```json
+{
+ "fontSize": 18,
+ "fontScale": 1.1,
+ "motionLevel": "reduced",
+ "contrast": "enhanced",
+ "touchTargetMin": 48,
+ "layoutDensity": "spacious",
+ "theme": "light"
+}
+```
+
+**CLAUDE.md Template**: `gentle-start`
+- Verbosity: maximum (every step explained, every action confirmed)
+- Safety: maximum
+- Style: warm, patient, jargon-free
+- Target lines: 40-55
+
+**Template Content**:
+```markdown
+# CLAUDE.md — {{project_name}}
+
+## How to work with me
+- Use plain, everyday language.
+- Explain each step before doing it.
+- Wait for my OK before making changes.
+- After each change, tell me exactly what happened.
+- Always tell me how to undo what you just did.
+- One thing at a time — don't rush.
+
+## Important safety rules
+- Never delete my files without asking.
+- Never run anything that could damage my computer.
+- Keep all my private information safe.
+- If you're not sure, ask me first — it's always OK to ask.
+
+## My project
+- Name: {{project_name}}
+- What it does: {{project_description}}
+
+## How to run things
+- Start: {{run_cmd}}
+- Test: {{test_cmd}}
+```
+
+**Skill Bundle**:
+- `senior-explain` (accessible language)
+
+**MCP Servers**:
+- `filesystem` (read-only, single directory)
+- `memory` (remember preferences)
+
+**Hooks**: Maximum protection — block all destructive commands, block sudo, block unrecognized commands
+
+**Learning Path**:
+1. [Sabrina's 90-min Tutorial](https://www.sabrina.dev/p/claude-code-full-course-for-beginners)
+2. [Official Best Practices](https://code.claude.com/docs/en/best-practices)
+
+**Subagents**: None
+
+---
+
+#### Profile 11: Pre-Senior Intermediate (age 51-65, skill: intermediate)
+
+**Slug**: `pre-senior-intermediate`
+**Persona**: Experienced professional with tech skills — IT manager, analyst, or self-taught developer. Comfortable with tools but prefers structured workflows.
+
+**UI Configuration**:
+```json
+{
+ "fontSize": 17,
+ "fontScale": 1.05,
+ "motionLevel": "reduced",
+ "contrast": "enhanced",
+ "touchTargetMin": 48,
+ "layoutDensity": "comfortable",
+ "theme": "light"
+}
+```
+
+**CLAUDE.md Template**: `structured-workflow`
+- Verbosity: medium-high (explain decisions, checklist approach)
+- Safety: high
+- Style: organized, step-by-step
+- Target lines: 60-80
+
+**Template Content**:
+```markdown
+# CLAUDE.md — {{project_name}}
+
+## Workflow preferences
+- Work through tasks as a checklist — one step at a time.
+- Confirm with me before making significant changes.
+- Provide clear summaries after completing each task.
+- If something goes wrong, explain what happened and how to fix it.
+
+## Safety
+- Don't modify configuration files without asking.
+- Don't run destructive operations.
+- Keep credentials and sensitive data secure.
+
+## Project
+- Name: {{project_name}}
+- Stack: {{stack}}
+- Description: {{project_description}}
+
+## Commands
+- Install: {{install_cmd}}
+- Dev: {{dev_cmd}}
+- Test: {{test_cmd}}
+- Build: {{build_cmd}}
+
+## Documentation
+@docs/ARCHITECTURE.md
+```
+
+**Skill Bundle**:
+- `update-claude-md`
+- `git-commit-helper`
+- `simplify`
+- `excel-analysis` (familiar tool)
+- `email-composer`
+
+**MCP Servers**:
+- `filesystem`, `github`, `context7`
+- `notion` (organized project management)
+- `memory`
+
+**Hooks**: Destructive guard + post-edit notification
+
+**Learning Path**:
+1. [Frontend Masters Pro AI Setup](https://frontendmasters.com/courses/pro-ai/)
+2. [CLAUDE.md Best Practices — Builder.io](https://www.builder.io/blog/claude-md-guide)
+3. [DataCamp Hooks Tutorial](https://www.datacamp.com/tutorial/claude-code-hooks)
+
+**Subagents**:
+- `code-reviewer` (Haiku, lightweight)
+
+---
+
+#### Profile 12: Pre-Senior Expert (age 51-65, skill: expert)
+
+**Slug**: `pre-senior-expert`
+**Persona**: Veteran technologist or domain expert. Deep knowledge in their field, strong opinions about tools. Wants privacy-first, full control, enterprise-grade.
+
+**UI Configuration**:
+```json
+{
+ "fontSize": 16,
+ "fontScale": 1.0,
+ "motionLevel": "reduced",
+ "contrast": "enhanced",
+ "touchTargetMin": 44,
+ "layoutDensity": "comfortable",
+ "theme": "auto"
+}
+```
+
+**CLAUDE.md Template**: `veteran-expert`
+- Same as `enterprise-architect` but with enhanced contrast, slightly higher verbosity
+- Safety: policy-driven
+- Target lines: 40-60
+
+**Skill Bundle**: Same as professional-expert
+**MCP Servers**: Full suite with privacy emphasis (local servers preferred over remote)
+**Hooks**: Full lifecycle
+**Subagents**: Full fleet
+
+**Learning Path**:
+1. [MCP: Build Rich-Context AI Apps](https://learn.deeplearning.ai/courses/mcp-build-rich-context-ai-apps-with-anthropic)
+2. [alirezarezvani Self-Improving Agent](https://github.com/alirezarezvani/claude-skills)
+3. [Code w/ Claude Conference](https://anthropic.com/events/code-with-claude-2025)
+
+---
+
+#### Profile 13: Senior Beginner (age 65+, skill: beginner)
+
+**Slug**: `senior-beginner`
+**Persona**: Retired or semi-retired adult exploring technology. May have limited computer experience. Needs the most accessible, patient, safe experience possible.
+
+**UI Configuration**:
+```json
+{
+ "fontSize": 20,
+ "fontScale": 1.2,
+ "motionLevel": "minimal",
+ "contrast": "high",
+ "touchTargetMin": 56,
+ "layoutDensity": "spacious",
+ "theme": "light",
+ "reducedMotion": true,
+ "hapticFeedback": true
+}
+```
+
+**CLAUDE.md Template**: `senior-safe`
+- Verbosity: maximum (every single step, confirm before and after)
+- Safety: maximum (most restrictive hooks, read-only by default)
+- Style: warm, encouraging, never condescending, extremely simple
+- Target lines: 30-40
+
+**Template Content**:
+```markdown
+# CLAUDE.md
+
+## How to help me
+- Use simple, everyday words.
+- Do one small thing at a time.
+- Always ask before changing anything.
+- After each step, tell me what happened in plain language.
+- Show me how to undo anything you do.
+- Be patient — I'm learning.
+
+## Safety (most important)
+- Never change or delete my files without asking first.
+- Never run anything that could harm my computer.
+- Keep everything safe and private.
+- When in doubt, always ask me.
+
+## My project
+- Name: {{project_name}}
+- What it does: {{project_description}}
+```
+
+**Skill Bundle**:
+- `senior-explain` (maximum accessibility)
+
+**MCP Servers**:
+- `memory` (remember preferences across sessions)
+- `filesystem` (read-only, single project directory)
+
+**Hooks**: Maximum protection — deny all potentially destructive operations
+
+**Learning Path**:
+1. [Sabrina's 90-min Tutorial](https://www.sabrina.dev/p/claude-code-full-course-for-beginners) (start here)
+
+**Subagents**: None
+
+---
+
+#### Profile 14: Senior Intermediate (age 65+, skill: intermediate)
+
+**Slug**: `senior-intermediate`
+**Persona**: Experienced older adult who has used computers extensively — retired IT professional, lifelong hobbyist programmer, or technical writer. Comfortable with tools but values clarity and safety.
+
+**UI Configuration**:
+```json
+{
+ "fontSize": 18,
+ "fontScale": 1.15,
+ "motionLevel": "reduced",
+ "contrast": "high",
+ "touchTargetMin": 52,
+ "layoutDensity": "spacious",
+ "theme": "light",
+ "reducedMotion": true
+}
+```
+
+**CLAUDE.md Template**: `confident-senior`
+- Verbosity: medium (explain key decisions, skip obvious stuff)
+- Safety: high
+- Style: respectful, organized
+- Target lines: 50-70
+
+**Template Content**:
+```markdown
+# CLAUDE.md — {{project_name}}
+
+## Working style
+- Explain your reasoning for non-obvious decisions.
+- Work methodically — complete one task before starting the next.
+- Run tests after making changes.
+- Provide a summary with undo steps when done.
+
+## Safety
+- Don't modify system files, credentials, or backups.
+- Don't run destructive commands without explicit permission.
+- Prefer safe, reversible operations.
+
+## Project
+- Name: {{project_name}}
+- Stack: {{stack}}
+- Description: {{project_description}}
+
+## Commands
+- Install: {{install_cmd}}
+- Dev: {{dev_cmd}}
+- Test: {{test_cmd}}
+```
+
+**Skill Bundle**:
+- `update-claude-md`
+- `git-commit-helper`
+- `senior-explain`
+- `simplify`
+
+**MCP Servers**:
+- `filesystem`, `github` (read-focused), `context7`, `memory`
+
+**Hooks**: Destructive guard + post-change notification
+
+**Learning Path**:
+1. [Sabrina's Tutorial](https://www.sabrina.dev/p/claude-code-full-course-for-beginners)
+2. [CLAUDE.md Guide — Builder.io](https://www.builder.io/blog/claude-md-guide)
+3. [Skills vs Commands vs Subagents](https://www.youngleaders.tech/p/claude-skills-commands-subagents-plugins)
+
+**Subagents**:
+- `code-reviewer` (Haiku, lightweight)
+
+---
+
+#### Profile 15: Senior Expert (age 65+, skill: expert)
+
+**Slug**: `senior-expert`
+**Persona**: Retired senior engineer, professor, or domain expert with deep technical knowledge. Values privacy, control, and efficiency. Has strong preferences and wants the system to respect them.
+
+**UI Configuration**:
+```json
+{
+ "fontSize": 17,
+ "fontScale": 1.1,
+ "motionLevel": "reduced",
+ "contrast": "enhanced",
+ "touchTargetMin": 48,
+ "layoutDensity": "comfortable",
+ "theme": "auto",
+ "reducedMotion": true
+}
+```
+
+**CLAUDE.md Template**: `veteran-privacy`
+- Same capability as `enterprise-architect` with privacy-first defaults
+- Enhanced contrast and larger text
+- Local-only MCP servers preferred
+- Private vault enabled by default
+
+**Template Content**:
+```markdown
+# CLAUDE.md
+
+## Principles
+- Hooks enforce all critical rules.
+- .claude/rules/ contains standards.
+- Skills handle repeatable workflows.
+- Privacy is non-negotiable — keep data local.
+
+## Commands
+- Install: {{install_cmd}}
+- Dev: {{dev_cmd}}
+- Test: {{test_cmd}}
+- Build: {{build_cmd}}
+
+## Architecture
+@docs/ARCHITECTURE.md
+
+## Private overlays
+@~/.claude/private/{{project_slug}}/PREFERENCES.md
+@~/.claude/private/{{project_slug}}/SECRETS_HANDLING.md
+```
+
+**Skill Bundle**: Same as professional-expert
+**MCP Servers**: Full suite, preference for local over remote
+**Hooks**: Full lifecycle
+**Subagents**: Full fleet
+
+**Learning Path**:
+1. [MCP Course — DeepLearning.AI](https://learn.deeplearning.ai/courses/mcp-build-rich-context-ai-apps-with-anthropic)
+2. [Agent Skills — DeepLearning.AI](https://learn.deeplearning.ai/courses/agent-skills-with-anthropic)
+3. [Code w/ Claude Conference](https://anthropic.com/events/code-with-claude-2025)
+
+---
+
+## 3. CLAUDE.md Analyzer
+
+### 3.1 Upload Interface
+
+Users can:
+- **Drag and drop** a CLAUDE.md file
+- **Paste** content directly
+- **Connect** to a Git repo and pull CLAUDE.md + .claude/ directory
+- **Import** from URL (GitHub raw file URL)
+
+### 3.2 Analysis Engine — 12 Scoring Criteria
+
+| # | Criterion | Weight | Pass Condition |
+|---|-----------|--------|---------------|
+| 1 | **Line count** | 15% | <200 lines (CLAUDE.md), <80 lines (project-level) |
+| 2 | **Structure** | 10% | Has clear markdown headers, organized sections |
+| 3 | **Specificity** | 12% | Contains concrete commands (not "run tests" but actual command) |
+| 4 | **Safety rules** | 12% | Has explicit safety/protection section |
+| 5 | **Build commands** | 8% | Has install, dev, test, lint commands defined |
+| 6 | **Code style** | 5% | Defines or delegates to linter/rules file |
+| 7 | **Modular imports** | 10% | Uses @imports or .claude/rules/ (not monolithic) |
+| 8 | **No secrets** | 10% | No API keys, passwords, tokens in plaintext |
+| 9 | **No bloat** | 8% | No contradictory rules, no redundant sections |
+| 10 | **Skills/subagents** | 3% | References or recommends appropriate skills |
+| 11 | **Hook enforcement** | 3% | Critical rules enforced via hooks (not just instructions) |
+| 12 | **MCP configuration** | 4% | MCP servers configured and scoped appropriately |
+
+### 3.3 Report Output
+
+**Score**: Letter grade (A+ through F) + percentage + radar chart
+
+**Sections**:
+- **Pros** (green) — What's working well, with specific line references
+- **Cons** (red) — Issues found, with severity (critical/warning/info)
+- **Suggestions** (blue) — Actionable improvements, each with:
+ - Description of what to change
+ - Before/after preview
+ - One-click "Apply" button
+ - Checkbox for batch selection
+
+### 3.4 Select & Apply Workflow
+
+1. User reviews suggestions
+2. Checks boxes next to desired suggestions (or "Select All")
+3. Clicks "Preview Changes" → sees unified diff
+4. Clicks "Apply" → changes are applied to the CLAUDE.md
+5. Can download result or push to Git
+
+### 3.5 Mass Record Update
+
+For users managing multiple files (.claude/rules/, skills, hooks):
+
+1. **Table view** — all records listed with columns: file, type, status, last modified, score
+2. **Multi-select** — checkboxes on each row
+3. **Bulk actions bar** appears when items selected:
+ - "Analyze Selected" — run analyzer on all
+ - "Apply All Suggestions" — auto-apply passing suggestions
+ - "Update Template" — switch to a different profile template
+ - "Export Bundle" — download as .zip
+ - "Delete Selected" — with confirmation
+
+### 3.6 Integration with Profile System
+
+When a user uploads their CLAUDE.md:
+1. Analyzer detects their current "profile level" from the content
+2. Suggests a matching ClawCoder profile
+3. Shows gap analysis: "Your config is closest to `young-adult-intermediate` but is missing: hooks, modular rules, MCP config"
+4. Offers one-click upgrade to the full profile bundle
+
+---
+
+## 4. Data Model (PostgreSQL)
+
+### Profile table
+```sql
+CREATE TABLE profiles (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ slug TEXT UNIQUE NOT NULL,
+ age_group TEXT NOT NULL CHECK (age_group IN ('teen', 'young_adult', 'professional', 'pre_senior', 'senior')),
+ skill_level TEXT NOT NULL CHECK (skill_level IN ('beginner', 'intermediate', 'expert')),
+ display_name TEXT NOT NULL,
+ persona_description TEXT,
+ ui_config JSONB NOT NULL,
+ template_slug TEXT NOT NULL,
+ skill_bundle JSONB NOT NULL DEFAULT '[]',
+ mcp_servers JSONB NOT NULL DEFAULT '[]',
+ hooks_config JSONB NOT NULL DEFAULT '{}',
+ subagents JSONB NOT NULL DEFAULT '[]',
+ learning_path JSONB NOT NULL DEFAULT '[]',
+ onboarding_config JSONB NOT NULL DEFAULT '{}',
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
+);
+```
+
+### Analysis results table
+```sql
+CREATE TABLE analyses (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ user_id UUID REFERENCES users(id),
+ input_content TEXT NOT NULL,
+ input_source TEXT, -- 'upload', 'paste', 'git', 'url'
+ overall_score NUMERIC(5,2),
+ letter_grade TEXT,
+ criteria_scores JSONB NOT NULL, -- {criterion: {score, max, findings[]}}
+ pros JSONB NOT NULL DEFAULT '[]',
+ cons JSONB NOT NULL DEFAULT '[]',
+ suggestions JSONB NOT NULL DEFAULT '[]',
+ detected_profile_slug TEXT,
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
+);
+```
+
+### Applied suggestions table
+```sql
+CREATE TABLE applied_suggestions (
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
+ analysis_id UUID REFERENCES analyses(id),
+ suggestion_index INT NOT NULL,
+ applied_at TIMESTAMPTZ NOT NULL DEFAULT now(),
+ result_content TEXT -- the output after applying
+);
+```
+
+---
+
+## 5. Marketplace Integration
+
+### Skill Discovery by Profile
+
+Each profile has a `skill_bundle` that references real marketplace skills. The app queries:
+1. [SkillHub](https://skillhub.club) (34,800+ skills) via `npx @skill-hub/cli`
+2. [OpenClaw/ClawHub](https://github.com/VoltAgent/awesome-openclaw-skills) (5,211 curated) via `clawhub install`
+3. [Anthropic Official](https://github.com/anthropics/claude-plugins-official) via `/plugin install`
+4. [vercel-labs/skills](https://github.com/vercel-labs/skills) via `npx skills add` (cross-agent)
+
+### Trust Gating
+
+Before any skill/MCP/plugin is recommended:
+1. Check against blocklist (mcp-remote CVE, postmark-mcp, etc.)
+2. Verify source repo exists and is maintained
+3. Check star count / download count
+4. Flag if it fetches untrusted content (prompt injection risk)
+5. Require explicit user approval before installation
+
+---
+
+## 6. Technology Stack
+
+- **Frontend**: Next.js 15 (App Router) + Tailwind CSS + Framer Motion
+- **Backend**: Next.js API routes + PostgreSQL
+- **Auth**: NextAuth.js
+- **State**: Zustand (client) + PostgreSQL (server)
+- **Deployment**: Vercel or self-hosted (this server)
+- **Package**: npm distribution for CLI companion
+
+---
+
+## 7. Ecosystem References
+
+### Skills & Agents
+- [awesome-claude-code](https://github.com/hesreallyhim/awesome-claude-code) — 30.2k stars, master directory
+- [vercel-labs/skills](https://github.com/vercel-labs/skills) — Cross-agent installation CLI
+- [alirezarezvani/claude-skills](https://github.com/alirezarezvani/claude-skills) — 205 skills, self-improving agent
+- [anthropics/skills](https://github.com/anthropics/skills) — Official Anthropic reference
+- [VoltAgent/awesome-claude-code-subagents](https://github.com/VoltAgent/awesome-claude-code-subagents) — 127+ subagents
+- [0xfurai/claude-code-subagents](https://github.com/0xfurai/claude-code-subagents) — 100+ production subagents
+- [ChrisWiles/claude-code-showcase](https://github.com/ChrisWiles/claude-code-showcase) — Full integration example
+- [disler/claude-code-hooks-mastery](https://github.com/disler/claude-code-hooks-mastery) — All 13 hook events
+
+### MCP Servers
+- [punkpeye/awesome-mcp-servers](https://github.com/punkpeye/awesome-mcp-servers) — 82.9k stars, 1200+ servers
+- [Official MCP Registry](https://registry.modelcontextprotocol.io/)
+- [Context7](https://github.com/upstash/context7) — #1 most installed
+- [21st.dev Magic](https://github.com/21st-dev/magic-mcp) — UI generation, 4.4k stars
+- [Playwright MCP](https://github.com/microsoft/playwright-mcp) — Microsoft, browser automation
+- [Firecrawl MCP](https://github.com/firecrawl/firecrawl-mcp-server) — Web scraping
+
+### Learning Resources
+- [Agent Skills — DeepLearning.AI](https://learn.deeplearning.ai/courses/agent-skills-with-anthropic)
+- [MCP Course — DeepLearning.AI](https://learn.deeplearning.ai/courses/mcp-build-rich-context-ai-apps-with-anthropic)
+- [Frontend Masters Pro AI](https://frontendmasters.com/courses/pro-ai/)
+- [Sabrina's 90-min Tutorial](https://www.sabrina.dev/p/claude-code-full-course-for-beginners)
+- [Anthropic Skilljar Training](https://anthropic.skilljar.com/introduction-to-agent-skills)
+- [agentskills.io Specification](https://agentskills.io/specification)
+
+### Marketplaces
+- [SkillHub](https://skillhub.club) — 34,800+ skills
+- [OpenClaw](https://openclawskill.ai) — 13,729 skills
+- [PulseMCP](https://www.pulsemcp.com) — 12,370+ MCP servers
+- [FastMCP](https://fastmcp.me) — 19,922+ MCP servers
+
+### Security References
+- [AgentSeal MCP Security Findings](https://agentseal.org/blog/mcp-server-security-findings) — 66% of servers had issues
+- [mcp-remote CVE-2025-6514](https://snyk.io/blog/malicious-mcp-server-on-npm-postmark-mcp-harvests-emails/) — CVSS 9.6
+- [Prompt Injection via MCP — Simon Willison](https://simonwillison.net/2025/Apr/9/mcp-prompt-injection/)
diff --git a/ecosystem.config.cjs b/ecosystem.config.cjs
new file mode 100644
index 0000000..30438cc
--- /dev/null
+++ b/ecosystem.config.cjs
@@ -0,0 +1,12 @@
+module.exports = {
+ apps: [{
+ name: 'clawcoder',
+ script: 'node_modules/.bin/next',
+ args: 'start -p 7350',
+ cwd: '/root/Projects/ClawCoder',
+ env: {
+ NODE_ENV: 'production',
+ PORT: 7350,
+ },
+ }],
+}
diff --git a/eslint.config.mjs b/eslint.config.mjs
new file mode 100644
index 0000000..05e726d
--- /dev/null
+++ b/eslint.config.mjs
@@ -0,0 +1,18 @@
+import { defineConfig, globalIgnores } from "eslint/config";
+import nextVitals from "eslint-config-next/core-web-vitals";
+import nextTs from "eslint-config-next/typescript";
+
+const eslintConfig = defineConfig([
+ ...nextVitals,
+ ...nextTs,
+ // Override default ignores of eslint-config-next.
+ globalIgnores([
+ // Default ignores of eslint-config-next:
+ ".next/**",
+ "out/**",
+ "build/**",
+ "next-env.d.ts",
+ ]),
+]);
+
+export default eslintConfig;
diff --git a/next.config.ts b/next.config.ts
new file mode 100644
index 0000000..e9ffa30
--- /dev/null
+++ b/next.config.ts
@@ -0,0 +1,7 @@
+import type { NextConfig } from "next";
+
+const nextConfig: NextConfig = {
+ /* config options here */
+};
+
+export default nextConfig;
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..99b62df
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,9393 @@
+{
+ "name": "clawcoder",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "clawcoder",
+ "version": "0.1.0",
+ "dependencies": {
+ "@anthropic-ai/sdk": "^0.80.0",
+ "@prisma/adapter-pg": "^7.5.0",
+ "@prisma/client": "^7.5.0",
+ "dotenv": "^17.3.1",
+ "framer-motion": "^12.38.0",
+ "mustache": "^4.2.0",
+ "next": "16.2.1",
+ "pg": "^8.20.0",
+ "prisma": "^7.5.0",
+ "react": "19.2.4",
+ "react-dom": "19.2.4",
+ "zustand": "^5.0.12"
+ },
+ "devDependencies": {
+ "@tailwindcss/postcss": "^4",
+ "@testing-library/jest-dom": "^6.9.1",
+ "@testing-library/react": "^16.3.2",
+ "@types/mustache": "^4.2.6",
+ "@types/node": "^20",
+ "@types/pg": "^8.20.0",
+ "@types/react": "^19",
+ "@types/react-dom": "^19",
+ "eslint": "^9",
+ "eslint-config-next": "16.2.1",
+ "jsdom": "^29.0.1",
+ "tailwindcss": "^4",
+ "typescript": "^5",
+ "vitest": "^4.1.0"
+ }
+ },
+ "node_modules/@adobe/css-tools": {
+ "version": "4.4.4",
+ "resolved": "https://registry.npmjs.org/@adobe/css-tools/-/css-tools-4.4.4.tgz",
+ "integrity": "sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@anthropic-ai/sdk": {
+ "version": "0.80.0",
+ "resolved": "https://registry.npmjs.org/@anthropic-ai/sdk/-/sdk-0.80.0.tgz",
+ "integrity": "sha512-WeXLn7zNVk3yjeshn+xZHvld6AoFUOR3Sep6pSoHho5YbSi6HwcirqgPA5ccFuW8QTVJAAU7N8uQQC6Wa9TG+g==",
+ "license": "MIT",
+ "dependencies": {
+ "json-schema-to-ts": "^3.1.1"
+ },
+ "bin": {
+ "anthropic-ai-sdk": "bin/cli"
+ },
+ "peerDependencies": {
+ "zod": "^3.25.0 || ^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "zod": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@asamuzakjp/css-color": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.0.1.tgz",
+ "integrity": "sha512-2SZFvqMyvboVV1d15lMf7XiI3m7SDqXUuKaTymJYLN6dSGadqp+fVojqJlVoMlbZnlTmu3S0TLwLTJpvBMO1Aw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/css-calc": "^3.1.1",
+ "@csstools/css-color-parser": "^4.0.2",
+ "@csstools/css-parser-algorithms": "^4.0.0",
+ "@csstools/css-tokenizer": "^4.0.0",
+ "lru-cache": "^11.2.6"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@asamuzakjp/css-color/node_modules/lru-cache": {
+ "version": "11.2.7",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz",
+ "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/@asamuzakjp/dom-selector": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-7.0.4.tgz",
+ "integrity": "sha512-jXR6x4AcT3eIrS2fSNAwJpwirOkGcd+E7F7CP3zjdTqz9B/2huHOL8YJZBgekKwLML+u7qB/6P1LXQuMScsx0w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@asamuzakjp/nwsapi": "^2.3.9",
+ "bidi-js": "^1.0.3",
+ "css-tree": "^3.2.1",
+ "is-potential-custom-element-name": "^1.0.1",
+ "lru-cache": "^11.2.7"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ }
+ },
+ "node_modules/@asamuzakjp/dom-selector/node_modules/lru-cache": {
+ "version": "11.2.7",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz",
+ "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/@asamuzakjp/nwsapi": {
+ "version": "2.3.9",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz",
+ "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
+ "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz",
+ "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz",
+ "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.29.2",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz",
+ "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@bramus/specificity": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/@bramus/specificity/-/specificity-2.4.2.tgz",
+ "integrity": "sha512-ctxtJ/eA+t+6q2++vj5j7FYX3nRu311q1wfYH3xjlLOsczhlhxAg2FWNUXhpGvAw3BWo1xBcvOV6/YLc2r5FJw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "css-tree": "^3.0.0"
+ },
+ "bin": {
+ "specificity": "bin/cli.js"
+ }
+ },
+ "node_modules/@chevrotain/cst-dts-gen": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/@chevrotain/cst-dts-gen/-/cst-dts-gen-10.5.0.tgz",
+ "integrity": "sha512-lhmC/FyqQ2o7pGK4Om+hzuDrm9rhFYIJ/AXoQBeongmn870Xeb0L6oGEiuR8nohFNL5sMaQEJWCxr1oIVIVXrw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@chevrotain/gast": "10.5.0",
+ "@chevrotain/types": "10.5.0",
+ "lodash": "4.17.21"
+ }
+ },
+ "node_modules/@chevrotain/gast": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/@chevrotain/gast/-/gast-10.5.0.tgz",
+ "integrity": "sha512-pXdMJ9XeDAbgOWKuD1Fldz4ieCs6+nLNmyVhe2gZVqoO7v8HXuHYs5OV2EzUtbuai37TlOAQHrTDvxMnvMJz3A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@chevrotain/types": "10.5.0",
+ "lodash": "4.17.21"
+ }
+ },
+ "node_modules/@chevrotain/types": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/@chevrotain/types/-/types-10.5.0.tgz",
+ "integrity": "sha512-f1MAia0x/pAVPWH/T73BJVyO2XU5tI4/iE7cnxb7tqdNTNhQI3Uq3XkqcoteTmD4t1aM0LbHCJOhgIDn07kl2A==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@chevrotain/utils": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/@chevrotain/utils/-/utils-10.5.0.tgz",
+ "integrity": "sha512-hBzuU5+JjB2cqNZyszkDHZgOSrUUT8V3dhgRl8Q9Gp6dAj/H5+KILGjbhDpc3Iy9qmqlm/akuOI2ut9VUtzJxQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@csstools/color-helpers": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.0.2.tgz",
+ "integrity": "sha512-LMGQLS9EuADloEFkcTBR3BwV/CGHV7zyDxVRtVDTwdI2Ca4it0CCVTT9wCkxSgokjE5Ho41hEPgb8OEUwoXr6Q==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "engines": {
+ "node": ">=20.19.0"
+ }
+ },
+ "node_modules/@csstools/css-calc": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.1.1.tgz",
+ "integrity": "sha512-HJ26Z/vmsZQqs/o3a6bgKslXGFAungXGbinULZO3eMsOyNJHeBBZfup5FiZInOghgoM4Hwnmw+OgbJCNg1wwUQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^4.0.0",
+ "@csstools/css-tokenizer": "^4.0.0"
+ }
+ },
+ "node_modules/@csstools/css-color-parser": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.0.2.tgz",
+ "integrity": "sha512-0GEfbBLmTFf0dJlpsNU7zwxRIH0/BGEMuXLTCvFYxuL1tNhqzTbtnFICyJLTNK4a+RechKP75e7w42ClXSnJQw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/color-helpers": "^6.0.2",
+ "@csstools/css-calc": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^4.0.0",
+ "@csstools/css-tokenizer": "^4.0.0"
+ }
+ },
+ "node_modules/@csstools/css-parser-algorithms": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz",
+ "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.19.0"
+ },
+ "peerDependencies": {
+ "@csstools/css-tokenizer": "^4.0.0"
+ }
+ },
+ "node_modules/@csstools/css-syntax-patches-for-csstree": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.1.tgz",
+ "integrity": "sha512-BvqN0AMWNAnLk9G8jnUT77D+mUbY/H2b3uDTvg2isJkHaOufUE2R3AOwxWo7VBQKT1lOdwdvorddo2B/lk64+w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "peerDependencies": {
+ "css-tree": "^3.2.1"
+ },
+ "peerDependenciesMeta": {
+ "css-tree": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@csstools/css-tokenizer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz",
+ "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.19.0"
+ }
+ },
+ "node_modules/@electric-sql/pglite": {
+ "version": "0.3.15",
+ "resolved": "https://registry.npmjs.org/@electric-sql/pglite/-/pglite-0.3.15.tgz",
+ "integrity": "sha512-Cj++n1Mekf9ETfdc16TlDi+cDDQF0W7EcbyRHYOAeZdsAe8M/FJg18itDTSwyHfar2WIezawM9o0EKaRGVKygQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@electric-sql/pglite-socket": {
+ "version": "0.0.20",
+ "resolved": "https://registry.npmjs.org/@electric-sql/pglite-socket/-/pglite-socket-0.0.20.tgz",
+ "integrity": "sha512-J5nLGsicnD9wJHnno9r+DGxfcZWh+YJMCe0q/aCgtG6XOm9Z7fKeite8IZSNXgZeGltSigM9U/vAWZQWdgcSFg==",
+ "license": "Apache-2.0",
+ "bin": {
+ "pglite-server": "dist/scripts/server.js"
+ },
+ "peerDependencies": {
+ "@electric-sql/pglite": "0.3.15"
+ }
+ },
+ "node_modules/@electric-sql/pglite-tools": {
+ "version": "0.2.20",
+ "resolved": "https://registry.npmjs.org/@electric-sql/pglite-tools/-/pglite-tools-0.2.20.tgz",
+ "integrity": "sha512-BK50ZnYa3IG7ztXhtgYf0Q7zijV32Iw1cYS8C+ThdQlwx12V5VZ9KRJ42y82Hyb4PkTxZQklVQA9JHyUlex33A==",
+ "license": "Apache-2.0",
+ "peerDependencies": {
+ "@electric-sql/pglite": "0.3.15"
+ }
+ },
+ "node_modules/@emnapi/core": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.1.tgz",
+ "integrity": "sha512-mukuNALVsoix/w1BJwFzwXBN/dHeejQtuVzcDsfOEsdpCumXb/E9j8w11h5S54tT1xhifGfbbSm/ICrObRb3KA==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/wasi-threads": "1.2.0",
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/runtime": {
+ "version": "1.9.1",
+ "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.1.tgz",
+ "integrity": "sha512-VYi5+ZVLhpgK4hQ0TAjiQiZ6ol0oe4mBx7mVv7IflsiEp0OWoVsp/+f9Vc1hOhE0TtkORVrI1GvzyreqpgWtkA==",
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@emnapi/wasi-threads": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.0.tgz",
+ "integrity": "sha512-N10dEJNSsUx41Z6pZsXU8FjPjpBEplgH24sfkmITrBED1/U2Esum9F3lfLrMjKHHjmi557zQn7kR9R+XWXu5Rg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/config-array": {
+ "version": "0.21.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz",
+ "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/object-schema": "^2.1.7",
+ "debug": "^4.3.1",
+ "minimatch": "^3.1.5"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/config-helpers": {
+ "version": "0.4.2",
+ "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz",
+ "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/core": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
+ "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/json-schema": "^7.0.15"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz",
+ "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.14.0",
+ "debug": "^4.3.2",
+ "espree": "^10.0.1",
+ "globals": "^14.0.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.1",
+ "minimatch": "^3.1.5",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "9.39.4",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz",
+ "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ }
+ },
+ "node_modules/@eslint/object-schema": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz",
+ "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@eslint/plugin-kit": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz",
+ "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@eslint/core": "^0.17.0",
+ "levn": "^0.4.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ }
+ },
+ "node_modules/@exodus/bytes": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/@exodus/bytes/-/bytes-1.15.0.tgz",
+ "integrity": "sha512-UY0nlA+feH81UGSHv92sLEPLCeZFjXOuHhrIo0HQydScuQc8s0A7kL/UdgwgDq8g8ilksmuoF35YVTNphV2aBQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "@noble/hashes": "^1.8.0 || ^2.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@noble/hashes": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@hono/node-server": {
+ "version": "1.19.9",
+ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.9.tgz",
+ "integrity": "sha512-vHL6w3ecZsky+8P5MD+eFfaGTyCeOHUIFYMGpQGbrBTSmNNoxv0if69rEZ5giu36weC5saFuznL411gRX7bJDw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.14.1"
+ },
+ "peerDependencies": {
+ "hono": "^4"
+ }
+ },
+ "node_modules/@humanfs/core": {
+ "version": "0.19.1",
+ "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
+ "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanfs/node": {
+ "version": "0.16.7",
+ "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz",
+ "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanfs/core": "^0.19.1",
+ "@humanwhocodes/retry": "^0.4.0"
+ },
+ "engines": {
+ "node": ">=18.18.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/retry": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz",
+ "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18.18"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@img/colour": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.1.0.tgz",
+ "integrity": "sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==",
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@img/sharp-darwin-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz",
+ "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-darwin-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz",
+ "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-darwin-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz",
+ "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-darwin-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz",
+ "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz",
+ "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz",
+ "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-ppc64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz",
+ "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-riscv64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz",
+ "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-s390x": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz",
+ "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linux-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz",
+ "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-arm64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz",
+ "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-libvips-linuxmusl-x64": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz",
+ "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz",
+ "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==",
+ "cpu": [
+ "arm"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz",
+ "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-ppc64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz",
+ "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-ppc64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-riscv64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz",
+ "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-riscv64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-s390x": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz",
+ "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==",
+ "cpu": [
+ "s390x"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-s390x": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linux-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz",
+ "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linux-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz",
+ "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-linuxmusl-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz",
+ "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.4"
+ }
+ },
+ "node_modules/@img/sharp-wasm32": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz",
+ "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==",
+ "cpu": [
+ "wasm32"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/runtime": "^1.7.0"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-arm64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz",
+ "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-ia32": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz",
+ "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==",
+ "cpu": [
+ "ia32"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@img/sharp-win32-x64": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz",
+ "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "Apache-2.0 AND LGPL-3.0-or-later",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@mrleebo/prisma-ast": {
+ "version": "0.13.1",
+ "resolved": "https://registry.npmjs.org/@mrleebo/prisma-ast/-/prisma-ast-0.13.1.tgz",
+ "integrity": "sha512-XyroGQXcHrZdvmrGJvsA9KNeOOgGMg1Vg9OlheUsBOSKznLMDl+YChxbkboRHvtFYJEMRYmlV3uoo/njCw05iw==",
+ "license": "MIT",
+ "dependencies": {
+ "chevrotain": "^10.5.0",
+ "lilconfig": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/@napi-rs/wasm-runtime": {
+ "version": "0.2.12",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz",
+ "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.4.3",
+ "@emnapi/runtime": "^1.4.3",
+ "@tybys/wasm-util": "^0.10.0"
+ }
+ },
+ "node_modules/@next/env": {
+ "version": "16.2.1",
+ "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.1.tgz",
+ "integrity": "sha512-n8P/HCkIWW+gVal2Z8XqXJ6aB3J0tuM29OcHpCsobWlChH/SITBs1DFBk/HajgrwDkqqBXPbuUuzgDvUekREPg==",
+ "license": "MIT"
+ },
+ "node_modules/@next/eslint-plugin-next": {
+ "version": "16.2.1",
+ "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.2.1.tgz",
+ "integrity": "sha512-r0epZGo24eT4g08jJlg2OEryBphXqO8aL18oajoTKLzHJ6jVr6P6FI58DLMug04MwD3j8Fj0YK0slyzneKVyzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-glob": "3.3.1"
+ }
+ },
+ "node_modules/@next/swc-darwin-arm64": {
+ "version": "16.2.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.1.tgz",
+ "integrity": "sha512-BwZ8w8YTaSEr2HIuXLMLxIdElNMPvY9fLqb20LX9A9OMGtJilhHLbCL3ggyd0TwjmMcTxi0XXt+ur1vWUoxj2Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-darwin-x64": {
+ "version": "16.2.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.1.tgz",
+ "integrity": "sha512-/vrcE6iQSJq3uL3VGVHiXeaKbn8Es10DGTGRJnRZlkNQQk3kaNtAJg8Y6xuAlrx/6INKVjkfi5rY0iEXorZ6uA==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-gnu": {
+ "version": "16.2.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.1.tgz",
+ "integrity": "sha512-uLn+0BK+C31LTVbQ/QU+UaVrV0rRSJQ8RfniQAHPghDdgE+SlroYqcmFnO5iNjNfVWCyKZHYrs3Nl0mUzWxbBw==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-arm64-musl": {
+ "version": "16.2.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.1.tgz",
+ "integrity": "sha512-ssKq6iMRnHdnycGp9hCuGnXJZ0YPr4/wNwrfE5DbmvEcgl9+yv97/Kq3TPVDfYome1SW5geciLB9aiEqKXQjlQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-gnu": {
+ "version": "16.2.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.1.tgz",
+ "integrity": "sha512-HQm7SrHRELJ30T1TSmT706IWovFFSRGxfgUkyWJZF/RKBMdbdRWJuFrcpDdE5vy9UXjFOx6L3mRdqH04Mmx0hg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-linux-x64-musl": {
+ "version": "16.2.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.1.tgz",
+ "integrity": "sha512-aV2iUaC/5HGEpbBkE+4B8aHIudoOy5DYekAKOMSHoIYQ66y/wIVeaRx8MS2ZMdxe/HIXlMho4ubdZs/J8441Tg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-arm64-msvc": {
+ "version": "16.2.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.1.tgz",
+ "integrity": "sha512-IXdNgiDHaSk0ZUJ+xp0OQTdTgnpx1RCfRTalhn3cjOP+IddTMINwA7DXZrwTmGDO8SUr5q2hdP/du4DcrB1GxA==",
+ "cpu": [
+ "arm64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@next/swc-win32-x64-msvc": {
+ "version": "16.2.1",
+ "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.1.tgz",
+ "integrity": "sha512-qvU+3a39Hay+ieIztkGSbF7+mccbbg1Tk25hc4JDylf8IHjYmY/Zm64Qq1602yPyQqvie+vf5T/uPwNxDNIoeg==",
+ "cpu": [
+ "x64"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nolyfill/is-core-module": {
+ "version": "1.0.39",
+ "resolved": "https://registry.npmjs.org/@nolyfill/is-core-module/-/is-core-module-1.0.39.tgz",
+ "integrity": "sha512-nn5ozdjYQpUCZlWGuxcJY/KpxkWQs4DcbMCmKojjyrYDEAGy4Ce19NN4v5MduafTwJlbKc99UA8YhSVqq9yPZA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.4.0"
+ }
+ },
+ "node_modules/@oxc-project/types": {
+ "version": "0.120.0",
+ "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.120.0.tgz",
+ "integrity": "sha512-k1YNu55DuvAip/MGE1FTsIuU3FUCn6v/ujG9V7Nq5Df/kX2CWb13hhwD0lmJGMGqE+bE1MXvv9SZVnMzEXlWcg==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Boshen"
+ }
+ },
+ "node_modules/@prisma/adapter-pg": {
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/@prisma/adapter-pg/-/adapter-pg-7.5.0.tgz",
+ "integrity": "sha512-EJx7OLULahcC3IjJgdx2qRDNCT+ToY2v66UkeETMCLhNOTgqVzRzYvOEphY7Zp0eHyzfkC33Edd/qqeadf9R4A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/driver-adapter-utils": "7.5.0",
+ "@types/pg": "8.11.11",
+ "pg": "^8.16.3",
+ "postgres-array": "3.0.4"
+ }
+ },
+ "node_modules/@prisma/adapter-pg/node_modules/@types/pg": {
+ "version": "8.11.11",
+ "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.11.11.tgz",
+ "integrity": "sha512-kGT1qKM8wJQ5qlawUrEkXgvMSXoV213KfMGXcwfDwUIfUHXqXYXOfS1nE1LINRJVVVx5wCm70XnFlMHaIcQAfw==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "pg-protocol": "*",
+ "pg-types": "^4.0.1"
+ }
+ },
+ "node_modules/@prisma/adapter-pg/node_modules/pg-types": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-4.1.0.tgz",
+ "integrity": "sha512-o2XFanIMy/3+mThw69O8d4n1E5zsLhdO+OPqswezu7Z5ekP4hYDqlDjlmOpYMbzY2Br0ufCwJLdDIXeNVwcWFg==",
+ "license": "MIT",
+ "dependencies": {
+ "pg-int8": "1.0.1",
+ "pg-numeric": "1.0.2",
+ "postgres-array": "~3.0.1",
+ "postgres-bytea": "~3.0.0",
+ "postgres-date": "~2.1.0",
+ "postgres-interval": "^3.0.0",
+ "postgres-range": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@prisma/adapter-pg/node_modules/postgres-bytea": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-3.0.0.tgz",
+ "integrity": "sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw==",
+ "license": "MIT",
+ "dependencies": {
+ "obuf": "~1.1.2"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/@prisma/adapter-pg/node_modules/postgres-date": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-2.1.0.tgz",
+ "integrity": "sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@prisma/adapter-pg/node_modules/postgres-interval": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-3.0.0.tgz",
+ "integrity": "sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@prisma/client": {
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/@prisma/client/-/client-7.5.0.tgz",
+ "integrity": "sha512-h4hF9ctp+kSRs7ENHGsFQmHAgHcfkOCxbYt6Ti9Xi8x7D+kP4tTi9x51UKmiTH/OqdyJAO+8V+r+JA5AWdav7w==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/client-runtime-utils": "7.5.0"
+ },
+ "engines": {
+ "node": "^20.19 || ^22.12 || >=24.0"
+ },
+ "peerDependencies": {
+ "prisma": "*",
+ "typescript": ">=5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "prisma": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@prisma/client-runtime-utils": {
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/@prisma/client-runtime-utils/-/client-runtime-utils-7.5.0.tgz",
+ "integrity": "sha512-KnJ2b4Si/pcWEtK68uM+h0h1oh80CZt2suhLTVuLaSKg4n58Q9jBF/A42Kw6Ma+aThy1yAhfDeTC0JvEmeZnFQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/config": {
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/@prisma/config/-/config-7.5.0.tgz",
+ "integrity": "sha512-1J/9YEX7A889xM46PYg9e8VAuSL1IUmXJW3tEhMv7XQHDWlfC9YSkIw9sTYRaq5GswGlxZ+GnnyiNsUZ9JJhSQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "c12": "3.1.0",
+ "deepmerge-ts": "7.1.5",
+ "effect": "3.18.4",
+ "empathic": "2.0.0"
+ }
+ },
+ "node_modules/@prisma/debug": {
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.5.0.tgz",
+ "integrity": "sha512-163+nffny0JoPEkDhfNco0vcuT3ymIJc9+WX7MHSQhfkeKUmKe9/wqvGk5SjppT93DtBjVwr5HPJYlXbzm6qtg==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/dev": {
+ "version": "0.20.0",
+ "resolved": "https://registry.npmjs.org/@prisma/dev/-/dev-0.20.0.tgz",
+ "integrity": "sha512-ovlBYwWor0OzG+yH4J3Ot+AneD818BttLA+Ii7wjbcLHUrnC4tbUPVGyNd3c/+71KETPKZfjhkTSpdS15dmXNQ==",
+ "license": "ISC",
+ "dependencies": {
+ "@electric-sql/pglite": "0.3.15",
+ "@electric-sql/pglite-socket": "0.0.20",
+ "@electric-sql/pglite-tools": "0.2.20",
+ "@hono/node-server": "1.19.9",
+ "@mrleebo/prisma-ast": "0.13.1",
+ "@prisma/get-platform": "7.2.0",
+ "@prisma/query-plan-executor": "7.2.0",
+ "foreground-child": "3.3.1",
+ "get-port-please": "3.2.0",
+ "hono": "4.11.4",
+ "http-status-codes": "2.3.0",
+ "pathe": "2.0.3",
+ "proper-lockfile": "4.1.2",
+ "remeda": "2.33.4",
+ "std-env": "3.10.0",
+ "valibot": "1.2.0",
+ "zeptomatch": "2.1.0"
+ }
+ },
+ "node_modules/@prisma/dev/node_modules/std-env": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
+ "license": "MIT"
+ },
+ "node_modules/@prisma/driver-adapter-utils": {
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/@prisma/driver-adapter-utils/-/driver-adapter-utils-7.5.0.tgz",
+ "integrity": "sha512-B79N/amgV677mFesFDBAdrW0OIaqawap9E0sjgLBtzIz2R3hIMS1QB8mLZuUEiS4q5Y8Oh3I25Kw4SLxMypk9Q==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.5.0"
+ }
+ },
+ "node_modules/@prisma/engines": {
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-7.5.0.tgz",
+ "integrity": "sha512-ondGRhzoaVpRWvFaQ5wH5zS1BIbhzbKqczKjCn6j3L0Zfe/LInjcEg8+xtB49AuZBX30qyx1ZtGoootUohz2pw==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.5.0",
+ "@prisma/engines-version": "7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e",
+ "@prisma/fetch-engine": "7.5.0",
+ "@prisma/get-platform": "7.5.0"
+ }
+ },
+ "node_modules/@prisma/engines-version": {
+ "version": "7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e",
+ "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e.tgz",
+ "integrity": "sha512-E+iRV/vbJLl8iGjVr6g/TEWokA+gjkV/doZkaQN1i/ULVdDwGnPJDfLUIFGS3BVwlG/m6L8T4x1x5isl8hGMxA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/engines/node_modules/@prisma/get-platform": {
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.5.0.tgz",
+ "integrity": "sha512-7I+2y1nu/gkEKSiHHbcZ1HPe/euGdEqJZxEEMT0246q4De1+hla0ZzlTgvaT9dHcVCgLSuCG8v39db5qUUWNgw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.5.0"
+ }
+ },
+ "node_modules/@prisma/fetch-engine": {
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-7.5.0.tgz",
+ "integrity": "sha512-kZCl2FV54qnyrVdnII8MI6qvt7HfU6Cbiz8dZ8PXz4f4lbSw45jEB9/gEMK2SGdiNhBKyk/Wv95uthoLhGMLYA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.5.0",
+ "@prisma/engines-version": "7.5.0-15.280c870be64f457428992c43c1f6d557fab6e29e",
+ "@prisma/get-platform": "7.5.0"
+ }
+ },
+ "node_modules/@prisma/fetch-engine/node_modules/@prisma/get-platform": {
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.5.0.tgz",
+ "integrity": "sha512-7I+2y1nu/gkEKSiHHbcZ1HPe/euGdEqJZxEEMT0246q4De1+hla0ZzlTgvaT9dHcVCgLSuCG8v39db5qUUWNgw==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.5.0"
+ }
+ },
+ "node_modules/@prisma/get-platform": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-7.2.0.tgz",
+ "integrity": "sha512-k1V0l0Td1732EHpAfi2eySTezyllok9dXb6UQanajkJQzPUGi3vO2z7jdkz67SypFTdmbnyGYxvEvYZdZsMAVA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/debug": "7.2.0"
+ }
+ },
+ "node_modules/@prisma/get-platform/node_modules/@prisma/debug": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-7.2.0.tgz",
+ "integrity": "sha512-YSGTiSlBAVJPzX4ONZmMotL+ozJwQjRmZweQNIq/ER0tQJKJynNkRB3kyvt37eOfsbMCXk3gnLF6J9OJ4QWftw==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/query-plan-executor": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/@prisma/query-plan-executor/-/query-plan-executor-7.2.0.tgz",
+ "integrity": "sha512-EOZmNzcV8uJ0mae3DhTsiHgoNCuu1J9mULQpGCh62zN3PxPTd+qI9tJvk5jOst8WHKQNwJWR3b39t0XvfBB0WQ==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/@prisma/studio-core": {
+ "version": "0.21.1",
+ "resolved": "https://registry.npmjs.org/@prisma/studio-core/-/studio-core-0.21.1.tgz",
+ "integrity": "sha512-bOGqG/eMQtKC0XVvcVLRmhWWzm/I+0QUWqAEhEBtetpuS3k3V4IWqKGUONkAIT223DNXJMxMtZp36b1FmcdPeg==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19 || ^22.12 || ^24.0",
+ "pnpm": "8"
+ },
+ "peerDependencies": {
+ "@types/react": "^18.0.0 || ^19.0.0",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/@rolldown/binding-android-arm64": {
+ "version": "1.0.0-rc.10",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.10.tgz",
+ "integrity": "sha512-jOHxwXhxmFKuXztiu1ORieJeTbx5vrTkcOkkkn2d35726+iwhrY1w/+nYY/AGgF12thg33qC3R1LMBF5tHTZHg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-arm64": {
+ "version": "1.0.0-rc.10",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.10.tgz",
+ "integrity": "sha512-gED05Teg/vtTZbIJBc4VNMAxAFDUPkuO/rAIyyxZjTj1a1/s6z5TII/5yMGZ0uLRCifEtwUQn8OlYzuYc0m70w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-darwin-x64": {
+ "version": "1.0.0-rc.10",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.10.tgz",
+ "integrity": "sha512-rI15NcM1mA48lqrIxVkHfAqcyFLcQwyXWThy+BQ5+mkKKPvSO26ir+ZDp36AgYoYVkqvMcdS8zOE6SeBsR9e8A==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-freebsd-x64": {
+ "version": "1.0.0-rc.10",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.10.tgz",
+ "integrity": "sha512-XZRXHdTa+4ME1MuDVp021+doQ+z6Ei4CCFmNc5/sKbqb8YmkiJdj8QKlV3rCI0AJtAeSB5n0WGPuJWNL9p/L2w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm-gnueabihf": {
+ "version": "1.0.0-rc.10",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.10.tgz",
+ "integrity": "sha512-R0SQMRluISSLzFE20sPWYHVmJdDQnRyc/FzSCN72BqQmh2SOZUFG+N3/vBZpR4C6WpEUVYJLrYUXaj43sJsNLA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-gnu": {
+ "version": "1.0.0-rc.10",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.10.tgz",
+ "integrity": "sha512-Y1reMrV/o+cwpduYhJuOE3OMKx32RMYCidf14y+HssARRmhDuWXJ4yVguDg2R/8SyyGNo+auzz64LnPK9Hq6jg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-arm64-musl": {
+ "version": "1.0.0-rc.10",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.10.tgz",
+ "integrity": "sha512-vELN+HNb2IzuzSBUOD4NHmP9yrGwl1DVM29wlQvx1OLSclL0NgVWnVDKl/8tEks79EFek/kebQKnNJkIAA4W2g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-ppc64-gnu": {
+ "version": "1.0.0-rc.10",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.10.tgz",
+ "integrity": "sha512-ZqrufYTgzxbHwpqOjzSsb0UV/aV2TFIY5rP8HdsiPTv/CuAgCRjM6s9cYFwQ4CNH+hf9Y4erHW1GjZuZ7WoI7w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-s390x-gnu": {
+ "version": "1.0.0-rc.10",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.10.tgz",
+ "integrity": "sha512-gSlmVS1FZJSRicA6IyjoRoKAFK7IIHBs7xJuHRSmjImqk3mPPWbR7RhbnfH2G6bcmMEllCt2vQ/7u9e6bBnByg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-gnu": {
+ "version": "1.0.0-rc.10",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.10.tgz",
+ "integrity": "sha512-eOCKUpluKgfObT2pHjztnaWEIbUabWzk3qPZ5PuacuPmr4+JtQG4k2vGTY0H15edaTnicgU428XW/IH6AimcQw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-linux-x64-musl": {
+ "version": "1.0.0-rc.10",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.10.tgz",
+ "integrity": "sha512-Xdf2jQbfQowJnLcgYfD/m0Uu0Qj5OdxKallD78/IPPfzaiaI4KRAwZzHcKQ4ig1gtg1SuzC7jovNiM2TzQsBXA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-openharmony-arm64": {
+ "version": "1.0.0-rc.10",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.10.tgz",
+ "integrity": "sha512-o1hYe8hLi1EY6jgPFyxQgQ1wcycX+qz8eEbVmot2hFkgUzPxy9+kF0u0NIQBeDq+Mko47AkaFFaChcvZa9UX9Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-wasm32-wasi": {
+ "version": "1.0.0-rc.10",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.10.tgz",
+ "integrity": "sha512-Ugv9o7qYJudqQO5Y5y2N2SOo6S4WiqiNOpuQyoPInnhVzCY+wi/GHltcLHypG9DEUYMB0iTB/huJrpadiAcNcA==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@napi-rs/wasm-runtime": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@rolldown/binding-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.1.tgz",
+ "integrity": "sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.7.1",
+ "@emnapi/runtime": "^1.7.1",
+ "@tybys/wasm-util": "^0.10.1"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/Brooooooklyn"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-arm64-msvc": {
+ "version": "1.0.0-rc.10",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.10.tgz",
+ "integrity": "sha512-7UODQb4fQUNT/vmgDZBl3XOBAIOutP5R3O/rkxg0aLfEGQ4opbCgU5vOw/scPe4xOqBwL9fw7/RP1vAMZ6QlAQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/binding-win32-x64-msvc": {
+ "version": "1.0.0-rc.10",
+ "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.10.tgz",
+ "integrity": "sha512-PYxKHMVHOb5NJuDL53vBUl1VwUjymDcYI6rzpIni0C9+9mTiJedvUxSk7/RPp7OOAm3v+EjgMu9bIy3N6b408w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-rc.10",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.10.tgz",
+ "integrity": "sha512-UkVDEFk1w3mveXeKgaTuYfKWtPbvgck1dT8TUG3bnccrH0XtLTuAyfCoks4Q/M5ZGToSVJTIQYCzy2g/atAOeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@rtsao/scc": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
+ "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@standard-schema/spec": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
+ "license": "MIT"
+ },
+ "node_modules/@swc/helpers": {
+ "version": "0.5.15",
+ "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz",
+ "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "tslib": "^2.8.0"
+ }
+ },
+ "node_modules/@tailwindcss/node": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.2.tgz",
+ "integrity": "sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/remapping": "^2.3.5",
+ "enhanced-resolve": "^5.19.0",
+ "jiti": "^2.6.1",
+ "lightningcss": "1.32.0",
+ "magic-string": "^0.30.21",
+ "source-map-js": "^1.2.1",
+ "tailwindcss": "4.2.2"
+ }
+ },
+ "node_modules/@tailwindcss/oxide": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.2.tgz",
+ "integrity": "sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 20"
+ },
+ "optionalDependencies": {
+ "@tailwindcss/oxide-android-arm64": "4.2.2",
+ "@tailwindcss/oxide-darwin-arm64": "4.2.2",
+ "@tailwindcss/oxide-darwin-x64": "4.2.2",
+ "@tailwindcss/oxide-freebsd-x64": "4.2.2",
+ "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.2",
+ "@tailwindcss/oxide-linux-arm64-gnu": "4.2.2",
+ "@tailwindcss/oxide-linux-arm64-musl": "4.2.2",
+ "@tailwindcss/oxide-linux-x64-gnu": "4.2.2",
+ "@tailwindcss/oxide-linux-x64-musl": "4.2.2",
+ "@tailwindcss/oxide-wasm32-wasi": "4.2.2",
+ "@tailwindcss/oxide-win32-arm64-msvc": "4.2.2",
+ "@tailwindcss/oxide-win32-x64-msvc": "4.2.2"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-android-arm64": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.2.tgz",
+ "integrity": "sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-arm64": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.2.tgz",
+ "integrity": "sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-darwin-x64": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.2.tgz",
+ "integrity": "sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-freebsd-x64": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.2.tgz",
+ "integrity": "sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.2.tgz",
+ "integrity": "sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-gnu": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.2.tgz",
+ "integrity": "sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-arm64-musl": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.2.tgz",
+ "integrity": "sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-gnu": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.2.tgz",
+ "integrity": "sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-linux-x64-musl": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.2.tgz",
+ "integrity": "sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-wasm32-wasi": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.2.tgz",
+ "integrity": "sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==",
+ "bundleDependencies": [
+ "@napi-rs/wasm-runtime",
+ "@emnapi/core",
+ "@emnapi/runtime",
+ "@tybys/wasm-util",
+ "@emnapi/wasi-threads",
+ "tslib"
+ ],
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@emnapi/core": "^1.8.1",
+ "@emnapi/runtime": "^1.8.1",
+ "@emnapi/wasi-threads": "^1.1.0",
+ "@napi-rs/wasm-runtime": "^1.1.1",
+ "@tybys/wasm-util": "^0.10.1",
+ "tslib": "^2.8.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.2.tgz",
+ "integrity": "sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/oxide-win32-x64-msvc": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.2.tgz",
+ "integrity": "sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/@tailwindcss/postcss": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.2.2.tgz",
+ "integrity": "sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "@tailwindcss/node": "4.2.2",
+ "@tailwindcss/oxide": "4.2.2",
+ "postcss": "^8.5.6",
+ "tailwindcss": "4.2.2"
+ }
+ },
+ "node_modules/@testing-library/dom": {
+ "version": "10.4.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.1.tgz",
+ "integrity": "sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.10.4",
+ "@babel/runtime": "^7.12.5",
+ "@types/aria-query": "^5.0.1",
+ "aria-query": "5.3.0",
+ "dom-accessibility-api": "^0.5.9",
+ "lz-string": "^1.5.0",
+ "picocolors": "1.1.1",
+ "pretty-format": "^27.0.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@testing-library/dom/node_modules/aria-query": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz",
+ "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "peer": true,
+ "dependencies": {
+ "dequal": "^2.0.3"
+ }
+ },
+ "node_modules/@testing-library/jest-dom": {
+ "version": "6.9.1",
+ "resolved": "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-6.9.1.tgz",
+ "integrity": "sha512-zIcONa+hVtVSSep9UT3jZ5rizo2BsxgyDYU7WFD5eICBE7no3881HGeb/QkGfsJs6JTkY1aQhT7rIPC7e+0nnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@adobe/css-tools": "^4.4.0",
+ "aria-query": "^5.0.0",
+ "css.escape": "^1.5.1",
+ "dom-accessibility-api": "^0.6.3",
+ "picocolors": "^1.1.1",
+ "redent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=14",
+ "npm": ">=6",
+ "yarn": ">=1"
+ }
+ },
+ "node_modules/@testing-library/jest-dom/node_modules/dom-accessibility-api": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.6.3.tgz",
+ "integrity": "sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@testing-library/react": {
+ "version": "16.3.2",
+ "resolved": "https://registry.npmjs.org/@testing-library/react/-/react-16.3.2.tgz",
+ "integrity": "sha512-XU5/SytQM+ykqMnAnvB2umaJNIOsLF3PVv//1Ew4CTcpz0/BRyy/af40qqrt7SjKpDdT1saBMc42CUok5gaw+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@testing-library/dom": "^10.0.0",
+ "@types/react": "^18.0.0 || ^19.0.0",
+ "@types/react-dom": "^18.0.0 || ^19.0.0",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@tybys/wasm-util": {
+ "version": "0.10.1",
+ "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
+ "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "tslib": "^2.4.0"
+ }
+ },
+ "node_modules/@types/aria-query": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz",
+ "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/@types/chai": {
+ "version": "5.2.3",
+ "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz",
+ "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/deep-eql": "*",
+ "assertion-error": "^2.0.1"
+ }
+ },
+ "node_modules/@types/deep-eql": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz",
+ "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/json5": {
+ "version": "0.0.29",
+ "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
+ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/mustache": {
+ "version": "4.2.6",
+ "resolved": "https://registry.npmjs.org/@types/mustache/-/mustache-4.2.6.tgz",
+ "integrity": "sha512-t+8/QWTAhOFlrF1IVZqKnMRJi84EgkIK5Kh0p2JV4OLywUvCwJPFxbJAl7XAow7DVIHsF+xW9f1MVzg0L6Szjw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "20.19.37",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz",
+ "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==",
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/pg": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.20.0.tgz",
+ "integrity": "sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*",
+ "pg-protocol": "*",
+ "pg-types": "^2.2.0"
+ }
+ },
+ "node_modules/@types/react": {
+ "version": "19.2.14",
+ "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz",
+ "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==",
+ "license": "MIT",
+ "dependencies": {
+ "csstype": "^3.2.2"
+ }
+ },
+ "node_modules/@types/react-dom": {
+ "version": "19.2.3",
+ "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
+ "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "^19.2.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.1.tgz",
+ "integrity": "sha512-Gn3aqnvNl4NGc6x3/Bqk1AOn0thyTU9bqDRhiRnUWezgvr2OnhYCWCgC8zXXRVqBsIL1pSDt7T9nJUe0oM0kDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.12.2",
+ "@typescript-eslint/scope-manager": "8.57.1",
+ "@typescript-eslint/type-utils": "8.57.1",
+ "@typescript-eslint/utils": "8.57.1",
+ "@typescript-eslint/visitor-keys": "8.57.1",
+ "ignore": "^7.0.5",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^2.4.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^8.57.1",
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
+ "version": "7.0.5",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz",
+ "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.1.tgz",
+ "integrity": "sha512-k4eNDan0EIMTT/dUKc/g+rsJ6wcHYhNPdY19VoX/EOtaAG8DLtKCykhrUnuHPYvinn5jhAPgD2Qw9hXBwrahsw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "8.57.1",
+ "@typescript-eslint/types": "8.57.1",
+ "@typescript-eslint/typescript-estree": "8.57.1",
+ "@typescript-eslint/visitor-keys": "8.57.1",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/project-service": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.1.tgz",
+ "integrity": "sha512-vx1F37BRO1OftsYlmG9xay1TqnjNVlqALymwWVuYTdo18XuKxtBpCj1QlzNIEHlvlB27osvXFWptYiEWsVdYsg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/tsconfig-utils": "^8.57.1",
+ "@typescript-eslint/types": "^8.57.1",
+ "debug": "^4.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.1.tgz",
+ "integrity": "sha512-hs/QcpCwlwT2L5S+3fT6gp0PabyGk4Q0Rv2doJXA0435/OpnSR3VRgvrp8Xdoc3UAYSg9cyUjTeFXZEPg/3OKg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.57.1",
+ "@typescript-eslint/visitor-keys": "8.57.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/tsconfig-utils": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.1.tgz",
+ "integrity": "sha512-0lgOZB8cl19fHO4eI46YUx2EceQqhgkPSuCGLlGi79L2jwYY1cxeYc1Nae8Aw1xjgW3PKVDLlr3YJ6Bxx8HkWg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.1.tgz",
+ "integrity": "sha512-+Bwwm0ScukFdyoJsh2u6pp4S9ktegF98pYUU0hkphOOqdMB+1sNQhIz8y5E9+4pOioZijrkfNO/HUJVAFFfPKA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.57.1",
+ "@typescript-eslint/typescript-estree": "8.57.1",
+ "@typescript-eslint/utils": "8.57.1",
+ "debug": "^4.4.3",
+ "ts-api-utils": "^2.4.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.1.tgz",
+ "integrity": "sha512-S29BOBPJSFUiblEl6RzPPjJt6w25A6XsBqRVDt53tA/tlL8q7ceQNZHTjPeONt/3S7KRI4quk+yP9jK2WjBiPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.1.tgz",
+ "integrity": "sha512-ybe2hS9G6pXpqGtPli9Gx9quNV0TWLOmh58ADlmZe9DguLq0tiAKVjirSbtM1szG6+QH6rVXyU6GTLQbWnMY+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/project-service": "8.57.1",
+ "@typescript-eslint/tsconfig-utils": "8.57.1",
+ "@typescript-eslint/types": "8.57.1",
+ "@typescript-eslint/visitor-keys": "8.57.1",
+ "debug": "^4.4.3",
+ "minimatch": "^10.2.2",
+ "semver": "^7.7.3",
+ "tinyglobby": "^0.2.15",
+ "ts-api-utils": "^2.4.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz",
+ "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
+ "version": "10.2.4",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz",
+ "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.1.tgz",
+ "integrity": "sha512-XUNSJ/lEVFttPMMoDVA2r2bwrl8/oPx8cURtczkSEswY5T3AeLmCy+EKWQNdL4u0MmAHOjcWrqJp2cdvgjn8dQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.9.1",
+ "@typescript-eslint/scope-manager": "8.57.1",
+ "@typescript-eslint/types": "8.57.1",
+ "@typescript-eslint/typescript-estree": "8.57.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.1.tgz",
+ "integrity": "sha512-YWnmJkXbofiz9KbnbbwuA2rpGkFPLbAIetcCNO6mJ8gdhdZ/v7WDXsoGFAJuM6ikUFKTlSQnjWnVO4ux+UzS6A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "8.57.1",
+ "eslint-visitor-keys": "^5.0.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz",
+ "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@unrs/resolver-binding-android-arm-eabi": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz",
+ "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-android-arm64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz",
+ "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-darwin-arm64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz",
+ "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-darwin-x64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz",
+ "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-freebsd-x64": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz",
+ "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz",
+ "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz",
+ "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz",
+ "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-arm64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz",
+ "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz",
+ "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz",
+ "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-riscv64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz",
+ "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-s390x-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz",
+ "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-x64-gnu": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz",
+ "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-linux-x64-musl": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz",
+ "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-wasm32-wasi": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz",
+ "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==",
+ "cpu": [
+ "wasm32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "dependencies": {
+ "@napi-rs/wasm-runtime": "^0.2.11"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/@unrs/resolver-binding-win32-arm64-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz",
+ "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-ia32-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz",
+ "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@unrs/resolver-binding-win32-x64-msvc": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz",
+ "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@vitest/expect": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.0.tgz",
+ "integrity": "sha512-EIxG7k4wlWweuCLG9Y5InKFwpMEOyrMb6ZJ1ihYu02LVj/bzUwn2VMU+13PinsjRW75XnITeFrQBMH5+dLvCDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.1.0",
+ "@types/chai": "^5.2.2",
+ "@vitest/spy": "4.1.0",
+ "@vitest/utils": "4.1.0",
+ "chai": "^6.2.2",
+ "tinyrainbow": "^3.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.0.tgz",
+ "integrity": "sha512-evxREh+Hork43+Y4IOhTo+h5lGmVRyjqI739Rz4RlUPqwrkFFDF6EMvOOYjTx4E8Tl6gyCLRL8Mu7Ry12a13Tw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "4.1.0",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.21"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.0.tgz",
+ "integrity": "sha512-3RZLZlh88Ib0J7NQTRATfc/3ZPOnSUn2uDBUoGNn5T36+bALixmzphN26OUD3LRXWkJu4H0s5vvUeqBiw+kS0A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^3.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.0.tgz",
+ "integrity": "sha512-Duvx2OzQ7d6OjchL+trw+aSrb9idh7pnNfxrklo14p3zmNL4qPCDeIJAK+eBKYjkIwG96Bc6vYuxhqDXQOWpoQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "4.1.0",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.0.tgz",
+ "integrity": "sha512-0Vy9euT1kgsnj1CHttwi9i9o+4rRLEaPRSOJ5gyv579GJkNpgJK+B4HSv/rAWixx2wdAFci1X4CEPjiu2bXIMg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.0",
+ "@vitest/utils": "4.1.0",
+ "magic-string": "^0.30.21",
+ "pathe": "^2.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.0.tgz",
+ "integrity": "sha512-pz77k+PgNpyMDv2FV6qmk5ZVau6c3R8HC8v342T2xlFxQKTrSeYw9waIJG8KgV9fFwAtTu4ceRzMivPTH6wSxw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.0.tgz",
+ "integrity": "sha512-XfPXT6a8TZY3dcGY8EdwsBulFCIw+BeeX0RZn2x/BtiY/75YGh8FeWGG8QISN/WhaqSrE2OrlDgtF8q5uhOTmw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "4.1.0",
+ "convert-source-map": "^2.0.0",
+ "tinyrainbow": "^3.0.3"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.14.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz",
+ "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/aria-query": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
+ "integrity": "sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/array-buffer-byte-length": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz",
+ "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "is-array-buffer": "^3.0.5"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array-includes": {
+ "version": "3.1.9",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.9.tgz",
+ "integrity": "sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.24.0",
+ "es-object-atoms": "^1.1.1",
+ "get-intrinsic": "^1.3.0",
+ "is-string": "^1.1.1",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.findlast": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/array.prototype.findlast/-/array.prototype.findlast-1.2.5.tgz",
+ "integrity": "sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.findlastindex": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.6.tgz",
+ "integrity": "sha512-F/TKATkzseUExPlfvmwQKGITM3DGTK+vkAsCZoDc5daVygbJBnjEUCbgkAvVFsgfXfX4YIqZ/27G3k3tdXrTxQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.9",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "es-shim-unscopables": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flat": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz",
+ "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flatmap": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz",
+ "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.tosorted": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/array.prototype.tosorted/-/array.prototype.tosorted-1.1.4.tgz",
+ "integrity": "sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.3",
+ "es-errors": "^1.3.0",
+ "es-shim-unscopables": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/arraybuffer.prototype.slice": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz",
+ "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.1",
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "is-array-buffer": "^3.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/ast-types-flow": {
+ "version": "0.0.8",
+ "resolved": "https://registry.npmjs.org/ast-types-flow/-/ast-types-flow-0.0.8.tgz",
+ "integrity": "sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/async-function": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz",
+ "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/available-typed-arrays": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
+ "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "possible-typed-array-names": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/aws-ssl-profiles": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
+ "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/axe-core": {
+ "version": "4.11.1",
+ "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-4.11.1.tgz",
+ "integrity": "sha512-BASOg+YwO2C+346x3LZOeoovTIoTrRqEsqMa6fmfAV0P+U9mFr9NsyOEpiYvFjbc64NMrSswhV50WdXzdb/Z5A==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/axobject-query": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
+ "integrity": "sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.10",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.10.tgz",
+ "integrity": "sha512-sUoJ3IMxx4AyRqO4MLeHlnGDkyXRoUG0/AI9fjK+vS72ekpV0yWVY7O0BVjmBcRtkNcsAO2QDZ4tdKKGoI6YaQ==",
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/bidi-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz",
+ "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "require-from-string": "^2.0.2"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.12",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
+ "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.9.0",
+ "caniuse-lite": "^1.0.30001759",
+ "electron-to-chromium": "^1.5.263",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.2.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/c12": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz",
+ "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==",
+ "license": "MIT",
+ "dependencies": {
+ "chokidar": "^4.0.3",
+ "confbox": "^0.2.2",
+ "defu": "^6.1.4",
+ "dotenv": "^16.6.1",
+ "exsolve": "^1.0.7",
+ "giget": "^2.0.0",
+ "jiti": "^2.4.2",
+ "ohash": "^2.0.11",
+ "pathe": "^2.0.3",
+ "perfect-debounce": "^1.0.0",
+ "pkg-types": "^2.2.0",
+ "rc9": "^2.1.2"
+ },
+ "peerDependencies": {
+ "magicast": "^0.3.5"
+ },
+ "peerDependenciesMeta": {
+ "magicast": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/c12/node_modules/dotenv": {
+ "version": "16.6.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
+ "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
+ "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.0",
+ "es-define-property": "^1.0.0",
+ "get-intrinsic": "^1.2.4",
+ "set-function-length": "^1.2.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/call-bound": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz",
+ "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "get-intrinsic": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001780",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001780.tgz",
+ "integrity": "sha512-llngX0E7nQci5BPJDqoZSbuZ5Bcs9F5db7EtgfwBerX9XGtkkiO4NwfDDIRzHTTwcYC8vC7bmeUEPGrKlR/TkQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/chai": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz",
+ "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chevrotain": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/chevrotain/-/chevrotain-10.5.0.tgz",
+ "integrity": "sha512-Pkv5rBY3+CsHOYfV5g/Vs5JY9WTHHDEKOlohI2XeygaZhUeqhAlldZ8Hz9cRmxu709bvS08YzxHdTPHhffc13A==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@chevrotain/cst-dts-gen": "10.5.0",
+ "@chevrotain/gast": "10.5.0",
+ "@chevrotain/types": "10.5.0",
+ "@chevrotain/utils": "10.5.0",
+ "lodash": "4.17.21",
+ "regexp-to-ast": "0.5.0"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14.16.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/citty": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz",
+ "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==",
+ "license": "MIT",
+ "dependencies": {
+ "consola": "^3.2.3"
+ }
+ },
+ "node_modules/client-only": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
+ "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
+ "license": "MIT"
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/confbox": {
+ "version": "0.2.4",
+ "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz",
+ "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==",
+ "license": "MIT"
+ },
+ "node_modules/consola": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz",
+ "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==",
+ "license": "MIT",
+ "engines": {
+ "node": "^14.18.0 || >=16.10.0"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/css-tree": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz",
+ "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "mdn-data": "2.27.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0"
+ }
+ },
+ "node_modules/css.escape": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/css.escape/-/css.escape-1.5.1.tgz",
+ "integrity": "sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "license": "MIT"
+ },
+ "node_modules/damerau-levenshtein": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/damerau-levenshtein/-/damerau-levenshtein-1.0.8.tgz",
+ "integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
+ "dev": true,
+ "license": "BSD-2-Clause"
+ },
+ "node_modules/data-urls": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-7.0.0.tgz",
+ "integrity": "sha512-23XHcCF+coGYevirZceTVD7NdJOqVn+49IHyxgszm+JIiHLoB2TkmPtsYkNWT1pvRSGkc35L6NHs0yHkN2SumA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-mimetype": "^5.0.0",
+ "whatwg-url": "^16.0.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ }
+ },
+ "node_modules/data-view-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz",
+ "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/data-view-byte-length": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz",
+ "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/inspect-js"
+ }
+ },
+ "node_modules/data-view-byte-offset": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz",
+ "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "is-data-view": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decimal.js": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
+ "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/deepmerge-ts": {
+ "version": "7.1.5",
+ "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz",
+ "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/define-data-property": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
+ "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-define-property": "^1.0.0",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/define-properties": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz",
+ "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.0.1",
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/defu": {
+ "version": "6.1.4",
+ "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz",
+ "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==",
+ "license": "MIT"
+ },
+ "node_modules/denque": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
+ "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/dequal": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
+ "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/destr": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz",
+ "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==",
+ "license": "MIT"
+ },
+ "node_modules/detect-libc": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
+ "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/dom-accessibility-api": {
+ "version": "0.5.16",
+ "resolved": "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.16.tgz",
+ "integrity": "sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/dotenv": {
+ "version": "17.3.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.3.1.tgz",
+ "integrity": "sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/effect": {
+ "version": "3.18.4",
+ "resolved": "https://registry.npmjs.org/effect/-/effect-3.18.4.tgz",
+ "integrity": "sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==",
+ "license": "MIT",
+ "dependencies": {
+ "@standard-schema/spec": "^1.0.0",
+ "fast-check": "^3.23.1"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.321",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.321.tgz",
+ "integrity": "sha512-L2C7Q279W2D/J4PLZLk7sebOILDSWos7bMsMNN06rK482umHUrh/3lM8G7IlHFOYip2oAg5nha1rCMxr/rs6ZQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/empathic": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz",
+ "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/enhanced-resolve": {
+ "version": "5.20.1",
+ "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz",
+ "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "tapable": "^2.3.0"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/entities": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/es-abstract": {
+ "version": "1.24.1",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.1.tgz",
+ "integrity": "sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-buffer-byte-length": "^1.0.2",
+ "arraybuffer.prototype.slice": "^1.0.4",
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "data-view-buffer": "^1.0.2",
+ "data-view-byte-length": "^1.0.2",
+ "data-view-byte-offset": "^1.0.1",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "es-set-tostringtag": "^2.1.0",
+ "es-to-primitive": "^1.3.0",
+ "function.prototype.name": "^1.1.8",
+ "get-intrinsic": "^1.3.0",
+ "get-proto": "^1.0.1",
+ "get-symbol-description": "^1.1.0",
+ "globalthis": "^1.0.4",
+ "gopd": "^1.2.0",
+ "has-property-descriptors": "^1.0.2",
+ "has-proto": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "internal-slot": "^1.1.0",
+ "is-array-buffer": "^3.0.5",
+ "is-callable": "^1.2.7",
+ "is-data-view": "^1.0.2",
+ "is-negative-zero": "^2.0.3",
+ "is-regex": "^1.2.1",
+ "is-set": "^2.0.3",
+ "is-shared-array-buffer": "^1.0.4",
+ "is-string": "^1.1.1",
+ "is-typed-array": "^1.1.15",
+ "is-weakref": "^1.1.1",
+ "math-intrinsics": "^1.1.0",
+ "object-inspect": "^1.13.4",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.7",
+ "own-keys": "^1.0.1",
+ "regexp.prototype.flags": "^1.5.4",
+ "safe-array-concat": "^1.1.3",
+ "safe-push-apply": "^1.0.0",
+ "safe-regex-test": "^1.1.0",
+ "set-proto": "^1.0.0",
+ "stop-iteration-iterator": "^1.1.0",
+ "string.prototype.trim": "^1.2.10",
+ "string.prototype.trimend": "^1.0.9",
+ "string.prototype.trimstart": "^1.0.8",
+ "typed-array-buffer": "^1.0.3",
+ "typed-array-byte-length": "^1.0.3",
+ "typed-array-byte-offset": "^1.0.4",
+ "typed-array-length": "^1.0.7",
+ "unbox-primitive": "^1.1.0",
+ "which-typed-array": "^1.1.19"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-iterator-helpers": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/es-iterator-helpers/-/es-iterator-helpers-1.3.1.tgz",
+ "integrity": "sha512-zWwRvqWiuBPr0muUG/78cW3aHROFCNIQ3zpmYDpwdbnt2m+xlNyRWpHBpa2lJjSBit7BQ+RXA1iwbSmu5yJ/EQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.24.1",
+ "es-errors": "^1.3.0",
+ "es-set-tostringtag": "^2.1.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.3.0",
+ "globalthis": "^1.0.4",
+ "gopd": "^1.2.0",
+ "has-property-descriptors": "^1.0.2",
+ "has-proto": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "internal-slot": "^1.1.0",
+ "iterator.prototype": "^1.1.5",
+ "math-intrinsics": "^1.1.0",
+ "safe-array-concat": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz",
+ "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+ "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-shim-unscopables": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz",
+ "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-to-primitive": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz",
+ "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-callable": "^1.2.7",
+ "is-date-object": "^1.0.5",
+ "is-symbol": "^1.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "9.39.4",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz",
+ "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.8.0",
+ "@eslint-community/regexpp": "^4.12.1",
+ "@eslint/config-array": "^0.21.2",
+ "@eslint/config-helpers": "^0.4.2",
+ "@eslint/core": "^0.17.0",
+ "@eslint/eslintrc": "^3.3.5",
+ "@eslint/js": "9.39.4",
+ "@eslint/plugin-kit": "^0.4.1",
+ "@humanfs/node": "^0.16.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@humanwhocodes/retry": "^0.4.2",
+ "@types/estree": "^1.0.6",
+ "ajv": "^6.14.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.6",
+ "debug": "^4.3.2",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^8.4.0",
+ "eslint-visitor-keys": "^4.2.1",
+ "espree": "^10.4.0",
+ "esquery": "^1.5.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^8.0.0",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.5",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://eslint.org/donate"
+ },
+ "peerDependencies": {
+ "jiti": "*"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-config-next": {
+ "version": "16.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.2.1.tgz",
+ "integrity": "sha512-qhabwjQZ1Mk53XzXvmogf8KQ0tG0CQXF0CZ56+2/lVhmObgmaqj7x5A1DSrWdZd3kwI7GTPGUjFne+krRxYmFg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@next/eslint-plugin-next": "16.2.1",
+ "eslint-import-resolver-node": "^0.3.6",
+ "eslint-import-resolver-typescript": "^3.5.2",
+ "eslint-plugin-import": "^2.32.0",
+ "eslint-plugin-jsx-a11y": "^6.10.0",
+ "eslint-plugin-react": "^7.37.0",
+ "eslint-plugin-react-hooks": "^7.0.0",
+ "globals": "16.4.0",
+ "typescript-eslint": "^8.46.0"
+ },
+ "peerDependencies": {
+ "eslint": ">=9.0.0",
+ "typescript": ">=3.3.1"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-config-next/node_modules/globals": {
+ "version": "16.4.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz",
+ "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint-import-resolver-node": {
+ "version": "0.3.9",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
+ "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^3.2.7",
+ "is-core-module": "^2.13.0",
+ "resolve": "^1.22.4"
+ }
+ },
+ "node_modules/eslint-import-resolver-node/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-import-resolver-typescript": {
+ "version": "3.10.1",
+ "resolved": "https://registry.npmjs.org/eslint-import-resolver-typescript/-/eslint-import-resolver-typescript-3.10.1.tgz",
+ "integrity": "sha512-A1rHYb06zjMGAxdLSkN2fXPBwuSaQ0iO5M/hdyS0Ajj1VBaRp0sPD3dn1FhME3c/JluGFbwSxyCfqdSbtQLAHQ==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@nolyfill/is-core-module": "1.0.39",
+ "debug": "^4.4.0",
+ "get-tsconfig": "^4.10.0",
+ "is-bun-module": "^2.0.0",
+ "stable-hash": "^0.0.5",
+ "tinyglobby": "^0.2.13",
+ "unrs-resolver": "^1.6.2"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-import-resolver-typescript"
+ },
+ "peerDependencies": {
+ "eslint": "*",
+ "eslint-plugin-import": "*",
+ "eslint-plugin-import-x": "*"
+ },
+ "peerDependenciesMeta": {
+ "eslint-plugin-import": {
+ "optional": true
+ },
+ "eslint-plugin-import-x": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-module-utils": {
+ "version": "2.12.1",
+ "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.1.tgz",
+ "integrity": "sha512-L8jSWTze7K2mTg0vos/RuLRS5soomksDPoJLXIslC7c8Wmut3bx7CPpJijDcBZtxQ5lrbUdM+s0OlNbz0DCDNw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^3.2.7"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependenciesMeta": {
+ "eslint": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-module-utils/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-plugin-import": {
+ "version": "2.32.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.32.0.tgz",
+ "integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@rtsao/scc": "^1.1.0",
+ "array-includes": "^3.1.9",
+ "array.prototype.findlastindex": "^1.2.6",
+ "array.prototype.flat": "^1.3.3",
+ "array.prototype.flatmap": "^1.3.3",
+ "debug": "^3.2.7",
+ "doctrine": "^2.1.0",
+ "eslint-import-resolver-node": "^0.3.9",
+ "eslint-module-utils": "^2.12.1",
+ "hasown": "^2.0.2",
+ "is-core-module": "^2.16.1",
+ "is-glob": "^4.0.3",
+ "minimatch": "^3.1.2",
+ "object.fromentries": "^2.0.8",
+ "object.groupby": "^1.0.3",
+ "object.values": "^1.2.1",
+ "semver": "^6.3.1",
+ "string.prototype.trimend": "^1.0.9",
+ "tsconfig-paths": "^3.15.0"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9"
+ }
+ },
+ "node_modules/eslint-plugin-import/node_modules/debug": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz",
+ "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.1"
+ }
+ },
+ "node_modules/eslint-plugin-jsx-a11y": {
+ "version": "6.10.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-jsx-a11y/-/eslint-plugin-jsx-a11y-6.10.2.tgz",
+ "integrity": "sha512-scB3nz4WmG75pV8+3eRUQOHZlNSUhFNq37xnpgRkCCELU3XMvXAxLk1eqWWyE22Ki4Q01Fnsw9BA3cJHDPgn2Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "aria-query": "^5.3.2",
+ "array-includes": "^3.1.8",
+ "array.prototype.flatmap": "^1.3.2",
+ "ast-types-flow": "^0.0.8",
+ "axe-core": "^4.10.0",
+ "axobject-query": "^4.1.0",
+ "damerau-levenshtein": "^1.0.8",
+ "emoji-regex": "^9.2.2",
+ "hasown": "^2.0.2",
+ "jsx-ast-utils": "^3.3.5",
+ "language-tags": "^1.0.9",
+ "minimatch": "^3.1.2",
+ "object.fromentries": "^2.0.8",
+ "safe-regex-test": "^1.0.3",
+ "string.prototype.includes": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependencies": {
+ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9"
+ }
+ },
+ "node_modules/eslint-plugin-react": {
+ "version": "7.37.5",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.37.5.tgz",
+ "integrity": "sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-includes": "^3.1.8",
+ "array.prototype.findlast": "^1.2.5",
+ "array.prototype.flatmap": "^1.3.3",
+ "array.prototype.tosorted": "^1.1.4",
+ "doctrine": "^2.1.0",
+ "es-iterator-helpers": "^1.2.1",
+ "estraverse": "^5.3.0",
+ "hasown": "^2.0.2",
+ "jsx-ast-utils": "^2.4.1 || ^3.0.0",
+ "minimatch": "^3.1.2",
+ "object.entries": "^1.1.9",
+ "object.fromentries": "^2.0.8",
+ "object.values": "^1.2.1",
+ "prop-types": "^15.8.1",
+ "resolve": "^2.0.0-next.5",
+ "semver": "^6.3.1",
+ "string.prototype.matchall": "^4.0.12",
+ "string.prototype.repeat": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7"
+ }
+ },
+ "node_modules/eslint-plugin-react-hooks": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz",
+ "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/core": "^7.24.4",
+ "@babel/parser": "^7.24.4",
+ "hermes-parser": "^0.25.1",
+ "zod": "^3.25.0 || ^4.0.0",
+ "zod-validation-error": "^3.5.0 || ^4.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-react/node_modules/resolve": {
+ "version": "2.0.0-next.6",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.6.tgz",
+ "integrity": "sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "is-core-module": "^2.16.1",
+ "node-exports-info": "^1.6.0",
+ "object-keys": "^1.1.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "8.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz",
+ "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "4.2.1",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
+ "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/espree": {
+ "version": "10.4.0",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz",
+ "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.15.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^4.2.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/expect-type": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
+ "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/exsolve": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz",
+ "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==",
+ "license": "MIT"
+ },
+ "node_modules/fast-check": {
+ "version": "3.23.2",
+ "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz",
+ "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "pure-rand": "^6.1.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
+ "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fastq": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz",
+ "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=16.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz",
+ "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.4"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
+ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/for-each": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
+ "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-callable": "^1.2.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/foreground-child": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/framer-motion": {
+ "version": "12.38.0",
+ "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.38.0.tgz",
+ "integrity": "sha512-rFYkY/pigbcswl1XQSb7q424kSTQ8q6eAC+YUsSKooHQYuLdzdHjrt6uxUC+PRAO++q5IS7+TamgIw1AphxR+g==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-dom": "^12.38.0",
+ "motion-utils": "^12.36.0",
+ "tslib": "^2.4.0"
+ },
+ "peerDependencies": {
+ "@emotion/is-prop-valid": "*",
+ "react": "^18.0.0 || ^19.0.0",
+ "react-dom": "^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@emotion/is-prop-valid": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/function.prototype.name": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz",
+ "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "functions-have-names": "^1.2.3",
+ "hasown": "^2.0.2",
+ "is-callable": "^1.2.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/functions-have-names": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
+ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/generate-function": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz",
+ "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-property": "^1.0.2"
+ }
+ },
+ "node_modules/generator-function": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz",
+ "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-port-please": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/get-port-please/-/get-port-please-3.2.0.tgz",
+ "integrity": "sha512-I9QVvBw5U/hw3RmWpYKRumUeaDgxTPd401x364rLmWBJcOQ753eov1eTgzDqRG9bqFIfDc7gfzcQEWrUri3o1A==",
+ "license": "MIT"
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/get-symbol-description": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz",
+ "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-tsconfig": {
+ "version": "4.13.7",
+ "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.13.7.tgz",
+ "integrity": "sha512-7tN6rFgBlMgpBML5j8typ92BKFi2sFQvIdpAqLA2beia5avZDrMs0FLZiM5etShWq5irVyGcGMEA1jcDaK7A/Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "resolve-pkg-maps": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
+ }
+ },
+ "node_modules/giget": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz",
+ "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==",
+ "license": "MIT",
+ "dependencies": {
+ "citty": "^0.1.6",
+ "consola": "^3.4.0",
+ "defu": "^6.1.4",
+ "node-fetch-native": "^1.6.6",
+ "nypm": "^0.6.0",
+ "pathe": "^2.0.3"
+ },
+ "bin": {
+ "giget": "dist/cli.mjs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz",
+ "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/globalthis": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz",
+ "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-properties": "^1.2.1",
+ "gopd": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "license": "ISC"
+ },
+ "node_modules/grammex": {
+ "version": "3.1.12",
+ "resolved": "https://registry.npmjs.org/grammex/-/grammex-3.1.12.tgz",
+ "integrity": "sha512-6ufJOsSA7LcQehIJNCO7HIBykfM7DXQual0Ny780/DEcJIpBlHRvcqEBWGPYd7hrXL2GJ3oJI1MIhaXjWmLQOQ==",
+ "license": "MIT"
+ },
+ "node_modules/graphmatch": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/graphmatch/-/graphmatch-1.1.1.tgz",
+ "integrity": "sha512-5ykVn/EXM1hF0XCaWh05VbYvEiOL2lY1kBxZtaYsyvjp7cmWOU1XsAdfQBwClraEofXDT197lFbXOEVMHpvQOg==",
+ "license": "MIT"
+ },
+ "node_modules/has-bigints": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz",
+ "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz",
+ "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-define-property": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-proto": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz",
+ "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
+ "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hermes-estree": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
+ "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/hermes-parser": {
+ "version": "0.25.1",
+ "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz",
+ "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hermes-estree": "0.25.1"
+ }
+ },
+ "node_modules/hono": {
+ "version": "4.11.4",
+ "resolved": "https://registry.npmjs.org/hono/-/hono-4.11.4.tgz",
+ "integrity": "sha512-U7tt8JsyrxSRKspfhtLET79pU8K+tInj5QZXs1jSugO1Vq5dFj3kmZsRldo29mTBfcjDRVRXrEZ6LS63Cog9ZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=16.9.0"
+ }
+ },
+ "node_modules/html-encoding-sniffer": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-6.0.0.tgz",
+ "integrity": "sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@exodus/bytes": "^1.6.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ }
+ },
+ "node_modules/http-status-codes": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz",
+ "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==",
+ "license": "MIT"
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/indent-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/internal-slot": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz",
+ "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "hasown": "^2.0.2",
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/is-array-buffer": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz",
+ "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-async-function": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz",
+ "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "async-function": "^1.0.0",
+ "call-bound": "^1.0.3",
+ "get-proto": "^1.0.1",
+ "has-tostringtag": "^1.0.2",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-bigint": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz",
+ "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-bigints": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-boolean-object": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz",
+ "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-bun-module": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/is-bun-module/-/is-bun-module-2.0.0.tgz",
+ "integrity": "sha512-gNCGbnnnnFAUGKeZ9PdbyeGYJqewpmc2aKHUEMO5nQPWU9lOmv7jcmQIv+qHD8fXW6W7qfuCwX4rY9LNRjXrkQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.7.1"
+ }
+ },
+ "node_modules/is-bun-module/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
+ "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-data-view": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz",
+ "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "get-intrinsic": "^1.2.6",
+ "is-typed-array": "^1.1.13"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-date-object": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz",
+ "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-finalizationregistry": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz",
+ "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-generator-function": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz",
+ "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.4",
+ "generator-function": "^2.0.0",
+ "get-proto": "^1.0.1",
+ "has-tostringtag": "^1.0.2",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-map": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz",
+ "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-negative-zero": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz",
+ "integrity": "sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-number-object": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz",
+ "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-potential-custom-element-name": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/is-property": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz",
+ "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
+ "license": "MIT"
+ },
+ "node_modules/is-regex": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz",
+ "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "gopd": "^1.2.0",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-set": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz",
+ "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-shared-array-buffer": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz",
+ "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-string": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz",
+ "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-symbol": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz",
+ "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "has-symbols": "^1.1.0",
+ "safe-regex-test": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-typed-array": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz",
+ "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "which-typed-array": "^1.1.16"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakmap": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz",
+ "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakref": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz",
+ "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-weakset": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz",
+ "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "get-intrinsic": "^1.2.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz",
+ "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "license": "ISC"
+ },
+ "node_modules/iterator.prototype": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz",
+ "integrity": "sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.6",
+ "get-proto": "^1.0.0",
+ "has-symbols": "^1.1.0",
+ "set-function-name": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "2.6.1",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz",
+ "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==",
+ "license": "MIT",
+ "bin": {
+ "jiti": "lib/jiti-cli.mjs"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsdom": {
+ "version": "29.0.1",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-29.0.1.tgz",
+ "integrity": "sha512-z6JOK5gRO7aMybVq/y/MlIpKh8JIi68FBKMUtKkK2KH/wMSRlCxQ682d08LB9fYXplyY/UXG8P4XXTScmdjApg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@asamuzakjp/css-color": "^5.0.1",
+ "@asamuzakjp/dom-selector": "^7.0.3",
+ "@bramus/specificity": "^2.4.2",
+ "@csstools/css-syntax-patches-for-csstree": "^1.1.1",
+ "@exodus/bytes": "^1.15.0",
+ "css-tree": "^3.2.1",
+ "data-urls": "^7.0.0",
+ "decimal.js": "^10.6.0",
+ "html-encoding-sniffer": "^6.0.0",
+ "is-potential-custom-element-name": "^1.0.1",
+ "lru-cache": "^11.2.7",
+ "parse5": "^8.0.0",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^6.0.1",
+ "undici": "^7.24.5",
+ "w3c-xmlserializer": "^5.0.0",
+ "webidl-conversions": "^8.0.1",
+ "whatwg-mimetype": "^5.0.0",
+ "whatwg-url": "^16.0.1",
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.13.0 || >=24.0.0"
+ },
+ "peerDependencies": {
+ "canvas": "^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jsdom/node_modules/lru-cache": {
+ "version": "11.2.7",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz",
+ "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": "20 || >=22"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-schema-to-ts": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz",
+ "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "ts-algebra": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsx-ast-utils": {
+ "version": "3.3.5",
+ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
+ "integrity": "sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-includes": "^3.1.6",
+ "array.prototype.flat": "^1.3.1",
+ "object.assign": "^4.1.4",
+ "object.values": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/language-subtag-registry": {
+ "version": "0.3.23",
+ "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz",
+ "integrity": "sha512-0K65Lea881pHotoGEa5gDlMxt3pctLi2RplBb7Ezh4rRdLEOtgi7n4EwK9lamnUCkKBqaeKRVebTq6BAxSkpXQ==",
+ "dev": true,
+ "license": "CC0-1.0"
+ },
+ "node_modules/language-tags": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/language-tags/-/language-tags-1.0.9.tgz",
+ "integrity": "sha512-MbjN408fEndfiQXbFQ1vnd+1NoLDsnQW41410oQBXiyXDMYH5z505juWa4KUE1LqxRC7DgOgZDbKLxHIwm27hA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "language-subtag-registry": "^0.3.20"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lightningcss": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz",
+ "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==",
+ "dev": true,
+ "license": "MPL-2.0",
+ "dependencies": {
+ "detect-libc": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ },
+ "optionalDependencies": {
+ "lightningcss-android-arm64": "1.32.0",
+ "lightningcss-darwin-arm64": "1.32.0",
+ "lightningcss-darwin-x64": "1.32.0",
+ "lightningcss-freebsd-x64": "1.32.0",
+ "lightningcss-linux-arm-gnueabihf": "1.32.0",
+ "lightningcss-linux-arm64-gnu": "1.32.0",
+ "lightningcss-linux-arm64-musl": "1.32.0",
+ "lightningcss-linux-x64-gnu": "1.32.0",
+ "lightningcss-linux-x64-musl": "1.32.0",
+ "lightningcss-win32-arm64-msvc": "1.32.0",
+ "lightningcss-win32-x64-msvc": "1.32.0"
+ }
+ },
+ "node_modules/lightningcss-android-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz",
+ "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-arm64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz",
+ "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-darwin-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz",
+ "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-freebsd-x64": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz",
+ "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm-gnueabihf": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz",
+ "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz",
+ "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-arm64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz",
+ "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-gnu": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz",
+ "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-linux-x64-musl": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz",
+ "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-arm64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz",
+ "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lightningcss-win32-x64-msvc": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz",
+ "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MPL-2.0",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/parcel"
+ }
+ },
+ "node_modules/lilconfig": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz",
+ "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/long": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/long/-/long-5.3.2.tgz",
+ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/lru.min": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/lru.min/-/lru.min-1.1.4.tgz",
+ "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==",
+ "license": "MIT",
+ "engines": {
+ "bun": ">=1.0.0",
+ "deno": ">=1.30.0",
+ "node": ">=8.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wellwelwel"
+ }
+ },
+ "node_modules/lz-string": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/lz-string/-/lz-string-1.5.0.tgz",
+ "integrity": "sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "bin": {
+ "lz-string": "bin/bin.js"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/mdn-data": {
+ "version": "2.27.1",
+ "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz",
+ "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==",
+ "dev": true,
+ "license": "CC0-1.0"
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
+ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/motion-dom": {
+ "version": "12.38.0",
+ "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.38.0.tgz",
+ "integrity": "sha512-pdkHLD8QYRp8VfiNLb8xIBJis1byQ9gPT3Jnh2jqfFtAsWUA3dEepDlsWe/xMpO8McV+VdpKVcp+E+TGJEtOoA==",
+ "license": "MIT",
+ "dependencies": {
+ "motion-utils": "^12.36.0"
+ }
+ },
+ "node_modules/motion-utils": {
+ "version": "12.36.0",
+ "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.36.0.tgz",
+ "integrity": "sha512-eHWisygbiwVvf6PZ1vhaHCLamvkSbPIeAYxWUuL3a2PD/TROgE7FvfHWTIH4vMl798QLfMw15nRqIaRDXTlYRg==",
+ "license": "MIT"
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/mustache": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/mustache/-/mustache-4.2.0.tgz",
+ "integrity": "sha512-71ippSywq5Yb7/tVYyGbkBggbU8H3u5Rz56fH60jGFgr8uHwxs+aSKeqmluIVzM0m0kB7xQjKS6qPfd0b2ZoqQ==",
+ "license": "MIT",
+ "bin": {
+ "mustache": "bin/mustache"
+ }
+ },
+ "node_modules/mysql2": {
+ "version": "3.15.3",
+ "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.15.3.tgz",
+ "integrity": "sha512-FBrGau0IXmuqg4haEZRBfHNWB5mUARw6hNwPDXXGg0XzVJ50mr/9hb267lvpVMnhZ1FON3qNd4Xfcez1rbFwSg==",
+ "license": "MIT",
+ "dependencies": {
+ "aws-ssl-profiles": "^1.1.1",
+ "denque": "^2.1.0",
+ "generate-function": "^2.3.1",
+ "iconv-lite": "^0.7.0",
+ "long": "^5.2.1",
+ "lru.min": "^1.0.0",
+ "named-placeholders": "^1.1.3",
+ "seq-queue": "^0.0.5",
+ "sqlstring": "^2.3.2"
+ },
+ "engines": {
+ "node": ">= 8.0"
+ }
+ },
+ "node_modules/named-placeholders": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/named-placeholders/-/named-placeholders-1.1.6.tgz",
+ "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==",
+ "license": "MIT",
+ "dependencies": {
+ "lru.min": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/napi-postinstall": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz",
+ "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "napi-postinstall": "lib/cli.js"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.18.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/napi-postinstall"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/next": {
+ "version": "16.2.1",
+ "resolved": "https://registry.npmjs.org/next/-/next-16.2.1.tgz",
+ "integrity": "sha512-VaChzNL7o9rbfdt60HUj8tev4m6d7iC1igAy157526+cJlXOQu5LzsBXNT+xaJnTP/k+utSX5vMv7m0G+zKH+Q==",
+ "license": "MIT",
+ "dependencies": {
+ "@next/env": "16.2.1",
+ "@swc/helpers": "0.5.15",
+ "baseline-browser-mapping": "^2.9.19",
+ "caniuse-lite": "^1.0.30001579",
+ "postcss": "8.4.31",
+ "styled-jsx": "5.1.6"
+ },
+ "bin": {
+ "next": "dist/bin/next"
+ },
+ "engines": {
+ "node": ">=20.9.0"
+ },
+ "optionalDependencies": {
+ "@next/swc-darwin-arm64": "16.2.1",
+ "@next/swc-darwin-x64": "16.2.1",
+ "@next/swc-linux-arm64-gnu": "16.2.1",
+ "@next/swc-linux-arm64-musl": "16.2.1",
+ "@next/swc-linux-x64-gnu": "16.2.1",
+ "@next/swc-linux-x64-musl": "16.2.1",
+ "@next/swc-win32-arm64-msvc": "16.2.1",
+ "@next/swc-win32-x64-msvc": "16.2.1",
+ "sharp": "^0.34.5"
+ },
+ "peerDependencies": {
+ "@opentelemetry/api": "^1.1.0",
+ "@playwright/test": "^1.51.1",
+ "babel-plugin-react-compiler": "*",
+ "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
+ "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0",
+ "sass": "^1.3.0"
+ },
+ "peerDependenciesMeta": {
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@playwright/test": {
+ "optional": true
+ },
+ "babel-plugin-react-compiler": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/next/node_modules/postcss": {
+ "version": "8.4.31",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
+ "integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.6",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/node-exports-info": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/node-exports-info/-/node-exports-info-1.6.0.tgz",
+ "integrity": "sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array.prototype.flatmap": "^1.3.3",
+ "es-errors": "^1.3.0",
+ "object.entries": "^1.1.9",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/node-fetch-native": {
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz",
+ "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==",
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.36",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz",
+ "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nypm": {
+ "version": "0.6.5",
+ "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.5.tgz",
+ "integrity": "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==",
+ "license": "MIT",
+ "dependencies": {
+ "citty": "^0.2.0",
+ "pathe": "^2.0.3",
+ "tinyexec": "^1.0.2"
+ },
+ "bin": {
+ "nypm": "dist/cli.mjs"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/nypm/node_modules/citty": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.1.tgz",
+ "integrity": "sha512-kEV95lFBhQgtogAPlQfJJ0WGVSokvLr/UEoFPiKKOXF7pl98HfUVUD0ejsuTCld/9xH9vogSywZ5KqHzXrZpqg==",
+ "license": "MIT"
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
+ "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.assign": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz",
+ "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0",
+ "has-symbols": "^1.1.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.entries": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.9.tgz",
+ "integrity": "sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.fromentries": {
+ "version": "2.0.8",
+ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz",
+ "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.groupby": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz",
+ "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.values": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz",
+ "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/obuf": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/obuf/-/obuf-1.1.2.tgz",
+ "integrity": "sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg==",
+ "license": "MIT"
+ },
+ "node_modules/obug": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.1.tgz",
+ "integrity": "sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==",
+ "dev": true,
+ "funding": [
+ "https://github.com/sponsors/sxzz",
+ "https://opencollective.com/debug"
+ ],
+ "license": "MIT"
+ },
+ "node_modules/ohash": {
+ "version": "2.0.11",
+ "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz",
+ "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==",
+ "license": "MIT"
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/own-keys": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz",
+ "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "get-intrinsic": "^1.2.6",
+ "object-keys": "^1.1.1",
+ "safe-push-apply": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse5": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.0.tgz",
+ "integrity": "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pathe": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
+ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==",
+ "license": "MIT"
+ },
+ "node_modules/perfect-debounce": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz",
+ "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==",
+ "license": "MIT"
+ },
+ "node_modules/pg": {
+ "version": "8.20.0",
+ "resolved": "https://registry.npmjs.org/pg/-/pg-8.20.0.tgz",
+ "integrity": "sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==",
+ "license": "MIT",
+ "dependencies": {
+ "pg-connection-string": "^2.12.0",
+ "pg-pool": "^3.13.0",
+ "pg-protocol": "^1.13.0",
+ "pg-types": "2.2.0",
+ "pgpass": "1.0.5"
+ },
+ "engines": {
+ "node": ">= 16.0.0"
+ },
+ "optionalDependencies": {
+ "pg-cloudflare": "^1.3.0"
+ },
+ "peerDependencies": {
+ "pg-native": ">=3.0.1"
+ },
+ "peerDependenciesMeta": {
+ "pg-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/pg-cloudflare": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.3.0.tgz",
+ "integrity": "sha512-6lswVVSztmHiRtD6I8hw4qP/nDm1EJbKMRhf3HCYaqud7frGysPv7FYJ5noZQdhQtN2xJnimfMtvQq21pdbzyQ==",
+ "license": "MIT",
+ "optional": true
+ },
+ "node_modules/pg-connection-string": {
+ "version": "2.12.0",
+ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.12.0.tgz",
+ "integrity": "sha512-U7qg+bpswf3Cs5xLzRqbXbQl85ng0mfSV/J0nnA31MCLgvEaAo7CIhmeyrmJpOr7o+zm0rXK+hNnT5l9RHkCkQ==",
+ "license": "MIT"
+ },
+ "node_modules/pg-int8": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
+ "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/pg-numeric": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/pg-numeric/-/pg-numeric-1.0.2.tgz",
+ "integrity": "sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pg-pool": {
+ "version": "3.13.0",
+ "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.13.0.tgz",
+ "integrity": "sha512-gB+R+Xud1gLFuRD/QgOIgGOBE2KCQPaPwkzBBGC9oG69pHTkhQeIuejVIk3/cnDyX39av2AxomQiyPT13WKHQA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "pg": ">=8.0"
+ }
+ },
+ "node_modules/pg-protocol": {
+ "version": "1.13.0",
+ "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.13.0.tgz",
+ "integrity": "sha512-zzdvXfS6v89r6v7OcFCHfHlyG/wvry1ALxZo4LqgUoy7W9xhBDMaqOuMiF3qEV45VqsN6rdlcehHrfDtlCPc8w==",
+ "license": "MIT"
+ },
+ "node_modules/pg-types": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
+ "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
+ "license": "MIT",
+ "dependencies": {
+ "pg-int8": "1.0.1",
+ "postgres-array": "~2.0.0",
+ "postgres-bytea": "~1.0.0",
+ "postgres-date": "~1.0.4",
+ "postgres-interval": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pg-types/node_modules/postgres-array": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
+ "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pgpass": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
+ "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
+ "license": "MIT",
+ "dependencies": {
+ "split2": "^4.1.0"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pkg-types": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz",
+ "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==",
+ "license": "MIT",
+ "dependencies": {
+ "confbox": "^0.2.2",
+ "exsolve": "^1.0.7",
+ "pathe": "^2.0.3"
+ }
+ },
+ "node_modules/possible-typed-array-names": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
+ "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.8",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
+ "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postgres": {
+ "version": "3.4.7",
+ "resolved": "https://registry.npmjs.org/postgres/-/postgres-3.4.7.tgz",
+ "integrity": "sha512-Jtc2612XINuBjIl/QTWsV5UvE8UHuNblcO3vVADSrKsrc6RqGX6lOW1cEo3CM2v0XG4Nat8nI+YM7/f26VxXLw==",
+ "license": "Unlicense",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/porsager"
+ }
+ },
+ "node_modules/postgres-array": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-3.0.4.tgz",
+ "integrity": "sha512-nAUSGfSDGOaOAEGwqsRY27GPOea7CNipJPOA7lPbdEpx5Kg3qzdP0AaWC5MlhTWV9s4hFX39nomVZ+C4tnGOJQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/postgres-bytea": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.1.tgz",
+ "integrity": "sha512-5+5HqXnsZPE65IJZSMkZtURARZelel2oXUEO8rH83VS/hxH5vv1uHquPg5wZs8yMAfdv971IU+kcPUczi7NVBQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postgres-date": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
+ "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postgres-interval": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
+ "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
+ "license": "MIT",
+ "dependencies": {
+ "xtend": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postgres-range": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/postgres-range/-/postgres-range-1.1.4.tgz",
+ "integrity": "sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w==",
+ "license": "MIT"
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/pretty-format": {
+ "version": "27.5.1",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-27.5.1.tgz",
+ "integrity": "sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^17.0.1"
+ },
+ "engines": {
+ "node": "^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/pretty-format/node_modules/react-is": {
+ "version": "17.0.2",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
+ "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==",
+ "dev": true,
+ "license": "MIT",
+ "peer": true
+ },
+ "node_modules/prisma": {
+ "version": "7.5.0",
+ "resolved": "https://registry.npmjs.org/prisma/-/prisma-7.5.0.tgz",
+ "integrity": "sha512-n30qZpWehaYQzigLjmuPisyEsvOzHt7bZeRyg8gZ5DvJo9FGjD+gNaY59Ns3hlLD5/jZH5GBeftIss0jDbUoLg==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@prisma/config": "7.5.0",
+ "@prisma/dev": "0.20.0",
+ "@prisma/engines": "7.5.0",
+ "@prisma/studio-core": "0.21.1",
+ "mysql2": "3.15.3",
+ "postgres": "3.4.7"
+ },
+ "bin": {
+ "prisma": "build/index.js"
+ },
+ "engines": {
+ "node": "^20.19 || ^22.12 || >=24.0"
+ },
+ "peerDependencies": {
+ "better-sqlite3": ">=9.0.0",
+ "typescript": ">=5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "better-sqlite3": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/proper-lockfile": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/proper-lockfile/-/proper-lockfile-4.1.2.tgz",
+ "integrity": "sha512-TjNPblN4BwAWMXU8s9AEz4JmQxnD1NNL7bNOY/AKUzyamc379FWASUhc/K1pL2noVb+XmZKLL68cjzLsiOAMaA==",
+ "license": "MIT",
+ "dependencies": {
+ "graceful-fs": "^4.2.4",
+ "retry": "^0.12.0",
+ "signal-exit": "^3.0.2"
+ }
+ },
+ "node_modules/proper-lockfile/node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "license": "ISC"
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/pure-rand": {
+ "version": "6.1.0",
+ "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
+ "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/dubzzz"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/fast-check"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/rc9": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz",
+ "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==",
+ "license": "MIT",
+ "dependencies": {
+ "defu": "^6.1.4",
+ "destr": "^2.0.3"
+ }
+ },
+ "node_modules/react": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
+ "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "19.2.4",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz",
+ "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==",
+ "license": "MIT",
+ "dependencies": {
+ "scheduler": "^0.27.0"
+ },
+ "peerDependencies": {
+ "react": "^19.2.4"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/readdirp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.18.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/redent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
+ "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "indent-string": "^4.0.0",
+ "strip-indent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/reflect.getprototypeof": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
+ "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.9",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.7",
+ "get-proto": "^1.0.1",
+ "which-builtin-type": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/regexp-to-ast": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/regexp-to-ast/-/regexp-to-ast-0.5.0.tgz",
+ "integrity": "sha512-tlbJqcMHnPKI9zSrystikWKwHkBqu2a/Sgw01h3zFjvYrMxEDYHzzoMZnUrbIfpTFEsoRnnviOXNCzFiSc54Qw==",
+ "license": "MIT"
+ },
+ "node_modules/regexp.prototype.flags": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz",
+ "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "define-properties": "^1.2.1",
+ "es-errors": "^1.3.0",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "set-function-name": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/remeda": {
+ "version": "2.33.4",
+ "resolved": "https://registry.npmjs.org/remeda/-/remeda-2.33.4.tgz",
+ "integrity": "sha512-ygHswjlc/opg2VrtiYvUOPLjxjtdKvjGz1/plDhkG66hjNjFr1xmfrs2ClNFo/E6TyUFiwYNh53bKV26oBoMGQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/remeda"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.11",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
+ "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-core-module": "^2.16.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/resolve-pkg-maps": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz",
+ "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1"
+ }
+ },
+ "node_modules/retry": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
+ "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rolldown": {
+ "version": "1.0.0-rc.10",
+ "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.10.tgz",
+ "integrity": "sha512-q7j6vvarRFmKpgJUT8HCAUljkgzEp4LAhPlJUvQhA5LA1SUL36s5QCysMutErzL3EbNOZOkoziSx9iZC4FddKA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@oxc-project/types": "=0.120.0",
+ "@rolldown/pluginutils": "1.0.0-rc.10"
+ },
+ "bin": {
+ "rolldown": "bin/cli.mjs"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "optionalDependencies": {
+ "@rolldown/binding-android-arm64": "1.0.0-rc.10",
+ "@rolldown/binding-darwin-arm64": "1.0.0-rc.10",
+ "@rolldown/binding-darwin-x64": "1.0.0-rc.10",
+ "@rolldown/binding-freebsd-x64": "1.0.0-rc.10",
+ "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.10",
+ "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.10",
+ "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.10",
+ "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.10",
+ "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.10",
+ "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.10",
+ "@rolldown/binding-linux-x64-musl": "1.0.0-rc.10",
+ "@rolldown/binding-openharmony-arm64": "1.0.0-rc.10",
+ "@rolldown/binding-wasm32-wasi": "1.0.0-rc.10",
+ "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.10",
+ "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.10"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/safe-array-concat": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz",
+ "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "get-intrinsic": "^1.2.6",
+ "has-symbols": "^1.1.0",
+ "isarray": "^2.0.5"
+ },
+ "engines": {
+ "node": ">=0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-push-apply": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
+ "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "isarray": "^2.0.5"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safe-regex-test": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz",
+ "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "is-regex": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/saxes": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "xmlchars": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=v12.22.7"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz",
+ "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==",
+ "license": "MIT"
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/seq-queue": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/seq-queue/-/seq-queue-0.0.5.tgz",
+ "integrity": "sha512-hr3Wtp/GZIc/6DAGPDcV4/9WoZhjrkXsi5B/07QgX8tsdc6ilr7BFM6PM6rbdAX1kFSDYeZGLipIZZKyQP0O5Q=="
+ },
+ "node_modules/set-function-length": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz",
+ "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2",
+ "get-intrinsic": "^1.2.4",
+ "gopd": "^1.0.1",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/set-function-name": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz",
+ "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-data-property": "^1.1.4",
+ "es-errors": "^1.3.0",
+ "functions-have-names": "^1.2.3",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/set-proto": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz",
+ "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/sharp": {
+ "version": "0.34.5",
+ "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz",
+ "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==",
+ "hasInstallScript": true,
+ "license": "Apache-2.0",
+ "optional": true,
+ "dependencies": {
+ "@img/colour": "^1.0.0",
+ "detect-libc": "^2.1.2",
+ "semver": "^7.7.3"
+ },
+ "engines": {
+ "node": "^18.17.0 || ^20.3.0 || >=21.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/libvips"
+ },
+ "optionalDependencies": {
+ "@img/sharp-darwin-arm64": "0.34.5",
+ "@img/sharp-darwin-x64": "0.34.5",
+ "@img/sharp-libvips-darwin-arm64": "1.2.4",
+ "@img/sharp-libvips-darwin-x64": "1.2.4",
+ "@img/sharp-libvips-linux-arm": "1.2.4",
+ "@img/sharp-libvips-linux-arm64": "1.2.4",
+ "@img/sharp-libvips-linux-ppc64": "1.2.4",
+ "@img/sharp-libvips-linux-riscv64": "1.2.4",
+ "@img/sharp-libvips-linux-s390x": "1.2.4",
+ "@img/sharp-libvips-linux-x64": "1.2.4",
+ "@img/sharp-libvips-linuxmusl-arm64": "1.2.4",
+ "@img/sharp-libvips-linuxmusl-x64": "1.2.4",
+ "@img/sharp-linux-arm": "0.34.5",
+ "@img/sharp-linux-arm64": "0.34.5",
+ "@img/sharp-linux-ppc64": "0.34.5",
+ "@img/sharp-linux-riscv64": "0.34.5",
+ "@img/sharp-linux-s390x": "0.34.5",
+ "@img/sharp-linux-x64": "0.34.5",
+ "@img/sharp-linuxmusl-arm64": "0.34.5",
+ "@img/sharp-linuxmusl-x64": "0.34.5",
+ "@img/sharp-wasm32": "0.34.5",
+ "@img/sharp-win32-arm64": "0.34.5",
+ "@img/sharp-win32-ia32": "0.34.5",
+ "@img/sharp-win32-x64": "0.34.5"
+ }
+ },
+ "node_modules/sharp/node_modules/semver": {
+ "version": "7.7.4",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
+ "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
+ "license": "ISC",
+ "optional": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz",
+ "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3",
+ "side-channel-list": "^1.0.0",
+ "side-channel-map": "^1.0.1",
+ "side-channel-weakmap": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-list": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz",
+ "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-map": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz",
+ "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/side-channel-weakmap": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz",
+ "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.5",
+ "object-inspect": "^1.13.3",
+ "side-channel-map": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/split2": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
+ "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">= 10.x"
+ }
+ },
+ "node_modules/sqlstring": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/sqlstring/-/sqlstring-2.3.3.tgz",
+ "integrity": "sha512-qC9iz2FlN7DQl3+wjwn3802RTyjCx7sDvfQEXchwa6CWOx07/WVfh91gBmQ9fahw8snwGEWU3xGzOt4tFyHLxg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/stable-hash": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.5.tgz",
+ "integrity": "sha512-+L3ccpzibovGXFK+Ap/f8LOS0ahMrHTf3xu7mMLSpEGU0EO9ucaysSylKo9eRDFNhWve/y275iPmIZ4z39a9iA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/std-env": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.0.0.tgz",
+ "integrity": "sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/stop-iteration-iterator": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz",
+ "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "internal-slot": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/string.prototype.includes": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz",
+ "integrity": "sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/string.prototype.matchall": {
+ "version": "4.0.12",
+ "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.12.tgz",
+ "integrity": "sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.3",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.6",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.0.0",
+ "get-intrinsic": "^1.2.6",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "internal-slot": "^1.1.0",
+ "regexp.prototype.flags": "^1.5.3",
+ "set-function-name": "^2.0.2",
+ "side-channel": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.repeat": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/string.prototype.repeat/-/string.prototype.repeat-1.0.0.tgz",
+ "integrity": "sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.17.5"
+ }
+ },
+ "node_modules/string.prototype.trim": {
+ "version": "1.2.10",
+ "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz",
+ "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "define-data-property": "^1.1.4",
+ "define-properties": "^1.2.1",
+ "es-abstract": "^1.23.5",
+ "es-object-atoms": "^1.0.0",
+ "has-property-descriptors": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimend": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz",
+ "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.2",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimstart": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz",
+ "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "define-properties": "^1.2.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/strip-bom": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+ "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "min-indent": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/styled-jsx": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz",
+ "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==",
+ "license": "MIT",
+ "dependencies": {
+ "client-only": "0.0.1"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "@babel/core": {
+ "optional": true
+ },
+ "babel-plugin-macros": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/symbol-tree": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tailwindcss": {
+ "version": "4.2.2",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.2.tgz",
+ "integrity": "sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tapable": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz",
+ "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/webpack"
+ }
+ },
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz",
+ "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyglobby/node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz",
+ "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tldts": {
+ "version": "7.0.27",
+ "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.27.tgz",
+ "integrity": "sha512-I4FZcVFcqCRuT0ph6dCDpPuO4Xgzvh+spkcTr1gK7peIvxWauoloVO0vuy1FQnijT63ss6AsHB6+OIM4aXHbPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tldts-core": "^7.0.27"
+ },
+ "bin": {
+ "tldts": "bin/cli.js"
+ }
+ },
+ "node_modules/tldts-core": {
+ "version": "7.0.27",
+ "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.27.tgz",
+ "integrity": "sha512-YQ7uPjgWUibIK6DW5lrKujGwUKhLevU4hcGbP5O6TcIUb+oTjJYJVWPS4nZsIHrEEEG6myk/oqAJUEQmpZrHsg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tough-cookie": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz",
+ "integrity": "sha512-LktZQb3IeoUWB9lqR5EWTHgW/VTITCXg4D21M+lvybRVdylLrRMnqaIONLVb5mav8vM19m44HIcGq4qASeu2Qw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "tldts": "^7.0.5"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz",
+ "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/ts-algebra": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz",
+ "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==",
+ "license": "MIT"
+ },
+ "node_modules/ts-api-utils": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz",
+ "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.12"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.8.4"
+ }
+ },
+ "node_modules/tsconfig-paths": {
+ "version": "3.15.0",
+ "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz",
+ "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/json5": "^0.0.29",
+ "json5": "^1.0.2",
+ "minimist": "^1.2.6",
+ "strip-bom": "^3.0.0"
+ }
+ },
+ "node_modules/tsconfig-paths/node_modules/json5": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz",
+ "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "minimist": "^1.2.0"
+ },
+ "bin": {
+ "json5": "lib/cli.js"
+ }
+ },
+ "node_modules/tslib": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
+ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
+ "license": "0BSD"
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/typed-array-buffer": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
+ "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "es-errors": "^1.3.0",
+ "is-typed-array": "^1.1.14"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/typed-array-byte-length": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz",
+ "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.8",
+ "for-each": "^0.3.3",
+ "gopd": "^1.2.0",
+ "has-proto": "^1.2.0",
+ "is-typed-array": "^1.1.14"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-byte-offset": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz",
+ "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "for-each": "^0.3.3",
+ "gopd": "^1.2.0",
+ "has-proto": "^1.2.0",
+ "is-typed-array": "^1.1.15",
+ "reflect.getprototypeof": "^1.0.9"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typed-array-length": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz",
+ "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bind": "^1.0.7",
+ "for-each": "^0.3.3",
+ "gopd": "^1.0.1",
+ "is-typed-array": "^1.1.13",
+ "possible-typed-array-names": "^1.0.0",
+ "reflect.getprototypeof": "^1.0.6"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "devOptional": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/typescript-eslint": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.57.1.tgz",
+ "integrity": "sha512-fLvZWf+cAGw3tqMCYzGIU6yR8K+Y9NT2z23RwOjlNFF2HwSB3KhdEFI5lSBv8tNmFkkBShSjsCjzx1vahZfISA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "8.57.1",
+ "@typescript-eslint/parser": "8.57.1",
+ "@typescript-eslint/typescript-estree": "8.57.1",
+ "@typescript-eslint/utils": "8.57.1"
+ },
+ "engines": {
+ "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0",
+ "typescript": ">=4.8.4 <6.0.0"
+ }
+ },
+ "node_modules/unbox-primitive": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz",
+ "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.3",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.1.0",
+ "which-boxed-primitive": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/undici": {
+ "version": "7.24.5",
+ "resolved": "https://registry.npmjs.org/undici/-/undici-7.24.5.tgz",
+ "integrity": "sha512-3IWdCpjgxp15CbJnsi/Y9TCDE7HWVN19j1hmzVhoAkY/+CJx449tVxT5wZc1Gwg8J+P0LWvzlBzxYRnHJ+1i7Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20.18.1"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "license": "MIT"
+ },
+ "node_modules/unrs-resolver": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz",
+ "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "dependencies": {
+ "napi-postinstall": "^0.3.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/unrs-resolver"
+ },
+ "optionalDependencies": {
+ "@unrs/resolver-binding-android-arm-eabi": "1.11.1",
+ "@unrs/resolver-binding-android-arm64": "1.11.1",
+ "@unrs/resolver-binding-darwin-arm64": "1.11.1",
+ "@unrs/resolver-binding-darwin-x64": "1.11.1",
+ "@unrs/resolver-binding-freebsd-x64": "1.11.1",
+ "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1",
+ "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1",
+ "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-arm64-musl": "1.11.1",
+ "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1",
+ "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-x64-gnu": "1.11.1",
+ "@unrs/resolver-binding-linux-x64-musl": "1.11.1",
+ "@unrs/resolver-binding-wasm32-wasi": "1.11.1",
+ "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1",
+ "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1",
+ "@unrs/resolver-binding-win32-x64-msvc": "1.11.1"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/valibot": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/valibot/-/valibot-1.2.0.tgz",
+ "integrity": "sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "typescript": ">=5"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.1.tgz",
+ "integrity": "sha512-wt+Z2qIhfFt85uiyRt5LPU4oVEJBXj8hZNWKeqFG4gRG/0RaRGJ7njQCwzFVjO+v4+Ipmf5CY7VdmZRAYYBPHw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "lightningcss": "^1.32.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.8",
+ "rolldown": "1.0.0-rc.10",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "@vitejs/devtools": "^0.1.0",
+ "esbuild": "^0.27.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "@vitejs/devtools": {
+ "optional": true
+ },
+ "esbuild": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.0.tgz",
+ "integrity": "sha512-YbDrMF9jM2Lqc++2530UourxZHmkKLxrs4+mYhEwqWS97WJ7wOYEkcr+QfRgJ3PW9wz3odRijLZjHEaRLTNbqw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "4.1.0",
+ "@vitest/mocker": "4.1.0",
+ "@vitest/pretty-format": "4.1.0",
+ "@vitest/runner": "4.1.0",
+ "@vitest/snapshot": "4.1.0",
+ "@vitest/spy": "4.1.0",
+ "@vitest/utils": "4.1.0",
+ "es-module-lexer": "^2.0.0",
+ "expect-type": "^1.3.0",
+ "magic-string": "^0.30.21",
+ "obug": "^2.1.1",
+ "pathe": "^2.0.3",
+ "picomatch": "^4.0.3",
+ "std-env": "^4.0.0-rc.1",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^1.0.2",
+ "tinyglobby": "^0.2.15",
+ "tinyrainbow": "^3.0.3",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^20.0.0 || ^22.0.0 || >=24.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@opentelemetry/api": "^1.9.0",
+ "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0",
+ "@vitest/browser-playwright": "4.1.0",
+ "@vitest/browser-preview": "4.1.0",
+ "@vitest/browser-webdriverio": "4.1.0",
+ "@vitest/ui": "4.1.0",
+ "happy-dom": "*",
+ "jsdom": "*",
+ "vite": "^6.0.0 || ^7.0.0 || ^8.0.0-0"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@opentelemetry/api": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser-playwright": {
+ "optional": true
+ },
+ "@vitest/browser-preview": {
+ "optional": true
+ },
+ "@vitest/browser-webdriverio": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ },
+ "vite": {
+ "optional": false
+ }
+ }
+ },
+ "node_modules/vitest/node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/w3c-xmlserializer": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
+ "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz",
+ "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/whatwg-mimetype": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz",
+ "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "16.0.1",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-16.0.1.tgz",
+ "integrity": "sha512-1to4zXBxmXHV3IiSSEInrreIlu02vUOvrhxJJH5vcxYTBDAx51cqZiKdyTxlecdKNSjj8EcxGBxNf6Vg+945gw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@exodus/bytes": "^1.11.0",
+ "tr46": "^6.0.0",
+ "webidl-conversions": "^8.0.1"
+ },
+ "engines": {
+ "node": "^20.19.0 || ^22.12.0 || >=24.0.0"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/which-boxed-primitive": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz",
+ "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-bigint": "^1.1.0",
+ "is-boolean-object": "^1.2.1",
+ "is-number-object": "^1.1.1",
+ "is-string": "^1.1.1",
+ "is-symbol": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-builtin-type": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz",
+ "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "call-bound": "^1.0.2",
+ "function.prototype.name": "^1.1.6",
+ "has-tostringtag": "^1.0.2",
+ "is-async-function": "^2.0.0",
+ "is-date-object": "^1.1.0",
+ "is-finalizationregistry": "^1.1.0",
+ "is-generator-function": "^1.0.10",
+ "is-regex": "^1.2.1",
+ "is-weakref": "^1.0.2",
+ "isarray": "^2.0.5",
+ "which-boxed-primitive": "^1.1.0",
+ "which-collection": "^1.0.2",
+ "which-typed-array": "^1.1.16"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-collection": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz",
+ "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-map": "^2.0.3",
+ "is-set": "^2.0.3",
+ "is-weakmap": "^2.0.2",
+ "is-weakset": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/which-typed-array": {
+ "version": "1.1.20",
+ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz",
+ "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "available-typed-arrays": "^1.0.7",
+ "call-bind": "^1.0.8",
+ "call-bound": "^1.0.4",
+ "for-each": "^0.3.5",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-tostringtag": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/xml-name-validator": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
+ "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/xmlchars": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/zeptomatch": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/zeptomatch/-/zeptomatch-2.1.0.tgz",
+ "integrity": "sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==",
+ "license": "MIT",
+ "dependencies": {
+ "grammex": "^3.1.11",
+ "graphmatch": "^1.1.0"
+ }
+ },
+ "node_modules/zod": {
+ "version": "4.3.6",
+ "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
+ "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
+ "devOptional": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/colinhacks"
+ }
+ },
+ "node_modules/zod-validation-error": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz",
+ "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18.0.0"
+ },
+ "peerDependencies": {
+ "zod": "^3.25.0 || ^4.0.0"
+ }
+ },
+ "node_modules/zustand": {
+ "version": "5.0.12",
+ "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.12.tgz",
+ "integrity": "sha512-i77ae3aZq4dhMlRhJVCYgMLKuSiZAaUPAct2AksxQ+gOtimhGMdXljRT21P5BNpeT4kXlLIckvkPM029OljD7g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.20.0"
+ },
+ "peerDependencies": {
+ "@types/react": ">=18.0.0",
+ "immer": ">=9.0.6",
+ "react": ">=18.0.0",
+ "use-sync-external-store": ">=1.2.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "immer": {
+ "optional": true
+ },
+ "react": {
+ "optional": true
+ },
+ "use-sync-external-store": {
+ "optional": true
+ }
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..8955b53
--- /dev/null
+++ b/package.json
@@ -0,0 +1,43 @@
+{
+ "name": "clawcoder",
+ "version": "0.1.0",
+ "private": true,
+ "scripts": {
+ "dev": "next dev",
+ "build": "next build",
+ "start": "next start",
+ "lint": "eslint",
+ "test": "vitest run",
+ "test:watch": "vitest"
+ },
+ "dependencies": {
+ "@anthropic-ai/sdk": "^0.80.0",
+ "@prisma/adapter-pg": "^7.5.0",
+ "@prisma/client": "^7.5.0",
+ "dotenv": "^17.3.1",
+ "framer-motion": "^12.38.0",
+ "mustache": "^4.2.0",
+ "next": "16.2.1",
+ "pg": "^8.20.0",
+ "prisma": "^7.5.0",
+ "react": "19.2.4",
+ "react-dom": "19.2.4",
+ "zustand": "^5.0.12"
+ },
+ "devDependencies": {
+ "@tailwindcss/postcss": "^4",
+ "@testing-library/jest-dom": "^6.9.1",
+ "@testing-library/react": "^16.3.2",
+ "@types/mustache": "^4.2.6",
+ "@types/node": "^20",
+ "@types/pg": "^8.20.0",
+ "@types/react": "^19",
+ "@types/react-dom": "^19",
+ "eslint": "^9",
+ "eslint-config-next": "16.2.1",
+ "jsdom": "^29.0.1",
+ "tailwindcss": "^4",
+ "typescript": "^5",
+ "vitest": "^4.1.0"
+ }
+}
diff --git a/postcss.config.mjs b/postcss.config.mjs
new file mode 100644
index 0000000..61e3684
--- /dev/null
+++ b/postcss.config.mjs
@@ -0,0 +1,7 @@
+const config = {
+ plugins: {
+ "@tailwindcss/postcss": {},
+ },
+};
+
+export default config;
diff --git a/prisma.config.ts b/prisma.config.ts
new file mode 100644
index 0000000..831a20f
--- /dev/null
+++ b/prisma.config.ts
@@ -0,0 +1,14 @@
+// This file was generated by Prisma, and assumes you have installed the following:
+// npm install --save-dev prisma dotenv
+import "dotenv/config";
+import { defineConfig } from "prisma/config";
+
+export default defineConfig({
+ schema: "prisma/schema.prisma",
+ migrations: {
+ path: "prisma/migrations",
+ },
+ datasource: {
+ url: process.env["DATABASE_URL"],
+ },
+});
diff --git a/prisma/migrations/20260323005809_init/migration.sql b/prisma/migrations/20260323005809_init/migration.sql
new file mode 100644
index 0000000..283c7a2
--- /dev/null
+++ b/prisma/migrations/20260323005809_init/migration.sql
@@ -0,0 +1,59 @@
+-- CreateTable
+CREATE TABLE "users" (
+ "id" TEXT NOT NULL,
+ "email" TEXT,
+ "tier" TEXT NOT NULL DEFAULT 'free',
+ "profile_slug" TEXT,
+ "preferences" JSONB NOT NULL DEFAULT '{}',
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "updated_at" TIMESTAMP(3) NOT NULL,
+
+ CONSTRAINT "users_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "analyses" (
+ "id" TEXT NOT NULL,
+ "user_id" TEXT,
+ "input_content" TEXT NOT NULL,
+ "input_source" TEXT,
+ "overall_score" DOUBLE PRECISION,
+ "letter_grade" TEXT,
+ "criteria_scores" JSONB NOT NULL,
+ "pros" JSONB NOT NULL DEFAULT '[]',
+ "cons" JSONB NOT NULL DEFAULT '[]',
+ "suggestions" JSONB NOT NULL DEFAULT '[]',
+ "detected_profile_slug" TEXT,
+ "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+
+ CONSTRAINT "analyses_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateTable
+CREATE TABLE "applied_suggestions" (
+ "id" TEXT NOT NULL,
+ "analysis_id" TEXT NOT NULL,
+ "suggestion_id" TEXT NOT NULL,
+ "applied_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ "result_content" TEXT,
+
+ CONSTRAINT "applied_suggestions_pkey" PRIMARY KEY ("id")
+);
+
+-- CreateIndex
+CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
+
+-- CreateIndex
+CREATE INDEX "analyses_user_id_idx" ON "analyses"("user_id");
+
+-- CreateIndex
+CREATE INDEX "analyses_created_at_idx" ON "analyses"("created_at");
+
+-- CreateIndex
+CREATE INDEX "applied_suggestions_analysis_id_idx" ON "applied_suggestions"("analysis_id");
+
+-- AddForeignKey
+ALTER TABLE "analyses" ADD CONSTRAINT "analyses_user_id_fkey" FOREIGN KEY ("user_id") REFERENCES "users"("id") ON DELETE SET NULL ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE "applied_suggestions" ADD CONSTRAINT "applied_suggestions_analysis_id_fkey" FOREIGN KEY ("analysis_id") REFERENCES "analyses"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml
new file mode 100644
index 0000000..044d57c
--- /dev/null
+++ b/prisma/migrations/migration_lock.toml
@@ -0,0 +1,3 @@
+# Please do not edit this file manually
+# It should be added in your version-control system (e.g., Git)
+provider = "postgresql"
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
new file mode 100644
index 0000000..121f09c
--- /dev/null
+++ b/prisma/schema.prisma
@@ -0,0 +1,54 @@
+generator client {
+ provider = "prisma-client"
+ output = "../src/generated/prisma"
+}
+
+datasource db {
+ provider = "postgresql"
+}
+
+model User {
+ id String @id @default(uuid())
+ email String? @unique
+ tier String @default("free")
+ profileSlug String? @map("profile_slug")
+ preferences Json @default("{}")
+ createdAt DateTime @default(now()) @map("created_at")
+ updatedAt DateTime @updatedAt @map("updated_at")
+ analyses Analysis[]
+
+ @@map("users")
+}
+
+model Analysis {
+ id String @id @default(uuid())
+ userId String? @map("user_id")
+ user User? @relation(fields: [userId], references: [id])
+ inputContent String @map("input_content")
+ inputSource String? @map("input_source")
+ overallScore Float? @map("overall_score")
+ letterGrade String? @map("letter_grade")
+ criteriaScores Json @map("criteria_scores")
+ pros Json @default("[]")
+ cons Json @default("[]")
+ suggestions Json @default("[]")
+ detectedProfileSlug String? @map("detected_profile_slug")
+ createdAt DateTime @default(now()) @map("created_at")
+ appliedSuggestions AppliedSuggestion[]
+
+ @@index([userId])
+ @@index([createdAt])
+ @@map("analyses")
+}
+
+model AppliedSuggestion {
+ id String @id @default(uuid())
+ analysisId String @map("analysis_id")
+ analysis Analysis @relation(fields: [analysisId], references: [id])
+ suggestionId String @map("suggestion_id")
+ appliedAt DateTime @default(now()) @map("applied_at")
+ resultContent String? @map("result_content")
+
+ @@index([analysisId])
+ @@map("applied_suggestions")
+}
diff --git a/public/favicon.svg b/public/favicon.svg
new file mode 100644
index 0000000..9ba9e64
--- /dev/null
+++ b/public/favicon.svg
@@ -0,0 +1,4 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
+<rect width="32" height="32" rx="6" fill="#10b981"/>
+<text x="50%" y="55%" text-anchor="middle" dominant-baseline="middle" font-size="20" font-family="Apple Color Emoji, Segoe UI Emoji, sans-serif" fill="white">C</text>
+</svg>
\ No newline at end of file
diff --git a/public/file.svg b/public/file.svg
new file mode 100644
index 0000000..004145c
--- /dev/null
+++ b/public/file.svg
@@ -0,0 +1 @@
+<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
\ No newline at end of file
diff --git a/public/globe.svg b/public/globe.svg
new file mode 100644
index 0000000..567f17b
--- /dev/null
+++ b/public/globe.svg
@@ -0,0 +1 @@
+<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
\ No newline at end of file
diff --git a/public/next.svg b/public/next.svg
new file mode 100644
index 0000000..5174b28
--- /dev/null
+++ b/public/next.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
\ No newline at end of file
diff --git a/public/vercel.svg b/public/vercel.svg
new file mode 100644
index 0000000..7705396
--- /dev/null
+++ b/public/vercel.svg
@@ -0,0 +1 @@
+<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
\ No newline at end of file
diff --git a/public/window.svg b/public/window.svg
new file mode 100644
index 0000000..b2b2a44
--- /dev/null
+++ b/public/window.svg
@@ -0,0 +1 @@
+<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
\ No newline at end of file
diff --git a/src/app/analyzer/page.tsx b/src/app/analyzer/page.tsx
new file mode 100644
index 0000000..ee8d279
--- /dev/null
+++ b/src/app/analyzer/page.tsx
@@ -0,0 +1,565 @@
+'use client'
+
+import { useState, useCallback, useRef } from 'react'
+import Link from 'next/link'
+import { useStore } from '@/lib/store'
+import { GradeBadge } from '@/components/ui/grade-badge'
+import { RadarChart } from '@/components/ui/radar-chart'
+import { DiffViewer } from '@/components/ui/diff-viewer'
+import type { AnalysisResult, Suggestion } from '@/lib/analyzer/types'
+
+export default function AnalyzerPage() {
+ const [inputContent, setInputContent] = useState('')
+ const [analysisResult, setAnalysisResult] = useState<AnalysisResult | null>(null)
+ const [isAnalyzing, setIsAnalyzing] = useState(false)
+ const [error, setError] = useState<string | null>(null)
+ const [expandedSuggestions, setExpandedSuggestions] = useState<Set<string>>(new Set())
+ const [showPreview, setShowPreview] = useState(false)
+ const [previewContent, setPreviewContent] = useState('')
+ const [isApplying, setIsApplying] = useState(false)
+ const [dragActive, setDragActive] = useState(false)
+
+ const {
+ selectedSuggestions,
+ toggleSuggestion,
+ selectAllSuggestions,
+ clearSuggestions,
+ } = useStore()
+
+ const fileInputRef = useRef<HTMLInputElement>(null)
+
+ const handleAnalyze = useCallback(async () => {
+ if (!inputContent.trim()) return
+ setIsAnalyzing(true)
+ setError(null)
+ setAnalysisResult(null)
+ clearSuggestions()
+
+ try {
+ const res = await fetch('/api/analyze', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ content: inputContent, source: 'paste' }),
+ })
+ const data = await res.json()
+ if (!res.ok) {
+ setError(data.error || 'Analysis failed')
+ return
+ }
+ setAnalysisResult(data as AnalysisResult)
+ } catch {
+ setError('Network error. Please try again.')
+ } finally {
+ setIsAnalyzing(false)
+ }
+ }, [inputContent, clearSuggestions])
+
+ const handleDrop = useCallback((e: React.DragEvent) => {
+ e.preventDefault()
+ setDragActive(false)
+ const file = e.dataTransfer.files?.[0]
+ if (file) {
+ const reader = new FileReader()
+ reader.onload = (ev) => {
+ const text = ev.target?.result as string
+ setInputContent(text)
+ }
+ reader.readAsText(file)
+ }
+ }, [])
+
+ const handleFileSelect = useCallback(
+ (e: React.ChangeEvent<HTMLInputElement>) => {
+ const file = e.target.files?.[0]
+ if (file) {
+ const reader = new FileReader()
+ reader.onload = (ev) => {
+ const text = ev.target?.result as string
+ setInputContent(text)
+ }
+ reader.readAsText(file)
+ }
+ },
+ []
+ )
+
+ const toggleExpanded = (id: string) => {
+ setExpandedSuggestions((prev) => {
+ const next = new Set(prev)
+ if (next.has(id)) next.delete(id)
+ else next.add(id)
+ return next
+ })
+ }
+
+ const handlePreview = useCallback(async () => {
+ if (selectedSuggestions.size === 0 || !analysisResult) return
+
+ const selected = analysisResult.suggestions.filter((s) =>
+ selectedSuggestions.has(s.id)
+ )
+
+ try {
+ const res = await fetch('/api/suggestions', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ content: inputContent,
+ selectedSuggestions: selected,
+ }),
+ })
+ const data = await res.json()
+ if (res.ok) {
+ setPreviewContent(data.updatedContent)
+ setShowPreview(true)
+ }
+ } catch {
+ // silent
+ }
+ }, [selectedSuggestions, analysisResult, inputContent])
+
+ const handleApply = useCallback(async () => {
+ if (selectedSuggestions.size === 0 || !analysisResult) return
+ setIsApplying(true)
+
+ const selected = analysisResult.suggestions.filter((s) =>
+ selectedSuggestions.has(s.id)
+ )
+
+ try {
+ const res = await fetch('/api/suggestions', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ content: inputContent,
+ selectedSuggestions: selected,
+ }),
+ })
+ const data = await res.json()
+ if (res.ok) {
+ setInputContent(data.updatedContent)
+ setPreviewContent(data.updatedContent)
+ clearSuggestions()
+ // Re-analyze with updated content
+ const res2 = await fetch('/api/analyze', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ content: data.updatedContent,
+ source: 'applied',
+ }),
+ })
+ const data2 = await res2.json()
+ if (res2.ok) {
+ setAnalysisResult(data2 as AnalysisResult)
+ }
+ }
+ } catch {
+ // silent
+ } finally {
+ setIsApplying(false)
+ }
+ }, [selectedSuggestions, analysisResult, inputContent, clearSuggestions])
+
+ const handleDownload = () => {
+ const content = previewContent || inputContent
+ const blob = new Blob([content], { type: 'text/markdown' })
+ const url = URL.createObjectURL(blob)
+ const a = document.createElement('a')
+ a.href = url
+ a.download = 'CLAUDE.md'
+ a.click()
+ URL.revokeObjectURL(url)
+ }
+
+ const handleCopy = async () => {
+ const content = previewContent || inputContent
+ await navigator.clipboard.writeText(content)
+ }
+
+ function priorityBadge(priority: Suggestion['priority']) {
+ const colors = {
+ high: 'bg-red-100 text-red-700',
+ medium: 'bg-yellow-100 text-yellow-700',
+ low: 'bg-slate-100 text-slate-600',
+ }
+ return (
+ <span
+ className={`rounded-full px-2 py-0.5 text-xs font-medium ${colors[priority]}`}
+ >
+ {priority}
+ </span>
+ )
+ }
+
+ return (
+ <div className="mx-auto w-full max-w-4xl px-4 py-10 sm:px-6">
+ <h1 className="text-3xl font-bold text-slate-900">
+ CLAUDE.md Analyzer
+ </h1>
+ <p className="mt-2 text-slate-600">
+ Drop your CLAUDE.md file or paste its contents to get an instant
+ analysis with actionable suggestions.
+ </p>
+
+ {/* Upload / Paste Section */}
+ {!analysisResult && (
+ <div className="mt-8 space-y-4">
+ {/* Drop zone */}
+ <div
+ onDragOver={(e) => {
+ e.preventDefault()
+ setDragActive(true)
+ }}
+ onDragLeave={() => setDragActive(false)}
+ onDrop={handleDrop}
+ onClick={() => fileInputRef.current?.click()}
+ className={`flex min-h-[120px] cursor-pointer items-center justify-center rounded-xl border-2 border-dashed p-6 text-center transition-colors ${
+ dragActive
+ ? 'border-blue-400 bg-blue-50'
+ : 'border-slate-300 hover:border-slate-400'
+ }`}
+ >
+ <div>
+ <p className="text-sm font-medium text-slate-600">
+ Drop your CLAUDE.md here or click to browse
+ </p>
+ <p className="mt-1 text-xs text-slate-400">
+ Supports .md and plain text files
+ </p>
+ </div>
+ <input
+ ref={fileInputRef}
+ type="file"
+ accept=".md,.txt,.markdown"
+ onChange={handleFileSelect}
+ className="hidden"
+ />
+ </div>
+
+ {/* Textarea */}
+ <div>
+ <label
+ htmlFor="claude-md-input"
+ className="mb-2 block text-sm font-medium text-slate-700"
+ >
+ Or paste your content below
+ </label>
+ <textarea
+ id="claude-md-input"
+ value={inputContent}
+ onChange={(e) => setInputContent(e.target.value)}
+ placeholder="# My Project — CLAUDE.md ## Commands install: npm install dev: npm run dev ..."
+ rows={12}
+ className="w-full rounded-xl border border-slate-300 p-4 font-mono text-sm text-slate-800 placeholder:text-slate-400 focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
+ />
+ </div>
+
+ {error && (
+ <div className="rounded-lg bg-red-50 p-4 text-sm text-red-700">
+ {error}
+ </div>
+ )}
+
+ <button
+ type="button"
+ onClick={handleAnalyze}
+ disabled={!inputContent.trim() || isAnalyzing}
+ className="inline-flex min-h-[48px] items-center justify-center rounded-xl bg-blue-600 px-8 py-3 text-base font-medium text-white transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50"
+ >
+ {isAnalyzing ? (
+ <>
+ <span className="mr-2 inline-block h-4 w-4 animate-spin rounded-full border-2 border-white/30 border-t-white" />
+ Analyzing...
+ </>
+ ) : (
+ 'Analyze'
+ )}
+ </button>
+ </div>
+ )}
+
+ {/* Results Section */}
+ {analysisResult && (
+ <div className="mt-10 space-y-8">
+ {/* Grade + Radar */}
+ <div className="grid gap-8 md:grid-cols-2">
+ <div className="flex flex-col items-center justify-center">
+ <GradeBadge
+ grade={analysisResult.letterGrade}
+ percentage={analysisResult.percentage}
+ />
+ <p className="mt-3 text-sm text-slate-500">
+ {analysisResult.lineCount} lines analyzed
+ </p>
+ {analysisResult.detectedProfileSlug && (
+ <p className="mt-2 text-sm text-slate-600">
+ Matches profile:{' '}
+ <Link
+ href={`/builder?profile=${analysisResult.detectedProfileSlug}`}
+ className="font-medium text-blue-600 hover:underline"
+ >
+ {analysisResult.detectedProfileSlug}
+ </Link>
+ </p>
+ )}
+ </div>
+ <RadarChart criteria={analysisResult.criteria} />
+ </div>
+
+ {/* Strengths & Issues */}
+ <div className="grid gap-4 md:grid-cols-2">
+ {/* Strengths */}
+ <div className="rounded-xl border border-green-200 bg-green-50/50">
+ <button
+ type="button"
+ className="flex w-full items-center justify-between px-5 py-4 text-left"
+ onClick={() => {
+ const el = document.getElementById('strengths-list')
+ el?.classList.toggle('hidden')
+ }}
+ >
+ <span className="text-sm font-semibold text-green-700">
+ Strengths ({analysisResult.pros.length})
+ </span>
+ <svg
+ className="h-4 w-4 text-green-500"
+ fill="none"
+ viewBox="0 0 24 24"
+ strokeWidth={2}
+ stroke="currentColor"
+ >
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ d="M19.5 8.25l-7.5 7.5-7.5-7.5"
+ />
+ </svg>
+ </button>
+ <ul id="strengths-list" className="space-y-2 px-5 pb-4">
+ {analysisResult.pros.map((pro, i) => (
+ <li
+ key={i}
+ className="flex items-start gap-2 text-sm text-green-800"
+ >
+ <span className="mt-0.5 flex-shrink-0">+</span>
+ <span>{pro}</span>
+ </li>
+ ))}
+ {analysisResult.pros.length === 0 && (
+ <li className="text-sm text-green-600">
+ No full-score criteria yet. Keep improving!
+ </li>
+ )}
+ </ul>
+ </div>
+
+ {/* Issues */}
+ <div className="rounded-xl border border-red-200 bg-red-50/50">
+ <button
+ type="button"
+ className="flex w-full items-center justify-between px-5 py-4 text-left"
+ onClick={() => {
+ const el = document.getElementById('issues-list')
+ el?.classList.toggle('hidden')
+ }}
+ >
+ <span className="text-sm font-semibold text-red-700">
+ Issues ({analysisResult.cons.length})
+ </span>
+ <svg
+ className="h-4 w-4 text-red-500"
+ fill="none"
+ viewBox="0 0 24 24"
+ strokeWidth={2}
+ stroke="currentColor"
+ >
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ d="M19.5 8.25l-7.5 7.5-7.5-7.5"
+ />
+ </svg>
+ </button>
+ <ul id="issues-list" className="space-y-2 px-5 pb-4">
+ {analysisResult.cons.map((con, i) => (
+ <li
+ key={i}
+ className="flex items-start gap-2 text-sm text-red-800"
+ >
+ <span className="mt-0.5 flex-shrink-0">-</span>
+ <span>{con}</span>
+ </li>
+ ))}
+ {analysisResult.cons.length === 0 && (
+ <li className="text-sm text-red-600">
+ No zero-score criteria. Great job!
+ </li>
+ )}
+ </ul>
+ </div>
+ </div>
+
+ {/* Suggestions */}
+ {analysisResult.suggestions.length > 0 && (
+ <div>
+ <div className="mb-4 flex flex-wrap items-center justify-between gap-3">
+ <h2 className="text-xl font-bold text-slate-900">
+ Suggestions ({analysisResult.suggestions.length})
+ </h2>
+ <div className="flex gap-2">
+ <button
+ type="button"
+ onClick={() =>
+ selectAllSuggestions(analysisResult.suggestions)
+ }
+ className="rounded-lg border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-600 hover:bg-slate-50"
+ >
+ Select All
+ </button>
+ <button
+ type="button"
+ onClick={clearSuggestions}
+ className="rounded-lg border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-600 hover:bg-slate-50"
+ >
+ Deselect All
+ </button>
+ </div>
+ </div>
+
+ <div className="space-y-3">
+ {analysisResult.suggestions.map((s) => (
+ <div
+ key={s.id}
+ className="rounded-xl border border-slate-200 bg-white shadow-sm"
+ >
+ <div className="flex items-start gap-3 p-4">
+ <input
+ type="checkbox"
+ checked={selectedSuggestions.has(s.id)}
+ onChange={() => toggleSuggestion(s.id)}
+ className="mt-0.5 min-h-[20px] min-w-[20px] rounded border-slate-300 text-blue-600 focus:ring-blue-500"
+ aria-label={`Select suggestion: ${s.description}`}
+ />
+ <div className="flex-1">
+ <div className="flex items-start justify-between gap-2">
+ <p className="text-sm font-medium text-slate-800">
+ {s.description}
+ </p>
+ {priorityBadge(s.priority)}
+ </div>
+ <p className="mt-1 text-xs text-slate-500">
+ {s.criterion}
+ </p>
+ </div>
+ <button
+ type="button"
+ onClick={() => toggleExpanded(s.id)}
+ className="min-h-[44px] min-w-[44px] rounded-lg text-slate-400 hover:bg-slate-100 hover:text-slate-600"
+ aria-label={
+ expandedSuggestions.has(s.id)
+ ? 'Collapse diff'
+ : 'Expand diff'
+ }
+ >
+ <svg
+ className={`mx-auto h-4 w-4 transition-transform ${
+ expandedSuggestions.has(s.id) ? 'rotate-180' : ''
+ }`}
+ fill="none"
+ viewBox="0 0 24 24"
+ strokeWidth={2}
+ stroke="currentColor"
+ >
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ d="M19.5 8.25l-7.5 7.5-7.5-7.5"
+ />
+ </svg>
+ </button>
+ </div>
+ {expandedSuggestions.has(s.id) && (
+ <div className="border-t border-slate-200 p-4">
+ <DiffViewer before={s.before} after={s.after} />
+ </div>
+ )}
+ </div>
+ ))}
+ </div>
+
+ {/* Action buttons */}
+ <div className="mt-6 flex flex-wrap gap-3">
+ <button
+ type="button"
+ onClick={handlePreview}
+ disabled={selectedSuggestions.size === 0}
+ className="min-h-[44px] rounded-xl border border-slate-300 px-6 py-2.5 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-50"
+ >
+ Preview Changes
+ </button>
+ <button
+ type="button"
+ onClick={handleApply}
+ disabled={selectedSuggestions.size === 0 || isApplying}
+ className="min-h-[44px] rounded-xl bg-blue-600 px-6 py-2.5 text-sm font-medium text-white transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50"
+ >
+ {isApplying ? 'Applying...' : 'Apply Selected'}
+ </button>
+ </div>
+ </div>
+ )}
+
+ {/* Preview Modal */}
+ {showPreview && (
+ <div>
+ <div className="mb-4 flex items-center justify-between">
+ <h2 className="text-xl font-bold text-slate-900">
+ Preview Changes
+ </h2>
+ <button
+ type="button"
+ onClick={() => setShowPreview(false)}
+ className="min-h-[44px] min-w-[44px] rounded-lg text-slate-400 hover:bg-slate-100 hover:text-slate-600"
+ >
+ Close
+ </button>
+ </div>
+ <DiffViewer before={inputContent} after={previewContent} />
+ </div>
+ )}
+
+ {/* Download / Copy */}
+ <div className="flex flex-wrap gap-3 border-t border-slate-200 pt-6">
+ <button
+ type="button"
+ onClick={handleDownload}
+ className="min-h-[44px] rounded-xl bg-slate-800 px-6 py-2.5 text-sm font-medium text-white transition-colors hover:bg-slate-700"
+ >
+ Download Result
+ </button>
+ <button
+ type="button"
+ onClick={handleCopy}
+ className="min-h-[44px] rounded-xl border border-slate-300 px-6 py-2.5 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-50"
+ >
+ Copy to Clipboard
+ </button>
+ <button
+ type="button"
+ onClick={() => {
+ setAnalysisResult(null)
+ clearSuggestions()
+ setShowPreview(false)
+ setPreviewContent('')
+ }}
+ className="min-h-[44px] rounded-xl border border-slate-300 px-6 py-2.5 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-50"
+ >
+ Analyze Another
+ </button>
+ </div>
+ </div>
+ )}
+ </div>
+ )
+}
diff --git a/src/app/api/analyze/route.ts b/src/app/api/analyze/route.ts
new file mode 100644
index 0000000..6d944d6
--- /dev/null
+++ b/src/app/api/analyze/route.ts
@@ -0,0 +1,69 @@
+import { NextResponse } from 'next/server'
+import { prisma } from '@/lib/db'
+
+export async function POST(request: Request) {
+ try {
+ const { content, source } = await request.json()
+
+ if (!content || typeof content !== 'string') {
+ return NextResponse.json({ error: 'content is required and must be a string' }, { status: 400 })
+ }
+
+ if (content.length > 100000) {
+ return NextResponse.json({ error: 'Content too large (max 100KB)' }, { status: 400 })
+ }
+
+ // Dynamic import to handle Phase 2 not being built yet.
+ // The analyzer module exports: analyzeClaudeMd(content) => AnalysisResult
+ // The suggester module exports: generateSuggestions(content, analysis) => Suggestion[]
+ // The matcher module exports: detectProfile(content) => string | null
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ let analysis: any
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ let suggestions: any
+ let detectedProfile: string | null | undefined
+ try {
+ const scorerModule = await import('@/lib/analyzer/scorer')
+ const suggesterModule = await import('@/lib/analyzer/suggester')
+ const matcherModule = await import('@/lib/analyzer/matcher')
+
+ analysis = scorerModule.analyzeClaudeMd(content)
+ suggestions = suggesterModule.generateSuggestions(content, analysis)
+ detectedProfile = matcherModule.detectProfile(content)
+ } catch {
+ return NextResponse.json({ error: 'Analyzer module not yet available' }, { status: 503 })
+ }
+
+ const saved = await prisma.analysis.create({
+ data: {
+ inputContent: content,
+ inputSource: source || 'paste',
+ overallScore: analysis.percentage,
+ letterGrade: analysis.letterGrade,
+ criteriaScores: analysis.criteria as object,
+ pros: analysis.pros as object,
+ cons: analysis.cons as object,
+ suggestions: suggestions as object,
+ detectedProfileSlug: detectedProfile ?? null,
+ },
+ })
+
+ return NextResponse.json({
+ id: saved.id,
+ overallScore: analysis.overallScore,
+ maxScore: analysis.maxScore,
+ percentage: analysis.percentage,
+ letterGrade: analysis.letterGrade,
+ criteria: analysis.criteria,
+ pros: analysis.pros,
+ cons: analysis.cons,
+ suggestions,
+ detectedProfileSlug: detectedProfile ?? null,
+ lineCount: analysis.lineCount,
+ })
+ } catch (err: unknown) {
+ const message = err instanceof Error ? err.message : 'Analysis failed'
+ console.error('Analyze error:', err)
+ return NextResponse.json({ error: message }, { status: 500 })
+ }
+}
diff --git a/src/app/api/auth/me/route.ts b/src/app/api/auth/me/route.ts
new file mode 100644
index 0000000..fc0f396
--- /dev/null
+++ b/src/app/api/auth/me/route.ts
@@ -0,0 +1,16 @@
+import { NextRequest, NextResponse } from 'next/server'
+import { authenticateRequest } from '@/lib/auth'
+
+export async function GET(request: NextRequest) {
+ const result = authenticateRequest(request)
+
+ if (result instanceof NextResponse) {
+ // Not authenticated
+ return NextResponse.json({ authenticated: false }, { status: 200 })
+ }
+
+ return NextResponse.json({
+ authenticated: true,
+ username: result,
+ })
+}
diff --git a/src/app/api/auth/route.ts b/src/app/api/auth/route.ts
new file mode 100644
index 0000000..112034f
--- /dev/null
+++ b/src/app/api/auth/route.ts
@@ -0,0 +1,35 @@
+import { NextRequest, NextResponse } from 'next/server'
+
+const AUTH_USER = 'Steve'
+const AUTH_PASS = 'DWSecure2024!'
+const SESSION_COOKIE = 'clawcoder_session'
+const THIRTY_DAYS = 30 * 24 * 60 * 60
+
+export async function POST(request: NextRequest) {
+ const { username, password } = await request.json()
+
+ if (username === AUTH_USER && password === AUTH_PASS) {
+ const session = Buffer.from(JSON.stringify({
+ authenticated: true,
+ username,
+ loginTime: Date.now(),
+ })).toString('base64url')
+
+ const response = NextResponse.json({ ok: true })
+ response.cookies.set(SESSION_COOKIE, session, {
+ httpOnly: true,
+ sameSite: 'lax',
+ maxAge: THIRTY_DAYS,
+ path: '/',
+ })
+ return response
+ }
+
+ return NextResponse.json({ error: 'Invalid credentials' }, { status: 401 })
+}
+
+export async function DELETE() {
+ const response = NextResponse.json({ ok: true })
+ response.cookies.delete(SESSION_COOKIE)
+ return response
+}
diff --git a/src/app/api/files/[id]/route.ts b/src/app/api/files/[id]/route.ts
new file mode 100644
index 0000000..78ce8b8
--- /dev/null
+++ b/src/app/api/files/[id]/route.ts
@@ -0,0 +1,81 @@
+import { NextRequest, NextResponse } from 'next/server'
+import fs from 'fs'
+import { authenticateRequest } from '@/lib/auth'
+import { CLAUDE_MD_PATHS, ALLOWED_PATHS } from '../route'
+
+function decodePath(id: string): string | null {
+ try {
+ const decoded = Buffer.from(decodeURIComponent(id), 'base64').toString('utf-8')
+ // Security: only allow paths in the allowlist
+ if (!ALLOWED_PATHS.has(decoded)) return null
+ return decoded
+ } catch {
+ return null
+ }
+}
+
+function getProject(filePath: string): string {
+ const entry = CLAUDE_MD_PATHS.find(e => e.path === filePath)
+ return entry?.project ?? 'Unknown'
+}
+
+export async function GET(
+ request: NextRequest,
+ { params }: { params: Promise<{ id: string }> }
+) {
+ const auth = authenticateRequest(request)
+ if (auth instanceof NextResponse) return auth
+
+ const { id } = await params
+ const filePath = decodePath(id)
+ if (!filePath) {
+ return NextResponse.json({ error: 'Invalid or disallowed file path' }, { status: 403 })
+ }
+
+ try {
+ const content = fs.readFileSync(filePath, 'utf-8')
+ const stat = fs.statSync(filePath)
+ return NextResponse.json({
+ path: filePath,
+ project: getProject(filePath),
+ content,
+ lines: content.split('\n').length,
+ lastModified: stat.mtime.toISOString(),
+ })
+ } catch {
+ return NextResponse.json({ error: 'File not found or unreadable' }, { status: 404 })
+ }
+}
+
+export async function PUT(
+ request: NextRequest,
+ { params }: { params: Promise<{ id: string }> }
+) {
+ const auth = authenticateRequest(request)
+ if (auth instanceof NextResponse) return auth
+
+ const { id } = await params
+ const filePath = decodePath(id)
+ if (!filePath) {
+ return NextResponse.json({ error: 'Invalid or disallowed file path' }, { status: 403 })
+ }
+
+ try {
+ const { content } = await request.json()
+ if (typeof content !== 'string') {
+ return NextResponse.json({ error: 'Content must be a string' }, { status: 400 })
+ }
+
+ fs.writeFileSync(filePath, content, 'utf-8')
+ const lines = content.split('\n').length
+
+ return NextResponse.json({
+ ok: true,
+ path: filePath,
+ lines,
+ })
+ } catch (err) {
+ const message = err instanceof Error ? err.message : 'Write failed'
+ return NextResponse.json({ error: message }, { status: 500 })
+ }
+}
diff --git a/src/app/api/files/route.ts b/src/app/api/files/route.ts
new file mode 100644
index 0000000..239dda7
--- /dev/null
+++ b/src/app/api/files/route.ts
@@ -0,0 +1,98 @@
+import { NextRequest, NextResponse } from 'next/server'
+import fs from 'fs'
+import path from 'path'
+import { authenticateRequest } from '@/lib/auth'
+
+export interface ClaudeMdEntry {
+ path: string
+ project: string
+}
+
+export const CLAUDE_MD_PATHS: ClaudeMdEntry[] = [
+ { path: '/root/.claude/CLAUDE.md', project: 'Global Configuration' },
+ { path: '/root/CLAUDE.md', project: 'Project Router' },
+ { path: '/root/DW-Agents/CLAUDE.md', project: 'DW-Agents' },
+ { path: '/root/Projects/Designer-Wallcoverings/CLAUDE.md', project: 'Designer Wallcoverings' },
+ { path: '/root/Projects/dw-war-room/CLAUDE.md', project: '3D War Room' },
+ { path: '/root/Projects/watches/CLAUDE.md', project: 'Omega Watches' },
+ { path: '/root/Projects/dear-bubbe-nextjs/CLAUDE.md', project: 'Bubbe AI' },
+ { path: '/root/Projects/wine-finder/CLAUDE.md', project: 'Wine Finder' },
+ { path: '/root/Projects/Letsbegin/CLAUDE.md', project: 'Letsbegin / Ralph' },
+ { path: '/root/Projects/Norma/CLAUDE.md', project: 'Norma Intelligence' },
+ { path: '/root/Projects/jill-website/CLAUDE.md', project: 'Jill Website (Nosara)' },
+ { path: '/root/Projects/Sascha/CLAUDE.md', project: 'Sascha Portfolio' },
+ { path: '/root/Projects/SmokeShop/CLAUDE.md', project: 'SmokeShop' },
+ { path: '/root/Projects/Grant/CLAUDE.md', project: 'Grant Fundraiser' },
+ { path: '/root/Projects/Freddy/CLAUDE.md', project: 'Freddy Fundraiser' },
+ { path: '/root/Projects/Patty/CLAUDE.md', project: 'Patty Petitions' },
+ { path: '/root/Projects/angels-flowers/CLAUDE.md', project: 'Angels Flowers' },
+ { path: '/root/Projects/wine-finder-next/CLAUDE.md', project: 'Wine Finder Next' },
+ { path: '/root/Projects/B_Version_1/.claude/CLAUDE.md', project: 'Dear Bubbe v1' },
+ { path: '/root/Projects/Letsbegin/.claude/CLAUDE.md', project: 'GoodQuestion Ralph' },
+ { path: '/root/Projects/Designer-Wallcoverings/DW-Agents/CLAUDE.md', project: 'DW-Agents (sub)' },
+ { path: '/root/Projects/Designer-Wallcoverings/DW-Websites/CLAUDE.md', project: 'DW Websites' },
+ { path: '/root/Projects/Designer-Wallcoverings/DW-Programming/CLAUDE.md', project: 'DW Programming' },
+ { path: '/root/Projects/Designer-Wallcoverings/DW-Programming/shopifyskutoblog/CLAUDE.md', project: 'DW SKU to Blog' },
+ { path: '/root/Projects/Designer-Wallcoverings/DW-Programming/ImportNewSkufromURL/CLAUDE.md', project: 'DW URL Import' },
+ { path: '/root/Projects/Designer-Wallcoverings/DW-MCP/CLAUDE.md', project: 'DW MCP' },
+ { path: '/root/Projects/Designer-Wallcoverings/DW-Agents/dw-agents/CLAUDE.md', project: 'DW-Agents Core' },
+ { path: '/root/WebsitesMisc/WineFinder/CLAUDE.md', project: 'Wine Finder Misc' },
+ { path: '/root/Projects/ClawCoder/docs/superpowers/specs/2026-03-23-clawcoder-profiles-analyzer-design.md', project: 'ClawCoder Spec' },
+]
+
+/** Set of allowed paths for security validation */
+export const ALLOWED_PATHS = new Set(CLAUDE_MD_PATHS.map(e => e.path))
+
+export interface ClaudeMdFile {
+ id: string
+ path: string
+ project: string
+ lines: number
+ hasRules: boolean
+ lastModified: string
+ preview: string
+ exists: boolean
+}
+
+function getFileInfo(entry: ClaudeMdEntry): ClaudeMdFile {
+ const id = Buffer.from(entry.path).toString('base64')
+ const result: ClaudeMdFile = {
+ id,
+ path: entry.path,
+ project: entry.project,
+ lines: 0,
+ hasRules: false,
+ lastModified: '',
+ preview: '',
+ exists: false,
+ }
+
+ try {
+ const content = fs.readFileSync(entry.path, 'utf-8')
+ const stat = fs.statSync(entry.path)
+ result.exists = true
+ result.lines = content.split('\n').length
+ result.lastModified = stat.mtime.toISOString()
+ result.preview = content.substring(0, 100).replace(/\n/g, ' ')
+
+ // Check for .claude/rules/ near the file
+ const dir = path.dirname(entry.path)
+ const rulesDir = entry.path.includes('.claude/CLAUDE.md')
+ ? path.join(dir, 'rules')
+ : path.join(dir, '.claude', 'rules')
+ result.hasRules = fs.existsSync(rulesDir) && fs.statSync(rulesDir).isDirectory()
+ } catch {
+ // File doesn't exist or can't be read
+ result.exists = false
+ }
+
+ return result
+}
+
+export async function GET(request: NextRequest) {
+ const auth = authenticateRequest(request)
+ if (auth instanceof NextResponse) return auth
+
+ const files = CLAUDE_MD_PATHS.map(getFileInfo)
+ return NextResponse.json(files)
+}
diff --git a/src/app/api/generate/route.ts b/src/app/api/generate/route.ts
new file mode 100644
index 0000000..e0da679
--- /dev/null
+++ b/src/app/api/generate/route.ts
@@ -0,0 +1,23 @@
+import { NextResponse } from 'next/server'
+import { getProfile } from '@/lib/profiles/data'
+import { generateBundle } from '@/lib/generator/bundle'
+
+export async function POST(request: Request) {
+ try {
+ const { profileSlug, vars } = await request.json()
+
+ if (!profileSlug) {
+ return NextResponse.json({ error: 'profileSlug is required' }, { status: 400 })
+ }
+
+ const profile = getProfile(profileSlug)
+ if (!profile) {
+ return NextResponse.json({ error: `Profile '${profileSlug}' not found` }, { status: 404 })
+ }
+
+ const bundle = generateBundle(profile, vars || {})
+ return NextResponse.json(bundle)
+ } catch {
+ return NextResponse.json({ error: 'Invalid request body' }, { status: 400 })
+ }
+}
diff --git a/src/app/api/marketplace/route.ts b/src/app/api/marketplace/route.ts
new file mode 100644
index 0000000..10cee5f
--- /dev/null
+++ b/src/app/api/marketplace/route.ts
@@ -0,0 +1,39 @@
+import { NextResponse } from 'next/server'
+import {
+ MARKETPLACE_LISTINGS,
+ getListingsByCategory,
+ getListingsForProfile,
+ searchListings,
+} from '@/lib/marketplace/data'
+
+export async function GET(request: Request) {
+ const { searchParams } = new URL(request.url)
+ const category = searchParams.get('category')
+ const profile = searchParams.get('profile')
+ const query = searchParams.get('q')
+
+ // Search takes priority
+ if (query) {
+ const results = searchListings(query)
+
+ // Optionally filter search results by category
+ if (category) {
+ return NextResponse.json(results.filter((l) => l.category === category))
+ }
+
+ return NextResponse.json(results)
+ }
+
+ // Filter by category
+ if (category) {
+ return NextResponse.json(getListingsByCategory(category))
+ }
+
+ // Filter by profile
+ if (profile) {
+ return NextResponse.json(getListingsForProfile(profile))
+ }
+
+ // Return all listings
+ return NextResponse.json(MARKETPLACE_LISTINGS)
+}
diff --git a/src/app/api/profiles/route.ts b/src/app/api/profiles/route.ts
new file mode 100644
index 0000000..641d6bb
--- /dev/null
+++ b/src/app/api/profiles/route.ts
@@ -0,0 +1,33 @@
+import { NextResponse } from 'next/server'
+import { PROFILES, getProfile, getProfilesByAge, getProfilesBySkill } from '@/lib/profiles/data'
+import type { AgeGroup, SkillLevel } from '@/lib/profiles/types'
+
+export async function GET(request: Request) {
+ const { searchParams } = new URL(request.url)
+ const slug = searchParams.get('slug')
+ const age = searchParams.get('age')
+ const skill = searchParams.get('skill')
+
+ if (slug) {
+ const profile = getProfile(slug)
+ return profile
+ ? NextResponse.json(profile)
+ : NextResponse.json({ error: 'Profile not found' }, { status: 404 })
+ }
+ if (age) return NextResponse.json(getProfilesByAge(age as AgeGroup))
+ if (skill) return NextResponse.json(getProfilesBySkill(skill as SkillLevel))
+
+ // Return summary (not full template content) for listing
+ const summaries = PROFILES.map(p => ({
+ slug: p.slug,
+ ageGroup: p.ageGroup,
+ skillLevel: p.skillLevel,
+ displayName: p.displayName,
+ persona: p.persona,
+ ui: p.ui,
+ skillCount: p.skillBundle.length,
+ mcpCount: p.mcpServers.length,
+ subagentCount: p.subagents.length,
+ }))
+ return NextResponse.json(summaries)
+}
diff --git a/src/app/api/suggestions/route.ts b/src/app/api/suggestions/route.ts
new file mode 100644
index 0000000..eaefbfd
--- /dev/null
+++ b/src/app/api/suggestions/route.ts
@@ -0,0 +1,27 @@
+import { NextResponse } from 'next/server'
+
+export async function POST(request: Request) {
+ try {
+ const { content, selectedSuggestions } = await request.json()
+
+ if (!content || !selectedSuggestions || !Array.isArray(selectedSuggestions)) {
+ return NextResponse.json(
+ { error: 'content (string) and selectedSuggestions (array) are required' },
+ { status: 400 }
+ )
+ }
+
+ let result: string
+ try {
+ const { applySuggestions } = await import('@/lib/analyzer/applier')
+ result = applySuggestions(content, selectedSuggestions)
+ } catch {
+ return NextResponse.json({ error: 'Applier module not yet available' }, { status: 503 })
+ }
+
+ return NextResponse.json({ updatedContent: result })
+ } catch (err: unknown) {
+ const message = err instanceof Error ? err.message : 'Failed to apply suggestions'
+ return NextResponse.json({ error: message }, { status: 500 })
+ }
+}
diff --git a/src/app/builder/page.tsx b/src/app/builder/page.tsx
new file mode 100644
index 0000000..d7e5a61
--- /dev/null
+++ b/src/app/builder/page.tsx
@@ -0,0 +1,584 @@
+'use client'
+
+import { useEffect, useState, useCallback, Suspense } from 'react'
+import { useSearchParams } from 'next/navigation'
+import type { Profile } from '@/lib/profiles/types'
+import type { MarketplaceListing } from '@/lib/marketplace/data'
+
+interface GeneratedBundle {
+ claudeMd: string
+ rulesFiles: { path: string; content: string }[]
+ settings: object
+ settingsJson: string
+ profile: {
+ slug: string
+ displayName: string
+ ageGroup: string
+ skillLevel: string
+ persona: string
+ }
+}
+
+type TabId = 'claude_md' | 'rules' | 'hooks' | 'bundle'
+
+const tabs: { id: TabId; label: string }[] = [
+ { id: 'claude_md', label: 'CLAUDE.md' },
+ { id: 'rules', label: 'Rules' },
+ { id: 'hooks', label: 'Hooks' },
+ { id: 'bundle', label: 'Bundle' },
+]
+
+function BuilderContent() {
+ const searchParams = useSearchParams()
+ const profileSlug = searchParams.get('profile') || ''
+
+ const [profile, setProfile] = useState<Profile | null>(null)
+ const [bundle, setBundle] = useState<GeneratedBundle | null>(null)
+ const [loading, setLoading] = useState(true)
+ const [generating, setGenerating] = useState(false)
+ const [activeTab, setActiveTab] = useState<TabId>('claude_md')
+ const [sidebarOpen, setSidebarOpen] = useState(true)
+ const [copied, setCopied] = useState(false)
+ const [marketplaceRecs, setMarketplaceRecs] = useState<MarketplaceListing[]>([])
+ const [copiedInstall, setCopiedInstall] = useState<string | null>(null)
+
+ // Project variables
+ const [vars, setVars] = useState({
+ projectName: 'My Project',
+ projectDescription: 'A new project built with Claude.',
+ techStack: 'TypeScript, Node.js',
+ installCmd: 'npm install',
+ devCmd: 'npm run dev',
+ testCmd: 'npm test',
+ lintCmd: 'npm run lint',
+ buildCmd: 'npm run build',
+ })
+
+ // Load profile
+ useEffect(() => {
+ if (!profileSlug) {
+ setLoading(false)
+ return
+ }
+
+ fetch(`/api/profiles?slug=${profileSlug}`)
+ .then((r) => {
+ if (!r.ok) throw new Error('Not found')
+ return r.json()
+ })
+ .then((data) => {
+ setProfile(data)
+ setLoading(false)
+ })
+ .catch(() => setLoading(false))
+ }, [profileSlug])
+
+ // Load marketplace recommendations for this profile
+ useEffect(() => {
+ if (!profileSlug) return
+ fetch(`/api/marketplace?profile=${profileSlug}`)
+ .then((r) => (r.ok ? r.json() : []))
+ .then((data: MarketplaceListing[]) => setMarketplaceRecs(data.slice(0, 5)))
+ .catch(() => setMarketplaceRecs([]))
+ }, [profileSlug])
+
+ // Generate bundle
+ const generate = useCallback(async () => {
+ if (!profileSlug) return
+ setGenerating(true)
+
+ try {
+ const res = await fetch('/api/generate', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ profileSlug, vars }),
+ })
+ const data = await res.json()
+ if (res.ok) {
+ setBundle(data as GeneratedBundle)
+ }
+ } catch {
+ // silent
+ } finally {
+ setGenerating(false)
+ }
+ }, [profileSlug, vars])
+
+ // Auto-generate on first load
+ useEffect(() => {
+ if (profile && !bundle) {
+ generate()
+ }
+ }, [profile, bundle, generate])
+
+ const handleCopy = async (content: string) => {
+ await navigator.clipboard.writeText(content)
+ setCopied(true)
+ setTimeout(() => setCopied(false), 2000)
+ }
+
+ const handleDownload = (content: string, filename: string) => {
+ const blob = new Blob([content], { type: 'text/plain' })
+ const url = URL.createObjectURL(blob)
+ const a = document.createElement('a')
+ a.href = url
+ a.download = filename
+ a.click()
+ URL.revokeObjectURL(url)
+ }
+
+ const handleDownloadAll = () => {
+ if (!bundle) return
+ // Create a combined text file as a zip-like bundle
+ let combined = '=== CLAUDE.md ===\n\n'
+ combined += bundle.claudeMd + '\n\n'
+
+ if (bundle.rulesFiles.length > 0) {
+ for (const rf of bundle.rulesFiles) {
+ combined += `=== ${rf.path} ===\n\n`
+ combined += rf.content + '\n\n'
+ }
+ }
+
+ combined += '=== .claude/settings.json ===\n\n'
+ combined += bundle.settingsJson + '\n'
+
+ handleDownload(combined, `clawcoder-bundle-${profileSlug}.txt`)
+ }
+
+ function getTabContent(): string {
+ if (!bundle) return ''
+ switch (activeTab) {
+ case 'claude_md':
+ return bundle.claudeMd
+ case 'rules':
+ return bundle.rulesFiles.length > 0
+ ? bundle.rulesFiles
+ .map((rf) => `--- ${rf.path} ---\n${rf.content}`)
+ .join('\n\n')
+ : '(No rules files for this profile)'
+ case 'hooks':
+ return bundle.settingsJson
+ case 'bundle':
+ return JSON.stringify(bundle, null, 2)
+ default:
+ return ''
+ }
+ }
+
+ if (loading) {
+ return (
+ <div className="flex flex-1 items-center justify-center">
+ <div className="h-8 w-8 animate-spin rounded-full border-2 border-slate-300 border-t-blue-600" />
+ </div>
+ )
+ }
+
+ if (!profileSlug || !profile) {
+ return (
+ <div className="flex flex-1 flex-col items-center justify-center px-4">
+ <h1 className="text-2xl font-bold text-slate-900">No profile selected</h1>
+ <p className="mt-2 text-slate-600">
+ Go to the{' '}
+ <a href="/" className="text-blue-600 hover:underline">
+ home page
+ </a>{' '}
+ to choose a profile, or use a URL like{' '}
+ <code className="rounded bg-slate-100 px-1.5 py-0.5 text-sm">
+ /builder?profile=teen-beginner
+ </code>
+ </p>
+ </div>
+ )
+ }
+
+ const ageLabels: Record<string, string> = {
+ teen: 'Teen',
+ young_adult: 'Young Adult',
+ professional: 'Professional',
+ pre_senior: 'Pre-Senior',
+ senior: 'Senior',
+ }
+
+ const skillLabels: Record<string, string> = {
+ beginner: 'Beginner',
+ intermediate: 'Intermediate',
+ expert: 'Expert',
+ }
+
+ return (
+ <div className="flex flex-1 overflow-hidden">
+ {/* Sidebar Toggle (mobile) */}
+ <button
+ type="button"
+ onClick={() => setSidebarOpen(!sidebarOpen)}
+ className="fixed bottom-20 left-4 z-30 min-h-[44px] min-w-[44px] rounded-full bg-slate-800 p-3 text-white shadow-lg lg:hidden"
+ aria-label={sidebarOpen ? 'Close sidebar' : 'Open sidebar'}
+ >
+ <svg
+ className="h-5 w-5"
+ fill="none"
+ viewBox="0 0 24 24"
+ strokeWidth={2}
+ stroke="currentColor"
+ >
+ {sidebarOpen ? (
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ d="M6 18L18 6M6 6l12 12"
+ />
+ ) : (
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
+ />
+ )}
+ </svg>
+ </button>
+
+ {/* Left Panel - Sidebar */}
+ <aside
+ className={`flex-shrink-0 overflow-y-auto border-r border-slate-200 bg-slate-50 transition-all ${
+ sidebarOpen ? 'w-80' : 'w-0 overflow-hidden'
+ } fixed inset-y-16 left-0 z-20 lg:relative lg:inset-auto`}
+ >
+ <div className="space-y-6 p-5">
+ {/* Profile Summary */}
+ <div>
+ <h2 className="text-lg font-bold text-slate-900">
+ {profile.displayName}
+ </h2>
+ <div className="mt-1 flex gap-2">
+ <span className="rounded-full bg-blue-100 px-2 py-0.5 text-xs font-medium text-blue-700">
+ {ageLabels[profile.ageGroup] || profile.ageGroup}
+ </span>
+ <span className="rounded-full bg-slate-200 px-2 py-0.5 text-xs font-medium text-slate-600">
+ {skillLabels[profile.skillLevel] || profile.skillLevel}
+ </span>
+ </div>
+ <p className="mt-2 text-xs text-slate-500">{profile.persona}</p>
+ </div>
+
+ {/* Project Variables Form */}
+ <div>
+ <h3 className="mb-3 text-sm font-semibold text-slate-700">
+ Project Variables
+ </h3>
+ <div className="space-y-3">
+ {(
+ [
+ ['projectName', 'Project Name'],
+ ['projectDescription', 'Description'],
+ ['techStack', 'Tech Stack'],
+ ['installCmd', 'Install Command'],
+ ['devCmd', 'Dev Command'],
+ ['testCmd', 'Test Command'],
+ ['lintCmd', 'Lint Command'],
+ ['buildCmd', 'Build Command'],
+ ] as const
+ ).map(([key, label]) => (
+ <div key={key}>
+ <label
+ htmlFor={`var-${key}`}
+ className="mb-1 block text-xs font-medium text-slate-600"
+ >
+ {label}
+ </label>
+ <input
+ id={`var-${key}`}
+ type="text"
+ value={vars[key as keyof typeof vars]}
+ onChange={(e) =>
+ setVars((prev) => ({
+ ...prev,
+ [key]: e.target.value,
+ }))
+ }
+ className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm focus:border-blue-500 focus:outline-none focus:ring-1 focus:ring-blue-500"
+ />
+ </div>
+ ))}
+ </div>
+ </div>
+
+ {/* Skills */}
+ {profile.skillBundle.length > 0 && (
+ <div>
+ <h3 className="mb-2 text-sm font-semibold text-slate-700">
+ Skills ({profile.skillBundle.length})
+ </h3>
+ <ul className="space-y-1">
+ {profile.skillBundle.map((s) => (
+ <li
+ key={s.name}
+ className="text-xs text-slate-600"
+ title={s.description}
+ >
+ {s.name}
+ </li>
+ ))}
+ </ul>
+ </div>
+ )}
+
+ {/* MCP Servers */}
+ {profile.mcpServers.length > 0 && (
+ <div>
+ <h3 className="mb-2 text-sm font-semibold text-slate-700">
+ MCP Servers ({profile.mcpServers.length})
+ </h3>
+ <ul className="space-y-2">
+ {profile.mcpServers.map((m) => (
+ <li key={m.name}>
+ <p className="text-xs font-medium text-slate-700">
+ {m.name}
+ {m.requiresKey && (
+ <span className="ml-1 text-yellow-600">(key required)</span>
+ )}
+ </p>
+ <code className="block rounded bg-slate-200 px-2 py-1 text-[10px] text-slate-600">
+ {m.installCmd}
+ </code>
+ </li>
+ ))}
+ </ul>
+ </div>
+ )}
+
+ {/* Subagents */}
+ {profile.subagents.length > 0 && (
+ <div>
+ <h3 className="mb-2 text-sm font-semibold text-slate-700">
+ Subagents ({profile.subagents.length})
+ </h3>
+ <ul className="space-y-1">
+ {profile.subagents.map((sa) => (
+ <li key={sa.name} className="text-xs text-slate-600">
+ {sa.name} ({sa.model}) - {sa.tools.join(', ')}
+ </li>
+ ))}
+ </ul>
+ </div>
+ )}
+
+ {/* Hooks Summary */}
+ <div>
+ <h3 className="mb-2 text-sm font-semibold text-slate-700">
+ Hooks
+ </h3>
+ <ul className="space-y-1 text-xs text-slate-600">
+ {profile.hooks.PreToolUse && (
+ <li>PreToolUse: {profile.hooks.PreToolUse.length} rule(s)</li>
+ )}
+ {profile.hooks.PostToolUse && (
+ <li>PostToolUse: {profile.hooks.PostToolUse.length} rule(s)</li>
+ )}
+ {profile.hooks.Stop && (
+ <li>Stop: {profile.hooks.Stop.length} rule(s)</li>
+ )}
+ {profile.hooks.SessionStart && (
+ <li>SessionStart: {profile.hooks.SessionStart.length} rule(s)</li>
+ )}
+ {profile.hooks.PreCompact && (
+ <li>PreCompact: {profile.hooks.PreCompact.length} rule(s)</li>
+ )}
+ {!profile.hooks.PreToolUse &&
+ !profile.hooks.PostToolUse &&
+ !profile.hooks.Stop &&
+ !profile.hooks.SessionStart &&
+ !profile.hooks.PreCompact && (
+ <li className="text-slate-400">No hooks configured</li>
+ )}
+ </ul>
+ </div>
+
+ {/* Learning Resources */}
+ {profile.learningPath.length > 0 && (
+ <div>
+ <h3 className="mb-2 text-sm font-semibold text-slate-700">
+ Learning Resources
+ </h3>
+ <ul className="space-y-1">
+ {profile.learningPath.map((r) => (
+ <li key={r.url}>
+ <a
+ href={r.url}
+ target="_blank"
+ rel="noopener noreferrer"
+ className="text-xs text-blue-600 hover:underline"
+ >
+ {r.title}
+ </a>
+ <span className="ml-1 text-[10px] text-slate-400">
+ ({r.type})
+ </span>
+ </li>
+ ))}
+ </ul>
+ </div>
+ )}
+
+ {/* Marketplace Recommendations */}
+ {marketplaceRecs.length > 0 && (
+ <div>
+ <h3 className="mb-2 text-sm font-semibold text-slate-700">
+ Recommended from{' '}
+ <a href="/marketplace" className="text-blue-600 hover:underline">
+ Marketplace
+ </a>
+ </h3>
+ <ul className="space-y-2">
+ {marketplaceRecs.map((rec) => (
+ <li
+ key={rec.id}
+ className="flex items-start justify-between gap-2 rounded-lg border border-slate-200 bg-white px-3 py-2"
+ >
+ <div className="min-w-0 flex-1">
+ <p className="truncate text-xs font-medium text-slate-700">
+ {rec.name}
+ </p>
+ <span
+ className={`mt-0.5 inline-block rounded-full px-1.5 py-0 text-[9px] font-semibold uppercase tracking-wide ${
+ rec.category === 'skill'
+ ? 'bg-violet-100 text-violet-700'
+ : rec.category === 'subagent'
+ ? 'bg-amber-100 text-amber-700'
+ : rec.category === 'mcp-server'
+ ? 'bg-sky-100 text-sky-700'
+ : rec.category === 'hook'
+ ? 'bg-rose-100 text-rose-700'
+ : 'bg-emerald-100 text-emerald-700'
+ }`}
+ >
+ {rec.category.replace('-', ' ')}
+ </span>
+ </div>
+ <button
+ type="button"
+ onClick={async () => {
+ try {
+ await navigator.clipboard.writeText(rec.installCmd)
+ } catch {
+ const ta = document.createElement('textarea')
+ ta.value = rec.installCmd
+ document.body.appendChild(ta)
+ ta.select()
+ document.execCommand('copy')
+ document.body.removeChild(ta)
+ }
+ setCopiedInstall(rec.id)
+ setTimeout(() => setCopiedInstall(null), 2000)
+ }}
+ className="shrink-0 rounded-md bg-blue-50 px-2 py-1 text-[10px] font-medium text-blue-600 hover:bg-blue-100 transition-colors"
+ >
+ {copiedInstall === rec.id ? 'Copied!' : 'Install'}
+ </button>
+ </li>
+ ))}
+ </ul>
+ </div>
+ )}
+
+ {/* Regenerate Button */}
+ <button
+ type="button"
+ onClick={generate}
+ disabled={generating}
+ className="w-full min-h-[44px] rounded-xl bg-blue-600 py-2.5 text-sm font-medium text-white transition-colors hover:bg-blue-700 disabled:opacity-50"
+ >
+ {generating ? 'Regenerating...' : 'Regenerate'}
+ </button>
+ </div>
+ </aside>
+
+ {/* Right Panel - Content */}
+ <div className="flex flex-1 flex-col overflow-hidden">
+ {/* Tabs */}
+ <div className="flex items-center justify-between border-b border-slate-200 bg-white px-4">
+ <div className="flex">
+ {tabs.map((tab) => (
+ <button
+ key={tab.id}
+ type="button"
+ onClick={() => setActiveTab(tab.id)}
+ className={`border-b-2 px-4 py-3 text-sm font-medium transition-colors ${
+ activeTab === tab.id
+ ? 'border-blue-600 text-blue-600'
+ : 'border-transparent text-slate-500 hover:text-slate-700'
+ }`}
+ >
+ {tab.label}
+ </button>
+ ))}
+ </div>
+ <div className="flex gap-2">
+ <button
+ type="button"
+ onClick={() => handleCopy(getTabContent())}
+ className="rounded-lg border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-600 hover:bg-slate-50"
+ >
+ {copied ? 'Copied!' : 'Copy'}
+ </button>
+ <button
+ type="button"
+ onClick={() => {
+ const ext =
+ activeTab === 'hooks' ? 'json' : activeTab === 'bundle' ? 'json' : 'md'
+ handleDownload(
+ getTabContent(),
+ activeTab === 'claude_md'
+ ? 'CLAUDE.md'
+ : `${activeTab}.${ext}`
+ )
+ }}
+ className="rounded-lg border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-600 hover:bg-slate-50"
+ >
+ Download
+ </button>
+ <button
+ type="button"
+ onClick={handleDownloadAll}
+ disabled={!bundle}
+ className="rounded-lg bg-slate-800 px-3 py-1.5 text-xs font-medium text-white hover:bg-slate-700 disabled:opacity-50"
+ >
+ Download All
+ </button>
+ </div>
+ </div>
+
+ {/* Content */}
+ <div className="flex-1 overflow-auto bg-slate-50 p-6">
+ {generating ? (
+ <div className="flex items-center justify-center py-20">
+ <div className="h-8 w-8 animate-spin rounded-full border-2 border-slate-300 border-t-blue-600" />
+ </div>
+ ) : bundle ? (
+ <pre className="whitespace-pre-wrap rounded-xl border border-slate-200 bg-white p-6 font-mono text-sm text-slate-700">
+ {getTabContent()}
+ </pre>
+ ) : (
+ <p className="text-sm text-slate-500">
+ Click "Regenerate" to generate configuration files.
+ </p>
+ )}
+ </div>
+ </div>
+ </div>
+ )
+}
+
+export default function BuilderPage() {
+ return (
+ <Suspense
+ fallback={
+ <div className="flex flex-1 items-center justify-center">
+ <div className="h-8 w-8 animate-spin rounded-full border-2 border-slate-300 border-t-blue-600" />
+ </div>
+ }
+ >
+ <BuilderContent />
+ </Suspense>
+ )
+}
diff --git a/src/app/favicon.ico b/src/app/favicon.ico
new file mode 100644
index 0000000..718d6fe
Binary files /dev/null and b/src/app/favicon.ico differ
diff --git a/src/app/globals.css b/src/app/globals.css
new file mode 100644
index 0000000..d246ba9
--- /dev/null
+++ b/src/app/globals.css
@@ -0,0 +1,7 @@
+@import "tailwindcss";
+
+@theme inline {
+ --font-sans: var(--font-inter), ui-sans-serif, system-ui, sans-serif;
+ --color-background: #ffffff;
+ --color-foreground: #1e293b;
+}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
new file mode 100644
index 0000000..4be416e
--- /dev/null
+++ b/src/app/layout.tsx
@@ -0,0 +1,30 @@
+import type { Metadata } from 'next'
+import { Inter } from 'next/font/google'
+import './globals.css'
+import { NavWrapper } from '@/components/nav-wrapper'
+
+const inter = Inter({
+ variable: '--font-inter',
+ subsets: ['latin'],
+})
+
+export const metadata: Metadata = {
+ title: 'ClawCoder — CLAUDE.md Generator & Analyzer',
+ description:
+ 'Build your perfect CLAUDE.md configuration with 15 profiles tailored to your age and experience level, or analyze your existing config for instant feedback.',
+}
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode
+}>) {
+ return (
+ <html lang="en" className={`${inter.variable} h-full antialiased`}>
+ <body className="flex min-h-full flex-col bg-white font-sans text-slate-800">
+ <NavWrapper />
+ <main className="flex flex-1 flex-col">{children}</main>
+ </body>
+ </html>
+ )
+}
diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx
new file mode 100644
index 0000000..1240ede
--- /dev/null
+++ b/src/app/login/page.tsx
@@ -0,0 +1,144 @@
+'use client'
+
+import { useState, FormEvent, Suspense } from 'react'
+import { useRouter, useSearchParams } from 'next/navigation'
+
+function LoginForm() {
+ const [username, setUsername] = useState('')
+ const [password, setPassword] = useState('')
+ const [error, setError] = useState('')
+ const [loading, setLoading] = useState(false)
+ const router = useRouter()
+ const searchParams = useSearchParams()
+
+ async function handleSubmit(e: FormEvent) {
+ e.preventDefault()
+ setLoading(true)
+ setError('')
+
+ const res = await fetch('/api/auth', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ username, password }),
+ })
+
+ if (res.ok) {
+ const redirect = searchParams.get('redirect') || '/'
+ window.location.href = redirect
+ } else {
+ setError('Invalid credentials')
+ setLoading(false)
+ }
+ }
+
+ return (
+ <div style={{
+ minHeight: '100vh',
+ display: 'flex',
+ alignItems: 'center',
+ justifyContent: 'center',
+ background: '#0a0a0a',
+ fontFamily: 'system-ui, -apple-system, sans-serif',
+ }}>
+ <form onSubmit={handleSubmit} style={{
+ background: '#141414',
+ border: '1px solid #222',
+ borderRadius: '12px',
+ padding: '40px',
+ width: '100%',
+ maxWidth: '380px',
+ }}>
+ <h1 style={{
+ fontSize: '20px',
+ fontWeight: 600,
+ color: '#e5e5e5',
+ marginBottom: '8px',
+ }}>ClawCoder</h1>
+ <p style={{
+ fontSize: '13px',
+ color: '#666',
+ marginBottom: '28px',
+ }}>Sign in to continue</p>
+
+ {error && (
+ <div style={{
+ background: '#1a0000',
+ border: '1px solid #3a0000',
+ borderRadius: '6px',
+ padding: '10px 14px',
+ fontSize: '13px',
+ color: '#ff4444',
+ marginBottom: '20px',
+ }}>{error}</div>
+ )}
+
+ <label style={{ display: 'block', marginBottom: '16px' }}>
+ <span style={{ fontSize: '12px', color: '#888', display: 'block', marginBottom: '6px' }}>Username</span>
+ <input
+ type="text"
+ value={username}
+ onChange={e => setUsername(e.target.value)}
+ autoFocus
+ required
+ style={{
+ width: '100%',
+ padding: '10px 12px',
+ background: '#0a0a0a',
+ border: '1px solid #333',
+ borderRadius: '6px',
+ color: '#e5e5e5',
+ fontSize: '14px',
+ outline: 'none',
+ boxSizing: 'border-box',
+ }}
+ />
+ </label>
+
+ <label style={{ display: 'block', marginBottom: '24px' }}>
+ <span style={{ fontSize: '12px', color: '#888', display: 'block', marginBottom: '6px' }}>Password</span>
+ <input
+ type="password"
+ value={password}
+ onChange={e => setPassword(e.target.value)}
+ required
+ style={{
+ width: '100%',
+ padding: '10px 12px',
+ background: '#0a0a0a',
+ border: '1px solid #333',
+ borderRadius: '6px',
+ color: '#e5e5e5',
+ fontSize: '14px',
+ outline: 'none',
+ boxSizing: 'border-box',
+ }}
+ />
+ </label>
+
+ <button
+ type="submit"
+ disabled={loading}
+ style={{
+ width: '100%',
+ padding: '11px',
+ background: loading ? '#333' : '#e5e5e5',
+ color: '#0a0a0a',
+ border: 'none',
+ borderRadius: '6px',
+ fontSize: '14px',
+ fontWeight: 600,
+ cursor: loading ? 'wait' : 'pointer',
+ }}
+ >{loading ? 'Signing in...' : 'Sign In'}</button>
+ </form>
+ </div>
+ )
+}
+
+export default function LoginPage() {
+ return (
+ <Suspense>
+ <LoginForm />
+ </Suspense>
+ )
+}
diff --git a/src/app/marketplace/page.tsx b/src/app/marketplace/page.tsx
new file mode 100644
index 0000000..10a133d
--- /dev/null
+++ b/src/app/marketplace/page.tsx
@@ -0,0 +1,394 @@
+'use client'
+
+import { useState, useEffect, useRef, useCallback } from 'react'
+import { motion, AnimatePresence } from 'framer-motion'
+import type { MarketplaceListing } from '@/lib/marketplace/data'
+
+// ---------------------------------------------------------------------------
+// Constants
+// ---------------------------------------------------------------------------
+
+type CategoryFilter = 'all' | 'skill' | 'subagent' | 'mcp-server' | 'hook' | 'template'
+
+const CATEGORY_TABS: { id: CategoryFilter; label: string; color: string }[] = [
+ { id: 'all', label: 'All', color: 'bg-slate-100 text-slate-700' },
+ { id: 'skill', label: 'Skills', color: 'bg-violet-100 text-violet-700' },
+ { id: 'subagent', label: 'Subagents', color: 'bg-amber-100 text-amber-700' },
+ { id: 'mcp-server', label: 'MCP Servers', color: 'bg-sky-100 text-sky-700' },
+ { id: 'hook', label: 'Hooks', color: 'bg-rose-100 text-rose-700' },
+ { id: 'template', label: 'Templates', color: 'bg-emerald-100 text-emerald-700' },
+]
+
+const CATEGORY_COLORS: Record<string, string> = {
+ skill: 'bg-violet-100 text-violet-700',
+ subagent: 'bg-amber-100 text-amber-700',
+ 'mcp-server': 'bg-sky-100 text-sky-700',
+ hook: 'bg-rose-100 text-rose-700',
+ template: 'bg-emerald-100 text-emerald-700',
+}
+
+const TRUST_BADGES: Record<string, { label: string; className: string }> = {
+ official: { label: 'Official', className: 'bg-green-100 text-green-700 ring-1 ring-green-300' },
+ verified: { label: 'Verified', className: 'bg-blue-100 text-blue-700 ring-1 ring-blue-300' },
+ community: { label: 'Community', className: 'bg-slate-100 text-slate-600 ring-1 ring-slate-300' },
+}
+
+const PROFILE_OPTIONS = [
+ { value: '', label: 'All profiles' },
+ { value: 'teen-beginner', label: 'Teen Beginner' },
+ { value: 'teen-intermediate', label: 'Teen Intermediate' },
+ { value: 'teen-expert', label: 'Teen Expert' },
+ { value: 'young-adult-beginner', label: 'Young Adult Beginner' },
+ { value: 'young-adult-intermediate', label: 'Young Adult Intermediate' },
+ { value: 'young-adult-expert', label: 'Young Adult Expert' },
+ { value: 'professional-beginner', label: 'Professional Beginner' },
+ { value: 'professional-intermediate', label: 'Professional Intermediate' },
+ { value: 'professional-expert', label: 'Professional Expert' },
+ { value: 'pre-senior-beginner', label: 'Pre-Senior Beginner' },
+ { value: 'pre-senior-intermediate', label: 'Pre-Senior Intermediate' },
+ { value: 'pre-senior-expert', label: 'Pre-Senior Expert' },
+ { value: 'senior-beginner', label: 'Senior Beginner' },
+ { value: 'senior-intermediate', label: 'Senior Intermediate' },
+ { value: 'senior-expert', label: 'Senior Expert' },
+]
+
+// ---------------------------------------------------------------------------
+// Helpers
+// ---------------------------------------------------------------------------
+
+function formatStars(n?: number): string {
+ if (!n) return ''
+ if (n >= 1000) return `${(n / 1000).toFixed(1)}k`
+ return String(n)
+}
+
+// ---------------------------------------------------------------------------
+// Listing Card
+// ---------------------------------------------------------------------------
+
+function ListingCard({ listing }: { listing: MarketplaceListing }) {
+ const [copied, setCopied] = useState(false)
+
+ const handleCopy = async () => {
+ try {
+ await navigator.clipboard.writeText(listing.installCmd)
+ setCopied(true)
+ setTimeout(() => setCopied(false), 2000)
+ } catch {
+ // Fallback for insecure contexts
+ const textarea = document.createElement('textarea')
+ textarea.value = listing.installCmd
+ document.body.appendChild(textarea)
+ textarea.select()
+ document.execCommand('copy')
+ document.body.removeChild(textarea)
+ setCopied(true)
+ setTimeout(() => setCopied(false), 2000)
+ }
+ }
+
+ const badge = TRUST_BADGES[listing.trustLevel]
+ const catColor = CATEGORY_COLORS[listing.category] || 'bg-slate-100 text-slate-600'
+ const maxTags = 3
+
+ return (
+ <motion.div
+ layout
+ initial={{ opacity: 0, y: 12 }}
+ animate={{ opacity: 1, y: 0 }}
+ exit={{ opacity: 0, y: -8, transition: { duration: 0.15 } }}
+ transition={{ duration: 0.25 }}
+ className="group flex flex-col rounded-xl border border-slate-200 bg-white shadow-sm transition-shadow hover:shadow-md"
+ >
+ {/* Header */}
+ <div className="flex items-start justify-between gap-2 p-5 pb-3">
+ <div className="min-w-0 flex-1">
+ <h3 className="truncate text-base font-semibold text-slate-900 group-hover:text-blue-600 transition-colors">
+ {listing.name}
+ </h3>
+ <div className="mt-1.5 flex flex-wrap items-center gap-1.5">
+ <span className={`inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-semibold uppercase tracking-wide ${catColor}`}>
+ {listing.category.replace('-', ' ')}
+ </span>
+ <span className={`inline-flex items-center rounded-full px-2 py-0.5 text-[10px] font-medium ${badge.className}`}>
+ {badge.label}
+ </span>
+ </div>
+ </div>
+ {listing.stars && (
+ <div className="flex shrink-0 items-center gap-1 rounded-md bg-slate-50 px-2 py-1 text-xs text-slate-500">
+ <svg className="h-3.5 w-3.5 text-amber-400" fill="currentColor" viewBox="0 0 20 20">
+ <path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z" />
+ </svg>
+ <span>{formatStars(listing.stars)}</span>
+ </div>
+ )}
+ </div>
+
+ {/* Description */}
+ <p className="px-5 text-sm leading-relaxed text-slate-600 line-clamp-2">
+ {listing.description}
+ </p>
+
+ {/* Author + Tags */}
+ <div className="mt-auto space-y-3 px-5 pt-3 pb-4">
+ <div className="flex items-center gap-2 text-xs text-slate-500">
+ <svg className="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" strokeWidth={1.5} stroke="currentColor">
+ <path strokeLinecap="round" strokeLinejoin="round" d="M15.75 6a3.75 3.75 0 11-7.5 0 3.75 3.75 0 017.5 0zM4.501 20.118a7.5 7.5 0 0114.998 0A17.933 17.933 0 0112 21.75c-2.676 0-5.216-.584-7.499-1.632z" />
+ </svg>
+ <span>{listing.author}</span>
+ </div>
+
+ {listing.tags.length > 0 && (
+ <div className="flex flex-wrap gap-1">
+ {listing.tags.slice(0, maxTags).map((tag) => (
+ <span
+ key={tag}
+ className="rounded-md bg-slate-100 px-1.5 py-0.5 text-[10px] font-medium text-slate-500"
+ >
+ {tag}
+ </span>
+ ))}
+ {listing.tags.length > maxTags && (
+ <span className="rounded-md bg-slate-50 px-1.5 py-0.5 text-[10px] text-slate-400">
+ +{listing.tags.length - maxTags}
+ </span>
+ )}
+ </div>
+ )}
+ </div>
+
+ {/* Actions */}
+ <div className="flex items-center gap-2 border-t border-slate-100 px-5 py-3">
+ <button
+ type="button"
+ onClick={handleCopy}
+ className="flex min-h-[36px] flex-1 items-center justify-center gap-1.5 rounded-lg bg-blue-600 px-3 text-xs font-medium text-white transition-colors hover:bg-blue-700 active:bg-blue-800"
+ >
+ {copied ? (
+ <>
+ <svg className="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
+ <path strokeLinecap="round" strokeLinejoin="round" d="M4.5 12.75l6 6 9-13.5" />
+ </svg>
+ Copied!
+ </>
+ ) : (
+ <>
+ <svg className="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
+ <path strokeLinecap="round" strokeLinejoin="round" d="M9 12h3.75M9 15h3.75M9 18h3.75m3 .75H18a2.25 2.25 0 002.25-2.25V6.108c0-1.135-.845-2.098-1.976-2.192a48.424 48.424 0 00-1.123-.08m-5.801 0c-.065.21-.1.433-.1.664 0 .414.336.75.75.75h4.5a.75.75 0 00.75-.75 2.25 2.25 0 00-.1-.664m-5.8 0A2.251 2.251 0 0113.5 2.25H15c1.012 0 1.867.668 2.15 1.586m-5.8 0c-.376.023-.75.05-1.124.08C9.095 4.01 8.25 4.973 8.25 6.108V8.25m0 0H4.875c-.621 0-1.125.504-1.125 1.125v11.25c0 .621.504 1.125 1.125 1.125h9.75c.621 0 1.125-.504 1.125-1.125V9.375c0-.621-.504-1.125-1.125-1.125H8.25z" />
+ </svg>
+ Install
+ </>
+ )}
+ </button>
+ <a
+ href={listing.source}
+ target="_blank"
+ rel="noopener noreferrer"
+ className="flex min-h-[36px] items-center gap-1 rounded-lg border border-slate-200 px-3 text-xs font-medium text-slate-600 transition-colors hover:bg-slate-50"
+ >
+ <svg className="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" strokeWidth={2} stroke="currentColor">
+ <path strokeLinecap="round" strokeLinejoin="round" d="M13.5 6H5.25A2.25 2.25 0 003 8.25v10.5A2.25 2.25 0 005.25 21h10.5A2.25 2.25 0 0018 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" />
+ </svg>
+ Source
+ </a>
+ </div>
+ </motion.div>
+ )
+}
+
+// ---------------------------------------------------------------------------
+// Main Page
+// ---------------------------------------------------------------------------
+
+export default function MarketplacePage() {
+ const [listings, setListings] = useState<MarketplaceListing[]>([])
+ const [loading, setLoading] = useState(true)
+ const [activeCategory, setActiveCategory] = useState<CategoryFilter>('all')
+ const [searchQuery, setSearchQuery] = useState('')
+ const [profileFilter, setProfileFilter] = useState('')
+ const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null)
+
+ const fetchListings = useCallback(async (query: string, category: CategoryFilter, profile: string) => {
+ setLoading(true)
+ try {
+ const params = new URLSearchParams()
+ if (query.trim()) params.set('q', query.trim())
+ if (category !== 'all') params.set('category', category)
+ if (profile) params.set('profile', profile)
+
+ const qs = params.toString()
+ const res = await fetch(`/api/marketplace${qs ? `?${qs}` : ''}`)
+ if (res.ok) {
+ const data = await res.json()
+ setListings(data)
+ }
+ } catch {
+ // silent
+ } finally {
+ setLoading(false)
+ }
+ }, [])
+
+ // Initial load
+ useEffect(() => {
+ fetchListings('', 'all', '')
+ }, [fetchListings])
+
+ // Debounced search
+ const handleSearch = (value: string) => {
+ setSearchQuery(value)
+ if (debounceRef.current) clearTimeout(debounceRef.current)
+ debounceRef.current = setTimeout(() => {
+ fetchListings(value, activeCategory, profileFilter)
+ }, 300)
+ }
+
+ // Category change
+ const handleCategoryChange = (cat: CategoryFilter) => {
+ setActiveCategory(cat)
+ fetchListings(searchQuery, cat, profileFilter)
+ }
+
+ // Profile change
+ const handleProfileChange = (profile: string) => {
+ setProfileFilter(profile)
+ fetchListings(searchQuery, activeCategory, profile)
+ }
+
+ // Count per category (from current query results, or show total without filter)
+ const categoryCounts: Record<string, number> = { all: listings.length }
+ for (const l of listings) {
+ categoryCounts[l.category] = (categoryCounts[l.category] || 0) + 1
+ }
+
+ return (
+ <div className="mx-auto w-full max-w-7xl px-4 py-8 sm:px-6">
+ {/* Header */}
+ <div className="mb-8">
+ <h1 className="text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl">
+ Marketplace
+ </h1>
+ <p className="mt-2 text-base text-slate-600 sm:text-lg">
+ Discover skills, subagents, MCP servers, hooks, and templates from the open-source Claude Code ecosystem.
+ </p>
+ </div>
+
+ {/* Search + Profile filter */}
+ <div className="mb-6 flex flex-col gap-3 sm:flex-row sm:items-center">
+ <div className="relative flex-1">
+ <svg
+ className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400"
+ fill="none"
+ viewBox="0 0 24 24"
+ strokeWidth={2}
+ stroke="currentColor"
+ >
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z"
+ />
+ </svg>
+ <input
+ type="text"
+ value={searchQuery}
+ onChange={(e) => handleSearch(e.target.value)}
+ placeholder="Search by name, tag, or description..."
+ className="h-11 w-full rounded-xl border border-slate-300 bg-white pl-10 pr-4 text-sm text-slate-800 placeholder:text-slate-400 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20"
+ />
+ </div>
+ <div className="flex items-center gap-2">
+ <label htmlFor="profile-filter" className="whitespace-nowrap text-xs font-medium text-slate-500">
+ Recommended for:
+ </label>
+ <select
+ id="profile-filter"
+ value={profileFilter}
+ onChange={(e) => handleProfileChange(e.target.value)}
+ className="h-11 rounded-xl border border-slate-300 bg-white px-3 text-sm text-slate-700 focus:border-blue-500 focus:outline-none focus:ring-2 focus:ring-blue-500/20"
+ >
+ {PROFILE_OPTIONS.map((opt) => (
+ <option key={opt.value} value={opt.value}>
+ {opt.label}
+ </option>
+ ))}
+ </select>
+ </div>
+ </div>
+
+ {/* Category Tabs */}
+ <div className="mb-6 -mx-4 overflow-x-auto px-4 sm:mx-0 sm:px-0">
+ <div className="flex gap-2">
+ {CATEGORY_TABS.map((tab) => {
+ const isActive = activeCategory === tab.id
+ return (
+ <button
+ key={tab.id}
+ type="button"
+ onClick={() => handleCategoryChange(tab.id)}
+ className={`flex shrink-0 items-center gap-1.5 rounded-full px-4 py-2 text-sm font-medium transition-all ${
+ isActive
+ ? 'bg-blue-600 text-white shadow-sm'
+ : 'bg-slate-100 text-slate-600 hover:bg-slate-200'
+ }`}
+ >
+ {tab.label}
+ {activeCategory === 'all' && tab.id !== 'all' && categoryCounts[tab.id] !== undefined && (
+ <span className={`rounded-full px-1.5 py-0 text-[10px] ${
+ isActive ? 'bg-blue-500 text-blue-100' : 'bg-slate-200 text-slate-500'
+ }`}>
+ {categoryCounts[tab.id]}
+ </span>
+ )}
+ </button>
+ )
+ })}
+ </div>
+ </div>
+
+ {/* Results */}
+ {loading ? (
+ <div className="flex items-center justify-center py-20">
+ <div className="h-8 w-8 animate-spin rounded-full border-2 border-slate-300 border-t-blue-600" />
+ </div>
+ ) : listings.length === 0 ? (
+ <div className="flex flex-col items-center justify-center rounded-xl border-2 border-dashed border-slate-200 py-20">
+ <svg
+ className="mb-4 h-12 w-12 text-slate-300"
+ fill="none"
+ viewBox="0 0 24 24"
+ strokeWidth={1}
+ stroke="currentColor"
+ >
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ d="M21 21l-5.197-5.197m0 0A7.5 7.5 0 105.196 5.196a7.5 7.5 0 0010.607 10.607z"
+ />
+ </svg>
+ <p className="text-base font-medium text-slate-500">No results found</p>
+ <p className="mt-1 text-sm text-slate-400">Try a different search or category.</p>
+ </div>
+ ) : (
+ <AnimatePresence mode="popLayout">
+ <motion.div
+ layout
+ className="grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3"
+ >
+ {listings.map((listing) => (
+ <ListingCard key={listing.id} listing={listing} />
+ ))}
+ </motion.div>
+ </AnimatePresence>
+ )}
+
+ {/* Footer count */}
+ {!loading && listings.length > 0 && (
+ <p className="mt-6 text-center text-xs text-slate-400">
+ Showing {listings.length} listing{listings.length !== 1 ? 's' : ''}
+ </p>
+ )}
+ </div>
+ )
+}
diff --git a/src/app/mass-update/page.tsx b/src/app/mass-update/page.tsx
new file mode 100644
index 0000000..cc8de55
--- /dev/null
+++ b/src/app/mass-update/page.tsx
@@ -0,0 +1,448 @@
+'use client'
+
+import { useCallback, useRef, useState } from 'react'
+import { useStore } from '@/lib/store'
+import { GradeBadge } from '@/components/ui/grade-badge'
+import type { AnalysisResult } from '@/lib/analyzer/types'
+
+function detectFileType(
+ name: string
+): 'claude_md' | 'rule' | 'skill' | 'hook' {
+ const lower = name.toLowerCase()
+ if (lower.includes('claude') && lower.endsWith('.md')) return 'claude_md'
+ if (lower.includes('hook') || lower.includes('settings.json')) return 'hook'
+ if (lower.includes('skill') || lower.includes('agent')) return 'skill'
+ return 'rule'
+}
+
+function typeBadge(type: string) {
+ const colors: Record<string, string> = {
+ claude_md: 'bg-blue-100 text-blue-700',
+ rule: 'bg-slate-100 text-slate-700',
+ skill: 'bg-purple-100 text-purple-700',
+ hook: 'bg-amber-100 text-amber-700',
+ }
+ return (
+ <span
+ className={`rounded-full px-2 py-0.5 text-xs font-medium ${colors[type] || colors.rule}`}
+ >
+ {type}
+ </span>
+ )
+}
+
+export default function MassUpdatePage() {
+ const {
+ massFiles,
+ addMassFiles,
+ removeMassFile,
+ updateMassFile,
+ clearMassFiles,
+ selectedMassFileIds,
+ toggleMassFileSelection,
+ selectAllMassFiles,
+ clearMassFileSelection,
+ } = useStore()
+
+ const [analyzing, setAnalyzing] = useState(false)
+ const [expandedRows, setExpandedRows] = useState<Set<string>>(new Set())
+ const fileInputRef = useRef<HTMLInputElement>(null)
+
+ const handleFileUpload = useCallback(
+ (e: React.ChangeEvent<HTMLInputElement>) => {
+ const files = e.target.files
+ if (!files) return
+
+ const newFiles: {
+ id: string
+ name: string
+ content: string
+ type: 'claude_md' | 'rule' | 'skill' | 'hook'
+ analysis: null
+ lastAnalyzed: null
+ }[] = []
+
+ const promises = Array.from(files).map(
+ (file) =>
+ new Promise<void>((resolve) => {
+ const reader = new FileReader()
+ reader.onload = (ev) => {
+ const text = ev.target?.result as string
+ newFiles.push({
+ id: `${file.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
+ name: file.name,
+ content: text,
+ type: detectFileType(file.name),
+ analysis: null,
+ lastAnalyzed: null,
+ })
+ resolve()
+ }
+ reader.readAsText(file)
+ })
+ )
+
+ Promise.all(promises).then(() => {
+ addMassFiles(newFiles)
+ })
+
+ // Reset input
+ if (fileInputRef.current) {
+ fileInputRef.current.value = ''
+ }
+ },
+ [addMassFiles]
+ )
+
+ const analyzeFile = useCallback(
+ async (id: string, content: string) => {
+ try {
+ const res = await fetch('/api/analyze', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ content, source: 'mass-update' }),
+ })
+ const data = await res.json()
+ if (res.ok) {
+ updateMassFile(id, {
+ analysis: data as AnalysisResult,
+ lastAnalyzed: new Date().toISOString(),
+ })
+ }
+ } catch {
+ // silent
+ }
+ },
+ [updateMassFile]
+ )
+
+ const handleAnalyzeAll = useCallback(async () => {
+ const selected = massFiles.filter((f) => selectedMassFileIds.has(f.id))
+ if (selected.length === 0) return
+ setAnalyzing(true)
+
+ for (const file of selected) {
+ await analyzeFile(file.id, file.content)
+ }
+
+ setAnalyzing(false)
+ }, [massFiles, selectedMassFileIds, analyzeFile])
+
+ const handleExportBundle = useCallback(() => {
+ const selected = massFiles.filter((f) => selectedMassFileIds.has(f.id))
+ if (selected.length === 0) return
+
+ let content = ''
+ for (const file of selected) {
+ content += `=== ${file.name} (${file.type}) ===\n`
+ if (file.analysis) {
+ content += `Score: ${file.analysis.letterGrade} (${file.analysis.percentage}%)\n`
+ }
+ content += '\n' + file.content + '\n\n'
+ }
+
+ const blob = new Blob([content], { type: 'text/plain' })
+ const url = URL.createObjectURL(blob)
+ const a = document.createElement('a')
+ a.href = url
+ a.download = 'clawcoder-bundle-export.txt'
+ a.click()
+ URL.revokeObjectURL(url)
+ }, [massFiles, selectedMassFileIds])
+
+ const toggleRow = (id: string) => {
+ setExpandedRows((prev) => {
+ const next = new Set(prev)
+ if (next.has(id)) next.delete(id)
+ else next.add(id)
+ return next
+ })
+ }
+
+ const selectedCount = selectedMassFileIds.size
+
+ return (
+ <div className="mx-auto w-full max-w-6xl px-4 py-10 sm:px-6">
+ <div className="mb-8 flex flex-wrap items-center justify-between gap-4">
+ <div>
+ <h1 className="text-3xl font-bold text-slate-900">Mass Update</h1>
+ <p className="mt-1 text-slate-600">
+ Upload and analyze multiple configuration files at once.
+ </p>
+ </div>
+ <div className="flex gap-3">
+ <button
+ type="button"
+ onClick={() => fileInputRef.current?.click()}
+ className="min-h-[44px] rounded-xl bg-blue-600 px-6 py-2.5 text-sm font-medium text-white transition-colors hover:bg-blue-700"
+ >
+ Upload Files
+ </button>
+ {massFiles.length > 0 && (
+ <button
+ type="button"
+ onClick={clearMassFiles}
+ className="min-h-[44px] rounded-xl border border-slate-300 px-6 py-2.5 text-sm font-medium text-slate-600 transition-colors hover:bg-slate-50"
+ >
+ Clear All
+ </button>
+ )}
+ </div>
+ <input
+ ref={fileInputRef}
+ type="file"
+ multiple
+ accept=".md,.txt,.json,.markdown"
+ onChange={handleFileUpload}
+ className="hidden"
+ />
+ </div>
+
+ {/* Bulk Actions Bar */}
+ {selectedCount > 0 && (
+ <div className="mb-6 flex flex-wrap items-center gap-3 rounded-xl border border-blue-200 bg-blue-50 p-4">
+ <span className="text-sm font-medium text-blue-700">
+ {selectedCount} selected
+ </span>
+ <button
+ type="button"
+ onClick={handleAnalyzeAll}
+ disabled={analyzing}
+ className="min-h-[36px] rounded-lg bg-blue-600 px-4 py-1.5 text-xs font-medium text-white hover:bg-blue-700 disabled:opacity-50"
+ >
+ {analyzing ? 'Analyzing...' : 'Analyze All'}
+ </button>
+ <button
+ type="button"
+ onClick={handleExportBundle}
+ className="min-h-[36px] rounded-lg border border-blue-300 px-4 py-1.5 text-xs font-medium text-blue-700 hover:bg-blue-100"
+ >
+ Export Bundle
+ </button>
+ <button
+ type="button"
+ onClick={clearMassFileSelection}
+ className="min-h-[36px] rounded-lg border border-blue-300 px-4 py-1.5 text-xs font-medium text-blue-700 hover:bg-blue-100"
+ >
+ Clear Selection
+ </button>
+ </div>
+ )}
+
+ {/* Empty State */}
+ {massFiles.length === 0 && (
+ <div className="flex flex-col items-center justify-center rounded-xl border-2 border-dashed border-slate-300 py-20 text-center">
+ <svg
+ className="mb-4 h-12 w-12 text-slate-300"
+ fill="none"
+ viewBox="0 0 24 24"
+ strokeWidth={1}
+ stroke="currentColor"
+ >
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ d="M19.5 14.25v-2.625a3.375 3.375 0 00-3.375-3.375h-1.5A1.125 1.125 0 0113.5 7.125v-1.5a3.375 3.375 0 00-3.375-3.375H8.25m6.75 12H9.75m3-3H9.75m10.5-4.875V18a2.25 2.25 0 01-2.25 2.25H5.25A2.25 2.25 0 013 18V6.75A2.25 2.25 0 015.25 4.5h4.5"
+ />
+ </svg>
+ <p className="text-sm font-medium text-slate-600">
+ No files uploaded yet
+ </p>
+ <p className="mt-1 text-xs text-slate-400">
+ Upload .md, .txt, or .json configuration files to get started
+ </p>
+ </div>
+ )}
+
+ {/* Files Table */}
+ {massFiles.length > 0 && (
+ <div className="overflow-hidden rounded-xl border border-slate-200 bg-white">
+ {/* Table header */}
+ <div className="grid grid-cols-[44px_1fr_100px_100px_140px_44px] items-center gap-2 border-b border-slate-200 bg-slate-50 px-4 py-3 text-xs font-medium uppercase tracking-wider text-slate-500">
+ <div className="flex items-center justify-center">
+ <input
+ type="checkbox"
+ checked={
+ selectedCount === massFiles.length && massFiles.length > 0
+ }
+ onChange={() => {
+ if (selectedCount === massFiles.length) {
+ clearMassFileSelection()
+ } else {
+ selectAllMassFiles()
+ }
+ }}
+ className="min-h-[20px] min-w-[20px] rounded border-slate-300 text-blue-600 focus:ring-blue-500"
+ aria-label="Select all files"
+ />
+ </div>
+ <div>File Name</div>
+ <div>Type</div>
+ <div>Score</div>
+ <div>Last Analyzed</div>
+ <div />
+ </div>
+
+ {/* Table rows */}
+ {massFiles.map((file) => (
+ <div key={file.id}>
+ <div className="grid grid-cols-[44px_1fr_100px_100px_140px_44px] items-center gap-2 border-b border-slate-100 px-4 py-3 hover:bg-slate-50">
+ <div className="flex items-center justify-center">
+ <input
+ type="checkbox"
+ checked={selectedMassFileIds.has(file.id)}
+ onChange={() => toggleMassFileSelection(file.id)}
+ className="min-h-[20px] min-w-[20px] rounded border-slate-300 text-blue-600 focus:ring-blue-500"
+ aria-label={`Select ${file.name}`}
+ />
+ </div>
+ <div className="truncate text-sm font-medium text-slate-800">
+ {file.name}
+ </div>
+ <div>{typeBadge(file.type)}</div>
+ <div>
+ {file.analysis ? (
+ <GradeBadge
+ grade={file.analysis.letterGrade}
+ percentage={file.analysis.percentage}
+ />
+ ) : (
+ <span className="text-xs text-slate-400">--</span>
+ )}
+ </div>
+ <div className="text-xs text-slate-500">
+ {file.lastAnalyzed
+ ? new Date(file.lastAnalyzed).toLocaleString()
+ : '--'}
+ </div>
+ <div className="flex gap-1">
+ <button
+ type="button"
+ onClick={() => toggleRow(file.id)}
+ className="min-h-[44px] min-w-[44px] rounded-lg text-slate-400 hover:bg-slate-100 hover:text-slate-600"
+ aria-label={
+ expandedRows.has(file.id)
+ ? 'Collapse details'
+ : 'Expand details'
+ }
+ >
+ <svg
+ className={`mx-auto h-4 w-4 transition-transform ${
+ expandedRows.has(file.id) ? 'rotate-180' : ''
+ }`}
+ fill="none"
+ viewBox="0 0 24 24"
+ strokeWidth={2}
+ stroke="currentColor"
+ >
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ d="M19.5 8.25l-7.5 7.5-7.5-7.5"
+ />
+ </svg>
+ </button>
+ </div>
+ </div>
+
+ {/* Expanded details */}
+ {expandedRows.has(file.id) && (
+ <div className="border-b border-slate-200 bg-slate-50 px-8 py-4">
+ {file.analysis ? (
+ <div className="space-y-3">
+ <div className="flex flex-wrap gap-4">
+ <div>
+ <span className="text-xs font-medium text-slate-500">
+ Score
+ </span>
+ <p className="text-sm text-slate-800">
+ {file.analysis.overallScore}/{file.analysis.maxScore}{' '}
+ ({file.analysis.percentage}%)
+ </p>
+ </div>
+ <div>
+ <span className="text-xs font-medium text-slate-500">
+ Lines
+ </span>
+ <p className="text-sm text-slate-800">
+ {file.analysis.lineCount}
+ </p>
+ </div>
+ <div>
+ <span className="text-xs font-medium text-slate-500">
+ Suggestions
+ </span>
+ <p className="text-sm text-slate-800">
+ {file.analysis.suggestions.length}
+ </p>
+ </div>
+ </div>
+
+ {file.analysis.pros.length > 0 && (
+ <div>
+ <p className="text-xs font-medium text-green-600">
+ Strengths:
+ </p>
+ <ul className="mt-1 space-y-0.5">
+ {file.analysis.pros.slice(0, 3).map((p, i) => (
+ <li
+ key={i}
+ className="text-xs text-green-700"
+ >
+ + {p}
+ </li>
+ ))}
+ </ul>
+ </div>
+ )}
+
+ {file.analysis.cons.length > 0 && (
+ <div>
+ <p className="text-xs font-medium text-red-600">
+ Issues:
+ </p>
+ <ul className="mt-1 space-y-0.5">
+ {file.analysis.cons.slice(0, 3).map((c, i) => (
+ <li
+ key={i}
+ className="text-xs text-red-700"
+ >
+ - {c}
+ </li>
+ ))}
+ </ul>
+ </div>
+ )}
+ </div>
+ ) : (
+ <p className="text-xs text-slate-400">
+ Not yet analyzed. Select this file and click
+ "Analyze All".
+ </p>
+ )}
+
+ <div className="mt-3 flex gap-2">
+ <button
+ type="button"
+ onClick={() => analyzeFile(file.id, file.content)}
+ className="rounded-lg border border-slate-300 px-3 py-1.5 text-xs font-medium text-slate-600 hover:bg-white"
+ >
+ Analyze
+ </button>
+ <button
+ type="button"
+ onClick={() => removeMassFile(file.id)}
+ className="rounded-lg border border-red-200 px-3 py-1.5 text-xs font-medium text-red-600 hover:bg-red-50"
+ >
+ Remove
+ </button>
+ </div>
+ </div>
+ )}
+ </div>
+ ))}
+ </div>
+ )}
+ </div>
+ )
+}
diff --git a/src/app/my-files/page.tsx b/src/app/my-files/page.tsx
new file mode 100644
index 0000000..6c78dab
--- /dev/null
+++ b/src/app/my-files/page.tsx
@@ -0,0 +1,520 @@
+'use client'
+
+import { useEffect, useState, useMemo, useCallback } from 'react'
+
+interface ClaudeMdFile {
+ id: string
+ path: string
+ project: string
+ lines: number
+ hasRules: boolean
+ lastModified: string
+ preview: string
+ exists: boolean
+}
+
+interface FileContent {
+ path: string
+ project: string
+ content: string
+ lines: number
+ lastModified: string
+}
+
+interface AnalyzeResult {
+ score: number
+ grade: string
+ criteria: { name: string; score: number; max: number; feedback: string }[]
+}
+
+type SortKey = 'project' | 'lines' | 'lastModified'
+type SortDir = 'asc' | 'desc'
+
+function Toast({ message, onClose }: { message: string; onClose: () => void }) {
+ useEffect(() => {
+ const t = setTimeout(onClose, 3000)
+ return () => clearTimeout(t)
+ }, [onClose])
+
+ return (
+ <div className="fixed bottom-6 right-6 z-50 rounded-lg bg-green-600 px-5 py-3 text-sm font-medium text-white shadow-lg">
+ {message}
+ </div>
+ )
+}
+
+function lineColor(lines: number): string {
+ if (lines > 500) return 'text-red-600 font-semibold'
+ if (lines > 200) return 'text-amber-600 font-semibold'
+ return 'text-slate-700'
+}
+
+function lineBadge(lines: number): string {
+ if (lines > 500) return 'bg-red-100 text-red-700'
+ if (lines > 200) return 'bg-amber-100 text-amber-700'
+ return 'bg-slate-100 text-slate-600'
+}
+
+function gradeColor(grade: string): string {
+ if (grade.startsWith('A')) return 'bg-green-100 text-green-700'
+ if (grade.startsWith('B')) return 'bg-blue-100 text-blue-700'
+ if (grade.startsWith('C')) return 'bg-amber-100 text-amber-700'
+ return 'bg-red-100 text-red-700'
+}
+
+export default function MyFilesPage() {
+ const [files, setFiles] = useState<ClaudeMdFile[]>([])
+ const [loading, setLoading] = useState(true)
+ const [selectedId, setSelectedId] = useState<string | null>(null)
+ const [fileContent, setFileContent] = useState<FileContent | null>(null)
+ const [editorContent, setEditorContent] = useState('')
+ const [originalContent, setOriginalContent] = useState('')
+ const [saving, setSaving] = useState(false)
+ const [toast, setToast] = useState<string | null>(null)
+ const [analyzing, setAnalyzing] = useState(false)
+ const [analyzeResult, setAnalyzeResult] = useState<AnalyzeResult | null>(null)
+ const [search, setSearch] = useState('')
+ const [sortKey, setSortKey] = useState<SortKey>('project')
+ const [sortDir, setSortDir] = useState<SortDir>('asc')
+ const [loadingFile, setLoadingFile] = useState(false)
+
+ // Fetch file list
+ useEffect(() => {
+ fetch('/api/files')
+ .then(r => r.json())
+ .then(data => {
+ setFiles(data)
+ setLoading(false)
+ })
+ .catch(() => setLoading(false))
+ }, [])
+
+ // Filtered and sorted files
+ const filteredFiles = useMemo(() => {
+ let result = files.filter(f => f.exists)
+ if (search) {
+ const q = search.toLowerCase()
+ result = result.filter(
+ f =>
+ f.project.toLowerCase().includes(q) ||
+ f.path.toLowerCase().includes(q)
+ )
+ }
+ result.sort((a, b) => {
+ let cmp = 0
+ if (sortKey === 'project') cmp = a.project.localeCompare(b.project)
+ else if (sortKey === 'lines') cmp = a.lines - b.lines
+ else if (sortKey === 'lastModified') cmp = new Date(a.lastModified).getTime() - new Date(b.lastModified).getTime()
+ return sortDir === 'desc' ? -cmp : cmp
+ })
+ return result
+ }, [files, search, sortKey, sortDir])
+
+ // Stats
+ const stats = useMemo(() => {
+ const existing = files.filter(f => f.exists)
+ const totalLines = existing.reduce((s, f) => s + f.lines, 0)
+ return {
+ total: existing.length,
+ avgLines: existing.length ? Math.round(totalLines / existing.length) : 0,
+ over200: existing.filter(f => f.lines > 200).length,
+ withRules: existing.filter(f => f.hasRules).length,
+ }
+ }, [files])
+
+ // Load a file
+ const loadFile = useCallback(async (id: string) => {
+ setSelectedId(id)
+ setLoadingFile(true)
+ setAnalyzeResult(null)
+ try {
+ const res = await fetch(`/api/files/${encodeURIComponent(id)}`)
+ if (!res.ok) throw new Error('Failed to load file')
+ const data: FileContent = await res.json()
+ setFileContent(data)
+ setEditorContent(data.content)
+ setOriginalContent(data.content)
+ } catch {
+ setFileContent(null)
+ setEditorContent('')
+ setOriginalContent('')
+ } finally {
+ setLoadingFile(false)
+ }
+ }, [])
+
+ // Save file
+ const saveFile = async () => {
+ if (!selectedId) return
+ setSaving(true)
+ try {
+ const res = await fetch(`/api/files/${encodeURIComponent(selectedId)}`, {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ content: editorContent }),
+ })
+ if (!res.ok) throw new Error('Save failed')
+ const data = await res.json()
+ setOriginalContent(editorContent)
+ setToast(`Saved! ${data.lines} lines written.`)
+ // Update line count in the list
+ setFiles(prev =>
+ prev.map(f =>
+ f.id === selectedId ? { ...f, lines: data.lines } : f
+ )
+ )
+ } catch {
+ setToast('Save failed. Check permissions.')
+ } finally {
+ setSaving(false)
+ }
+ }
+
+ // Analyze file
+ const analyzeFile = async () => {
+ setAnalyzing(true)
+ try {
+ const res = await fetch('/api/analyze', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ content: editorContent }),
+ })
+ if (!res.ok) throw new Error('Analyze failed')
+ const data = await res.json()
+ // Map API response format to component format
+ setAnalyzeResult({
+ score: data.percentage ?? data.score ?? 0,
+ grade: data.letterGrade ?? data.grade ?? 'F',
+ criteria: (data.criteria || []).map((c: any) => ({
+ name: c.name || c.id || '',
+ score: c.score ?? 0,
+ max: c.maxScore ?? c.max ?? 0,
+ feedback: c.findings?.[0]?.message || c.feedback || (c.score === c.maxScore ? 'Pass' : 'Needs improvement'),
+ })),
+ })
+ } catch {
+ setToast('Analysis failed.')
+ } finally {
+ setAnalyzing(false)
+ }
+ }
+
+ // Download file
+ const downloadFile = () => {
+ if (!fileContent) return
+ const blob = new Blob([editorContent], { type: 'text/markdown' })
+ const url = URL.createObjectURL(blob)
+ const a = document.createElement('a')
+ a.href = url
+ a.download = 'CLAUDE.md'
+ a.click()
+ URL.revokeObjectURL(url)
+ }
+
+ // Revert
+ const revertFile = () => {
+ setEditorContent(originalContent)
+ setToast('Reverted to original content.')
+ }
+
+ const editorLines = editorContent.split('\n').length
+ const isDirty = editorContent !== originalContent
+
+ function toggleSort(key: SortKey) {
+ if (sortKey === key) {
+ setSortDir(d => (d === 'asc' ? 'desc' : 'asc'))
+ } else {
+ setSortKey(key)
+ setSortDir('asc')
+ }
+ }
+
+ function sortArrow(key: SortKey) {
+ if (sortKey !== key) return ''
+ return sortDir === 'asc' ? ' \u2191' : ' \u2193'
+ }
+
+ if (loading) {
+ return (
+ <div className="flex min-h-[60vh] items-center justify-center">
+ <div className="text-slate-500">Loading files...</div>
+ </div>
+ )
+ }
+
+ return (
+ <div className="mx-auto w-full max-w-7xl px-4 py-6 sm:px-6">
+ {toast && <Toast message={toast} onClose={() => setToast(null)} />}
+
+ {/* Header */}
+ <div className="mb-6 flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
+ <div>
+ <h1 className="text-2xl font-bold text-slate-900">My CLAUDE.md Files</h1>
+ <p className="mt-1 text-sm text-slate-500">
+ Manage all {stats.total} CLAUDE.md configuration files across the server
+ </p>
+ </div>
+ </div>
+
+ {/* Stats bar */}
+ <div className="mb-6 grid grid-cols-2 gap-3 sm:grid-cols-4">
+ <div className="rounded-lg border border-slate-200 bg-white p-4 text-center">
+ <div className="text-2xl font-bold text-slate-800">{stats.total}</div>
+ <div className="text-xs text-slate-500">Total Files</div>
+ </div>
+ <div className="rounded-lg border border-slate-200 bg-white p-4 text-center">
+ <div className="text-2xl font-bold text-slate-800">{stats.avgLines}</div>
+ <div className="text-xs text-slate-500">Avg Lines</div>
+ </div>
+ <div className={`rounded-lg border p-4 text-center ${stats.over200 > 0 ? 'border-amber-300 bg-amber-50' : 'border-slate-200 bg-white'}`}>
+ <div className={`text-2xl font-bold ${stats.over200 > 0 ? 'text-amber-700' : 'text-slate-800'}`}>
+ {stats.over200}
+ </div>
+ <div className="text-xs text-slate-500">Over 200 Lines</div>
+ </div>
+ <div className="rounded-lg border border-slate-200 bg-white p-4 text-center">
+ <div className="text-2xl font-bold text-blue-600">{stats.withRules}</div>
+ <div className="text-xs text-slate-500">With .claude/rules/</div>
+ </div>
+ </div>
+
+ <div className="flex flex-col gap-6 lg:flex-row">
+ {/* File list panel */}
+ <div className={`w-full shrink-0 ${selectedId ? 'lg:w-[420px]' : ''}`}>
+ {/* Search */}
+ <div className="mb-3">
+ <input
+ type="text"
+ placeholder="Search by project or path..."
+ value={search}
+ onChange={e => setSearch(e.target.value)}
+ className="w-full rounded-lg border border-slate-300 px-4 py-2.5 text-sm text-slate-800 placeholder-slate-400 outline-none transition-colors focus:border-blue-500 focus:ring-2 focus:ring-blue-100"
+ />
+ </div>
+
+ {/* Table */}
+ <div className="overflow-hidden rounded-lg border border-slate-200 bg-white">
+ <div className="max-h-[calc(100vh-320px)] overflow-y-auto">
+ <table className="w-full text-left text-sm">
+ <thead className="sticky top-0 bg-slate-50 text-xs uppercase text-slate-500">
+ <tr>
+ <th
+ className="cursor-pointer px-4 py-3 hover:text-slate-700"
+ onClick={() => toggleSort('project')}
+ >
+ Project{sortArrow('project')}
+ </th>
+ <th
+ className="cursor-pointer px-3 py-3 text-right hover:text-slate-700"
+ onClick={() => toggleSort('lines')}
+ >
+ Lines{sortArrow('lines')}
+ </th>
+ {!selectedId && (
+ <>
+ <th className="px-3 py-3 text-center">Rules</th>
+ <th
+ className="cursor-pointer px-3 py-3 hover:text-slate-700"
+ onClick={() => toggleSort('lastModified')}
+ >
+ Modified{sortArrow('lastModified')}
+ </th>
+ </>
+ )}
+ </tr>
+ </thead>
+ <tbody className="divide-y divide-slate-100">
+ {filteredFiles.map(f => (
+ <tr
+ key={f.id}
+ onClick={() => loadFile(f.id)}
+ className={`cursor-pointer transition-colors hover:bg-blue-50 ${
+ selectedId === f.id ? 'bg-blue-50' : ''
+ }`}
+ >
+ <td className="px-4 py-3">
+ <div className="font-medium text-slate-800">{f.project}</div>
+ <div className="mt-0.5 truncate text-xs text-slate-400" title={f.path}>
+ {f.path.length > 45 ? '...' + f.path.slice(-42) : f.path}
+ </div>
+ </td>
+ <td className="px-3 py-3 text-right">
+ <span className={`inline-block rounded-full px-2 py-0.5 text-xs ${lineBadge(f.lines)}`}>
+ {f.lines}
+ </span>
+ </td>
+ {!selectedId && (
+ <>
+ <td className="px-3 py-3 text-center">
+ {f.hasRules && (
+ <span className="inline-block rounded-full bg-green-100 px-2 py-0.5 text-xs text-green-700">
+ Yes
+ </span>
+ )}
+ </td>
+ <td className="whitespace-nowrap px-3 py-3 text-xs text-slate-500">
+ {f.lastModified
+ ? new Date(f.lastModified).toLocaleDateString('en-US', {
+ month: 'short',
+ day: 'numeric',
+ year: 'numeric',
+ })
+ : '-'}
+ </td>
+ </>
+ )}
+ </tr>
+ ))}
+ {filteredFiles.length === 0 && (
+ <tr>
+ <td colSpan={4} className="px-4 py-8 text-center text-sm text-slate-400">
+ No files found matching "{search}"
+ </td>
+ </tr>
+ )}
+ </tbody>
+ </table>
+ </div>
+ </div>
+ </div>
+
+ {/* Editor panel */}
+ {selectedId && (
+ <div className="flex min-w-0 flex-1 flex-col">
+ {loadingFile ? (
+ <div className="flex h-64 items-center justify-center rounded-lg border border-slate-200 bg-white">
+ <div className="text-sm text-slate-500">Loading file...</div>
+ </div>
+ ) : fileContent ? (
+ <>
+ {/* Editor header */}
+ <div className="mb-3 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
+ <div className="min-w-0">
+ <h2 className="truncate text-lg font-semibold text-slate-800">
+ {fileContent.project}
+ </h2>
+ <p className="truncate text-xs text-slate-400" title={fileContent.path}>
+ {fileContent.path}
+ </p>
+ </div>
+ <div className="flex shrink-0 items-center gap-2">
+ <span className={`rounded-full px-2.5 py-1 text-xs font-medium ${lineBadge(editorLines)}`}>
+ {editorLines} lines
+ </span>
+ {analyzeResult && (
+ <span className={`rounded-full px-2.5 py-1 text-xs font-bold ${gradeColor(analyzeResult.grade)}`}>
+ {analyzeResult.grade} ({analyzeResult.score}%)
+ </span>
+ )}
+ {isDirty && (
+ <span className="rounded-full bg-orange-100 px-2.5 py-1 text-xs font-medium text-orange-700">
+ Unsaved
+ </span>
+ )}
+ </div>
+ </div>
+
+ {/* Line count warning */}
+ {editorLines > 200 && (
+ <div className={`mb-3 rounded-lg border px-4 py-2.5 text-sm ${
+ editorLines > 500
+ ? 'border-red-300 bg-red-50 text-red-700'
+ : 'border-amber-300 bg-amber-50 text-amber-700'
+ }`}>
+ This file is {editorLines} lines. Claude Code recommends under 200 lines for optimal performance.
+ {editorLines > 500 && ' Consider moving sections into .claude/rules/ files.'}
+ </div>
+ )}
+
+ {/* Textarea editor */}
+ <textarea
+ value={editorContent}
+ onChange={e => setEditorContent(e.target.value)}
+ spellCheck={false}
+ className="min-h-[400px] flex-1 resize-y rounded-lg border border-slate-300 bg-slate-50 p-4 font-mono text-sm leading-relaxed text-slate-800 outline-none transition-colors focus:border-blue-500 focus:ring-2 focus:ring-blue-100 lg:min-h-[500px]"
+ />
+
+ {/* Action buttons */}
+ <div className="mt-3 flex flex-wrap gap-2">
+ <button
+ onClick={saveFile}
+ disabled={saving || !isDirty}
+ className="rounded-lg bg-blue-600 px-5 py-2.5 text-sm font-medium text-white transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50"
+ >
+ {saving ? 'Saving...' : 'Save'}
+ </button>
+ <button
+ onClick={analyzeFile}
+ disabled={analyzing}
+ className="rounded-lg bg-slate-800 px-5 py-2.5 text-sm font-medium text-white transition-colors hover:bg-slate-900 disabled:cursor-not-allowed disabled:opacity-50"
+ >
+ {analyzing ? 'Analyzing...' : 'Analyze'}
+ </button>
+ <button
+ onClick={revertFile}
+ disabled={!isDirty}
+ className="rounded-lg border border-slate-300 bg-white px-5 py-2.5 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-50 disabled:cursor-not-allowed disabled:opacity-50"
+ >
+ Revert
+ </button>
+ <button
+ onClick={downloadFile}
+ className="rounded-lg border border-slate-300 bg-white px-5 py-2.5 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-50"
+ >
+ Download
+ </button>
+ <button
+ onClick={() => {
+ setSelectedId(null)
+ setFileContent(null)
+ setAnalyzeResult(null)
+ }}
+ className="rounded-lg border border-slate-300 bg-white px-5 py-2.5 text-sm font-medium text-slate-500 transition-colors hover:bg-slate-50"
+ >
+ Close
+ </button>
+ </div>
+
+ {/* Analysis results */}
+ {analyzeResult && (
+ <div className="mt-4 rounded-lg border border-slate-200 bg-white p-4">
+ <h3 className="mb-3 text-sm font-semibold text-slate-700">
+ Analysis: {analyzeResult.grade} ({analyzeResult.score}%)
+ </h3>
+ <div className="space-y-2">
+ {analyzeResult.criteria.map(c => (
+ <div key={c.name} className="flex items-start gap-3 text-sm">
+ <span className="shrink-0 font-mono text-xs text-slate-400">
+ {c.score}/{c.max}
+ </span>
+ <div>
+ <span className="font-medium text-slate-700">{c.name}</span>
+ <span className="ml-2 text-slate-500">{c.feedback}</span>
+ </div>
+ </div>
+ ))}
+ </div>
+ </div>
+ )}
+ </>
+ ) : (
+ <div className="flex h-64 items-center justify-center rounded-lg border border-slate-200 bg-white">
+ <div className="text-sm text-slate-400">File not found or unreadable.</div>
+ </div>
+ )}
+ </div>
+ )}
+
+ {/* Empty state when no file selected */}
+ {!selectedId && (
+ <div className="hidden flex-1 items-center justify-center rounded-lg border-2 border-dashed border-slate-200 bg-slate-50 lg:flex">
+ <div className="text-center">
+ <svg className="mx-auto mb-2 h-12 w-12 text-slate-300" fill="none" viewBox="0 0 24 24" strokeWidth={1} stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" d="M19.5 14.25v-2.625a3.375 3.375 0 0 0-3.375-3.375h-1.5A1.125 1.125 0 0 1 13.5 7.125v-1.5a3.375 3.375 0 0 0-3.375-3.375H8.25m0 12.75h7.5m-7.5 3H12M10.5 2.25H5.625c-.621 0-1.125.504-1.125 1.125v17.25c0 .621.504 1.125 1.125 1.125h12.75c.621 0 1.125-.504 1.125-1.125V11.25a9 9 0 0 0-9-9Z" /></svg>
+ <p className="text-sm text-slate-400">Select a file from the list to view and edit</p>
+ </div>
+ </div>
+ )}
+ </div>
+ </div>
+ )
+}
diff --git a/src/app/onboarding/page.tsx b/src/app/onboarding/page.tsx
new file mode 100644
index 0000000..adc7a38
--- /dev/null
+++ b/src/app/onboarding/page.tsx
@@ -0,0 +1,219 @@
+'use client'
+
+import { useCallback, useState } from 'react'
+import { useStore } from '@/lib/store'
+import { ONBOARDING_QUESTIONS } from '@/lib/onboarding/questions'
+import { resolveProfile } from '@/lib/onboarding/resolver'
+import { QuestionScreen } from '@/components/onboarding/question-screen'
+import { ReviewScreen } from '@/components/onboarding/review-screen'
+import { ResultScreen } from '@/components/onboarding/result-screen'
+
+const TOTAL_STEPS = ONBOARDING_QUESTIONS.length
+
+interface GeneratedBundle {
+ claudeMd: string
+ rulesFiles: { path: string; content: string }[]
+ settings: object
+ settingsJson: string
+ profile: {
+ slug: string
+ displayName: string
+ ageGroup: string
+ skillLevel: string
+ persona: string
+ }
+}
+
+type Phase = 'questions' | 'review' | 'generating' | 'result'
+
+export default function OnboardingPage() {
+ const {
+ currentStep,
+ setStep,
+ onboardingAnswers,
+ setAnswer,
+ isLoading,
+ setLoading,
+ } = useStore()
+
+ const [phase, setPhase] = useState<Phase>('questions')
+ const [bundle, setBundle] = useState<GeneratedBundle | null>(null)
+ const [fullProfile, setFullProfile] = useState<{
+ skillBundle: { name: string; description: string }[]
+ mcpServers: {
+ name: string
+ package: string
+ installCmd: string
+ requiresKey: boolean
+ trustLevel: string
+ }[]
+ learningPath: {
+ title: string
+ url: string
+ type: string
+ level: string
+ }[]
+ } | null>(null)
+ const [error, setError] = useState<string | null>(null)
+
+ // The step index is 0-based internally; questions are 1-based.
+ const stepIndex = currentStep
+ const question = ONBOARDING_QUESTIONS[stepIndex]
+
+ const handleSelect = useCallback(
+ (value: string) => {
+ if (!question) return
+ setAnswer(question.id, value)
+ },
+ [question, setAnswer]
+ )
+
+ const handleNext = useCallback(() => {
+ if (!question) return
+
+ // On the final step, check the user's choice
+ if (stepIndex === TOTAL_STEPS - 1) {
+ const finishAnswer = onboardingAnswers.finish
+ if (finishAnswer === 'review') {
+ setPhase('review')
+ } else {
+ // generate immediately
+ doGenerate()
+ }
+ return
+ }
+
+ setStep(stepIndex + 1)
+ }, [stepIndex, question, onboardingAnswers, setStep])
+
+ const handleBack = useCallback(() => {
+ if (phase === 'review') {
+ setPhase('questions')
+ return
+ }
+ if (stepIndex > 0) {
+ setStep(stepIndex - 1)
+ }
+ }, [stepIndex, phase, setStep])
+
+ const handleEditFromReview = useCallback(
+ (step: number) => {
+ // step is 1-based in questions data, store index is 0-based
+ setStep(step - 1)
+ setPhase('questions')
+ },
+ [setStep]
+ )
+
+ const doGenerate = useCallback(async () => {
+ setPhase('generating')
+ setLoading(true)
+ setError(null)
+
+ try {
+ const resolved = resolveProfile(onboardingAnswers)
+ const res = await fetch('/api/generate', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ profileSlug: resolved.slug,
+ vars: {
+ projectName: 'My Project',
+ projectDescription:
+ 'A project configured by ClawCoder onboarding wizard.',
+ techStack: 'To be configured',
+ },
+ }),
+ })
+
+ if (!res.ok) {
+ const err = await res.json().catch(() => ({ error: 'Generation failed' }))
+ throw new Error(err.error || 'Generation failed')
+ }
+
+ const data: GeneratedBundle = await res.json()
+ setBundle(data)
+
+ // Store full profile data for Skills/MCP/Learning tabs
+ setFullProfile({
+ skillBundle: resolved.skillBundle,
+ mcpServers: resolved.mcpServers,
+ learningPath: resolved.learningPath,
+ })
+
+ setPhase('result')
+ } catch (err) {
+ setError(err instanceof Error ? err.message : 'Something went wrong')
+ setPhase('questions')
+ // Go back to the finish step so the user can retry
+ setStep(TOTAL_STEPS - 1)
+ } finally {
+ setLoading(false)
+ }
+ }, [onboardingAnswers, setLoading, setStep])
+
+ // --- Render by phase ---
+
+ if (phase === 'generating') {
+ return (
+ <div className="flex min-h-screen flex-col items-center justify-center px-4">
+ <div className="flex flex-col items-center gap-6">
+ <div className="h-12 w-12 animate-spin rounded-full border-4 border-slate-200 border-t-blue-600" />
+ <h2 className="text-xl font-semibold text-slate-900">
+ Generating your setup...
+ </h2>
+ <p className="text-sm text-slate-500">
+ Building CLAUDE.md, rules, hooks, and more.
+ </p>
+ </div>
+ </div>
+ )
+ }
+
+ if (phase === 'result' && bundle) {
+ return <ResultScreen bundle={bundle} fullProfile={fullProfile} />
+ }
+
+ if (phase === 'review') {
+ return (
+ <ReviewScreen
+ answers={onboardingAnswers}
+ onEdit={handleEditFromReview}
+ onGenerate={doGenerate}
+ />
+ )
+ }
+
+ // Default: questions phase
+ if (!question) {
+ // Shouldn't happen, but safety net
+ setStep(0)
+ return null
+ }
+
+ return (
+ <>
+ {error && (
+ <div className="fixed left-1/2 top-4 z-[60] -translate-x-1/2 rounded-xl border border-red-200 bg-red-50 px-6 py-3 text-sm text-red-700 shadow-lg">
+ {error}
+ <button
+ type="button"
+ onClick={() => setError(null)}
+ className="ml-3 font-medium text-red-900 hover:text-red-700"
+ >
+ Dismiss
+ </button>
+ </div>
+ )}
+ <QuestionScreen
+ question={question}
+ totalSteps={TOTAL_STEPS}
+ selectedValue={onboardingAnswers[question.id]}
+ onSelect={handleSelect}
+ onBack={handleBack}
+ onNext={handleNext}
+ isFirst={stepIndex === 0}
+ />
+ </>
+ )
+}
diff --git a/src/app/page.tsx b/src/app/page.tsx
new file mode 100644
index 0000000..bf767e3
--- /dev/null
+++ b/src/app/page.tsx
@@ -0,0 +1,212 @@
+'use client'
+
+import { useEffect, useState } from 'react'
+import Link from 'next/link'
+import { useRouter } from 'next/navigation'
+
+interface ProfileSummary {
+ slug: string
+ ageGroup: string
+ skillLevel: string
+ displayName: string
+ persona: string
+ ui: { fontSize: number }
+ skillCount: number
+ mcpCount: number
+ subagentCount: number
+}
+
+const ageGroupLabels: Record<string, string> = {
+ teen: 'Teen (13-17)',
+ young_adult: 'Young Adult (18-30)',
+ professional: 'Professional (31-50)',
+ pre_senior: 'Pre-Senior (51-65)',
+ senior: 'Senior (65+)',
+}
+
+const ageGroupOrder = ['teen', 'young_adult', 'professional', 'pre_senior', 'senior']
+const skillLevelOrder = ['beginner', 'intermediate', 'expert']
+const skillLevelLabels: Record<string, string> = {
+ beginner: 'Beginner',
+ intermediate: 'Intermediate',
+ expert: 'Expert',
+}
+
+export default function HomePage() {
+ const [profiles, setProfiles] = useState<ProfileSummary[]>([])
+ const [loading, setLoading] = useState(true)
+ const router = useRouter()
+
+ useEffect(() => {
+ fetch('/api/profiles')
+ .then((r) => r.json())
+ .then((data) => {
+ setProfiles(data)
+ setLoading(false)
+ })
+ .catch(() => setLoading(false))
+ }, [])
+
+ function getProfile(age: string, skill: string) {
+ return profiles.find(
+ (p) => p.ageGroup === age && p.skillLevel === skill
+ )
+ }
+
+ return (
+ <div className="flex flex-col">
+ {/* Hero Section */}
+ <section className="flex flex-col items-center justify-center px-4 py-20 text-center sm:py-28">
+ <h1 className="max-w-2xl text-4xl font-bold tracking-tight text-slate-900 sm:text-5xl">
+ Build your perfect CLAUDE.md
+ </h1>
+ <p className="mt-6 max-w-xl text-lg leading-8 text-slate-600">
+ 15 profiles tailored to your age and experience level. Or upload
+ your existing config for instant analysis.
+ </p>
+ <div className="mt-10 flex flex-col gap-4 sm:flex-row">
+ <Link
+ href="/onboarding"
+ className="inline-flex min-h-[48px] items-center justify-center rounded-xl bg-blue-600 px-8 py-3 text-base font-medium text-white transition-colors hover:bg-blue-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
+ >
+ Start Guided Setup
+ </Link>
+ <a
+ href="#profiles"
+ className="inline-flex min-h-[48px] items-center justify-center rounded-xl border border-slate-300 px-8 py-3 text-base font-medium text-slate-700 transition-colors hover:bg-slate-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
+ >
+ Choose a Profile
+ </a>
+ <Link
+ href="/analyzer"
+ className="inline-flex min-h-[48px] items-center justify-center rounded-xl border border-slate-300 px-8 py-3 text-base font-medium text-slate-700 transition-colors hover:bg-slate-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
+ >
+ Analyze My CLAUDE.md
+ </Link>
+ </div>
+ </section>
+
+ {/* Profile Grid */}
+ <section id="profiles" className="mx-auto w-full max-w-7xl px-4 pb-20 sm:px-6">
+ <h2 className="mb-8 text-center text-2xl font-bold text-slate-900">
+ Choose Your Profile
+ </h2>
+
+ {loading ? (
+ <div className="flex items-center justify-center py-20">
+ <div className="h-8 w-8 animate-spin rounded-full border-2 border-slate-300 border-t-blue-600" />
+ </div>
+ ) : (
+ <>
+ {/* Desktop Grid */}
+ <div className="hidden lg:block">
+ {/* Skill level headers */}
+ <div className="mb-4 grid grid-cols-[180px_1fr_1fr_1fr] gap-4">
+ <div />
+ {skillLevelOrder.map((skill) => (
+ <div
+ key={skill}
+ className="text-center text-sm font-semibold uppercase tracking-wider text-slate-500"
+ >
+ {skillLevelLabels[skill]}
+ </div>
+ ))}
+ </div>
+
+ {/* Rows by age group */}
+ {ageGroupOrder.map((age) => (
+ <div
+ key={age}
+ className="mb-4 grid grid-cols-[180px_1fr_1fr_1fr] gap-4"
+ >
+ <div className="flex items-center text-sm font-semibold text-slate-700">
+ {ageGroupLabels[age]}
+ </div>
+ {skillLevelOrder.map((skill) => {
+ const profile = getProfile(age, skill)
+ if (!profile) {
+ return (
+ <div
+ key={skill}
+ className="rounded-xl border border-dashed border-slate-200 p-4 text-center text-sm text-slate-400"
+ >
+ Coming soon
+ </div>
+ )
+ }
+ return (
+ <button
+ key={skill}
+ type="button"
+ onClick={() =>
+ router.push(`/builder?profile=${profile.slug}`)
+ }
+ className="group cursor-pointer rounded-xl border border-slate-200 bg-white p-5 text-left shadow-sm transition-all hover:-translate-y-0.5 hover:border-blue-300 hover:shadow-md focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 motion-reduce:transform-none"
+ >
+ <h3 className="text-sm font-semibold text-slate-900 group-hover:text-blue-600">
+ {profile.displayName}
+ </h3>
+ <p className="mt-1 line-clamp-2 text-xs text-slate-500">
+ {profile.persona}
+ </p>
+ <div className="mt-3 flex gap-3 text-xs text-slate-400">
+ <span>Font {profile.ui.fontSize}px</span>
+ <span>{profile.skillCount} skills</span>
+ <span>{profile.mcpCount} MCP</span>
+ </div>
+ </button>
+ )
+ })}
+ </div>
+ ))}
+ </div>
+
+ {/* Mobile List */}
+ <div className="space-y-6 lg:hidden">
+ {ageGroupOrder.map((age) => (
+ <div key={age}>
+ <h3 className="mb-3 text-sm font-semibold uppercase tracking-wider text-slate-500">
+ {ageGroupLabels[age]}
+ </h3>
+ <div className="space-y-3">
+ {skillLevelOrder.map((skill) => {
+ const profile = getProfile(age, skill)
+ if (!profile) return null
+ return (
+ <button
+ key={skill}
+ type="button"
+ onClick={() =>
+ router.push(`/builder?profile=${profile.slug}`)
+ }
+ className="w-full cursor-pointer rounded-xl border border-slate-200 bg-white p-4 text-left shadow-sm transition-all hover:border-blue-300 hover:shadow-md focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
+ >
+ <div className="flex items-center justify-between">
+ <h4 className="text-sm font-semibold text-slate-900">
+ {profile.displayName}
+ </h4>
+ <span className="rounded-full bg-slate-100 px-2 py-0.5 text-xs text-slate-500">
+ {skillLevelLabels[skill]}
+ </span>
+ </div>
+ <p className="mt-1 line-clamp-2 text-xs text-slate-500">
+ {profile.persona}
+ </p>
+ <div className="mt-2 flex gap-3 text-xs text-slate-400">
+ <span>Font {profile.ui.fontSize}px</span>
+ <span>{profile.skillCount} skills</span>
+ <span>{profile.mcpCount} MCP</span>
+ </div>
+ </button>
+ )
+ })}
+ </div>
+ </div>
+ ))}
+ </div>
+ </>
+ )}
+ </section>
+ </div>
+ )
+}
diff --git a/src/components/nav-wrapper.tsx b/src/components/nav-wrapper.tsx
new file mode 100644
index 0000000..b111d3d
--- /dev/null
+++ b/src/components/nav-wrapper.tsx
@@ -0,0 +1,7 @@
+'use client'
+
+import { Nav } from './nav'
+
+export function NavWrapper() {
+ return <Nav />
+}
diff --git a/src/components/nav.tsx b/src/components/nav.tsx
new file mode 100644
index 0000000..95fec1f
--- /dev/null
+++ b/src/components/nav.tsx
@@ -0,0 +1,117 @@
+'use client'
+
+import Link from 'next/link'
+import { usePathname } from 'next/navigation'
+import { useState } from 'react'
+import { UserBadge } from './user-badge'
+
+const navLinks = [
+ { href: '/', label: 'Home' },
+ { href: '/analyzer', label: 'Analyzer' },
+ { href: '/builder', label: 'Builder' },
+ { href: '/marketplace', label: 'Marketplace' },
+ { href: '/my-files', label: 'My Files' },
+ { href: '/mass-update', label: 'Mass Update' },
+]
+
+export function Nav() {
+ const pathname = usePathname()
+ const [menuOpen, setMenuOpen] = useState(false)
+
+ function isActive(href: string) {
+ if (href === '/') return pathname === '/'
+ return pathname.startsWith(href)
+ }
+
+ return (
+ <nav className="sticky top-0 z-40 h-16 border-b border-slate-200 bg-white shadow-sm">
+ <div className="mx-auto flex h-full max-w-7xl items-center justify-between px-4 sm:px-6">
+ {/* Logo */}
+ <Link
+ href="/"
+ className="text-xl font-bold text-slate-800 transition-colors hover:text-blue-600"
+ >
+ ClawCoder
+ </Link>
+
+ {/* Desktop links */}
+ <div className="hidden items-center gap-1 md:flex">
+ {navLinks.map((link) => (
+ <Link
+ key={link.href}
+ href={link.href}
+ className={`rounded-lg px-3 py-2 text-sm font-medium transition-colors ${
+ isActive(link.href)
+ ? 'bg-blue-50 text-blue-600'
+ : 'text-slate-600 hover:bg-slate-100 hover:text-slate-900'
+ }`}
+ >
+ {link.label}
+ </Link>
+ ))}
+ </div>
+
+ {/* User badge (desktop) */}
+ <div className="hidden md:flex">
+ <UserBadge />
+ </div>
+
+ {/* Mobile hamburger */}
+ <button
+ type="button"
+ onClick={() => setMenuOpen(!menuOpen)}
+ className="inline-flex min-h-[44px] min-w-[44px] items-center justify-center rounded-lg text-slate-600 hover:bg-slate-100 md:hidden"
+ aria-label={menuOpen ? 'Close menu' : 'Open menu'}
+ aria-expanded={menuOpen}
+ >
+ <svg
+ className="h-6 w-6"
+ fill="none"
+ viewBox="0 0 24 24"
+ strokeWidth={1.5}
+ stroke="currentColor"
+ >
+ {menuOpen ? (
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ d="M6 18L18 6M6 6l12 12"
+ />
+ ) : (
+ <path
+ strokeLinecap="round"
+ strokeLinejoin="round"
+ d="M3.75 6.75h16.5M3.75 12h16.5m-16.5 5.25h16.5"
+ />
+ )}
+ </svg>
+ </button>
+ </div>
+
+ {/* Mobile menu */}
+ {menuOpen && (
+ <div className="border-b border-slate-200 bg-white shadow-lg md:hidden">
+ <div className="space-y-1 px-4 py-3">
+ {navLinks.map((link) => (
+ <Link
+ key={link.href}
+ href={link.href}
+ onClick={() => setMenuOpen(false)}
+ className={`block rounded-lg px-4 py-3 text-sm font-medium transition-colors ${
+ isActive(link.href)
+ ? 'bg-blue-50 text-blue-600'
+ : 'text-slate-600 hover:bg-slate-100 hover:text-slate-900'
+ }`}
+ >
+ {link.label}
+ </Link>
+ ))}
+ <div className="border-t border-slate-200 pt-3">
+ <UserBadge />
+ </div>
+ </div>
+ </div>
+ )}
+ </nav>
+ )
+}
diff --git a/src/components/onboarding/question-screen.tsx b/src/components/onboarding/question-screen.tsx
new file mode 100644
index 0000000..7f93dee
--- /dev/null
+++ b/src/components/onboarding/question-screen.tsx
@@ -0,0 +1,186 @@
+'use client'
+
+import { useState, useCallback, useEffect } from 'react'
+import { motion, AnimatePresence } from 'framer-motion'
+import type { OnboardingQuestion } from '@/lib/onboarding/questions'
+import { ProgressIndicator } from '@/components/ui/progress-indicator'
+
+interface QuestionScreenProps {
+ question: OnboardingQuestion
+ totalSteps: number
+ selectedValue: string | undefined
+ onSelect: (value: string) => void
+ onBack: () => void
+ onNext: () => void
+ isFirst: boolean
+}
+
+/** Detect prefers-reduced-motion at render time. */
+function usePrefersReducedMotion() {
+ const [reduced, setReduced] = useState(false)
+ useEffect(() => {
+ const mq = window.matchMedia('(prefers-reduced-motion: reduce)')
+ setReduced(mq.matches)
+ const handler = (e: MediaQueryListEvent) => setReduced(e.matches)
+ mq.addEventListener('change', handler)
+ return () => mq.removeEventListener('change', handler)
+ }, [])
+ return reduced
+}
+
+const fadeVariants = {
+ initial: { opacity: 0, x: 40 },
+ animate: { opacity: 1, x: 0 },
+ exit: { opacity: 0, x: -40 },
+}
+
+const fadeFallback = {
+ initial: { opacity: 0 },
+ animate: { opacity: 1 },
+ exit: { opacity: 0 },
+}
+
+export function QuestionScreen({
+ question,
+ totalSteps,
+ selectedValue,
+ onSelect,
+ onBack,
+ onNext,
+ isFirst,
+}: QuestionScreenProps) {
+ const [showHint, setShowHint] = useState(false)
+ const reducedMotion = usePrefersReducedMotion()
+ const variants = reducedMotion ? fadeFallback : fadeVariants
+
+ const handleSelect = useCallback(
+ (value: string) => {
+ onSelect(value)
+ // Auto-advance after a short delay so the user sees the selection
+ setTimeout(() => {
+ onNext()
+ }, 300)
+ },
+ [onSelect, onNext]
+ )
+
+ return (
+ <div className="flex min-h-screen flex-col">
+ {/* Main content, centered vertically with padding for bottom bar */}
+ <div className="flex flex-1 flex-col items-center justify-center px-4 pb-28 pt-8">
+ <AnimatePresence mode="wait">
+ <motion.div
+ key={question.id}
+ variants={variants}
+ initial="initial"
+ animate="animate"
+ exit="exit"
+ transition={{ duration: 0.25, ease: 'easeInOut' }}
+ className="flex w-full max-w-2xl flex-col items-center"
+ >
+ {/* Step label */}
+ <p className="mb-3 text-sm font-medium uppercase tracking-wider text-slate-400">
+ Step {question.step} of {totalSteps}
+ </p>
+
+ {/* Question */}
+ <h1 className="mb-2 text-center text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl">
+ {question.question}
+ </h1>
+
+ {/* Why we ask (collapsible) */}
+ <button
+ type="button"
+ onClick={() => setShowHint((v) => !v)}
+ className="mb-8 text-sm text-blue-500 transition-colors hover:text-blue-600 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
+ aria-expanded={showHint}
+ >
+ {showHint ? 'Hide' : 'Why do we ask?'}
+ </button>
+
+ <AnimatePresence>
+ {showHint && (
+ <motion.p
+ initial={{ opacity: 0, height: 0 }}
+ animate={{ opacity: 1, height: 'auto' }}
+ exit={{ opacity: 0, height: 0 }}
+ transition={{ duration: 0.2 }}
+ className="-mt-4 mb-8 max-w-md text-center text-sm text-slate-500"
+ >
+ {question.whyWeAsk}
+ </motion.p>
+ )}
+ </AnimatePresence>
+
+ {/* Pill options grid */}
+ <div
+ className={`grid w-full gap-4 ${
+ question.options.length <= 3
+ ? 'grid-cols-1 sm:grid-cols-3'
+ : question.options.length === 4
+ ? 'grid-cols-1 sm:grid-cols-2'
+ : 'grid-cols-1 sm:grid-cols-2 lg:grid-cols-3'
+ }`}
+ >
+ {question.options.map((opt) => (
+ <button
+ key={opt.value}
+ type="button"
+ onClick={() => handleSelect(opt.value)}
+ className={`group flex min-h-[72px] flex-col items-center justify-center rounded-2xl border-2 px-6 py-4 text-center transition-all focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 motion-reduce:transform-none ${
+ selectedValue === opt.value
+ ? 'border-blue-500 bg-blue-50 shadow-md'
+ : 'border-slate-200 bg-white hover:border-blue-300 hover:shadow-sm'
+ }`}
+ >
+ <span
+ className={`text-base font-semibold ${
+ selectedValue === opt.value
+ ? 'text-blue-700'
+ : 'text-slate-900 group-hover:text-blue-600'
+ }`}
+ >
+ {opt.label}
+ </span>
+ {opt.description && (
+ <span className="mt-1 text-sm text-slate-500">
+ {opt.description}
+ </span>
+ )}
+ </button>
+ ))}
+ </div>
+ </motion.div>
+ </AnimatePresence>
+ </div>
+
+ {/* Fixed bottom bar - progress + navigation */}
+ <div className="fixed bottom-0 left-0 right-0 z-50 border-t border-slate-200 bg-white/80 backdrop-blur">
+ <div className="mx-auto max-w-2xl px-4 pb-4 pt-3">
+ <ProgressIndicator current={question.step} total={totalSteps} />
+ <div className="mt-3 flex justify-between">
+ {!isFirst ? (
+ <button
+ type="button"
+ onClick={onBack}
+ className="min-h-[48px] rounded-xl border border-slate-300 px-6 py-2.5 text-sm font-medium text-slate-700 transition-colors hover:bg-slate-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
+ >
+ Back
+ </button>
+ ) : (
+ <div />
+ )}
+ <button
+ type="button"
+ onClick={onNext}
+ disabled={!selectedValue}
+ className="min-h-[48px] rounded-xl bg-blue-600 px-6 py-2.5 text-sm font-medium text-white transition-colors hover:bg-blue-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 disabled:cursor-not-allowed disabled:opacity-50"
+ >
+ {question.step === totalSteps ? 'Finish' : 'Next'}
+ </button>
+ </div>
+ </div>
+ </div>
+ </div>
+ )
+}
diff --git a/src/components/onboarding/result-screen.tsx b/src/components/onboarding/result-screen.tsx
new file mode 100644
index 0000000..94ccb35
--- /dev/null
+++ b/src/components/onboarding/result-screen.tsx
@@ -0,0 +1,258 @@
+'use client'
+
+import { useState, useCallback } from 'react'
+import { motion } from 'framer-motion'
+import Link from 'next/link'
+
+interface GeneratedBundle {
+ claudeMd: string
+ rulesFiles: { path: string; content: string }[]
+ settings: object
+ settingsJson: string
+ profile: {
+ slug: string
+ displayName: string
+ ageGroup: string
+ skillLevel: string
+ persona: string
+ }
+}
+
+type TabId = 'claude-md' | 'rules' | 'hooks' | 'skills' | 'mcp' | 'learning'
+
+interface TabDef {
+ id: TabId
+ label: string
+}
+
+const TABS: TabDef[] = [
+ { id: 'claude-md', label: 'CLAUDE.md' },
+ { id: 'rules', label: 'Rules' },
+ { id: 'hooks', label: 'Hooks' },
+ { id: 'skills', label: 'Skills' },
+ { id: 'mcp', label: 'MCP' },
+ { id: 'learning', label: 'Learning Path' },
+]
+
+interface ResultScreenProps {
+ bundle: GeneratedBundle
+ fullProfile: {
+ skillBundle: { name: string; description: string }[]
+ mcpServers: {
+ name: string
+ package: string
+ installCmd: string
+ requiresKey: boolean
+ trustLevel: string
+ }[]
+ learningPath: {
+ title: string
+ url: string
+ type: string
+ level: string
+ }[]
+ } | null
+}
+
+export function ResultScreen({ bundle, fullProfile }: ResultScreenProps) {
+ const [activeTab, setActiveTab] = useState<TabId>('claude-md')
+ const [copiedTab, setCopiedTab] = useState<string | null>(null)
+
+ const getTabContent = useCallback(
+ (tab: TabId): string => {
+ switch (tab) {
+ case 'claude-md':
+ return bundle.claudeMd
+ case 'rules':
+ return bundle.rulesFiles
+ .map((f) => `# ${f.path}\n\n${f.content}`)
+ .join('\n\n---\n\n')
+ case 'hooks':
+ return bundle.settingsJson
+ case 'skills':
+ if (!fullProfile) return 'No skills data available.'
+ return fullProfile.skillBundle
+ .map((s) => `## ${s.name}\n${s.description}`)
+ .join('\n\n')
+ case 'mcp':
+ if (!fullProfile) return 'No MCP server data available.'
+ if (fullProfile.mcpServers.length === 0) return 'No MCP servers recommended for this profile.'
+ return fullProfile.mcpServers
+ .map(
+ (m) =>
+ `## ${m.name}\nPackage: ${m.package}\nInstall: ${m.installCmd}\nRequires API Key: ${m.requiresKey ? 'Yes' : 'No'}\nTrust: ${m.trustLevel}`
+ )
+ .join('\n\n')
+ case 'learning':
+ if (!fullProfile) return 'No learning path data available.'
+ if (fullProfile.learningPath.length === 0) return 'No learning resources listed for this profile.'
+ return fullProfile.learningPath
+ .map(
+ (l) =>
+ `- [${l.title}](${l.url}) (${l.type}, ${l.level})`
+ )
+ .join('\n')
+ default:
+ return ''
+ }
+ },
+ [bundle, fullProfile]
+ )
+
+ const handleCopy = useCallback(
+ async (tab: TabId) => {
+ const content = getTabContent(tab)
+ await navigator.clipboard.writeText(content)
+ setCopiedTab(tab)
+ setTimeout(() => setCopiedTab(null), 2000)
+ },
+ [getTabContent]
+ )
+
+ const handleDownloadTab = useCallback(
+ (tab: TabId) => {
+ const content = getTabContent(tab)
+ const filename =
+ tab === 'claude-md'
+ ? 'CLAUDE.md'
+ : tab === 'hooks'
+ ? 'settings.json'
+ : `${tab}.md`
+ const blob = new Blob([content], { type: 'text/plain' })
+ const url = URL.createObjectURL(blob)
+ const a = document.createElement('a')
+ a.href = url
+ a.download = filename
+ a.click()
+ URL.revokeObjectURL(url)
+ },
+ [getTabContent]
+ )
+
+ const handleDownloadAll = useCallback(() => {
+ // Create a simple text bundle (no zip library dependency)
+ const parts: string[] = [
+ `=== CLAUDE.md ===\n\n${bundle.claudeMd}`,
+ `\n\n=== settings.json ===\n\n${bundle.settingsJson}`,
+ ]
+ for (const rule of bundle.rulesFiles) {
+ parts.push(`\n\n=== ${rule.path} ===\n\n${rule.content}`)
+ }
+ const blob = new Blob([parts.join('')], { type: 'text/plain' })
+ const url = URL.createObjectURL(blob)
+ const a = document.createElement('a')
+ a.href = url
+ a.download = `clawcoder-bundle-${bundle.profile.slug}.txt`
+ a.click()
+ URL.revokeObjectURL(url)
+ }, [bundle])
+
+ return (
+ <div className="flex min-h-screen flex-col items-center px-4 py-12">
+ <motion.div
+ initial={{ opacity: 0, y: 20 }}
+ animate={{ opacity: 1, y: 0 }}
+ transition={{ duration: 0.3 }}
+ className="w-full max-w-4xl"
+ >
+ {/* Header */}
+ <div className="mb-8 text-center">
+ <h1 className="mb-2 text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl">
+ Your setup is ready!
+ </h1>
+ <p className="text-slate-500">
+ Copy the files below into your project to get started.
+ </p>
+ </div>
+
+ {/* Profile badge */}
+ <div className="mb-8 flex flex-col items-center gap-2 rounded-2xl border border-green-200 bg-green-50 p-6">
+ <p className="text-sm font-medium uppercase tracking-wider text-green-600">
+ Generated for
+ </p>
+ <h2 className="text-xl font-bold text-green-900">
+ {bundle.profile.displayName}
+ </h2>
+ <p className="max-w-md text-center text-sm text-green-700">
+ {bundle.profile.persona}
+ </p>
+ <div className="mt-2 flex gap-3 text-xs text-green-700">
+ <span className="rounded-full bg-green-100 px-3 py-1">
+ {bundle.profile.ageGroup.replace('_', ' ')}
+ </span>
+ <span className="rounded-full bg-green-100 px-3 py-1">
+ {bundle.profile.skillLevel}
+ </span>
+ </div>
+ </div>
+
+ {/* Tabs */}
+ <div className="mb-4 flex flex-wrap gap-2 border-b border-slate-200 pb-2">
+ {TABS.map((tab) => (
+ <button
+ key={tab.id}
+ type="button"
+ onClick={() => setActiveTab(tab.id)}
+ className={`rounded-lg px-4 py-2 text-sm font-medium transition-colors focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 ${
+ activeTab === tab.id
+ ? 'bg-blue-600 text-white'
+ : 'text-slate-600 hover:bg-slate-100'
+ }`}
+ >
+ {tab.label}
+ </button>
+ ))}
+ </div>
+
+ {/* Tab content */}
+ <div className="relative rounded-2xl border border-slate-200 bg-slate-900">
+ {/* Action buttons */}
+ <div className="absolute right-3 top-3 z-10 flex gap-2">
+ <button
+ type="button"
+ onClick={() => handleCopy(activeTab)}
+ className="rounded-lg bg-slate-700 px-3 py-1.5 text-xs font-medium text-slate-200 transition-colors hover:bg-slate-600"
+ >
+ {copiedTab === activeTab ? 'Copied!' : 'Copy'}
+ </button>
+ <button
+ type="button"
+ onClick={() => handleDownloadTab(activeTab)}
+ className="rounded-lg bg-slate-700 px-3 py-1.5 text-xs font-medium text-slate-200 transition-colors hover:bg-slate-600"
+ >
+ Download
+ </button>
+ </div>
+
+ {/* Content */}
+ <pre className="max-h-[500px] overflow-auto p-6 pt-12 text-sm leading-relaxed text-slate-200">
+ <code>{getTabContent(activeTab)}</code>
+ </pre>
+ </div>
+
+ {/* Bottom actions */}
+ <div className="mt-8 flex flex-col items-center gap-4 sm:flex-row sm:justify-center">
+ <button
+ type="button"
+ onClick={handleDownloadAll}
+ className="min-h-[48px] rounded-xl bg-blue-600 px-8 py-3 text-base font-medium text-white transition-colors hover:bg-blue-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
+ >
+ Download All
+ </button>
+ <Link
+ href={`/builder?profile=${bundle.profile.slug}`}
+ className="inline-flex min-h-[48px] items-center justify-center rounded-xl border border-slate-300 px-8 py-3 text-base font-medium text-slate-700 transition-colors hover:bg-slate-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
+ >
+ Go to Builder
+ </Link>
+ <Link
+ href="/analyzer"
+ className="inline-flex min-h-[48px] items-center justify-center rounded-xl border border-slate-300 px-8 py-3 text-base font-medium text-slate-700 transition-colors hover:bg-slate-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
+ >
+ Analyze This
+ </Link>
+ </div>
+ </motion.div>
+ </div>
+ )
+}
diff --git a/src/components/onboarding/review-screen.tsx b/src/components/onboarding/review-screen.tsx
new file mode 100644
index 0000000..5ec366f
--- /dev/null
+++ b/src/components/onboarding/review-screen.tsx
@@ -0,0 +1,103 @@
+'use client'
+
+import { motion } from 'framer-motion'
+import { ONBOARDING_QUESTIONS } from '@/lib/onboarding/questions'
+import { resolveProfile, answerLabel } from '@/lib/onboarding/resolver'
+
+interface ReviewScreenProps {
+ answers: Record<string, string>
+ onEdit: (step: number) => void
+ onGenerate: () => void
+}
+
+export function ReviewScreen({
+ answers,
+ onEdit,
+ onGenerate,
+}: ReviewScreenProps) {
+ const profile = resolveProfile(answers)
+ // Show steps 1-8 (skip the "finish" question)
+ const reviewQuestions = ONBOARDING_QUESTIONS.filter((q) => q.id !== 'finish')
+
+ return (
+ <div className="flex min-h-screen flex-col items-center justify-center px-4 py-12">
+ <motion.div
+ initial={{ opacity: 0, y: 20 }}
+ animate={{ opacity: 1, y: 0 }}
+ transition={{ duration: 0.3 }}
+ className="w-full max-w-2xl"
+ >
+ <h1 className="mb-2 text-center text-3xl font-bold tracking-tight text-slate-900">
+ Review your answers
+ </h1>
+ <p className="mb-8 text-center text-slate-500">
+ Make sure everything looks right before we generate your setup.
+ </p>
+
+ {/* Detected profile badge */}
+ <div className="mb-8 flex flex-col items-center gap-2 rounded-2xl border border-blue-200 bg-blue-50 p-6">
+ <p className="text-sm font-medium uppercase tracking-wider text-blue-500">
+ Your detected profile
+ </p>
+ <h2 className="text-xl font-bold text-blue-900">
+ {profile.displayName}
+ </h2>
+ <p className="max-w-md text-center text-sm text-blue-700">
+ {profile.persona}
+ </p>
+ <div className="mt-2 flex gap-3 text-xs text-blue-600">
+ <span className="rounded-full bg-blue-100 px-3 py-1">
+ {profile.ageGroup.replace('_', ' ')}
+ </span>
+ <span className="rounded-full bg-blue-100 px-3 py-1">
+ {profile.skillLevel}
+ </span>
+ <span className="rounded-full bg-blue-100 px-3 py-1">
+ {profile.skillBundle.length} skills
+ </span>
+ </div>
+ </div>
+
+ {/* Answer list */}
+ <div className="divide-y divide-slate-200 rounded-2xl border border-slate-200 bg-white">
+ {reviewQuestions.map((q) => {
+ const val = answers[q.id]
+ return (
+ <div
+ key={q.id}
+ className="flex items-center justify-between px-6 py-4"
+ >
+ <div className="min-w-0 flex-1">
+ <p className="text-sm font-medium text-slate-500">
+ {q.question}
+ </p>
+ <p className="mt-0.5 text-base font-semibold text-slate-900">
+ {val ? answerLabel(q.id, val) : '(not answered)'}
+ </p>
+ </div>
+ <button
+ type="button"
+ onClick={() => onEdit(q.step)}
+ className="ml-4 shrink-0 rounded-lg border border-slate-200 px-3 py-1.5 text-sm font-medium text-blue-600 transition-colors hover:bg-blue-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
+ >
+ Edit
+ </button>
+ </div>
+ )
+ })}
+ </div>
+
+ {/* Generate button */}
+ <div className="mt-8 flex justify-center">
+ <button
+ type="button"
+ onClick={onGenerate}
+ className="min-h-[56px] rounded-xl bg-blue-600 px-12 py-3 text-lg font-semibold text-white transition-colors hover:bg-blue-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500"
+ >
+ Generate my setup!
+ </button>
+ </div>
+ </motion.div>
+ </div>
+ )
+}
diff --git a/src/components/ui/back-next-bar.tsx b/src/components/ui/back-next-bar.tsx
new file mode 100644
index 0000000..119bace
--- /dev/null
+++ b/src/components/ui/back-next-bar.tsx
@@ -0,0 +1,46 @@
+'use client'
+
+interface BackNextBarProps {
+ onBack?: () => void
+ onNext?: () => void
+ backLabel?: string
+ nextLabel?: string
+ backDisabled?: boolean
+ nextDisabled?: boolean
+ showBack?: boolean
+}
+
+export function BackNextBar({
+ onBack,
+ onNext,
+ backLabel = 'Back',
+ nextLabel = 'Next',
+ backDisabled = false,
+ nextDisabled = false,
+ showBack = true,
+}: BackNextBarProps) {
+ return (
+ <div className="fixed bottom-0 left-0 right-0 z-50 flex justify-between border-t border-slate-200 bg-white/80 p-4 backdrop-blur">
+ {showBack ? (
+ <button
+ type="button"
+ onClick={onBack}
+ disabled={backDisabled}
+ className="min-h-[56px] rounded-xl border border-slate-300 px-8 py-3 text-base font-medium text-slate-700 transition-colors hover:bg-slate-50 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 disabled:cursor-not-allowed disabled:opacity-50"
+ >
+ {backLabel}
+ </button>
+ ) : (
+ <div />
+ )}
+ <button
+ type="button"
+ onClick={onNext}
+ disabled={nextDisabled}
+ className="min-h-[56px] rounded-xl bg-blue-600 px-8 py-3 text-base font-medium text-white transition-colors hover:bg-blue-700 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500 disabled:cursor-not-allowed disabled:opacity-50"
+ >
+ {nextLabel}
+ </button>
+ </div>
+ )
+}
diff --git a/src/components/ui/diff-viewer.tsx b/src/components/ui/diff-viewer.tsx
new file mode 100644
index 0000000..4a87fb3
--- /dev/null
+++ b/src/components/ui/diff-viewer.tsx
@@ -0,0 +1,29 @@
+'use client'
+
+interface DiffViewerProps {
+ before: string
+ after: string
+}
+
+export function DiffViewer({ before, after }: DiffViewerProps) {
+ return (
+ <div className="grid gap-4 md:grid-cols-2">
+ <div className="overflow-hidden rounded-lg border border-red-200">
+ <div className="border-b border-red-200 bg-red-50 px-4 py-2">
+ <span className="text-sm font-medium text-red-700">Before</span>
+ </div>
+ <pre className="overflow-x-auto bg-red-50/30 p-4 text-sm text-slate-700">
+ <code>{before || '(empty)'}</code>
+ </pre>
+ </div>
+ <div className="overflow-hidden rounded-lg border border-green-200">
+ <div className="border-b border-green-200 bg-green-50 px-4 py-2">
+ <span className="text-sm font-medium text-green-700">After</span>
+ </div>
+ <pre className="overflow-x-auto bg-green-50/30 p-4 text-sm text-slate-700">
+ <code>{after || '(empty)'}</code>
+ </pre>
+ </div>
+ </div>
+ )
+}
diff --git a/src/components/ui/grade-badge.tsx b/src/components/ui/grade-badge.tsx
new file mode 100644
index 0000000..b98c998
--- /dev/null
+++ b/src/components/ui/grade-badge.tsx
@@ -0,0 +1,36 @@
+'use client'
+
+interface GradeBadgeProps {
+ grade: string
+ percentage: number
+}
+
+function gradeColor(grade: string): string {
+ const letter = grade.charAt(0).toUpperCase()
+ if (letter === 'A' || letter === 'B') return 'text-green-600'
+ if (letter === 'C') return 'text-yellow-600'
+ return 'text-red-600'
+}
+
+function gradeBgColor(grade: string): string {
+ const letter = grade.charAt(0).toUpperCase()
+ if (letter === 'A' || letter === 'B') return 'bg-green-50 ring-green-200'
+ if (letter === 'C') return 'bg-yellow-50 ring-yellow-200'
+ return 'bg-red-50 ring-red-200'
+}
+
+export function GradeBadge({ grade, percentage }: GradeBadgeProps) {
+ return (
+ <div
+ className={`inline-flex flex-col items-center justify-center rounded-2xl p-6 ring-1 ${gradeBgColor(grade)}`}
+ aria-label={`Grade: ${grade}, Score: ${percentage}%`}
+ >
+ <span className={`text-5xl font-bold ${gradeColor(grade)}`}>
+ {grade}
+ </span>
+ <span className="mt-1 text-lg font-medium text-slate-600">
+ {percentage}%
+ </span>
+ </div>
+ )
+}
diff --git a/src/components/ui/pill-button.tsx b/src/components/ui/pill-button.tsx
new file mode 100644
index 0000000..4c69b52
--- /dev/null
+++ b/src/components/ui/pill-button.tsx
@@ -0,0 +1,40 @@
+'use client'
+
+import { motion } from 'framer-motion'
+
+interface PillButtonProps {
+ label: string
+ selected?: boolean
+ onClick?: () => void
+ disabled?: boolean
+}
+
+export function PillButton({
+ label,
+ selected = false,
+ onClick,
+ disabled = false,
+}: PillButtonProps) {
+ return (
+ <motion.button
+ type="button"
+ onClick={onClick}
+ disabled={disabled}
+ whileTap={{ scale: 0.97 }}
+ transition={{ type: 'spring', stiffness: 400, damping: 20 }}
+ className={`
+ min-h-[56px] rounded-full px-8 py-3 text-base font-medium
+ transition-colors focus-visible:outline-2 focus-visible:outline-offset-2
+ focus-visible:outline-blue-500 disabled:cursor-not-allowed disabled:opacity-50
+ motion-reduce:transform-none
+ ${
+ selected
+ ? 'bg-blue-600 text-white ring-2 ring-blue-400'
+ : 'bg-slate-800 text-white hover:bg-slate-700'
+ }
+ `}
+ >
+ {label}
+ </motion.button>
+ )
+}
diff --git a/src/components/ui/progress-indicator.tsx b/src/components/ui/progress-indicator.tsx
new file mode 100644
index 0000000..a2efddf
--- /dev/null
+++ b/src/components/ui/progress-indicator.tsx
@@ -0,0 +1,29 @@
+'use client'
+
+interface ProgressIndicatorProps {
+ current: number
+ total: number
+}
+
+export function ProgressIndicator({ current, total }: ProgressIndicatorProps) {
+ const percentage = total > 0 ? Math.round((current / total) * 100) : 0
+
+ return (
+ <div className="w-full">
+ <p className="mb-2 text-sm font-medium text-slate-600">
+ Step {current} of {total}
+ </p>
+ <div className="h-1 w-full overflow-hidden rounded-full bg-slate-200">
+ <div
+ role="progressbar"
+ aria-valuenow={current}
+ aria-valuemin={0}
+ aria-valuemax={total}
+ aria-label={`Step ${current} of ${total}`}
+ className="h-full rounded-full bg-blue-600 transition-all duration-300 motion-reduce:transition-none"
+ style={{ width: `${percentage}%` }}
+ />
+ </div>
+ </div>
+ )
+}
diff --git a/src/components/ui/radar-chart.tsx b/src/components/ui/radar-chart.tsx
new file mode 100644
index 0000000..83b6dd3
--- /dev/null
+++ b/src/components/ui/radar-chart.tsx
@@ -0,0 +1,137 @@
+'use client'
+
+interface CriterionData {
+ name: string
+ score: number
+ maxScore: number
+}
+
+interface RadarChartProps {
+ criteria: CriterionData[]
+}
+
+export function RadarChart({ criteria }: RadarChartProps) {
+ const size = 300
+ const center = size / 2
+ const radius = size / 2 - 40
+ const n = criteria.length
+
+ if (n === 0) return null
+
+ // Calculate angle for each axis (starting from top, going clockwise)
+ const angleStep = (2 * Math.PI) / n
+ const startAngle = -Math.PI / 2
+
+ // Get point on the circle for a given axis index and normalized value (0-1)
+ function getPoint(index: number, value: number): { x: number; y: number } {
+ const angle = startAngle + index * angleStep
+ return {
+ x: center + radius * value * Math.cos(angle),
+ y: center + radius * value * Math.sin(angle),
+ }
+ }
+
+ // Build the polygon path for the data
+ const dataPoints = criteria.map((c, i) => {
+ const normalized = c.maxScore > 0 ? c.score / c.maxScore : 0
+ return getPoint(i, normalized)
+ })
+ const dataPath =
+ dataPoints.map((p, i) => `${i === 0 ? 'M' : 'L'} ${p.x} ${p.y}`).join(' ') + ' Z'
+
+ // Grid rings at 25%, 50%, 75%, 100%
+ const rings = [0.25, 0.5, 0.75, 1.0]
+
+ // Build label description for screen readers
+ const ariaDescription = criteria
+ .map((c) => `${c.name}: ${c.score}/${c.maxScore}`)
+ .join(', ')
+
+ return (
+ <div className="flex items-center justify-center" role="img" aria-label={`Radar chart showing scores: ${ariaDescription}`}>
+ <svg
+ viewBox={`0 0 ${size} ${size}`}
+ className="h-auto w-full max-w-[300px]"
+ >
+ {/* Grid rings */}
+ {rings.map((r) => {
+ const ringPoints = Array.from({ length: n }, (_, i) => getPoint(i, r))
+ const ringPath =
+ ringPoints
+ .map((p, i) => `${i === 0 ? 'M' : 'L'} ${p.x} ${p.y}`)
+ .join(' ') + ' Z'
+ return (
+ <path
+ key={r}
+ d={ringPath}
+ fill="none"
+ stroke="#e2e8f0"
+ strokeWidth={1}
+ />
+ )
+ })}
+
+ {/* Axis lines */}
+ {criteria.map((_, i) => {
+ const outerPoint = getPoint(i, 1)
+ return (
+ <line
+ key={`axis-${i}`}
+ x1={center}
+ y1={center}
+ x2={outerPoint.x}
+ y2={outerPoint.y}
+ stroke="#e2e8f0"
+ strokeWidth={1}
+ />
+ )
+ })}
+
+ {/* Data polygon */}
+ <path
+ d={dataPath}
+ fill="rgba(37, 99, 235, 0.2)"
+ stroke="#2563eb"
+ strokeWidth={2}
+ />
+
+ {/* Data points */}
+ {dataPoints.map((p, i) => (
+ <circle
+ key={`point-${i}`}
+ cx={p.x}
+ cy={p.y}
+ r={3}
+ fill="#2563eb"
+ />
+ ))}
+
+ {/* Labels */}
+ {criteria.map((c, i) => {
+ const labelPoint = getPoint(i, 1.25)
+ const angle = startAngle + i * angleStep
+ // Determine text-anchor based on position
+ let textAnchor: 'start' | 'middle' | 'end' = 'middle'
+ if (Math.cos(angle) > 0.1) textAnchor = 'start'
+ else if (Math.cos(angle) < -0.1) textAnchor = 'end'
+
+ // Truncate long labels
+ const label = c.name.length > 14 ? c.name.slice(0, 12) + '...' : c.name
+
+ return (
+ <text
+ key={`label-${i}`}
+ x={labelPoint.x}
+ y={labelPoint.y}
+ textAnchor={textAnchor}
+ dominantBaseline="central"
+ className="fill-slate-600 text-[9px]"
+ >
+ {label}
+ </text>
+ )
+ })}
+ </svg>
+ </div>
+ )
+}
diff --git a/src/components/user-badge.tsx b/src/components/user-badge.tsx
new file mode 100644
index 0000000..c33563c
--- /dev/null
+++ b/src/components/user-badge.tsx
@@ -0,0 +1,47 @@
+'use client'
+
+import { useEffect, useState } from 'react'
+import { useRouter } from 'next/navigation'
+
+interface UserInfo {
+ authenticated: boolean
+ username?: string
+}
+
+export function UserBadge() {
+ const [user, setUser] = useState<UserInfo | null>(null)
+ const router = useRouter()
+
+ useEffect(() => {
+ fetch('/api/auth/me')
+ .then(r => r.json())
+ .then(data => setUser(data))
+ .catch(() => setUser(null))
+ }, [])
+
+ if (!user?.authenticated) return null
+
+ async function handleLogout() {
+ await fetch('/api/auth', { method: 'DELETE' })
+ router.push('/login')
+ }
+
+ const initial = user.username?.charAt(0).toUpperCase() ?? '?'
+
+ return (
+ <div className="flex items-center gap-2">
+ <div className="flex h-8 w-8 items-center justify-center rounded-full bg-blue-600 text-xs font-bold text-white">
+ {initial}
+ </div>
+ <span className="hidden text-sm font-medium text-slate-700 sm:inline">
+ {user.username}
+ </span>
+ <button
+ onClick={handleLogout}
+ className="ml-1 rounded px-2 py-1 text-xs text-slate-500 transition-colors hover:bg-slate-100 hover:text-slate-700"
+ >
+ Logout
+ </button>
+ </div>
+ )
+}
diff --git a/src/lib/analyzer/applier.ts b/src/lib/analyzer/applier.ts
new file mode 100644
index 0000000..f4b4ef4
--- /dev/null
+++ b/src/lib/analyzer/applier.ts
@@ -0,0 +1,39 @@
+import type { Suggestion } from './types'
+
+/**
+ * Apply selected suggestions to CLAUDE.md content.
+ *
+ * - If a suggestion's `before` is "missing", the `after` block is appended
+ * at an appropriate location (end of file, before any trailing newlines).
+ * - If `before` matches text in the content, it is replaced with `after`.
+ * - Suggestions are applied in order; later suggestions see the result of earlier ones.
+ */
+export function applySuggestions(
+ content: string,
+ suggestions: Suggestion[]
+): string {
+ let result = content
+
+ for (const suggestion of suggestions) {
+ if (
+ suggestion.before === 'missing' ||
+ suggestion.before.startsWith('Potential ') ||
+ suggestion.before.startsWith('excessive ')
+ ) {
+ // Append the new block at the end of the file
+ const trimmed = result.trimEnd()
+ result = trimmed + '\n\n' + suggestion.after + '\n'
+ } else {
+ // Try to find and replace the `before` text
+ if (result.includes(suggestion.before)) {
+ result = result.replace(suggestion.before, suggestion.after)
+ } else {
+ // If before text not found verbatim, just append
+ const trimmed = result.trimEnd()
+ result = trimmed + '\n\n' + suggestion.after + '\n'
+ }
+ }
+ }
+
+ return result
+}
diff --git a/src/lib/analyzer/index.ts b/src/lib/analyzer/index.ts
new file mode 100644
index 0000000..b3ad6a8
--- /dev/null
+++ b/src/lib/analyzer/index.ts
@@ -0,0 +1,6 @@
+export * from './types'
+export * from './rules'
+export * from './scorer'
+export * from './suggester'
+export * from './applier'
+export * from './matcher'
diff --git a/src/lib/analyzer/matcher.ts b/src/lib/analyzer/matcher.ts
new file mode 100644
index 0000000..5d33e84
--- /dev/null
+++ b/src/lib/analyzer/matcher.ts
@@ -0,0 +1,60 @@
+import { PROFILES } from '../profiles/data'
+import type { Profile } from '../profiles/types'
+
+/**
+ * Extract meaningful keywords from a text block for comparison.
+ * Strips markdown syntax and returns lowercased unique words.
+ */
+function extractKeywords(text: string): Set<string> {
+ const cleaned = text
+ .toLowerCase()
+ .replace(/[#*`\[\](){}|>_~]/g, ' ')
+ .replace(/https?:\/\/\S+/g, '')
+ .replace(/[^a-z0-9\s-]/g, ' ')
+
+ const words = cleaned.split(/\s+/).filter(w => w.length > 2)
+ return new Set(words)
+}
+
+/**
+ * Calculate Jaccard similarity between two sets.
+ */
+function jaccardSimilarity(a: Set<string>, b: Set<string>): number {
+ let intersection = 0
+ for (const word of a) {
+ if (b.has(word)) intersection++
+ }
+ const union = new Set([...a, ...b]).size
+ return union > 0 ? intersection / union : 0
+}
+
+/**
+ * Match CLAUDE.md content against all 15 profiles and return
+ * the slug of the closest matching profile.
+ */
+export function detectProfile(content: string): string | undefined {
+ const contentKeywords = extractKeywords(content)
+
+ if (contentKeywords.size === 0) return undefined
+
+ let bestSlug: string | undefined
+ let bestScore = 0
+
+ for (const profile of PROFILES) {
+ const templateKeywords = extractKeywords(profile.template.content)
+
+ // Also include persona keywords for better matching
+ const personaKeywords = extractKeywords(profile.persona)
+ const combined = new Set([...templateKeywords, ...personaKeywords])
+
+ const score = jaccardSimilarity(contentKeywords, combined)
+
+ if (score > bestScore) {
+ bestScore = score
+ bestSlug = profile.slug
+ }
+ }
+
+ // Only return a match if similarity is above a minimum threshold
+ return bestScore > 0.05 ? bestSlug : undefined
+}
diff --git a/src/lib/analyzer/rules.ts b/src/lib/analyzer/rules.ts
new file mode 100644
index 0000000..944288c
--- /dev/null
+++ b/src/lib/analyzer/rules.ts
@@ -0,0 +1,517 @@
+import type { CriterionResult, Finding } from './types'
+
+// ---------------------------------------------------------------------------
+// Helpers
+// ---------------------------------------------------------------------------
+
+function makeCriterion(
+ id: string,
+ name: string,
+ weight: number,
+ maxScore: number,
+ score: number,
+ findings: Finding[] = []
+): CriterionResult {
+ return { id, name, weight, maxScore, score, findings }
+}
+
+// ---------------------------------------------------------------------------
+// 1. Line Count (15%)
+// ---------------------------------------------------------------------------
+export function scoreLineCount(content: string): CriterionResult {
+ const lines = content.split('\n').length
+ let score: number
+ const findings: Finding[] = []
+
+ if (lines <= 80) {
+ score = 15
+ } else if (lines <= 150) {
+ score = 12
+ findings.push({
+ severity: 'info',
+ message: `File is ${lines} lines. Under 80 is ideal.`,
+ })
+ } else if (lines <= 200) {
+ score = 8
+ findings.push({
+ severity: 'warning',
+ message: `File is ${lines} lines. Consider splitting into .claude/rules/ files.`,
+ })
+ } else if (lines <= 300) {
+ score = 4
+ findings.push({
+ severity: 'warning',
+ message: `File is ${lines} lines — well above the recommended 200-line limit.`,
+ })
+ } else {
+ score = 0
+ findings.push({
+ severity: 'critical',
+ message: `File is ${lines} lines — far too long. Split into modular .claude/rules/ files.`,
+ })
+ }
+
+ return makeCriterion('line-count', 'Line Count', 15, 15, score, findings)
+}
+
+// ---------------------------------------------------------------------------
+// 2. Structure (10%)
+// ---------------------------------------------------------------------------
+export function scoreStructure(content: string): CriterionResult {
+ const headerMatches = content.match(/^## .+/gm)
+ const headerCount = headerMatches ? headerMatches.length : 0
+ const findings: Finding[] = []
+
+ let score: number
+ if (headerCount >= 4) {
+ score = 10
+ } else if (headerCount === 3) {
+ score = 8
+ findings.push({
+ severity: 'info',
+ message: `Found ${headerCount} section headers. 4+ is recommended.`,
+ })
+ } else if (headerCount === 2) {
+ score = 5
+ findings.push({
+ severity: 'warning',
+ message: `Only ${headerCount} section headers. Add more structure with ## headings.`,
+ })
+ } else if (headerCount === 1) {
+ score = 3
+ findings.push({
+ severity: 'warning',
+ message: 'Only 1 section header found. Structure your file with multiple ## sections.',
+ })
+ } else {
+ score = 0
+ findings.push({
+ severity: 'critical',
+ message: 'No ## section headers found. Add sections like ## Commands, ## Safety, ## Workflow.',
+ })
+ }
+
+ return makeCriterion('structure', 'Structure', 10, 10, score, findings)
+}
+
+// ---------------------------------------------------------------------------
+// 3. Specificity (12%)
+// ---------------------------------------------------------------------------
+export function scoreSpecificity(content: string): CriterionResult {
+ const commandPatterns = [
+ /\b(npm|yarn|pnpm)\s+(run\s+)?\w+/g,
+ /\b(pip|pip3)\s+install\b/g,
+ /\bcargo\s+(build|run|test|check)\b/g,
+ /\bmake\s+\w+/g,
+ /\b(pytest|jest|vitest)\b/g,
+ /\bnpx\s+\w+/g,
+ /\bgo\s+(build|test|run)\b/g,
+ /\bdocker\s+(build|run|compose)\b/g,
+ /\bbun\s+(run|test|install)\b/g,
+ ]
+
+ const allMatches = new Set<string>()
+ for (const pattern of commandPatterns) {
+ const matches = content.match(pattern)
+ if (matches) {
+ for (const m of matches) allMatches.add(m)
+ }
+ }
+
+ const count = allMatches.size
+ const findings: Finding[] = []
+ let score: number
+
+ if (count >= 4) {
+ score = 12
+ } else if (count >= 2) {
+ score = 8
+ findings.push({
+ severity: 'info',
+ message: `Found ${count} specific commands. 4+ is ideal.`,
+ })
+ } else if (count === 1) {
+ score = 4
+ findings.push({
+ severity: 'warning',
+ message: 'Only 1 specific command found. Add more concrete tool commands.',
+ })
+ } else {
+ score = 0
+ findings.push({
+ severity: 'warning',
+ message: 'No specific tool commands found (npm, yarn, pip, cargo, pytest, jest, etc.).',
+ })
+ }
+
+ return makeCriterion('specificity', 'Specificity', 12, 12, score, findings)
+}
+
+// ---------------------------------------------------------------------------
+// 4. Safety Rules (12%)
+// ---------------------------------------------------------------------------
+export function scoreSafetyRules(content: string): CriterionResult {
+ const safetyKeywords = [
+ 'safety',
+ "don't delete",
+ "don't modify",
+ 'do not delete',
+ 'do not modify',
+ 'never',
+ 'protected',
+ 'credential',
+ 'secret',
+ 'destructive',
+ 'permission',
+ 'force push',
+ 'reset hard',
+ 'dangerous',
+ 'careful',
+ 'backup',
+ ]
+
+ const lower = content.toLowerCase()
+ const found = safetyKeywords.filter(kw => lower.includes(kw))
+ const findings: Finding[] = []
+ let score: number
+
+ if (found.length >= 4) {
+ score = 12
+ } else if (found.length >= 2) {
+ score = 8
+ findings.push({
+ severity: 'info',
+ message: `Found ${found.length} safety keywords. 4+ is ideal for thorough safety coverage.`,
+ })
+ } else if (found.length === 1) {
+ score = 4
+ findings.push({
+ severity: 'warning',
+ message: `Only 1 safety keyword found ("${found[0]}"). Add explicit safety rules.`,
+ })
+ } else {
+ score = 0
+ findings.push({
+ severity: 'critical',
+ message: 'No safety rules detected. Add a ## Safety section with explicit constraints.',
+ })
+ }
+
+ return makeCriterion('safety-rules', 'Safety Rules', 12, 12, score, findings)
+}
+
+// ---------------------------------------------------------------------------
+// 5. Build Commands (8%)
+// ---------------------------------------------------------------------------
+export function scoreBuildCommands(content: string): CriterionResult {
+ const commandLabels = [
+ /\binstall\s*:/im,
+ /\bdev\s*:/im,
+ /\btest\s*:/im,
+ /\blint\s*:/im,
+ /\bbuild\s*:/im,
+ /\bformat\s*:/im,
+ /\bstart\s*:/im,
+ /\bdeploy\s*:/im,
+ ]
+
+ const found = commandLabels.filter(rx => rx.test(content))
+ const findings: Finding[] = []
+ let score: number
+
+ if (found.length >= 4) {
+ score = 8
+ } else if (found.length >= 2) {
+ score = 5
+ findings.push({
+ severity: 'info',
+ message: `Found ${found.length} labeled commands. 4+ recommended (install, dev, test, lint, build, format).`,
+ })
+ } else if (found.length === 1) {
+ score = 2
+ findings.push({
+ severity: 'warning',
+ message: 'Only 1 labeled command found. Add install:, dev:, test:, lint:, build: labels.',
+ })
+ } else {
+ score = 0
+ findings.push({
+ severity: 'warning',
+ message: 'No labeled build commands found. Add a ## Commands section with install:, dev:, test:, etc.',
+ })
+ }
+
+ return makeCriterion('build-commands', 'Build Commands', 8, 8, score, findings)
+}
+
+// ---------------------------------------------------------------------------
+// 6. Code Style (5%)
+// ---------------------------------------------------------------------------
+export function scoreCodeStyle(content: string): CriterionResult {
+ const styleKeywords = [
+ 'style',
+ 'indent',
+ 'naming',
+ 'linter',
+ 'eslint',
+ 'prettier',
+ 'biome',
+ 'formatting',
+ 'code style',
+ 'code-style',
+ '.claude/rules/',
+ ]
+
+ const lower = content.toLowerCase()
+ const found = styleKeywords.filter(kw => lower.includes(kw))
+ const findings: Finding[] = []
+ let score: number
+
+ if (found.length > 0) {
+ score = 5
+ } else {
+ score = 0
+ findings.push({
+ severity: 'info',
+ message: 'No code style references found. Consider adding a ## Code Style section or referencing .claude/rules/code-style.md.',
+ })
+ }
+
+ return makeCriterion('code-style', 'Code Style', 5, 5, score, findings)
+}
+
+// ---------------------------------------------------------------------------
+// 7. Modular Imports (10%)
+// ---------------------------------------------------------------------------
+export function scoreModularImports(content: string): CriterionResult {
+ const hasDocsImport = /@docs\//.test(content) || /@~\//.test(content)
+ const hasRulesRef = /\.claude\/rules\//.test(content)
+ const findings: Finding[] = []
+ let score: number
+
+ if (hasDocsImport && hasRulesRef) {
+ score = 10
+ } else if (hasDocsImport || hasRulesRef) {
+ score = 5
+ const missing = hasDocsImport
+ ? '.claude/rules/ references'
+ : '@docs/ or @~/ imports'
+ findings.push({
+ severity: 'info',
+ message: `Found one type of modular import but missing ${missing}.`,
+ })
+ } else {
+ score = 0
+ findings.push({
+ severity: 'warning',
+ message: 'No modular imports found. Reference @docs/ architecture files and .claude/rules/ for split configuration.',
+ })
+ }
+
+ return makeCriterion('modular-imports', 'Modular Imports', 10, 10, score, findings)
+}
+
+// ---------------------------------------------------------------------------
+// 8. No Secrets (10%)
+// ---------------------------------------------------------------------------
+export function scoreNoSecrets(content: string): CriterionResult {
+ const secretPatterns = [
+ { rx: /(?:api_key|api[-_]?secret|token|password|passwd)\s*[=:]\s*\S+/gi, label: 'key/password assignment' },
+ { rx: /\bsk-[a-zA-Z0-9]{20,}/g, label: 'OpenAI-style secret key (sk-...)' },
+ { rx: /\bghp_[a-zA-Z0-9]{36,}/g, label: 'GitHub personal access token (ghp_...)' },
+ { rx: /\bshpat_[a-zA-Z0-9]{20,}/g, label: 'Shopify access token (shpat_...)' },
+ { rx: /Bearer\s+[a-zA-Z0-9._\-]{20,}/g, label: 'Bearer token' },
+ { rx: /Basic\s+[A-Za-z0-9+/=]{20,}/g, label: 'Basic auth token' },
+ { rx: /\bAKIA[A-Z0-9]{16}\b/g, label: 'AWS access key (AKIA...)' },
+ ]
+
+ const findings: Finding[] = []
+ const lines = content.split('\n')
+
+ for (const { rx, label } of secretPatterns) {
+ for (let i = 0; i < lines.length; i++) {
+ const matches = lines[i].match(rx)
+ if (matches) {
+ for (const match of matches) {
+ findings.push({
+ severity: 'critical',
+ message: `Potential ${label} found: "${match.substring(0, 30)}..."`,
+ line: i + 1,
+ })
+ }
+ }
+ }
+ }
+
+ const score = findings.length > 0 ? 0 : 10
+
+ return makeCriterion('no-secrets', 'No Secrets', 10, 10, score, findings)
+}
+
+// ---------------------------------------------------------------------------
+// 9. No Bloat (8%)
+// ---------------------------------------------------------------------------
+export function scoreNoBloat(content: string): CriterionResult {
+ const findings: Finding[] = []
+ const lines = content.split('\n')
+ let bloatScore = 0
+
+ // Check for excessive consecutive empty lines (3+ in a row)
+ let consecutiveEmpty = 0
+ for (let i = 0; i < lines.length; i++) {
+ if (lines[i].trim() === '') {
+ consecutiveEmpty++
+ if (consecutiveEmpty >= 3) {
+ bloatScore++
+ if (consecutiveEmpty === 3) {
+ findings.push({
+ severity: 'info',
+ message: `Excessive empty lines around line ${i + 1}.`,
+ line: i + 1,
+ })
+ }
+ }
+ } else {
+ consecutiveEmpty = 0
+ }
+ }
+
+ // Check for duplicate ## headers
+ const headers = lines
+ .filter(l => /^## /.test(l))
+ .map(l => l.trim().toLowerCase())
+ const seen = new Set<string>()
+ for (const h of headers) {
+ if (seen.has(h)) {
+ bloatScore += 3
+ findings.push({
+ severity: 'warning',
+ message: `Duplicate section header: "${h}"`,
+ })
+ }
+ seen.add(h)
+ }
+
+ // Check for very long lines (>300 characters, ignoring URLs)
+ for (let i = 0; i < lines.length; i++) {
+ if (lines[i].length > 300 && !lines[i].match(/^https?:\/\//)) {
+ bloatScore++
+ if (bloatScore <= 5) {
+ findings.push({
+ severity: 'info',
+ message: `Line ${i + 1} is ${lines[i].length} characters long.`,
+ line: i + 1,
+ })
+ }
+ }
+ }
+
+ let score: number
+ if (bloatScore === 0) {
+ score = 8
+ } else if (bloatScore <= 3) {
+ score = 4
+ } else {
+ score = 0
+ }
+
+ return makeCriterion('no-bloat', 'No Bloat', 8, 8, score, findings)
+}
+
+// ---------------------------------------------------------------------------
+// 10. Skills / Subagents (3%)
+// ---------------------------------------------------------------------------
+export function scoreSkillsSubagents(content: string): CriterionResult {
+ const keywords = ['skill', 'subagent', 'sub-agent', '.claude/agents']
+ const lower = content.toLowerCase()
+ const found = keywords.some(kw => lower.includes(kw))
+ const findings: Finding[] = []
+
+ if (!found) {
+ findings.push({
+ severity: 'info',
+ message: 'No skill or subagent references found. Consider defining .claude/agents/ if your workflow uses delegation.',
+ })
+ }
+
+ return makeCriterion(
+ 'skills-subagents',
+ 'Skills & Subagents',
+ 3,
+ 3,
+ found ? 3 : 0,
+ findings
+ )
+}
+
+// ---------------------------------------------------------------------------
+// 11. Hook Enforcement (3%)
+// ---------------------------------------------------------------------------
+export function scoreHookEnforcement(content: string): CriterionResult {
+ const keywords = ['hook', '.claude/settings', 'PreToolUse', 'PostToolUse', 'SessionStart']
+ const lower = content.toLowerCase()
+ const found = keywords.some(kw => lower.includes(kw) || content.includes(kw))
+ const findings: Finding[] = []
+
+ if (!found) {
+ findings.push({
+ severity: 'info',
+ message: 'No hook or .claude/settings references found. Hooks can automate linting and testing on file changes.',
+ })
+ }
+
+ return makeCriterion(
+ 'hook-enforcement',
+ 'Hook Enforcement',
+ 3,
+ 3,
+ found ? 3 : 0,
+ findings
+ )
+}
+
+// ---------------------------------------------------------------------------
+// 12. MCP Config (4%)
+// ---------------------------------------------------------------------------
+export function scoreMcpConfig(content: string): CriterionResult {
+ const keywords = ['mcp', '.mcp.json', 'mcp server', 'mcp-server']
+ const lower = content.toLowerCase()
+ const found = keywords.some(kw => lower.includes(kw))
+ const findings: Finding[] = []
+
+ if (!found) {
+ findings.push({
+ severity: 'info',
+ message: 'No MCP configuration references found. MCP servers extend Claude with external tools.',
+ })
+ }
+
+ return makeCriterion(
+ 'mcp-config',
+ 'MCP Config',
+ 4,
+ 4,
+ found ? 4 : 0,
+ findings
+ )
+}
+
+// ---------------------------------------------------------------------------
+// Export all rules as an array factory
+// ---------------------------------------------------------------------------
+export type RuleFunction = (content: string) => CriterionResult
+
+export const ALL_RULES: RuleFunction[] = [
+ scoreLineCount,
+ scoreStructure,
+ scoreSpecificity,
+ scoreSafetyRules,
+ scoreBuildCommands,
+ scoreCodeStyle,
+ scoreModularImports,
+ scoreNoSecrets,
+ scoreNoBloat,
+ scoreSkillsSubagents,
+ scoreHookEnforcement,
+ scoreMcpConfig,
+]
diff --git a/src/lib/analyzer/scorer.ts b/src/lib/analyzer/scorer.ts
new file mode 100644
index 0000000..6c18dd0
--- /dev/null
+++ b/src/lib/analyzer/scorer.ts
@@ -0,0 +1,68 @@
+import type { AnalysisResult, CriterionResult } from './types'
+import { ALL_RULES } from './rules'
+
+/**
+ * Calculate a letter grade from a percentage (0-100).
+ */
+function letterGrade(pct: number): string {
+ if (pct >= 97) return 'A+'
+ if (pct >= 93) return 'A'
+ if (pct >= 90) return 'A-'
+ if (pct >= 87) return 'B+'
+ if (pct >= 83) return 'B'
+ if (pct >= 80) return 'B-'
+ if (pct >= 77) return 'C+'
+ if (pct >= 73) return 'C'
+ if (pct >= 70) return 'C-'
+ if (pct >= 67) return 'D+'
+ if (pct >= 63) return 'D'
+ if (pct >= 60) return 'D-'
+ return 'F'
+}
+
+/**
+ * Analyze a CLAUDE.md content string and return a full AnalysisResult.
+ */
+export function analyzeClaudeMd(content: string): AnalysisResult {
+ // Run all 12 rules
+ const criteria: CriterionResult[] = ALL_RULES.map(rule => rule(content))
+
+ // Calculate totals
+ const overallScore = criteria.reduce((sum, c) => sum + c.score, 0)
+ const maxScore = criteria.reduce((sum, c) => sum + c.maxScore, 0)
+ const percentage = maxScore > 0 ? Math.round((overallScore / maxScore) * 100) : 0
+
+ // Generate pros: criteria that earned full score
+ const pros: string[] = criteria
+ .filter(c => c.score === c.maxScore)
+ .map(c => `${c.name}: full marks (${c.score}/${c.maxScore})`)
+
+ // Generate cons: criteria with zero score or critical findings
+ const cons: string[] = []
+ for (const c of criteria) {
+ if (c.score === 0) {
+ const criticalFindings = c.findings.filter(f => f.severity === 'critical')
+ if (criticalFindings.length > 0) {
+ cons.push(`${c.name}: ${criticalFindings[0].message}`)
+ } else if (c.findings.length > 0) {
+ cons.push(`${c.name}: ${c.findings[0].message}`)
+ } else {
+ cons.push(`${c.name}: scored 0/${c.maxScore}`)
+ }
+ }
+ }
+
+ const lineCount = content.split('\n').length
+
+ return {
+ overallScore,
+ maxScore,
+ percentage,
+ letterGrade: letterGrade(percentage),
+ criteria,
+ pros,
+ cons,
+ suggestions: [], // Suggestions are generated separately by the suggester
+ lineCount,
+ }
+}
diff --git a/src/lib/analyzer/suggester.ts b/src/lib/analyzer/suggester.ts
new file mode 100644
index 0000000..f78e079
--- /dev/null
+++ b/src/lib/analyzer/suggester.ts
@@ -0,0 +1,215 @@
+import type { AnalysisResult, Suggestion } from './types'
+
+/**
+ * Determine priority based on criterion weight.
+ */
+function priorityFromWeight(weight: number): 'high' | 'medium' | 'low' {
+ if (weight >= 10) return 'high'
+ if (weight >= 5) return 'medium'
+ return 'low'
+}
+
+let suggestionCounter = 0
+
+function nextId(criterion: string): string {
+ suggestionCounter++
+ return `suggestion-${criterion}-${suggestionCounter}`
+}
+
+/**
+ * Generate improvement suggestions based on the analysis result.
+ * Only generates suggestions for criteria that scored below their maximum.
+ */
+export function generateSuggestions(
+ content: string,
+ analysis: AnalysisResult
+): Suggestion[] {
+ // Reset counter for deterministic ids within a single call
+ suggestionCounter = 0
+
+ const suggestions: Suggestion[] = []
+
+ for (const criterion of analysis.criteria) {
+ // Skip criteria that already have full marks
+ if (criterion.score === criterion.maxScore) continue
+
+ const priority = priorityFromWeight(criterion.weight)
+
+ switch (criterion.id) {
+ case 'line-count': {
+ if (analysis.lineCount > 200) {
+ suggestions.push({
+ id: nextId(criterion.id),
+ criterion: criterion.id,
+ description:
+ 'File exceeds 200 lines. Split detailed rules into .claude/rules/ files and reference them from CLAUDE.md.',
+ before: 'missing',
+ after:
+ '## Additional Rules\nSee .claude/rules/ for detailed configuration:\n- .claude/rules/code-style.md\n- .claude/rules/safety.md\n- .claude/rules/workflow.md\n',
+ priority,
+ })
+ }
+ break
+ }
+
+ case 'structure': {
+ suggestions.push({
+ id: nextId(criterion.id),
+ criterion: criterion.id,
+ description:
+ 'Add more ## section headers to organize your CLAUDE.md. Recommended sections: Workflow, Safety, Commands, Code Style.',
+ before: 'missing',
+ after:
+ '## Workflow\n- Make small, reviewable changes\n- Run tests after behavior changes\n\n## Safety\n- Never force push or reset hard without confirmation\n- Never modify .env files directly\n',
+ priority,
+ })
+ break
+ }
+
+ case 'specificity': {
+ suggestions.push({
+ id: nextId(criterion.id),
+ criterion: criterion.id,
+ description:
+ 'Add specific tool commands so Claude knows exactly how to build, test, and lint your project.',
+ before: 'missing',
+ after:
+ '## Commands\n- Install: npm install\n- Dev: npm run dev\n- Test: npm test\n- Lint: npm run lint\n- Build: npm run build\n',
+ priority,
+ })
+ break
+ }
+
+ case 'safety-rules': {
+ suggestions.push({
+ id: nextId(criterion.id),
+ criterion: criterion.id,
+ description:
+ 'Add a dedicated ## Safety section with explicit constraints on destructive operations.',
+ before: 'missing',
+ after:
+ '## Safety\n- Don\'t delete or modify .env, credentials, or secret files\n- Never force push or reset hard without asking\n- Don\'t run destructive database commands without confirmation\n- Always backup before bulk operations\n',
+ priority,
+ })
+ break
+ }
+
+ case 'build-commands': {
+ suggestions.push({
+ id: nextId(criterion.id),
+ criterion: criterion.id,
+ description:
+ 'Add labeled build commands (install:, dev:, test:, lint:, build:) so Claude knows the project toolchain.',
+ before: 'missing',
+ after:
+ '## Commands\n- Install: npm install\n- Dev: npm run dev\n- Test: npm test\n- Lint: npm run lint\n- Build: npm run build\n- Format: npm run format\n',
+ priority,
+ })
+ break
+ }
+
+ case 'code-style': {
+ suggestions.push({
+ id: nextId(criterion.id),
+ criterion: criterion.id,
+ description:
+ 'Add code style guidance or reference a .claude/rules/code-style.md file.',
+ before: 'missing',
+ after:
+ '## Code Style\n- Follow the project\'s ESLint and Prettier configuration\n- Use consistent naming conventions (camelCase for variables, PascalCase for components)\n- See .claude/rules/code-style.md for detailed rules\n',
+ priority,
+ })
+ break
+ }
+
+ case 'modular-imports': {
+ suggestions.push({
+ id: nextId(criterion.id),
+ criterion: criterion.id,
+ description:
+ 'Add references to modular documentation (@docs/) and .claude/rules/ files to keep CLAUDE.md lean.',
+ before: 'missing',
+ after:
+ '## Architecture\n@docs/ARCHITECTURE.md\n\n## Rules\nSee .claude/rules/ for detailed configuration.\n',
+ priority,
+ })
+ break
+ }
+
+ case 'no-secrets': {
+ const secretFindings = criterion.findings.filter(
+ f => f.severity === 'critical'
+ )
+ for (const finding of secretFindings) {
+ suggestions.push({
+ id: nextId(criterion.id),
+ criterion: criterion.id,
+ description: `Remove secret from line ${finding.line ?? '?'}. Use environment variables instead.`,
+ before: finding.message,
+ after:
+ '# Use environment variables for secrets\n# API_KEY=$API_KEY (set in .env, never commit)\n',
+ priority: 'high',
+ })
+ }
+ break
+ }
+
+ case 'no-bloat': {
+ suggestions.push({
+ id: nextId(criterion.id),
+ criterion: criterion.id,
+ description:
+ 'Reduce bloat: remove excessive empty lines, duplicate sections, and overly long lines.',
+ before: 'excessive whitespace or duplicates',
+ after: '(clean up empty lines and remove duplicate ## headers)',
+ priority,
+ })
+ break
+ }
+
+ case 'skills-subagents': {
+ suggestions.push({
+ id: nextId(criterion.id),
+ criterion: criterion.id,
+ description:
+ 'Consider adding skill or subagent references if your workflow benefits from task delegation.',
+ before: 'missing',
+ after:
+ '## Agents\nSee .claude/agents/ for subagent definitions.\n',
+ priority,
+ })
+ break
+ }
+
+ case 'hook-enforcement': {
+ suggestions.push({
+ id: nextId(criterion.id),
+ criterion: criterion.id,
+ description:
+ 'Add hook references to automate linting/testing on file changes via .claude/settings.json.',
+ before: 'missing',
+ after:
+ '## Hooks\nConfigured in .claude/settings.json — auto-runs lint on file save and tests before commit.\n',
+ priority,
+ })
+ break
+ }
+
+ case 'mcp-config': {
+ suggestions.push({
+ id: nextId(criterion.id),
+ criterion: criterion.id,
+ description:
+ 'Reference MCP server configuration if you use external tool integrations.',
+ before: 'missing',
+ after:
+ '## MCP Servers\nConfigured in .mcp.json — see project docs for available MCP integrations.\n',
+ priority,
+ })
+ break
+ }
+ }
+ }
+
+ return suggestions
+}
diff --git a/src/lib/analyzer/types.ts b/src/lib/analyzer/types.ts
new file mode 100644
index 0000000..b21a973
--- /dev/null
+++ b/src/lib/analyzer/types.ts
@@ -0,0 +1,36 @@
+export interface CriterionResult {
+ id: string
+ name: string
+ weight: number
+ maxScore: number
+ score: number
+ findings: Finding[]
+}
+
+export interface Finding {
+ severity: 'critical' | 'warning' | 'info'
+ message: string
+ line?: number
+}
+
+export interface Suggestion {
+ id: string
+ criterion: string
+ description: string
+ before: string
+ after: string
+ priority: 'high' | 'medium' | 'low'
+}
+
+export interface AnalysisResult {
+ overallScore: number
+ maxScore: number
+ percentage: number
+ letterGrade: string
+ criteria: CriterionResult[]
+ pros: string[]
+ cons: string[]
+ suggestions: Suggestion[]
+ detectedProfileSlug?: string
+ lineCount: number
+}
diff --git a/src/lib/auth.ts b/src/lib/auth.ts
new file mode 100644
index 0000000..c52ab0f
--- /dev/null
+++ b/src/lib/auth.ts
@@ -0,0 +1,67 @@
+import { NextRequest, NextResponse } from 'next/server'
+
+const AUTH_USER = 'Steve'
+const AUTH_PASS = 'DWSecure2024!'
+const SESSION_COOKIE = 'clawcoder_session'
+const SESSION_MAX_AGE = 30 * 24 * 60 * 60 * 1000 // 30 days
+
+export interface SessionData {
+ authenticated: boolean
+ username: string
+ loginTime: number
+}
+
+/**
+ * Parse session from cookie value (base64-encoded JSON).
+ * Returns null if invalid or expired.
+ */
+export function parseSession(cookieValue: string | undefined): SessionData | null {
+ if (!cookieValue) return null
+ try {
+ const data = JSON.parse(Buffer.from(cookieValue, 'base64').toString()) as SessionData
+ if (data.authenticated && (Date.now() - data.loginTime) < SESSION_MAX_AGE) {
+ return data
+ }
+ } catch {
+ // malformed cookie
+ }
+ return null
+}
+
+/**
+ * Check Basic Auth header (for curl/API access).
+ * Returns the username if valid, null otherwise.
+ */
+export function checkBasicAuth(request: NextRequest): string | null {
+ const authHeader = request.headers.get('authorization')
+ if (authHeader?.startsWith('Basic ')) {
+ const decoded = Buffer.from(authHeader.slice(6), 'base64').toString()
+ const [user, pass] = decoded.split(':')
+ if (user === AUTH_USER && pass === AUTH_PASS) {
+ return user
+ }
+ }
+ return null
+}
+
+/**
+ * Authenticate an API request. Checks session cookie first, then Basic Auth.
+ * Returns the username if authenticated, or a 401 NextResponse if not.
+ */
+export function authenticateRequest(request: NextRequest): string | NextResponse {
+ // Check session cookie
+ const session = parseSession(request.cookies.get(SESSION_COOKIE)?.value)
+ if (session) {
+ return session.username
+ }
+
+ // Check Basic Auth
+ const basicUser = checkBasicAuth(request)
+ if (basicUser) {
+ return basicUser
+ }
+
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
+}
+
+export { AUTH_USER, AUTH_PASS, SESSION_COOKIE, SESSION_MAX_AGE }
diff --git a/src/lib/db.ts b/src/lib/db.ts
new file mode 100644
index 0000000..9346eba
--- /dev/null
+++ b/src/lib/db.ts
@@ -0,0 +1,13 @@
+import { PrismaClient } from '@/generated/prisma/client'
+import { PrismaPg } from '@prisma/adapter-pg'
+
+const globalForPrisma = globalThis as unknown as { prisma: PrismaClient }
+
+function createPrismaClient(): PrismaClient {
+ const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL! })
+ return new PrismaClient({ adapter }) as unknown as PrismaClient
+}
+
+export const prisma = globalForPrisma.prisma || createPrismaClient()
+
+if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
diff --git a/src/lib/generator/bundle.ts b/src/lib/generator/bundle.ts
new file mode 100644
index 0000000..6c3c972
--- /dev/null
+++ b/src/lib/generator/bundle.ts
@@ -0,0 +1,51 @@
+import type { Profile } from '../profiles/types'
+import { generateClaudeMd, type GeneratorVars } from './claude-md'
+import { generateRulesFiles, type RuleFile } from './rules'
+import { generateSettingsJson, serializeSettingsJson, type SettingsJson } from './hooks'
+
+/**
+ * A complete generated bundle for a profile.
+ */
+export interface GeneratedBundle {
+ /** The rendered CLAUDE.md content */
+ claudeMd: string
+ /** Rule files to place in .claude/rules/ */
+ rulesFiles: RuleFile[]
+ /** The .claude/settings.json content (object form) */
+ settings: SettingsJson
+ /** The .claude/settings.json content (serialized JSON string) */
+ settingsJson: string
+ /** Profile metadata for reference */
+ profile: {
+ slug: string
+ displayName: string
+ ageGroup: string
+ skillLevel: string
+ persona: string
+ }
+}
+
+/**
+ * Generate a complete bundle of all Claude configuration files
+ * for a given profile and user-provided variables.
+ */
+export function generateBundle(profile: Profile, vars: GeneratorVars): GeneratedBundle {
+ const claudeMd = generateClaudeMd(profile, vars)
+ const rulesFiles = generateRulesFiles(profile)
+ const settings = generateSettingsJson(profile)
+ const settingsJson = serializeSettingsJson(settings)
+
+ return {
+ claudeMd,
+ rulesFiles,
+ settings,
+ settingsJson,
+ profile: {
+ slug: profile.slug,
+ displayName: profile.displayName,
+ ageGroup: profile.ageGroup,
+ skillLevel: profile.skillLevel,
+ persona: profile.persona,
+ },
+ }
+}
diff --git a/src/lib/generator/claude-md.ts b/src/lib/generator/claude-md.ts
new file mode 100644
index 0000000..2195db3
--- /dev/null
+++ b/src/lib/generator/claude-md.ts
@@ -0,0 +1,21 @@
+import type { Profile } from '../profiles/types'
+import { renderTemplate } from '../profiles/templates'
+
+export interface GeneratorVars {
+ projectName: string
+ projectDescription: string
+ techStack: string
+}
+
+/**
+ * Generate the CLAUDE.md content for a given profile and user variables.
+ */
+export function generateClaudeMd(profile: Profile, vars: GeneratorVars): string {
+ const templateVars: Record<string, string> = {
+ projectName: vars.projectName,
+ projectDescription: vars.projectDescription,
+ techStack: vars.techStack,
+ }
+
+ return renderTemplate(profile.template.content, templateVars)
+}
diff --git a/src/lib/generator/hooks.ts b/src/lib/generator/hooks.ts
new file mode 100644
index 0000000..6da36e1
--- /dev/null
+++ b/src/lib/generator/hooks.ts
@@ -0,0 +1,57 @@
+import type { Profile, HookConfig } from '../profiles/types'
+
+/**
+ * Settings JSON structure for .claude/settings.json
+ */
+export interface SettingsJson {
+ hooks?: HookConfig
+ permissions?: {
+ allow?: string[]
+ deny?: string[]
+ }
+}
+
+/**
+ * Generate .claude/settings.json content from a profile's hook config.
+ */
+export function generateSettingsJson(profile: Profile): SettingsJson {
+ const settings: SettingsJson = {}
+
+ // Only include hooks if the profile has any defined
+ const hasHooks = profile.hooks && (
+ (profile.hooks.PreToolUse && profile.hooks.PreToolUse.length > 0) ||
+ (profile.hooks.PostToolUse && profile.hooks.PostToolUse.length > 0) ||
+ (profile.hooks.Stop && profile.hooks.Stop.length > 0) ||
+ (profile.hooks.SessionStart && profile.hooks.SessionStart.length > 0) ||
+ (profile.hooks.PreCompact && profile.hooks.PreCompact.length > 0)
+ )
+
+ if (hasHooks) {
+ settings.hooks = {}
+
+ if (profile.hooks.PreToolUse && profile.hooks.PreToolUse.length > 0) {
+ settings.hooks.PreToolUse = profile.hooks.PreToolUse
+ }
+ if (profile.hooks.PostToolUse && profile.hooks.PostToolUse.length > 0) {
+ settings.hooks.PostToolUse = profile.hooks.PostToolUse
+ }
+ if (profile.hooks.Stop && profile.hooks.Stop.length > 0) {
+ settings.hooks.Stop = profile.hooks.Stop
+ }
+ if (profile.hooks.SessionStart && profile.hooks.SessionStart.length > 0) {
+ settings.hooks.SessionStart = profile.hooks.SessionStart
+ }
+ if (profile.hooks.PreCompact && profile.hooks.PreCompact.length > 0) {
+ settings.hooks.PreCompact = profile.hooks.PreCompact
+ }
+ }
+
+ return settings
+}
+
+/**
+ * Serialize settings to a formatted JSON string.
+ */
+export function serializeSettingsJson(settings: SettingsJson): string {
+ return JSON.stringify(settings, null, 2)
+}
diff --git a/src/lib/generator/index.ts b/src/lib/generator/index.ts
new file mode 100644
index 0000000..7dd1307
--- /dev/null
+++ b/src/lib/generator/index.ts
@@ -0,0 +1,4 @@
+export * from './claude-md'
+export * from './rules'
+export * from './hooks'
+export * from './bundle'
diff --git a/src/lib/generator/rules.ts b/src/lib/generator/rules.ts
new file mode 100644
index 0000000..35e7bc0
--- /dev/null
+++ b/src/lib/generator/rules.ts
@@ -0,0 +1,18 @@
+import type { Profile } from '../profiles/types'
+
+export interface RuleFile {
+ path: string
+ content: string
+}
+
+/**
+ * Generate .claude/rules/ files for the given profile.
+ * Beginners typically have no rules files; intermediate+ profiles
+ * include project-specific rules.
+ */
+export function generateRulesFiles(profile: Profile): RuleFile[] {
+ return profile.rulesFiles.map(rf => ({
+ path: rf.path,
+ content: rf.content,
+ }))
+}
diff --git a/src/lib/marketplace/data.ts b/src/lib/marketplace/data.ts
new file mode 100644
index 0000000..f3bcf15
--- /dev/null
+++ b/src/lib/marketplace/data.ts
@@ -0,0 +1,843 @@
+// =============================================================================
+// Marketplace Data — Curated listings from the Claude Code ecosystem
+// =============================================================================
+
+export interface MarketplaceListing {
+ id: string
+ name: string
+ category: 'skill' | 'subagent' | 'mcp-server' | 'hook' | 'template'
+ description: string
+ source: string
+ installCmd: string
+ author: string
+ trustLevel: 'official' | 'verified' | 'community'
+ stars?: number
+ tags: string[]
+ profileMatch: string[]
+}
+
+// =============================================================================
+// SKILLS (10)
+// =============================================================================
+
+const skills: MarketplaceListing[] = [
+ {
+ id: 'skill-self-improving-agent',
+ name: 'Self-Improving Agent',
+ category: 'skill',
+ description:
+ 'Enables Claude to iteratively improve its own outputs by self-critiquing, revising, and validating results against defined quality criteria.',
+ source: 'https://github.com/alirezarezvani/claude-skills',
+ installCmd: 'claude skills add self-improving-agent --from alirezarezvani/claude-skills',
+ author: 'alirezarezvani',
+ trustLevel: 'community',
+ stars: 1200,
+ tags: ['meta', 'self-improvement', 'iteration', 'quality'],
+ profileMatch: [
+ 'professional-intermediate',
+ 'professional-expert',
+ 'young-adult-expert',
+ 'pre-senior-expert',
+ 'senior-expert',
+ ],
+ },
+ {
+ id: 'skill-security-auditor',
+ name: 'Security Auditor',
+ category: 'skill',
+ description:
+ 'Scans code for common vulnerabilities including injection attacks, hardcoded secrets, insecure dependencies, and OWASP Top 10 issues.',
+ source: 'https://github.com/alirezarezvani/claude-skills',
+ installCmd: 'claude skills add security-auditor --from alirezarezvani/claude-skills',
+ author: 'alirezarezvani',
+ trustLevel: 'community',
+ stars: 1200,
+ tags: ['security', 'audit', 'OWASP', 'vulnerability'],
+ profileMatch: [
+ 'professional-intermediate',
+ 'professional-expert',
+ 'young-adult-expert',
+ 'pre-senior-intermediate',
+ 'pre-senior-expert',
+ ],
+ },
+ {
+ id: 'skill-compound-engineering',
+ name: 'Compound Engineering',
+ category: 'skill',
+ description:
+ 'Built-in Claude Code superpower that chains multiple tools together for complex multi-step engineering tasks like refactoring entire modules.',
+ source: 'https://docs.anthropic.com/en/docs/claude-code/skills',
+ installCmd: '# Built-in — enable in CLAUDE.md with: "Use compound tool chains for complex tasks"',
+ author: 'Anthropic',
+ trustLevel: 'official',
+ tags: ['built-in', 'multi-step', 'refactoring', 'automation'],
+ profileMatch: [
+ 'professional-intermediate',
+ 'professional-expert',
+ 'young-adult-intermediate',
+ 'young-adult-expert',
+ ],
+ },
+ {
+ id: 'skill-git-commit-helper',
+ name: 'Git Commit Helper',
+ category: 'skill',
+ description:
+ 'Analyzes staged changes and generates conventional commit messages with proper scope, type, and breaking change annotations.',
+ source: 'https://github.com/anthropics/claude-code-skills',
+ installCmd: 'claude skills add git-commit-helper',
+ author: 'community',
+ trustLevel: 'verified',
+ stars: 850,
+ tags: ['git', 'commit', 'conventional-commits', 'automation'],
+ profileMatch: [
+ 'teen-intermediate',
+ 'teen-expert',
+ 'young-adult-beginner',
+ 'young-adult-intermediate',
+ 'young-adult-expert',
+ 'professional-beginner',
+ 'professional-intermediate',
+ 'professional-expert',
+ ],
+ },
+ {
+ id: 'skill-playwright-testing',
+ name: 'Playwright Testing',
+ category: 'skill',
+ description:
+ 'Generates end-to-end browser tests using Playwright with page object models, fixtures, and cross-browser validation strategies.',
+ source: 'https://github.com/anthropics/claude-code-skills',
+ installCmd: 'claude skills add playwright-testing',
+ author: 'Anthropic',
+ trustLevel: 'official',
+ stars: 2100,
+ tags: ['testing', 'e2e', 'playwright', 'browser', 'automation'],
+ profileMatch: [
+ 'young-adult-intermediate',
+ 'young-adult-expert',
+ 'professional-intermediate',
+ 'professional-expert',
+ ],
+ },
+ {
+ id: 'skill-excel-analysis',
+ name: 'Excel Analysis',
+ category: 'skill',
+ description:
+ 'Reads, analyzes, and transforms Excel/CSV files with pivot tables, charts, formulas, and data cleaning operations.',
+ source: 'https://github.com/anthropics/claude-code-skills',
+ installCmd: 'claude skills add excel-analysis',
+ author: 'Anthropic',
+ trustLevel: 'official',
+ stars: 980,
+ tags: ['excel', 'csv', 'data', 'analysis', 'spreadsheet'],
+ profileMatch: [
+ 'professional-beginner',
+ 'professional-intermediate',
+ 'pre-senior-beginner',
+ 'pre-senior-intermediate',
+ 'senior-beginner',
+ 'senior-intermediate',
+ ],
+ },
+ {
+ id: 'skill-pdf-processing',
+ name: 'PDF Processing',
+ category: 'skill',
+ description:
+ 'Extracts text, tables, and metadata from PDF documents. Supports multi-page parsing, OCR fallback, and structured data extraction.',
+ source: 'https://github.com/anthropics/claude-code-skills',
+ installCmd: 'claude skills add pdf-processing',
+ author: 'Anthropic',
+ trustLevel: 'official',
+ stars: 760,
+ tags: ['pdf', 'extraction', 'documents', 'OCR'],
+ profileMatch: [
+ 'professional-beginner',
+ 'professional-intermediate',
+ 'professional-expert',
+ 'pre-senior-beginner',
+ 'pre-senior-intermediate',
+ 'senior-beginner',
+ 'senior-intermediate',
+ ],
+ },
+ {
+ id: 'skill-code-review',
+ name: 'Code Review',
+ category: 'skill',
+ description:
+ 'Performs thorough code reviews checking for bugs, performance issues, naming conventions, SOLID principles, and suggests improvements with rationale.',
+ source: 'https://github.com/anthropics/claude-code-skills',
+ installCmd: 'claude skills add code-review',
+ author: 'community',
+ trustLevel: 'verified',
+ stars: 1450,
+ tags: ['review', 'quality', 'best-practices', 'SOLID'],
+ profileMatch: [
+ 'teen-expert',
+ 'young-adult-intermediate',
+ 'young-adult-expert',
+ 'professional-beginner',
+ 'professional-intermediate',
+ 'professional-expert',
+ ],
+ },
+ {
+ id: 'skill-music-creation',
+ name: 'Music Creation',
+ category: 'skill',
+ description:
+ 'Generates MIDI files, music notation, and audio synthesis scripts. Supports chord progressions, melodies, and basic arrangement.',
+ source: 'https://github.com/anthropics/claude-code-skills',
+ installCmd: 'claude skills add music-creation',
+ author: 'Anthropic',
+ trustLevel: 'official',
+ stars: 520,
+ tags: ['music', 'MIDI', 'audio', 'creative'],
+ profileMatch: [
+ 'teen-beginner',
+ 'teen-intermediate',
+ 'teen-expert',
+ 'young-adult-beginner',
+ 'young-adult-intermediate',
+ ],
+ },
+ {
+ id: 'skill-web-app-testing',
+ name: 'Web App Testing',
+ category: 'skill',
+ description:
+ 'Generates comprehensive test suites for web applications including unit tests, integration tests, and accessibility audits.',
+ source: 'https://github.com/anthropics/claude-code-skills',
+ installCmd: 'claude skills add web-app-testing',
+ author: 'Anthropic',
+ trustLevel: 'official',
+ stars: 1780,
+ tags: ['testing', 'unit', 'integration', 'accessibility', 'web'],
+ profileMatch: [
+ 'young-adult-intermediate',
+ 'young-adult-expert',
+ 'professional-beginner',
+ 'professional-intermediate',
+ 'professional-expert',
+ ],
+ },
+]
+
+// =============================================================================
+// SUBAGENTS (8)
+// =============================================================================
+
+const subagents: MarketplaceListing[] = [
+ {
+ id: 'subagent-python-expert',
+ name: 'Python Expert',
+ category: 'subagent',
+ description:
+ 'Specialized subagent for Python development with expertise in type hints, async patterns, FastAPI, Django, and scientific computing.',
+ source: 'https://github.com/VoltAgent/claude-subagents',
+ installCmd: 'claude subagent add python-expert --from VoltAgent/claude-subagents',
+ author: 'VoltAgent',
+ trustLevel: 'verified',
+ stars: 640,
+ tags: ['python', 'FastAPI', 'Django', 'async', 'typing'],
+ profileMatch: [
+ 'young-adult-intermediate',
+ 'young-adult-expert',
+ 'professional-intermediate',
+ 'professional-expert',
+ 'pre-senior-intermediate',
+ ],
+ },
+ {
+ id: 'subagent-typescript-expert',
+ name: 'TypeScript Expert',
+ category: 'subagent',
+ description:
+ 'Specialized subagent for TypeScript with deep knowledge of generics, conditional types, mapped types, and strict compiler configurations.',
+ source: 'https://github.com/VoltAgent/claude-subagents',
+ installCmd: 'claude subagent add typescript-expert --from VoltAgent/claude-subagents',
+ author: 'VoltAgent',
+ trustLevel: 'verified',
+ stars: 640,
+ tags: ['typescript', 'generics', 'types', 'compiler'],
+ profileMatch: [
+ 'young-adult-intermediate',
+ 'young-adult-expert',
+ 'professional-intermediate',
+ 'professional-expert',
+ ],
+ },
+ {
+ id: 'subagent-react-specialist',
+ name: 'React Specialist',
+ category: 'subagent',
+ description:
+ 'Focused on React 19+, Server Components, Suspense boundaries, hooks composition, and performance optimization patterns.',
+ source: 'https://github.com/0xfurai/claude-react-agent',
+ installCmd: 'claude subagent add react-specialist --from 0xfurai/claude-react-agent',
+ author: '0xfurai',
+ trustLevel: 'community',
+ stars: 380,
+ tags: ['react', 'server-components', 'hooks', 'RSC', 'Next.js'],
+ profileMatch: [
+ 'teen-expert',
+ 'young-adult-intermediate',
+ 'young-adult-expert',
+ 'professional-intermediate',
+ 'professional-expert',
+ ],
+ },
+ {
+ id: 'subagent-database-architect',
+ name: 'Database Architect',
+ category: 'subagent',
+ description:
+ 'Designs schemas, writes migrations, optimizes queries, and manages indexes for PostgreSQL, MySQL, and MongoDB with performance analysis.',
+ source: 'https://github.com/VoltAgent/claude-subagents',
+ installCmd: 'claude subagent add database-architect --from VoltAgent/claude-subagents',
+ author: 'VoltAgent',
+ trustLevel: 'verified',
+ stars: 640,
+ tags: ['database', 'SQL', 'PostgreSQL', 'schema', 'optimization'],
+ profileMatch: [
+ 'professional-intermediate',
+ 'professional-expert',
+ 'pre-senior-intermediate',
+ 'pre-senior-expert',
+ 'senior-intermediate',
+ 'senior-expert',
+ ],
+ },
+ {
+ id: 'subagent-security-auditor',
+ name: 'Security Auditor Agent',
+ category: 'subagent',
+ description:
+ 'Autonomous security analysis subagent that scans for vulnerabilities, checks dependencies, and generates security reports with remediation steps.',
+ source: 'https://github.com/VoltAgent/claude-subagents',
+ installCmd: 'claude subagent add security-auditor --from VoltAgent/claude-subagents',
+ author: 'VoltAgent',
+ trustLevel: 'verified',
+ stars: 640,
+ tags: ['security', 'audit', 'dependencies', 'CVE', 'remediation'],
+ profileMatch: [
+ 'professional-expert',
+ 'pre-senior-expert',
+ 'senior-expert',
+ ],
+ },
+ {
+ id: 'subagent-performance-engineer',
+ name: 'Performance Engineer',
+ category: 'subagent',
+ description:
+ 'Analyzes application performance bottlenecks, suggests optimizations, profiles memory usage, and benchmarks critical code paths.',
+ source: 'https://github.com/VoltAgent/claude-subagents',
+ installCmd: 'claude subagent add performance-engineer --from VoltAgent/claude-subagents',
+ author: 'VoltAgent',
+ trustLevel: 'verified',
+ stars: 640,
+ tags: ['performance', 'profiling', 'optimization', 'benchmarks', 'memory'],
+ profileMatch: [
+ 'professional-intermediate',
+ 'professional-expert',
+ 'young-adult-expert',
+ ],
+ },
+ {
+ id: 'subagent-code-reviewer',
+ name: 'Code Reviewer Agent',
+ category: 'subagent',
+ description:
+ 'Automated PR review subagent that checks code quality, style consistency, test coverage, and provides actionable feedback comments.',
+ source: 'https://github.com/0xfurai/claude-code-reviewer',
+ installCmd: 'claude subagent add code-reviewer --from 0xfurai/claude-code-reviewer',
+ author: '0xfurai',
+ trustLevel: 'community',
+ stars: 290,
+ tags: ['code-review', 'PR', 'quality', 'style', 'coverage'],
+ profileMatch: [
+ 'young-adult-intermediate',
+ 'young-adult-expert',
+ 'professional-beginner',
+ 'professional-intermediate',
+ 'professional-expert',
+ ],
+ },
+ {
+ id: 'subagent-test-engineer',
+ name: 'Test Engineer Agent',
+ category: 'subagent',
+ description:
+ 'Generates comprehensive test suites with unit, integration, and E2E tests. Understands testing pyramids and mock strategies.',
+ source: 'https://github.com/0xfurai/claude-test-agent',
+ installCmd: 'claude subagent add test-engineer --from 0xfurai/claude-test-agent',
+ author: '0xfurai',
+ trustLevel: 'community',
+ stars: 310,
+ tags: ['testing', 'unit-tests', 'integration', 'mocks', 'coverage'],
+ profileMatch: [
+ 'young-adult-intermediate',
+ 'young-adult-expert',
+ 'professional-intermediate',
+ 'professional-expert',
+ 'teen-expert',
+ ],
+ },
+]
+
+// =============================================================================
+// MCP SERVERS (8)
+// =============================================================================
+
+const mcpServers: MarketplaceListing[] = [
+ {
+ id: 'mcp-context7',
+ name: 'Context7',
+ category: 'mcp-server',
+ description:
+ 'Injects up-to-date documentation for any library directly into Claude context. The most popular MCP server with version-aware docs resolution.',
+ source: 'https://github.com/upstash/context7',
+ installCmd: 'claude mcp add context7 -- npx -y @upstash/context7-mcp@latest',
+ author: 'Upstash',
+ trustLevel: 'verified',
+ stars: 8500,
+ tags: ['documentation', 'context', 'libraries', 'up-to-date'],
+ profileMatch: [
+ 'teen-beginner',
+ 'teen-intermediate',
+ 'teen-expert',
+ 'young-adult-beginner',
+ 'young-adult-intermediate',
+ 'young-adult-expert',
+ 'professional-beginner',
+ 'professional-intermediate',
+ 'professional-expert',
+ 'pre-senior-beginner',
+ 'pre-senior-intermediate',
+ 'pre-senior-expert',
+ 'senior-beginner',
+ 'senior-intermediate',
+ 'senior-expert',
+ ],
+ },
+ {
+ id: 'mcp-playwright',
+ name: 'Playwright MCP',
+ category: 'mcp-server',
+ description:
+ 'Browser automation via Playwright. Navigate pages, click elements, fill forms, take screenshots, and run E2E tests from Claude.',
+ source: 'https://github.com/microsoft/playwright-mcp',
+ installCmd: 'claude mcp add playwright -- npx -y @anthropic-ai/mcp-server-playwright',
+ author: 'Microsoft',
+ trustLevel: 'official',
+ stars: 5200,
+ tags: ['browser', 'automation', 'testing', 'e2e', 'screenshots'],
+ profileMatch: [
+ 'young-adult-intermediate',
+ 'young-adult-expert',
+ 'professional-intermediate',
+ 'professional-expert',
+ 'teen-expert',
+ ],
+ },
+ {
+ id: 'mcp-github',
+ name: 'GitHub MCP',
+ category: 'mcp-server',
+ description:
+ 'Full GitHub API access: create repos, manage issues, open PRs, review code, manage releases, and query repository data.',
+ source: 'https://github.com/modelcontextprotocol/servers',
+ installCmd: 'claude mcp add github -- npx -y @modelcontextprotocol/server-github',
+ author: 'Anthropic',
+ trustLevel: 'official',
+ stars: 15000,
+ tags: ['github', 'git', 'issues', 'PRs', 'repos'],
+ profileMatch: [
+ 'teen-intermediate',
+ 'teen-expert',
+ 'young-adult-beginner',
+ 'young-adult-intermediate',
+ 'young-adult-expert',
+ 'professional-beginner',
+ 'professional-intermediate',
+ 'professional-expert',
+ 'pre-senior-intermediate',
+ 'pre-senior-expert',
+ ],
+ },
+ {
+ id: 'mcp-filesystem',
+ name: 'Filesystem MCP',
+ category: 'mcp-server',
+ description:
+ 'Secure file system access with configurable root directories. Read, write, search, and manage files with sandboxed permissions.',
+ source: 'https://github.com/modelcontextprotocol/servers',
+ installCmd: 'claude mcp add filesystem -- npx -y @modelcontextprotocol/server-filesystem /path/to/dir',
+ author: 'Anthropic',
+ trustLevel: 'official',
+ stars: 15000,
+ tags: ['files', 'filesystem', 'read', 'write', 'search'],
+ profileMatch: [
+ 'teen-beginner',
+ 'teen-intermediate',
+ 'teen-expert',
+ 'young-adult-beginner',
+ 'young-adult-intermediate',
+ 'young-adult-expert',
+ 'professional-beginner',
+ 'professional-intermediate',
+ 'professional-expert',
+ 'pre-senior-beginner',
+ 'pre-senior-intermediate',
+ 'pre-senior-expert',
+ 'senior-beginner',
+ 'senior-intermediate',
+ 'senior-expert',
+ ],
+ },
+ {
+ id: 'mcp-brave-search',
+ name: 'Brave Search MCP',
+ category: 'mcp-server',
+ description:
+ 'Web search powered by Brave Search API. Perform web and news searches with privacy-focused results directly from Claude.',
+ source: 'https://github.com/modelcontextprotocol/servers',
+ installCmd: 'claude mcp add brave-search -- npx -y @modelcontextprotocol/server-brave-search',
+ author: 'Anthropic',
+ trustLevel: 'official',
+ stars: 15000,
+ tags: ['search', 'web', 'brave', 'news', 'research'],
+ profileMatch: [
+ 'teen-intermediate',
+ 'teen-expert',
+ 'young-adult-intermediate',
+ 'young-adult-expert',
+ 'professional-intermediate',
+ 'professional-expert',
+ 'pre-senior-intermediate',
+ 'senior-intermediate',
+ ],
+ },
+ {
+ id: 'mcp-21st-magic',
+ name: '21st.dev Magic',
+ category: 'mcp-server',
+ description:
+ 'AI-powered UI component generation. Describe a component in natural language and get production-ready React/Tailwind code instantly.',
+ source: 'https://github.com/21st-dev/magic-mcp',
+ installCmd: 'claude mcp add magic -- npx -y @21st-dev/magic-mcp',
+ author: '21st.dev',
+ trustLevel: 'verified',
+ stars: 3400,
+ tags: ['UI', 'components', 'React', 'Tailwind', 'generation'],
+ profileMatch: [
+ 'teen-beginner',
+ 'teen-intermediate',
+ 'young-adult-beginner',
+ 'young-adult-intermediate',
+ 'professional-beginner',
+ ],
+ },
+ {
+ id: 'mcp-firecrawl',
+ name: 'Firecrawl MCP',
+ category: 'mcp-server',
+ description:
+ 'Web scraping and crawling server. Extract structured data, scrape entire sites, and convert web pages to clean markdown.',
+ source: 'https://github.com/firecrawl/firecrawl-mcp-server',
+ installCmd: 'claude mcp add firecrawl -- npx -y firecrawl-mcp',
+ author: 'Firecrawl',
+ trustLevel: 'verified',
+ stars: 2800,
+ tags: ['scraping', 'crawling', 'extraction', 'markdown', 'web'],
+ profileMatch: [
+ 'young-adult-expert',
+ 'professional-intermediate',
+ 'professional-expert',
+ 'pre-senior-expert',
+ ],
+ },
+ {
+ id: 'mcp-postgresql',
+ name: 'PostgreSQL MCP',
+ category: 'mcp-server',
+ description:
+ 'Direct PostgreSQL database access. Run queries, inspect schemas, manage tables, and perform database operations from Claude.',
+ source: 'https://github.com/modelcontextprotocol/servers',
+ installCmd: 'claude mcp add postgres -- npx -y @modelcontextprotocol/server-postgres postgresql://user:pass@host/db',
+ author: 'Anthropic',
+ trustLevel: 'official',
+ stars: 15000,
+ tags: ['database', 'PostgreSQL', 'SQL', 'queries', 'schema'],
+ profileMatch: [
+ 'professional-intermediate',
+ 'professional-expert',
+ 'pre-senior-intermediate',
+ 'pre-senior-expert',
+ 'senior-intermediate',
+ 'senior-expert',
+ ],
+ },
+]
+
+// =============================================================================
+// HOOKS (5)
+// =============================================================================
+
+const hooks: MarketplaceListing[] = [
+ {
+ id: 'hook-destructive-blocker',
+ name: 'Destructive Command Blocker',
+ category: 'hook',
+ description:
+ 'PreToolUse hook that blocks dangerous commands like rm -rf, git reset --hard, DROP TABLE, and force pushes. Returns exit code 2 to deny.',
+ source: 'https://github.com/disler/hooks-mastery',
+ installCmd: `# Add to .claude/settings.json hooks.PreToolUse:
+# { "matcher": "Bash", "hooks": [{ "type": "command", "command": "bash .claude/hooks/block-destructive.sh \\"$TOOL_INPUT\\"" }] }`,
+ author: 'disler',
+ trustLevel: 'verified',
+ stars: 920,
+ tags: ['safety', 'protection', 'destructive', 'blocker'],
+ profileMatch: [
+ 'teen-beginner',
+ 'teen-intermediate',
+ 'teen-expert',
+ 'young-adult-beginner',
+ 'young-adult-intermediate',
+ 'pre-senior-beginner',
+ 'pre-senior-intermediate',
+ 'senior-beginner',
+ 'senior-intermediate',
+ 'senior-expert',
+ ],
+ },
+ {
+ id: 'hook-auto-formatter',
+ name: 'Auto-Formatter',
+ category: 'hook',
+ description:
+ 'PostToolUse hook that automatically runs Prettier or your configured formatter after every file write operation to maintain code style.',
+ source: 'https://github.com/ChrisWiles/claude-hooks-showcase',
+ installCmd: `# Add to .claude/settings.json hooks.PostToolUse:
+# { "matcher": "Write|Edit", "hooks": [{ "type": "command", "command": "npx prettier --write \\"$FILEPATH\\"" }] }`,
+ author: 'ChrisWiles',
+ trustLevel: 'community',
+ stars: 340,
+ tags: ['formatting', 'prettier', 'style', 'auto'],
+ profileMatch: [
+ 'young-adult-intermediate',
+ 'young-adult-expert',
+ 'professional-beginner',
+ 'professional-intermediate',
+ 'professional-expert',
+ 'teen-expert',
+ ],
+ },
+ {
+ id: 'hook-credential-warning',
+ name: 'Credential Warning',
+ category: 'hook',
+ description:
+ 'PreToolUse hook that scans file writes for hardcoded API keys, passwords, tokens, and secrets. Warns before committing sensitive data.',
+ source: 'https://docs.anthropic.com/en/docs/claude-code/hooks',
+ installCmd: `# Add to .claude/settings.json hooks.PreToolUse:
+# { "matcher": "Write|Edit", "hooks": [{ "type": "command", "command": "bash .claude/hooks/check-credentials.sh \\"$TOOL_INPUT\\"" }] }`,
+ author: 'Anthropic',
+ trustLevel: 'official',
+ tags: ['security', 'credentials', 'secrets', 'prevention'],
+ profileMatch: [
+ 'teen-beginner',
+ 'teen-intermediate',
+ 'teen-expert',
+ 'young-adult-beginner',
+ 'young-adult-intermediate',
+ 'young-adult-expert',
+ 'professional-beginner',
+ 'professional-intermediate',
+ 'professional-expert',
+ 'pre-senior-beginner',
+ 'pre-senior-intermediate',
+ 'pre-senior-expert',
+ 'senior-beginner',
+ 'senior-intermediate',
+ 'senior-expert',
+ ],
+ },
+ {
+ id: 'hook-test-reminder',
+ name: 'Test Reminder',
+ category: 'hook',
+ description:
+ 'Stop hook that checks if tests were run during the session and reminds the developer to run the test suite before ending.',
+ source: 'https://docs.anthropic.com/en/docs/claude-code/hooks',
+ installCmd: `# Add to .claude/settings.json hooks.Stop:
+# { "matcher": ".*", "hooks": [{ "type": "command", "command": "bash .claude/hooks/remind-tests.sh" }] }`,
+ author: 'Anthropic',
+ trustLevel: 'official',
+ tags: ['testing', 'reminder', 'quality', 'CI'],
+ profileMatch: [
+ 'young-adult-beginner',
+ 'young-adult-intermediate',
+ 'young-adult-expert',
+ 'professional-beginner',
+ 'professional-intermediate',
+ 'teen-intermediate',
+ 'teen-expert',
+ ],
+ },
+ {
+ id: 'hook-commit-lint',
+ name: 'Commit Message Linter',
+ category: 'hook',
+ description:
+ 'PostToolUse hook that validates git commit messages against conventional commit format and rejects non-conforming messages.',
+ source: 'https://github.com/disler/hooks-mastery',
+ installCmd: `# Add to .claude/settings.json hooks.PostToolUse:
+# { "matcher": "Bash(git commit)", "hooks": [{ "type": "command", "command": "bash .claude/hooks/lint-commit.sh \\"$TOOL_INPUT\\"" }] }`,
+ author: 'disler',
+ trustLevel: 'verified',
+ stars: 920,
+ tags: ['git', 'commit', 'linting', 'conventional'],
+ profileMatch: [
+ 'young-adult-intermediate',
+ 'young-adult-expert',
+ 'professional-beginner',
+ 'professional-intermediate',
+ 'professional-expert',
+ ],
+ },
+]
+
+// =============================================================================
+// TEMPLATES (4)
+// =============================================================================
+
+const templates: MarketplaceListing[] = [
+ {
+ id: 'template-minimal-starter',
+ name: 'Minimal Starter CLAUDE.md',
+ category: 'template',
+ description:
+ 'A clean, minimal CLAUDE.md template under 40 lines. Perfect for small projects that need basic rules without complexity.',
+ source: 'https://docs.anthropic.com/en/docs/claude-code/claude-md',
+ installCmd: '# Use ClawCoder Builder: /builder?profile=professional-beginner',
+ author: 'Anthropic',
+ trustLevel: 'official',
+ tags: ['minimal', 'starter', 'simple', 'template'],
+ profileMatch: [
+ 'teen-beginner',
+ 'young-adult-beginner',
+ 'professional-beginner',
+ 'pre-senior-beginner',
+ 'senior-beginner',
+ ],
+ },
+ {
+ id: 'template-full-stack',
+ name: 'Full-Stack CLAUDE.md',
+ category: 'template',
+ description:
+ 'Comprehensive template for full-stack web applications with frontend, backend, database, testing, and deployment sections.',
+ source: 'https://docs.anthropic.com/en/docs/claude-code/claude-md',
+ installCmd: '# Use ClawCoder Builder: /builder?profile=professional-intermediate',
+ author: 'ClawCoder',
+ trustLevel: 'verified',
+ tags: ['full-stack', 'web', 'comprehensive', 'production'],
+ profileMatch: [
+ 'young-adult-intermediate',
+ 'young-adult-expert',
+ 'professional-intermediate',
+ 'professional-expert',
+ ],
+ },
+ {
+ id: 'template-enterprise',
+ name: 'Enterprise CLAUDE.md',
+ category: 'template',
+ description:
+ 'Enterprise-grade template with compliance rules, audit trails, security policies, team conventions, and CI/CD integration hooks.',
+ source: 'https://docs.anthropic.com/en/docs/claude-code/claude-md',
+ installCmd: '# Use ClawCoder Builder: /builder?profile=professional-expert',
+ author: 'ClawCoder',
+ trustLevel: 'verified',
+ tags: ['enterprise', 'compliance', 'security', 'team', 'CI/CD'],
+ profileMatch: [
+ 'professional-expert',
+ 'pre-senior-expert',
+ 'senior-expert',
+ ],
+ },
+ {
+ id: 'template-learning',
+ name: 'Learning-Focused CLAUDE.md',
+ category: 'template',
+ description:
+ 'Template optimized for learning with verbose explanations, step-by-step guidance, safety guardrails, and beginner-friendly language.',
+ source: 'https://docs.anthropic.com/en/docs/claude-code/claude-md',
+ installCmd: '# Use ClawCoder Builder: /builder?profile=teen-beginner',
+ author: 'ClawCoder',
+ trustLevel: 'verified',
+ tags: ['learning', 'beginner', 'education', 'guided'],
+ profileMatch: [
+ 'teen-beginner',
+ 'teen-intermediate',
+ 'young-adult-beginner',
+ 'pre-senior-beginner',
+ 'senior-beginner',
+ ],
+ },
+]
+
+// =============================================================================
+// Combined listings
+// =============================================================================
+
+export const MARKETPLACE_LISTINGS: MarketplaceListing[] = [
+ ...skills,
+ ...subagents,
+ ...mcpServers,
+ ...hooks,
+ ...templates,
+]
+
+// =============================================================================
+// Query functions
+// =============================================================================
+
+export function getListingsByCategory(category: string): MarketplaceListing[] {
+ return MARKETPLACE_LISTINGS.filter((l) => l.category === category)
+}
+
+export function getListingsForProfile(profileSlug: string): MarketplaceListing[] {
+ return MARKETPLACE_LISTINGS.filter((l) => l.profileMatch.includes(profileSlug))
+}
+
+export function searchListings(query: string): MarketplaceListing[] {
+ const q = query.toLowerCase().trim()
+ if (!q) return MARKETPLACE_LISTINGS
+
+ return MARKETPLACE_LISTINGS.filter((l) => {
+ const haystack = [
+ l.name,
+ l.description,
+ l.author,
+ l.category,
+ ...l.tags,
+ ]
+ .join(' ')
+ .toLowerCase()
+
+ // Support multi-word search: all terms must match
+ const terms = q.split(/\s+/)
+ return terms.every((term) => haystack.includes(term))
+ })
+}
+
+export function getListingById(id: string): MarketplaceListing | undefined {
+ return MARKETPLACE_LISTINGS.find((l) => l.id === id)
+}
diff --git a/src/lib/onboarding/questions.ts b/src/lib/onboarding/questions.ts
new file mode 100644
index 0000000..e0858bb
--- /dev/null
+++ b/src/lib/onboarding/questions.ts
@@ -0,0 +1,222 @@
+export interface OnboardingQuestion {
+ id: string
+ step: number
+ question: string
+ whyWeAsk: string
+ options: { value: string; label: string; description?: string }[]
+}
+
+export const ONBOARDING_QUESTIONS: OnboardingQuestion[] = [
+ {
+ id: 'profile',
+ step: 1,
+ question: 'What brings you here?',
+ whyWeAsk: 'This helps us pick the right starting template for you.',
+ options: [
+ {
+ value: 'learning',
+ label: 'Help with my computer',
+ description: 'I want technology to be easier',
+ },
+ {
+ value: 'building',
+ label: 'Build websites or apps',
+ description: 'I write code or want to start',
+ },
+ {
+ value: 'documents',
+ label: 'Work with documents',
+ description: 'Writing, spreadsheets, reports',
+ },
+ {
+ value: 'business',
+ label: 'Business workflows',
+ description: 'Automate tasks and processes',
+ },
+ ],
+ },
+ {
+ id: 'comfort',
+ step: 2,
+ question: 'How comfortable are you with technology?',
+ whyWeAsk: 'We adjust our language and pace based on your experience.',
+ options: [
+ {
+ value: 'beginner',
+ label: 'New to this',
+ description: 'I need things explained simply',
+ },
+ {
+ value: 'somewhat',
+ label: 'Somewhat comfortable',
+ description: 'I know the basics',
+ },
+ {
+ value: 'comfortable',
+ label: 'Comfortable',
+ description: 'I use tech tools daily',
+ },
+ {
+ value: 'expert',
+ label: 'Expert',
+ description: 'I build things with technology',
+ },
+ ],
+ },
+ {
+ id: 'age',
+ step: 3,
+ question: 'Which best describes you?',
+ whyWeAsk: 'We optimize text size and interface design for comfort.',
+ options: [
+ {
+ value: 'teen',
+ label: 'Student (13-17)',
+ description: 'Learning and exploring',
+ },
+ {
+ value: 'young_adult',
+ label: 'Young professional (18-30)',
+ description: 'Starting my career',
+ },
+ {
+ value: 'professional',
+ label: 'Mid-career (31-50)',
+ description: 'Experienced professional',
+ },
+ {
+ value: 'pre_senior',
+ label: 'Seasoned pro (51-65)',
+ description: 'Decades of experience',
+ },
+ {
+ value: 'senior',
+ label: 'Lifelong learner (65+)',
+ description: 'Exploring new horizons',
+ },
+ ],
+ },
+ {
+ id: 'risk',
+ step: 4,
+ question: 'Is any of your data sensitive?',
+ whyWeAsk: 'We set safety levels based on how careful we need to be.',
+ options: [
+ {
+ value: 'no',
+ label: 'No',
+ description: 'Just personal projects',
+ },
+ {
+ value: 'some',
+ label: 'Some',
+ description: 'Some work files or passwords',
+ },
+ {
+ value: 'yes',
+ label: 'Yes (health/finance/work)',
+ description: 'I handle sensitive information',
+ },
+ ],
+ },
+ {
+ id: 'style',
+ step: 5,
+ question: 'How should the assistant communicate?',
+ whyWeAsk: 'This controls how much explanation you get.',
+ options: [
+ {
+ value: 'detailed',
+ label: 'Explain step-by-step',
+ description: 'Walk me through everything',
+ },
+ {
+ value: 'balanced',
+ label: 'Only key points',
+ description: 'Explain the important stuff',
+ },
+ {
+ value: 'minimal',
+ label: 'Just do it',
+ description: 'Less talk, more action',
+ },
+ ],
+ },
+ {
+ id: 'tools',
+ step: 6,
+ question: 'Will you connect other tools?',
+ whyWeAsk:
+ 'This determines which MCP servers and integrations we recommend.',
+ options: [
+ { value: 'no', label: 'No, keep it simple' },
+ { value: 'maybe', label: 'Maybe later' },
+ {
+ value: 'yes',
+ label: 'Yes -- design, docs, or databases',
+ description: 'I want to connect external tools',
+ },
+ ],
+ },
+ {
+ id: 'workflow',
+ step: 7,
+ question: 'How do you prefer to work?',
+ whyWeAsk: 'This shapes how we organize your rules and skills.',
+ options: [
+ {
+ value: 'single',
+ label: 'One task at a time',
+ description: 'Focus on one thing',
+ },
+ {
+ value: 'checklist',
+ label: 'Checklists',
+ description: 'Step-by-step lists',
+ },
+ {
+ value: 'projects',
+ label: 'Full projects',
+ description: 'Plan and build end-to-end',
+ },
+ ],
+ },
+ {
+ id: 'updates',
+ step: 8,
+ question: 'Should your CLAUDE.md improve over time?',
+ whyWeAsk: 'Auto-updating keeps your config current as you learn.',
+ options: [
+ {
+ value: 'yes',
+ label: 'Yes, auto-update',
+ description: 'Learn from my usage',
+ },
+ {
+ value: 'ask',
+ label: 'Ask me first',
+ description: 'Suggest but let me decide',
+ },
+ {
+ value: 'no',
+ label: 'No, keep it fixed',
+ description: 'I prefer manual control',
+ },
+ ],
+ },
+ {
+ id: 'finish',
+ step: 9,
+ question: 'Ready to generate your setup?',
+ whyWeAsk:
+ 'We will create your CLAUDE.md, rules, and recommended tools.',
+ options: [
+ { value: 'generate', label: 'Generate my setup!' },
+ {
+ value: 'review',
+ label: 'Let me review first',
+ description: 'Show me what you picked before generating',
+ },
+ ],
+ },
+]
diff --git a/src/lib/onboarding/resolver.ts b/src/lib/onboarding/resolver.ts
new file mode 100644
index 0000000..bbad452
--- /dev/null
+++ b/src/lib/onboarding/resolver.ts
@@ -0,0 +1,87 @@
+import type { AgeGroup, SkillLevel } from '@/lib/profiles/types'
+import { getProfile } from '@/lib/profiles/data'
+
+const skillMap: Record<string, SkillLevel> = {
+ beginner: 'beginner',
+ somewhat: 'beginner',
+ comfortable: 'intermediate',
+ expert: 'expert',
+}
+
+const ageMap: Record<string, AgeGroup> = {
+ teen: 'teen',
+ young_adult: 'young_adult',
+ professional: 'professional',
+ pre_senior: 'pre_senior',
+ senior: 'senior',
+}
+
+/**
+ * Map onboarding wizard answers to the best-fit profile.
+ * Falls back to professional-beginner when no exact match is found.
+ */
+export function resolveProfile(answers: Record<string, string>) {
+ const skill = skillMap[answers.comfort] || 'beginner'
+ const age = ageMap[answers.age] || 'professional'
+ const slug = `${age}-${skill}`
+
+ return getProfile(slug) || getProfile('professional-beginner')!
+}
+
+/**
+ * Produce a human-readable label for a given answer value.
+ */
+export function answerLabel(questionId: string, value: string): string {
+ const labels: Record<string, Record<string, string>> = {
+ profile: {
+ learning: 'Help with my computer',
+ building: 'Build websites or apps',
+ documents: 'Work with documents',
+ business: 'Business workflows',
+ },
+ comfort: {
+ beginner: 'New to this',
+ somewhat: 'Somewhat comfortable',
+ comfortable: 'Comfortable',
+ expert: 'Expert',
+ },
+ age: {
+ teen: 'Student (13-17)',
+ young_adult: 'Young professional (18-30)',
+ professional: 'Mid-career (31-50)',
+ pre_senior: 'Seasoned pro (51-65)',
+ senior: 'Lifelong learner (65+)',
+ },
+ risk: {
+ no: 'No',
+ some: 'Some',
+ yes: 'Yes (health/finance/work)',
+ },
+ style: {
+ detailed: 'Explain step-by-step',
+ balanced: 'Only key points',
+ minimal: 'Just do it',
+ },
+ tools: {
+ no: 'No, keep it simple',
+ maybe: 'Maybe later',
+ yes: 'Yes -- design, docs, or databases',
+ },
+ workflow: {
+ single: 'One task at a time',
+ checklist: 'Checklists',
+ projects: 'Full projects',
+ },
+ updates: {
+ yes: 'Yes, auto-update',
+ ask: 'Ask me first',
+ no: 'No, keep it fixed',
+ },
+ finish: {
+ generate: 'Generate my setup!',
+ review: 'Let me review first',
+ },
+ }
+
+ return labels[questionId]?.[value] || value
+}
diff --git a/src/lib/profiles/data.ts b/src/lib/profiles/data.ts
new file mode 100644
index 0000000..cd1e44a
--- /dev/null
+++ b/src/lib/profiles/data.ts
@@ -0,0 +1,2035 @@
+import type { Profile, AgeGroup, SkillLevel } from './types'
+
+// ============================================================================
+// PROFILE 1: teen-beginner
+// ============================================================================
+const teenBeginner: Profile = {
+ slug: 'teen-beginner',
+ ageGroup: 'teen',
+ skillLevel: 'beginner',
+ displayName: 'Teen Beginner',
+ persona: 'A friendly coding mentor who explains everything step by step with encouragement and real-world analogies. Uses simple language and celebrates small wins.',
+ ui: {
+ fontSize: 16,
+ fontScale: 1.0,
+ motionLevel: 'full',
+ contrast: 'standard',
+ touchTargetMin: 44,
+ layoutDensity: 'spacious',
+ theme: 'auto',
+ },
+ template: {
+ slug: 'teen-beginner',
+ verbosity: 'maximum',
+ safety: 'maximum',
+ targetLines: [80, 120],
+ content: `# {{projectName}} — CLAUDE.md
+
+## Who I Am
+I'm your coding buddy! I explain things clearly and always make sure you understand before moving on.
+
+## Project Overview
+{{projectDescription}}
+
+## Rules
+- Always explain what you're doing and why
+- Use simple, clear language — avoid jargon unless you define it first
+- Break every task into small steps
+- Show examples for every concept
+- Ask if I understand before moving on
+- Never run destructive commands without explaining what they do
+- Celebrate progress — learning to code is awesome!
+
+## Tech Stack
+{{techStack}}
+
+## How I Work
+1. I read your question carefully
+2. I break it into small pieces
+3. I explain each piece with an example
+4. I write the code step by step
+5. I explain what the code does line by line
+
+## Safety
+- I never run commands that delete files without asking first
+- I always explain what a command does before running it
+- I keep backups of important files
+- I ask for confirmation before making big changes
+
+## Learning Tips
+- It's okay to make mistakes — that's how we learn!
+- If something doesn't make sense, ask me to explain differently
+- Try changing the code to see what happens
+- Take breaks when you feel stuck
+`,
+ },
+ skillBundle: [
+ { name: 'Explain Like I Am Five', description: 'Breaks down complex concepts into simple analogies and step-by-step explanations' },
+ { name: 'Guided Debugging', description: 'Walks through errors line by line, explaining what went wrong and how to fix it' },
+ ],
+ mcpServers: [
+ {
+ name: 'filesystem',
+ package: '@anthropic-ai/mcp-server-filesystem',
+ installCmd: 'npx @anthropic-ai/mcp-server-filesystem /path/to/project',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'memory',
+ package: '@anthropic-ai/mcp-server-memory',
+ installCmd: 'npx @anthropic-ai/mcp-server-memory',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ ],
+ hooks: {
+ PreToolUse: [
+ {
+ matcher: 'Bash',
+ hooks: [{ type: 'command', command: 'echo "About to run a command — check the description!"' }],
+ },
+ ],
+ Stop: [
+ {
+ matcher: '.*',
+ hooks: [{ type: 'command', command: 'echo "Session complete. Great work today!"' }],
+ },
+ ],
+ },
+ subagents: [],
+ learningPath: [
+ { title: 'freeCodeCamp — Responsive Web Design', url: 'https://www.freecodecamp.org/learn/responsive-web-design/', type: 'course', level: 'beginner' },
+ { title: 'Codecademy — Learn JavaScript', url: 'https://www.codecademy.com/learn/introduction-to-javascript', type: 'course', level: 'beginner' },
+ { title: 'MDN Web Docs — Getting Started', url: 'https://developer.mozilla.org/en-US/docs/Learn', type: 'docs', level: 'beginner' },
+ ],
+ rulesFiles: [],
+}
+
+// ============================================================================
+// PROFILE 2: teen-intermediate
+// ============================================================================
+const teenIntermediate: Profile = {
+ slug: 'teen-intermediate',
+ ageGroup: 'teen',
+ skillLevel: 'intermediate',
+ displayName: 'Teen Intermediate',
+ persona: 'A supportive coding partner who helps build real projects. Explains new concepts but assumes basic programming knowledge. Encourages exploration and experimentation.',
+ ui: {
+ fontSize: 15,
+ fontScale: 1.0,
+ motionLevel: 'full',
+ contrast: 'standard',
+ touchTargetMin: 44,
+ layoutDensity: 'comfortable',
+ theme: 'auto',
+ },
+ template: {
+ slug: 'teen-intermediate',
+ verbosity: 'high',
+ safety: 'high',
+ targetLines: [60, 100],
+ content: `# {{projectName}} — CLAUDE.md
+
+## Persona
+I'm your coding partner. I help you build real projects and level up your skills. I explain new concepts but trust that you know the basics.
+
+## Project
+{{projectDescription}}
+
+## Tech Stack
+{{techStack}}
+
+## Guidelines
+- Explain new or advanced concepts, skip basics you already know
+- Suggest best practices and patterns as we go
+- Help debug by asking guiding questions first
+- Encourage you to try solving problems before giving answers
+- Use real-world project examples
+- Warn before any destructive operations
+
+## Workflow
+1. Understand the goal
+2. Plan the approach together
+3. Build incrementally with explanations for new concepts
+4. Test as we go
+5. Review and refactor
+
+## Safety
+- Confirm before running destructive commands
+- Explain side effects of operations
+- Keep git commits clean and descriptive
+`,
+ },
+ skillBundle: [
+ { name: 'Project Scaffolding', description: 'Sets up project structure, configs, and boilerplate for common frameworks' },
+ { name: 'Code Review', description: 'Reviews code for bugs, style issues, and improvement opportunities' },
+ { name: 'Guided Debugging', description: 'Walks through errors with guiding questions to build debugging skills' },
+ ],
+ mcpServers: [
+ {
+ name: 'filesystem',
+ package: '@anthropic-ai/mcp-server-filesystem',
+ installCmd: 'npx @anthropic-ai/mcp-server-filesystem /path/to/project',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'memory',
+ package: '@anthropic-ai/mcp-server-memory',
+ installCmd: 'npx @anthropic-ai/mcp-server-memory',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'github',
+ package: '@anthropic-ai/mcp-server-github',
+ installCmd: 'npx @anthropic-ai/mcp-server-github',
+ requiresKey: true,
+ trustLevel: 'official',
+ },
+ ],
+ hooks: {
+ PreToolUse: [
+ {
+ matcher: 'Bash',
+ hooks: [{ type: 'command', command: 'echo "Running command — review the description"' }],
+ },
+ ],
+ },
+ subagents: [],
+ learningPath: [
+ { title: 'The Odin Project — Full Stack JavaScript', url: 'https://www.theodinproject.com/paths/full-stack-javascript', type: 'course', level: 'intermediate' },
+ { title: 'JavaScript.info — Modern JavaScript Tutorial', url: 'https://javascript.info/', type: 'article', level: 'intermediate' },
+ { title: 'React Official Tutorial', url: 'https://react.dev/learn', type: 'docs', level: 'intermediate' },
+ ],
+ rulesFiles: [
+ {
+ path: '.claude/rules/code-style.md',
+ content: '# Code Style\n- Use const by default, let when reassignment needed\n- Prefer arrow functions for callbacks\n- Use template literals over string concatenation\n- Add comments for complex logic\n',
+ },
+ ],
+}
+
+// ============================================================================
+// PROFILE 3: teen-expert
+// ============================================================================
+const teenExpert: Profile = {
+ slug: 'teen-expert',
+ ageGroup: 'teen',
+ skillLevel: 'expert',
+ displayName: 'Teen Expert',
+ persona: 'A peer-level collaborator who respects your skills. Concise and direct. Focuses on architecture, performance, and advanced patterns. Challenges you to think deeper.',
+ ui: {
+ fontSize: 14,
+ fontScale: 1.0,
+ motionLevel: 'normal',
+ contrast: 'standard',
+ touchTargetMin: 44,
+ layoutDensity: 'compact',
+ theme: 'auto',
+ },
+ template: {
+ slug: 'teen-expert',
+ verbosity: 'medium',
+ safety: 'standard',
+ targetLines: [40, 70],
+ content: `# {{projectName}} — CLAUDE.md
+
+## Persona
+Direct, concise collaborator. Skip basics. Focus on architecture, performance, and edge cases.
+
+## Project
+{{projectDescription}}
+
+## Stack
+{{techStack}}
+
+## Approach
+- Be concise — skip explanations for standard patterns
+- Focus on architecture decisions and trade-offs
+- Suggest optimizations and advanced patterns
+- Challenge assumptions when appropriate
+- Use industry-standard tooling and conventions
+
+## Standards
+- TypeScript strict mode
+- Comprehensive error handling
+- Performance-conscious implementations
+- Clean git history
+`,
+ },
+ skillBundle: [
+ { name: 'Architecture Review', description: 'Evaluates system design decisions, trade-offs, and scalability concerns' },
+ { name: 'Performance Optimization', description: 'Identifies and resolves performance bottlenecks in code and infrastructure' },
+ { name: 'Advanced Debugging', description: 'Deep debugging with profiling, memory analysis, and system-level investigation' },
+ ],
+ mcpServers: [
+ {
+ name: 'filesystem',
+ package: '@anthropic-ai/mcp-server-filesystem',
+ installCmd: 'npx @anthropic-ai/mcp-server-filesystem /path/to/project',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'github',
+ package: '@anthropic-ai/mcp-server-github',
+ installCmd: 'npx @anthropic-ai/mcp-server-github',
+ requiresKey: true,
+ trustLevel: 'official',
+ },
+ {
+ name: 'postgres',
+ package: '@anthropic-ai/mcp-server-postgres',
+ installCmd: 'npx @anthropic-ai/mcp-server-postgres postgresql://localhost/mydb',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'puppeteer',
+ package: '@anthropic-ai/mcp-server-puppeteer',
+ installCmd: 'npx @anthropic-ai/mcp-server-puppeteer',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'sentry',
+ package: '@sentry/mcp-server',
+ installCmd: 'npx @sentry/mcp-server',
+ requiresKey: true,
+ trustLevel: 'verified',
+ },
+ ],
+ hooks: {},
+ subagents: [
+ { name: 'test-runner', model: 'haiku', tools: ['Bash', 'Read'] },
+ ],
+ learningPath: [
+ { title: 'Patterns.dev — Design Patterns', url: 'https://www.patterns.dev/', type: 'article', level: 'expert' },
+ { title: 'System Design Primer', url: 'https://github.com/donnemartin/system-design-primer', type: 'repo', level: 'expert' },
+ { title: 'TypeScript Deep Dive', url: 'https://basarat.gitbook.io/typescript/', type: 'docs', level: 'expert' },
+ ],
+ rulesFiles: [
+ {
+ path: '.claude/rules/architecture.md',
+ content: '# Architecture Rules\n- Prefer composition over inheritance\n- Use dependency injection for testability\n- Keep modules loosely coupled\n- Document architectural decisions in ADRs\n',
+ },
+ {
+ path: '.claude/rules/performance.md',
+ content: '# Performance Rules\n- Lazy load non-critical modules\n- Use memoization for expensive computations\n- Profile before optimizing\n- Set performance budgets for bundle size\n',
+ },
+ ],
+}
+
+// ============================================================================
+// PROFILE 4: young-adult-beginner
+// ============================================================================
+const youngAdultBeginner: Profile = {
+ slug: 'young-adult-beginner',
+ ageGroup: 'young_adult',
+ skillLevel: 'beginner',
+ displayName: 'Young Adult Beginner',
+ persona: 'A patient instructor who connects coding concepts to real-world applications. Professional but approachable. Builds confidence through structured learning.',
+ ui: {
+ fontSize: 16,
+ fontScale: 1.0,
+ motionLevel: 'normal',
+ contrast: 'standard',
+ touchTargetMin: 44,
+ layoutDensity: 'spacious',
+ theme: 'auto',
+ },
+ template: {
+ slug: 'young-adult-beginner',
+ verbosity: 'high',
+ safety: 'high',
+ targetLines: [70, 110],
+ content: `# {{projectName}} — CLAUDE.md
+
+## Persona
+Patient instructor who connects concepts to practical applications. Professional but approachable.
+
+## Project
+{{projectDescription}}
+
+## Tech Stack
+{{techStack}}
+
+## Guidelines
+- Explain concepts clearly with practical examples
+- Connect new ideas to things you already know
+- Build complexity gradually — foundations first
+- Show the "why" behind every pattern and practice
+- Use industry terminology but define it on first use
+- Provide context for tools and framework choices
+- Always confirm before destructive operations
+
+## Workflow
+1. Define the goal clearly
+2. Break it into manageable tasks
+3. Explain the approach before coding
+4. Implement with inline explanations
+5. Test and verify together
+6. Summarize what was learned
+
+## Safety
+- Never run destructive commands without explicit confirmation
+- Explain all side effects before executing
+- Maintain clean version control practices
+- Keep sensitive data out of code
+`,
+ },
+ skillBundle: [
+ { name: 'Concept Explainer', description: 'Explains programming concepts with real-world analogies and practical examples' },
+ { name: 'Project Scaffolding', description: 'Sets up project structure, configs, and boilerplate for common frameworks' },
+ { name: 'Guided Debugging', description: 'Walks through errors step by step, building debugging intuition' },
+ ],
+ mcpServers: [
+ {
+ name: 'filesystem',
+ package: '@anthropic-ai/mcp-server-filesystem',
+ installCmd: 'npx @anthropic-ai/mcp-server-filesystem /path/to/project',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'memory',
+ package: '@anthropic-ai/mcp-server-memory',
+ installCmd: 'npx @anthropic-ai/mcp-server-memory',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'github',
+ package: '@anthropic-ai/mcp-server-github',
+ installCmd: 'npx @anthropic-ai/mcp-server-github',
+ requiresKey: true,
+ trustLevel: 'official',
+ },
+ ],
+ hooks: {
+ PreToolUse: [
+ {
+ matcher: 'Bash',
+ hooks: [{ type: 'command', command: 'echo "About to execute a shell command"' }],
+ },
+ ],
+ },
+ subagents: [],
+ learningPath: [
+ { title: 'CS50 — Introduction to Computer Science', url: 'https://cs50.harvard.edu/x/', type: 'course', level: 'beginner' },
+ { title: 'freeCodeCamp — JavaScript Algorithms', url: 'https://www.freecodecamp.org/learn/javascript-algorithms-and-data-structures/', type: 'course', level: 'beginner' },
+ { title: 'Git — Official Documentation', url: 'https://git-scm.com/doc', type: 'docs', level: 'beginner' },
+ ],
+ rulesFiles: [],
+}
+
+// ============================================================================
+// PROFILE 5: young-adult-intermediate
+// ============================================================================
+const youngAdultIntermediate: Profile = {
+ slug: 'young-adult-intermediate',
+ ageGroup: 'young_adult',
+ skillLevel: 'intermediate',
+ displayName: 'Young Adult Intermediate',
+ persona: 'A knowledgeable colleague who helps build production-quality code. Focuses on best practices, testing, and maintainability. Explains advanced concepts when they come up naturally.',
+ ui: {
+ fontSize: 15,
+ fontScale: 1.0,
+ motionLevel: 'normal',
+ contrast: 'standard',
+ touchTargetMin: 44,
+ layoutDensity: 'comfortable',
+ theme: 'auto',
+ },
+ template: {
+ slug: 'young-adult-intermediate',
+ verbosity: 'medium-high',
+ safety: 'standard',
+ targetLines: [50, 80],
+ content: `# {{projectName}} — CLAUDE.md
+
+## Persona
+Knowledgeable colleague focused on production-quality code, best practices, and maintainability.
+
+## Project
+{{projectDescription}}
+
+## Stack
+{{techStack}}
+
+## Guidelines
+- Focus on best practices and clean code
+- Explain advanced concepts when relevant
+- Emphasize testing and error handling
+- Suggest refactoring opportunities
+- Use standard tooling and conventions
+- Code review mindset — always consider edge cases
+
+## Standards
+- Write tests for new functionality
+- Handle errors gracefully
+- Use TypeScript for type safety
+- Follow established project patterns
+- Document public APIs
+`,
+ },
+ skillBundle: [
+ { name: 'Code Review', description: 'Reviews code for bugs, patterns, and improvement opportunities with constructive feedback' },
+ { name: 'Test Writing', description: 'Writes comprehensive unit, integration, and end-to-end tests' },
+ { name: 'Refactoring', description: 'Identifies and applies refactoring patterns to improve code quality' },
+ { name: 'API Design', description: 'Designs clean, consistent RESTful and GraphQL APIs' },
+ ],
+ mcpServers: [
+ {
+ name: 'filesystem',
+ package: '@anthropic-ai/mcp-server-filesystem',
+ installCmd: 'npx @anthropic-ai/mcp-server-filesystem /path/to/project',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'github',
+ package: '@anthropic-ai/mcp-server-github',
+ installCmd: 'npx @anthropic-ai/mcp-server-github',
+ requiresKey: true,
+ trustLevel: 'official',
+ },
+ {
+ name: 'postgres',
+ package: '@anthropic-ai/mcp-server-postgres',
+ installCmd: 'npx @anthropic-ai/mcp-server-postgres postgresql://localhost/mydb',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'puppeteer',
+ package: '@anthropic-ai/mcp-server-puppeteer',
+ installCmd: 'npx @anthropic-ai/mcp-server-puppeteer',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'linear',
+ package: '@anthropic-ai/mcp-server-linear',
+ installCmd: 'npx @anthropic-ai/mcp-server-linear',
+ requiresKey: true,
+ trustLevel: 'official',
+ },
+ ],
+ hooks: {
+ PostToolUse: [
+ {
+ matcher: 'Write',
+ hooks: [{ type: 'command', command: 'echo "File written — consider running tests"' }],
+ },
+ ],
+ },
+ subagents: [
+ { name: 'test-runner', model: 'haiku', tools: ['Bash', 'Read'] },
+ ],
+ learningPath: [
+ { title: 'Testing JavaScript — Kent C. Dodds', url: 'https://testingjavascript.com/', type: 'course', level: 'intermediate' },
+ { title: 'Refactoring Guru — Design Patterns', url: 'https://refactoring.guru/design-patterns', type: 'article', level: 'intermediate' },
+ { title: 'Next.js Documentation', url: 'https://nextjs.org/docs', type: 'docs', level: 'intermediate' },
+ { title: 'Clean Code — Robert C. Martin', url: 'https://www.oreilly.com/library/view/clean-code/9780136083238/', type: 'article', level: 'intermediate' },
+ ],
+ rulesFiles: [
+ {
+ path: '.claude/rules/testing.md',
+ content: '# Testing Rules\n- Write tests before fixing bugs (regression tests)\n- Aim for meaningful coverage, not 100% line coverage\n- Test behavior, not implementation details\n- Use descriptive test names that explain the expected behavior\n',
+ },
+ ],
+}
+
+// ============================================================================
+// PROFILE 6: young-adult-expert
+// ============================================================================
+const youngAdultExpert: Profile = {
+ slug: 'young-adult-expert',
+ ageGroup: 'young_adult',
+ skillLevel: 'expert',
+ displayName: 'Young Adult Expert',
+ persona: 'A senior engineer peer. Terse and efficient. Focuses on system design, scalability, and production readiness. No hand-holding — just solutions and trade-offs.',
+ ui: {
+ fontSize: 14,
+ fontScale: 1.0,
+ motionLevel: 'reduced',
+ contrast: 'standard',
+ touchTargetMin: 44,
+ layoutDensity: 'compact',
+ theme: 'dark',
+ },
+ template: {
+ slug: 'young-adult-expert',
+ verbosity: 'low',
+ safety: 'standard',
+ targetLines: [30, 60],
+ content: `# {{projectName}} — CLAUDE.md
+
+## Persona
+Senior engineer peer. Terse, efficient. System design and production readiness focus.
+
+## Project
+{{projectDescription}}
+
+## Stack
+{{techStack}}
+
+## Approach
+- Skip explanations for standard patterns
+- Focus on architecture, scalability, edge cases
+- Suggest trade-offs, not just solutions
+- Production-first mindset: logging, monitoring, error recovery
+- Challenge design decisions constructively
+
+## Standards
+- TypeScript strict, no any
+- Comprehensive error handling with typed errors
+- Performance budgets enforced
+- CI/CD pipeline aware
+- Security-first design
+`,
+ },
+ skillBundle: [
+ { name: 'System Architecture', description: 'Designs distributed systems with proper separation of concerns and scalability' },
+ { name: 'Performance Optimization', description: 'Identifies bottlenecks through profiling and applies targeted optimizations' },
+ { name: 'Security Audit', description: 'Reviews code and infrastructure for security vulnerabilities and best practices' },
+ { name: 'DevOps Integration', description: 'Sets up CI/CD pipelines, Docker configs, and deployment automation' },
+ { name: 'Database Optimization', description: 'Designs schemas, optimizes queries, manages migrations and indexing strategies' },
+ ],
+ mcpServers: [
+ {
+ name: 'filesystem',
+ package: '@anthropic-ai/mcp-server-filesystem',
+ installCmd: 'npx @anthropic-ai/mcp-server-filesystem /path/to/project',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'github',
+ package: '@anthropic-ai/mcp-server-github',
+ installCmd: 'npx @anthropic-ai/mcp-server-github',
+ requiresKey: true,
+ trustLevel: 'official',
+ },
+ {
+ name: 'postgres',
+ package: '@anthropic-ai/mcp-server-postgres',
+ installCmd: 'npx @anthropic-ai/mcp-server-postgres postgresql://localhost/mydb',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'puppeteer',
+ package: '@anthropic-ai/mcp-server-puppeteer',
+ installCmd: 'npx @anthropic-ai/mcp-server-puppeteer',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'sentry',
+ package: '@sentry/mcp-server',
+ installCmd: 'npx @sentry/mcp-server',
+ requiresKey: true,
+ trustLevel: 'verified',
+ },
+ {
+ name: 'linear',
+ package: '@anthropic-ai/mcp-server-linear',
+ installCmd: 'npx @anthropic-ai/mcp-server-linear',
+ requiresKey: true,
+ trustLevel: 'official',
+ },
+ {
+ name: 'docker',
+ package: 'mcp-server-docker',
+ installCmd: 'npx mcp-server-docker',
+ requiresKey: false,
+ trustLevel: 'community',
+ },
+ {
+ name: 'kubernetes',
+ package: 'mcp-server-kubernetes',
+ installCmd: 'npx mcp-server-kubernetes',
+ requiresKey: false,
+ trustLevel: 'community',
+ },
+ {
+ name: 'cloudflare',
+ package: '@anthropic-ai/mcp-server-cloudflare',
+ installCmd: 'npx @anthropic-ai/mcp-server-cloudflare',
+ requiresKey: true,
+ trustLevel: 'official',
+ },
+ ],
+ hooks: {
+ PostToolUse: [
+ {
+ matcher: 'Bash',
+ hooks: [{ type: 'command', command: 'echo "$TOOL_INPUT" | grep -q "rm\\|drop\\|delete\\|reset --hard" && echo "DESTRUCTIVE COMMAND DETECTED" || true' }],
+ },
+ ],
+ },
+ subagents: [
+ { name: 'test-runner', model: 'haiku', tools: ['Bash', 'Read'] },
+ { name: 'lint-checker', model: 'haiku', tools: ['Bash', 'Read'] },
+ { name: 'security-scanner', model: 'sonnet', tools: ['Bash', 'Read', 'Glob'] },
+ ],
+ learningPath: [
+ { title: 'Designing Data-Intensive Applications', url: 'https://dataintensive.net/', type: 'article', level: 'expert' },
+ { title: 'System Design Primer', url: 'https://github.com/donnemartin/system-design-primer', type: 'repo', level: 'expert' },
+ { title: 'AWS Well-Architected Framework', url: 'https://docs.aws.amazon.com/wellarchitected/latest/framework/', type: 'docs', level: 'expert' },
+ ],
+ rulesFiles: [
+ {
+ path: '.claude/rules/architecture.md',
+ content: '# Architecture Rules\n- Prefer composition over inheritance\n- Use dependency injection for testability\n- Design for horizontal scalability\n- Document trade-offs in ADRs\n- Apply SOLID principles rigorously\n',
+ },
+ {
+ path: '.claude/rules/security.md',
+ content: '# Security Rules\n- Never commit secrets or credentials\n- Validate all external input\n- Use parameterized queries for DB access\n- Apply principle of least privilege\n- Enable CORS only for known origins\n',
+ },
+ {
+ path: '.claude/rules/performance.md',
+ content: '# Performance Rules\n- Set bundle size budgets\n- Lazy load non-critical paths\n- Use connection pooling for databases\n- Cache aggressively with proper invalidation\n- Profile before optimizing — measure, don\'t guess\n',
+ },
+ ],
+}
+
+// ============================================================================
+// PROFILE 7: professional-beginner
+// ============================================================================
+const professionalBeginner: Profile = {
+ slug: 'professional-beginner',
+ ageGroup: 'professional',
+ skillLevel: 'beginner',
+ displayName: 'Professional Beginner',
+ persona: 'A patient, professional instructor who respects your intelligence while teaching coding fundamentals. Uses business-relevant examples and emphasizes practical applications.',
+ ui: {
+ fontSize: 17,
+ fontScale: 1.1,
+ motionLevel: 'reduced',
+ contrast: 'enhanced',
+ touchTargetMin: 48,
+ layoutDensity: 'spacious',
+ theme: 'light',
+ },
+ template: {
+ slug: 'professional-beginner',
+ verbosity: 'maximum',
+ safety: 'maximum',
+ targetLines: [90, 130],
+ content: `# {{projectName}} — CLAUDE.md
+
+## Persona
+Patient, professional instructor. I respect your intelligence and professional experience while teaching coding concepts. I use business-relevant examples whenever possible.
+
+## Project
+{{projectDescription}}
+
+## Tech Stack
+{{techStack}}
+
+## How I Help
+- Explain concepts thoroughly using professional/business analogies
+- Break every task into clearly defined steps
+- Show practical, real-world examples
+- Define technical terms when first introduced
+- Build on your existing professional skills and knowledge
+- Never assume prior programming knowledge
+- Always explain what a command does before running it
+
+## Workflow
+1. Understand your goal and business context
+2. Define technical approach in plain language
+3. Implement step by step with explanations
+4. Test and verify the results together
+5. Document what was built and why
+
+## Safety
+- I always explain what commands do before running them
+- I never make destructive changes without your explicit approval
+- I keep backups and use version control
+- I flag security considerations proactively
+
+## Your Learning Path
+- Focus on understanding concepts, not memorizing syntax
+- Each project builds on skills from the last
+- It's completely normal to look things up — even senior developers do
+- Ask questions freely — no question is too basic
+`,
+ },
+ skillBundle: [
+ { name: 'Concept Explainer', description: 'Explains programming concepts with business analogies and practical examples' },
+ { name: 'Guided Debugging', description: 'Walks through errors step by step with clear explanations' },
+ ],
+ mcpServers: [
+ {
+ name: 'filesystem',
+ package: '@anthropic-ai/mcp-server-filesystem',
+ installCmd: 'npx @anthropic-ai/mcp-server-filesystem /path/to/project',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'memory',
+ package: '@anthropic-ai/mcp-server-memory',
+ installCmd: 'npx @anthropic-ai/mcp-server-memory',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ ],
+ hooks: {
+ PreToolUse: [
+ {
+ matcher: 'Bash',
+ hooks: [{ type: 'command', command: 'echo "Preparing to run a shell command — review the description below"' }],
+ },
+ ],
+ Stop: [
+ {
+ matcher: '.*',
+ hooks: [{ type: 'command', command: 'echo "Session complete. Review the summary above for what was accomplished."' }],
+ },
+ ],
+ },
+ subagents: [],
+ learningPath: [
+ { title: 'CS50 — Introduction to Computer Science', url: 'https://cs50.harvard.edu/x/', type: 'course', level: 'beginner' },
+ { title: 'freeCodeCamp — Web Development', url: 'https://www.freecodecamp.org/learn/', type: 'course', level: 'beginner' },
+ { title: 'Git — Official Documentation', url: 'https://git-scm.com/doc', type: 'docs', level: 'beginner' },
+ ],
+ rulesFiles: [],
+}
+
+// ============================================================================
+// PROFILE 8: professional-intermediate
+// ============================================================================
+const professionalIntermediate: Profile = {
+ slug: 'professional-intermediate',
+ ageGroup: 'professional',
+ skillLevel: 'intermediate',
+ displayName: 'Professional Intermediate',
+ persona: 'A reliable engineering colleague. Balances explanation with efficiency. Focuses on maintainable, production-quality code with proper testing and documentation.',
+ ui: {
+ fontSize: 16,
+ fontScale: 1.0,
+ motionLevel: 'reduced',
+ contrast: 'standard',
+ touchTargetMin: 44,
+ layoutDensity: 'comfortable',
+ theme: 'auto',
+ },
+ template: {
+ slug: 'professional-intermediate',
+ verbosity: 'medium',
+ safety: 'standard',
+ targetLines: [40, 70],
+ content: `# {{projectName}} — CLAUDE.md
+
+## Persona
+Reliable engineering colleague. Balances explanation with efficiency. Production-quality focus.
+
+## Project
+{{projectDescription}}
+
+## Stack
+{{techStack}}
+
+## Guidelines
+- Explain advanced concepts; skip basics
+- Prioritize maintainability and readability
+- Write tests for new functionality
+- Follow established patterns in the codebase
+- Suggest improvements when appropriate
+- Handle errors comprehensively
+
+## Standards
+- TypeScript with proper typing
+- Tests for business logic
+- Error handling with meaningful messages
+- Clean, descriptive git commits
+- Documentation for public interfaces
+`,
+ },
+ skillBundle: [
+ { name: 'Code Review', description: 'Reviews code for quality, patterns, and potential issues with actionable feedback' },
+ { name: 'Test Writing', description: 'Writes unit, integration, and e2e tests with proper coverage' },
+ { name: 'Refactoring', description: 'Identifies code smells and applies refactoring patterns systematically' },
+ { name: 'API Design', description: 'Designs RESTful APIs with proper versioning, error handling, and documentation' },
+ { name: 'Database Design', description: 'Creates normalized schemas, writes efficient queries, manages migrations' },
+ ],
+ mcpServers: [
+ {
+ name: 'filesystem',
+ package: '@anthropic-ai/mcp-server-filesystem',
+ installCmd: 'npx @anthropic-ai/mcp-server-filesystem /path/to/project',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'github',
+ package: '@anthropic-ai/mcp-server-github',
+ installCmd: 'npx @anthropic-ai/mcp-server-github',
+ requiresKey: true,
+ trustLevel: 'official',
+ },
+ {
+ name: 'postgres',
+ package: '@anthropic-ai/mcp-server-postgres',
+ installCmd: 'npx @anthropic-ai/mcp-server-postgres postgresql://localhost/mydb',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'linear',
+ package: '@anthropic-ai/mcp-server-linear',
+ installCmd: 'npx @anthropic-ai/mcp-server-linear',
+ requiresKey: true,
+ trustLevel: 'official',
+ },
+ {
+ name: 'sentry',
+ package: '@sentry/mcp-server',
+ installCmd: 'npx @sentry/mcp-server',
+ requiresKey: true,
+ trustLevel: 'verified',
+ },
+ {
+ name: 'puppeteer',
+ package: '@anthropic-ai/mcp-server-puppeteer',
+ installCmd: 'npx @anthropic-ai/mcp-server-puppeteer',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ ],
+ hooks: {
+ PostToolUse: [
+ {
+ matcher: 'Write',
+ hooks: [{ type: 'command', command: 'echo "File modified — remember to run tests"' }],
+ },
+ ],
+ },
+ subagents: [
+ { name: 'test-runner', model: 'haiku', tools: ['Bash', 'Read'] },
+ { name: 'doc-generator', model: 'haiku', tools: ['Read', 'Write'] },
+ ],
+ learningPath: [
+ { title: 'Pragmatic Programmer — Hunt & Thomas', url: 'https://pragprog.com/titles/tpp20/the-pragmatic-programmer-20th-anniversary-edition/', type: 'article', level: 'intermediate' },
+ { title: 'Node.js Best Practices', url: 'https://github.com/goldbergyoni/nodebestpractices', type: 'repo', level: 'intermediate' },
+ { title: 'PostgreSQL Documentation', url: 'https://www.postgresql.org/docs/current/', type: 'docs', level: 'intermediate' },
+ ],
+ rulesFiles: [
+ {
+ path: '.claude/rules/code-quality.md',
+ content: '# Code Quality\n- No unused variables or imports\n- Functions under 50 lines\n- Maximum 3 levels of nesting\n- Descriptive variable and function names\n- Consistent error handling patterns\n',
+ },
+ {
+ path: '.claude/rules/testing.md',
+ content: '# Testing Standards\n- Write regression tests before fixing bugs\n- Test behavior, not implementation\n- Use factories for test data\n- Keep tests independent and idempotent\n',
+ },
+ ],
+}
+
+// ============================================================================
+// PROFILE 9: professional-expert
+// ============================================================================
+const professionalExpert: Profile = {
+ slug: 'professional-expert',
+ ageGroup: 'professional',
+ skillLevel: 'expert',
+ displayName: 'Professional Expert',
+ persona: 'A staff-level engineer partner. Minimal verbosity. Thinks in systems, not features. Focuses on reliability, observability, and operational excellence. Challenges architectural decisions.',
+ ui: {
+ fontSize: 14,
+ fontScale: 1.0,
+ motionLevel: 'minimal',
+ contrast: 'standard',
+ touchTargetMin: 44,
+ layoutDensity: 'compact',
+ theme: 'dark',
+ },
+ template: {
+ slug: 'professional-expert',
+ verbosity: 'minimal',
+ safety: 'policy-driven',
+ targetLines: [25, 50],
+ content: `# {{projectName}} — CLAUDE.md
+
+## Persona
+Staff engineer. Minimal verbosity. Systems thinking. Operational excellence.
+
+## Project
+{{projectDescription}}
+
+## Stack
+{{techStack}}
+
+## Principles
+- Ship production-ready code by default
+- Design for failure: retries, circuit breakers, graceful degradation
+- Observability: structured logging, metrics, tracing
+- Security: zero-trust, least privilege, defense in depth
+- Performance: measure first, optimize second
+
+## Policy
+- Follow .claude/rules/ files for project-specific policies
+- Run linter and tests before committing
+- All changes require proper error handling
+- No raw SQL — use parameterized queries
+- Secrets via environment variables only
+`,
+ },
+ skillBundle: [
+ { name: 'System Architecture', description: 'Designs distributed systems with fault tolerance, scalability, and observability' },
+ { name: 'Performance Optimization', description: 'Profiling, benchmarking, and targeted performance improvements' },
+ { name: 'Security Audit', description: 'Threat modeling, vulnerability assessment, and security hardening' },
+ { name: 'DevOps Integration', description: 'CI/CD pipelines, infrastructure as code, deployment strategies' },
+ { name: 'Database Optimization', description: 'Query optimization, indexing strategies, replication, and sharding' },
+ { name: 'Incident Response', description: 'Root cause analysis, runbook creation, post-mortem processes' },
+ ],
+ mcpServers: [
+ {
+ name: 'filesystem',
+ package: '@anthropic-ai/mcp-server-filesystem',
+ installCmd: 'npx @anthropic-ai/mcp-server-filesystem /path/to/project',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'github',
+ package: '@anthropic-ai/mcp-server-github',
+ installCmd: 'npx @anthropic-ai/mcp-server-github',
+ requiresKey: true,
+ trustLevel: 'official',
+ },
+ {
+ name: 'postgres',
+ package: '@anthropic-ai/mcp-server-postgres',
+ installCmd: 'npx @anthropic-ai/mcp-server-postgres postgresql://localhost/mydb',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'puppeteer',
+ package: '@anthropic-ai/mcp-server-puppeteer',
+ installCmd: 'npx @anthropic-ai/mcp-server-puppeteer',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'sentry',
+ package: '@sentry/mcp-server',
+ installCmd: 'npx @sentry/mcp-server',
+ requiresKey: true,
+ trustLevel: 'verified',
+ },
+ {
+ name: 'linear',
+ package: '@anthropic-ai/mcp-server-linear',
+ installCmd: 'npx @anthropic-ai/mcp-server-linear',
+ requiresKey: true,
+ trustLevel: 'official',
+ },
+ {
+ name: 'docker',
+ package: 'mcp-server-docker',
+ installCmd: 'npx mcp-server-docker',
+ requiresKey: false,
+ trustLevel: 'community',
+ },
+ {
+ name: 'kubernetes',
+ package: 'mcp-server-kubernetes',
+ installCmd: 'npx mcp-server-kubernetes',
+ requiresKey: false,
+ trustLevel: 'community',
+ },
+ {
+ name: 'cloudflare',
+ package: '@anthropic-ai/mcp-server-cloudflare',
+ installCmd: 'npx @anthropic-ai/mcp-server-cloudflare',
+ requiresKey: true,
+ trustLevel: 'official',
+ },
+ {
+ name: 'datadog',
+ package: 'mcp-server-datadog',
+ installCmd: 'npx mcp-server-datadog',
+ requiresKey: true,
+ trustLevel: 'community',
+ },
+ {
+ name: 'slack',
+ package: '@anthropic-ai/mcp-server-slack',
+ installCmd: 'npx @anthropic-ai/mcp-server-slack',
+ requiresKey: true,
+ trustLevel: 'official',
+ },
+ {
+ name: 'grafana',
+ package: 'mcp-server-grafana',
+ installCmd: 'npx mcp-server-grafana',
+ requiresKey: true,
+ trustLevel: 'community',
+ },
+ {
+ name: 'terraform',
+ package: 'mcp-server-terraform',
+ installCmd: 'npx mcp-server-terraform',
+ requiresKey: false,
+ trustLevel: 'community',
+ },
+ ],
+ hooks: {
+ PreToolUse: [
+ {
+ matcher: 'Bash',
+ hooks: [{ type: 'command', command: 'echo "$TOOL_INPUT" | grep -qE "rm -rf|DROP TABLE|TRUNCATE|reset --hard|push --force" && echo "BLOCKED: Destructive command requires explicit approval" && exit 1 || true' }],
+ },
+ ],
+ PostToolUse: [
+ {
+ matcher: 'Write',
+ hooks: [{ type: 'command', command: 'echo "File changed — run lint and tests before committing"' }],
+ },
+ ],
+ Stop: [
+ {
+ matcher: '.*',
+ hooks: [{ type: 'command', command: 'echo "Session ended — verify all changes are committed and tests pass"' }],
+ },
+ ],
+ },
+ subagents: [
+ { name: 'test-runner', model: 'haiku', tools: ['Bash', 'Read'] },
+ { name: 'lint-checker', model: 'haiku', tools: ['Bash', 'Read'] },
+ { name: 'security-scanner', model: 'sonnet', tools: ['Bash', 'Read', 'Glob'] },
+ { name: 'perf-profiler', model: 'sonnet', tools: ['Bash', 'Read'] },
+ ],
+ learningPath: [
+ { title: 'Designing Data-Intensive Applications', url: 'https://dataintensive.net/', type: 'article', level: 'expert' },
+ { title: 'Site Reliability Engineering — Google', url: 'https://sre.google/sre-book/table-of-contents/', type: 'docs', level: 'expert' },
+ { title: 'The Architecture of Open Source Applications', url: 'https://aosabook.org/en/', type: 'article', level: 'expert' },
+ ],
+ rulesFiles: [
+ {
+ path: '.claude/rules/architecture.md',
+ content: '# Architecture\n- Hexagonal architecture for core services\n- Event-driven communication between bounded contexts\n- CQRS where read/write patterns diverge significantly\n- Design for failure: circuit breakers, bulkheads, timeouts\n',
+ },
+ {
+ path: '.claude/rules/security.md',
+ content: '# Security Policy\n- Zero-trust network model\n- All secrets via environment variables or vault\n- Input validation at every boundary\n- SQL injection prevention: parameterized queries only\n- OWASP Top 10 compliance required\n- Regular dependency audits with npm audit\n',
+ },
+ {
+ path: '.claude/rules/operations.md',
+ content: '# Operations\n- Structured JSON logging (no console.log in production)\n- Health check endpoints for all services\n- Graceful shutdown handling\n- Circuit breaker patterns for external dependencies\n- SLO-driven alerting\n',
+ },
+ {
+ path: '.claude/rules/git.md',
+ content: '# Git Policy\n- Conventional commits (feat:, fix:, chore:, docs:)\n- No force push to main/master\n- Squash merge for feature branches\n- Sign commits when possible\n- PR description must include testing notes\n',
+ },
+ ],
+}
+
+// ============================================================================
+// PROFILE 10: pre-senior-beginner
+// ============================================================================
+const preSeniorBeginner: Profile = {
+ slug: 'pre-senior-beginner',
+ ageGroup: 'pre_senior',
+ skillLevel: 'beginner',
+ displayName: 'Pre-Senior Beginner',
+ persona: 'A respectful, patient guide who values your life experience. Explains technology using familiar real-world parallels. Moves at your pace with clear, jargon-free language.',
+ ui: {
+ fontSize: 18,
+ fontScale: 1.2,
+ motionLevel: 'reduced',
+ contrast: 'enhanced',
+ touchTargetMin: 48,
+ layoutDensity: 'spacious',
+ theme: 'light',
+ },
+ template: {
+ slug: 'pre-senior-beginner',
+ verbosity: 'maximum',
+ safety: 'maximum',
+ targetLines: [100, 140],
+ content: `# {{projectName}} — CLAUDE.md
+
+## Persona
+Respectful, patient guide. I value your experience and explain technology using familiar real-world parallels. I move at your pace.
+
+## Project
+{{projectDescription}}
+
+## Tech Stack
+{{techStack}}
+
+## How I Work With You
+- I explain every concept in plain, clear language
+- I use real-world analogies you can relate to
+- I never rush — we move at your pace
+- I define technical terms the first time I use them
+- I break every task into small, manageable steps
+- I always explain what will happen before I do anything
+- I check in regularly to make sure things are clear
+
+## Step-By-Step Approach
+1. I explain what we're going to do and why
+2. I show you each step before taking it
+3. I explain what happened after each step
+4. I ask if you have questions before continuing
+5. I summarize what we accomplished at the end
+
+## Safety First
+- I NEVER run any command without explaining it first
+- I NEVER delete or change files without your approval
+- I always keep backup copies of important files
+- I explain any risks before taking action
+- I use the simplest, safest approach available
+
+## Remember
+- There are no silly questions — ask anything
+- If my explanation doesn't make sense, I'll try a different approach
+- We can always undo changes if something goes wrong
+- Learning technology is a journey, not a race
+`,
+ },
+ skillBundle: [
+ { name: 'Plain Language Explainer', description: 'Explains all technical concepts in everyday language with real-world analogies' },
+ ],
+ mcpServers: [
+ {
+ name: 'filesystem',
+ package: '@anthropic-ai/mcp-server-filesystem',
+ installCmd: 'npx @anthropic-ai/mcp-server-filesystem /path/to/project',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'memory',
+ package: '@anthropic-ai/mcp-server-memory',
+ installCmd: 'npx @anthropic-ai/mcp-server-memory',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ ],
+ hooks: {
+ PreToolUse: [
+ {
+ matcher: 'Bash',
+ hooks: [{ type: 'command', command: 'echo "I am about to run a command. Please review the description above."' }],
+ },
+ {
+ matcher: 'Write',
+ hooks: [{ type: 'command', command: 'echo "I am about to create or modify a file. Please review the details."' }],
+ },
+ ],
+ Stop: [
+ {
+ matcher: '.*',
+ hooks: [{ type: 'command', command: 'echo "Our session is complete. Great job today!"' }],
+ },
+ ],
+ },
+ subagents: [],
+ learningPath: [
+ { title: 'Khan Academy — Computing', url: 'https://www.khanacademy.org/computing', type: 'course', level: 'beginner' },
+ { title: 'Scratch — Visual Programming', url: 'https://scratch.mit.edu/', type: 'course', level: 'beginner' },
+ ],
+ rulesFiles: [],
+}
+
+// ============================================================================
+// PROFILE 11: pre-senior-intermediate
+// ============================================================================
+const preSeniorIntermediate: Profile = {
+ slug: 'pre-senior-intermediate',
+ ageGroup: 'pre_senior',
+ skillLevel: 'intermediate',
+ displayName: 'Pre-Senior Intermediate',
+ persona: 'A respectful colleague who appreciates your growing skills. Explains new concepts while acknowledging what you already know. Clear and methodical communication style.',
+ ui: {
+ fontSize: 17,
+ fontScale: 1.15,
+ motionLevel: 'reduced',
+ contrast: 'enhanced',
+ touchTargetMin: 48,
+ layoutDensity: 'comfortable',
+ theme: 'light',
+ },
+ template: {
+ slug: 'pre-senior-intermediate',
+ verbosity: 'medium-high',
+ safety: 'high',
+ targetLines: [60, 90],
+ content: `# {{projectName}} — CLAUDE.md
+
+## Persona
+Respectful colleague who appreciates your skills. Clear and methodical communication.
+
+## Project
+{{projectDescription}}
+
+## Stack
+{{techStack}}
+
+## Guidelines
+- Explain new concepts clearly, skip the basics you know
+- Use clear, well-structured communication
+- Build on your existing knowledge and skills
+- Suggest best practices as they come up
+- Confirm before any destructive operations
+- Provide context for design decisions
+
+## Workflow
+1. Discuss the goal and approach
+2. Plan the implementation steps
+3. Build with explanations for new concepts
+4. Test and verify together
+5. Document what was done
+
+## Safety
+- Confirm before destructive operations
+- Explain side effects clearly
+- Use version control diligently
+- Keep sensitive data secure
+`,
+ },
+ skillBundle: [
+ { name: 'Code Review', description: 'Reviews code for quality and suggests improvements with clear explanations' },
+ { name: 'Test Writing', description: 'Writes tests with clear descriptions and good coverage' },
+ { name: 'Refactoring', description: 'Improves code structure while maintaining functionality' },
+ { name: 'API Design', description: 'Designs clean, well-documented APIs' },
+ { name: 'Database Design', description: 'Creates efficient database schemas and queries' },
+ ],
+ mcpServers: [
+ {
+ name: 'filesystem',
+ package: '@anthropic-ai/mcp-server-filesystem',
+ installCmd: 'npx @anthropic-ai/mcp-server-filesystem /path/to/project',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'github',
+ package: '@anthropic-ai/mcp-server-github',
+ installCmd: 'npx @anthropic-ai/mcp-server-github',
+ requiresKey: true,
+ trustLevel: 'official',
+ },
+ {
+ name: 'postgres',
+ package: '@anthropic-ai/mcp-server-postgres',
+ installCmd: 'npx @anthropic-ai/mcp-server-postgres postgresql://localhost/mydb',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'memory',
+ package: '@anthropic-ai/mcp-server-memory',
+ installCmd: 'npx @anthropic-ai/mcp-server-memory',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'puppeteer',
+ package: '@anthropic-ai/mcp-server-puppeteer',
+ installCmd: 'npx @anthropic-ai/mcp-server-puppeteer',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ ],
+ hooks: {
+ PreToolUse: [
+ {
+ matcher: 'Bash',
+ hooks: [{ type: 'command', command: 'echo "Running a shell command — review the description"' }],
+ },
+ ],
+ },
+ subagents: [
+ { name: 'test-runner', model: 'haiku', tools: ['Bash', 'Read'] },
+ ],
+ learningPath: [
+ { title: 'The Odin Project — Full Stack JavaScript', url: 'https://www.theodinproject.com/paths/full-stack-javascript', type: 'course', level: 'intermediate' },
+ { title: 'PostgreSQL Tutorial', url: 'https://www.postgresqltutorial.com/', type: 'docs', level: 'intermediate' },
+ { title: 'Node.js Best Practices', url: 'https://github.com/goldbergyoni/nodebestpractices', type: 'repo', level: 'intermediate' },
+ ],
+ rulesFiles: [
+ {
+ path: '.claude/rules/code-quality.md',
+ content: '# Code Quality\n- Clear, descriptive variable names\n- Functions should do one thing well\n- Handle errors explicitly\n- Comment complex logic\n- Keep files focused and manageable in size\n',
+ },
+ ],
+}
+
+// ============================================================================
+// PROFILE 12: pre-senior-expert
+// ============================================================================
+const preSeniorExpert: Profile = {
+ slug: 'pre-senior-expert',
+ ageGroup: 'pre_senior',
+ skillLevel: 'expert',
+ displayName: 'Pre-Senior Expert',
+ persona: 'A respected peer engineer. Concise and direct. Focuses on system design, reliability, and operational maturity. Values clarity in communication and well-documented decisions.',
+ ui: {
+ fontSize: 16,
+ fontScale: 1.1,
+ motionLevel: 'minimal',
+ contrast: 'enhanced',
+ touchTargetMin: 44,
+ layoutDensity: 'comfortable',
+ theme: 'auto',
+ },
+ template: {
+ slug: 'pre-senior-expert',
+ verbosity: 'low',
+ safety: 'policy-driven',
+ targetLines: [30, 55],
+ content: `# {{projectName}} — CLAUDE.md
+
+## Persona
+Peer engineer. Concise, direct. System design and operational maturity focus.
+
+## Project
+{{projectDescription}}
+
+## Stack
+{{techStack}}
+
+## Principles
+- Production-ready by default
+- Design for failure and recovery
+- Observability: logging, metrics, tracing
+- Security: defense in depth
+- Performance: measure before optimizing
+
+## Policy
+- Follow .claude/rules/ for project-specific standards
+- Lint and test before committing
+- Comprehensive error handling required
+- Parameterized queries only
+- Secrets via env vars or vault
+`,
+ },
+ skillBundle: [
+ { name: 'System Architecture', description: 'Designs distributed systems with fault tolerance and scalability' },
+ { name: 'Performance Optimization', description: 'Profiling, benchmarking, and targeted optimizations' },
+ { name: 'Security Audit', description: 'Threat modeling and security hardening' },
+ { name: 'DevOps Integration', description: 'CI/CD, infrastructure as code, deployment automation' },
+ { name: 'Database Optimization', description: 'Query tuning, indexing, replication strategies' },
+ { name: 'Incident Response', description: 'Root cause analysis, runbooks, and post-mortem processes' },
+ ],
+ mcpServers: [
+ {
+ name: 'filesystem',
+ package: '@anthropic-ai/mcp-server-filesystem',
+ installCmd: 'npx @anthropic-ai/mcp-server-filesystem /path/to/project',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'github',
+ package: '@anthropic-ai/mcp-server-github',
+ installCmd: 'npx @anthropic-ai/mcp-server-github',
+ requiresKey: true,
+ trustLevel: 'official',
+ },
+ {
+ name: 'postgres',
+ package: '@anthropic-ai/mcp-server-postgres',
+ installCmd: 'npx @anthropic-ai/mcp-server-postgres postgresql://localhost/mydb',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'puppeteer',
+ package: '@anthropic-ai/mcp-server-puppeteer',
+ installCmd: 'npx @anthropic-ai/mcp-server-puppeteer',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'sentry',
+ package: '@sentry/mcp-server',
+ installCmd: 'npx @sentry/mcp-server',
+ requiresKey: true,
+ trustLevel: 'verified',
+ },
+ {
+ name: 'linear',
+ package: '@anthropic-ai/mcp-server-linear',
+ installCmd: 'npx @anthropic-ai/mcp-server-linear',
+ requiresKey: true,
+ trustLevel: 'official',
+ },
+ {
+ name: 'docker',
+ package: 'mcp-server-docker',
+ installCmd: 'npx mcp-server-docker',
+ requiresKey: false,
+ trustLevel: 'community',
+ },
+ {
+ name: 'kubernetes',
+ package: 'mcp-server-kubernetes',
+ installCmd: 'npx mcp-server-kubernetes',
+ requiresKey: false,
+ trustLevel: 'community',
+ },
+ {
+ name: 'cloudflare',
+ package: '@anthropic-ai/mcp-server-cloudflare',
+ installCmd: 'npx @anthropic-ai/mcp-server-cloudflare',
+ requiresKey: true,
+ trustLevel: 'official',
+ },
+ {
+ name: 'slack',
+ package: '@anthropic-ai/mcp-server-slack',
+ installCmd: 'npx @anthropic-ai/mcp-server-slack',
+ requiresKey: true,
+ trustLevel: 'official',
+ },
+ {
+ name: 'datadog',
+ package: 'mcp-server-datadog',
+ installCmd: 'npx mcp-server-datadog',
+ requiresKey: true,
+ trustLevel: 'community',
+ },
+ {
+ name: 'grafana',
+ package: 'mcp-server-grafana',
+ installCmd: 'npx mcp-server-grafana',
+ requiresKey: true,
+ trustLevel: 'community',
+ },
+ {
+ name: 'terraform',
+ package: 'mcp-server-terraform',
+ installCmd: 'npx mcp-server-terraform',
+ requiresKey: false,
+ trustLevel: 'community',
+ },
+ ],
+ hooks: {
+ PreToolUse: [
+ {
+ matcher: 'Bash',
+ hooks: [{ type: 'command', command: 'echo "$TOOL_INPUT" | grep -qE "rm -rf|DROP TABLE|TRUNCATE|reset --hard|push --force" && echo "BLOCKED: Destructive command requires explicit approval" && exit 1 || true' }],
+ },
+ ],
+ PostToolUse: [
+ {
+ matcher: 'Write',
+ hooks: [{ type: 'command', command: 'echo "File changed — run lint and tests before committing"' }],
+ },
+ ],
+ Stop: [
+ {
+ matcher: '.*',
+ hooks: [{ type: 'command', command: 'echo "Session ended — verify changes are committed and tested"' }],
+ },
+ ],
+ },
+ subagents: [
+ { name: 'test-runner', model: 'haiku', tools: ['Bash', 'Read'] },
+ { name: 'lint-checker', model: 'haiku', tools: ['Bash', 'Read'] },
+ { name: 'security-scanner', model: 'sonnet', tools: ['Bash', 'Read', 'Glob'] },
+ { name: 'perf-profiler', model: 'sonnet', tools: ['Bash', 'Read'] },
+ ],
+ learningPath: [
+ { title: 'Designing Data-Intensive Applications', url: 'https://dataintensive.net/', type: 'article', level: 'expert' },
+ { title: 'Site Reliability Engineering — Google', url: 'https://sre.google/sre-book/table-of-contents/', type: 'docs', level: 'expert' },
+ { title: 'The Architecture of Open Source Applications', url: 'https://aosabook.org/en/', type: 'article', level: 'expert' },
+ ],
+ rulesFiles: [
+ {
+ path: '.claude/rules/architecture.md',
+ content: '# Architecture\n- Hexagonal architecture for core services\n- Event-driven communication between contexts\n- CQRS where read/write patterns diverge\n- Design for failure: circuit breakers, bulkheads, timeouts\n',
+ },
+ {
+ path: '.claude/rules/security.md',
+ content: '# Security Policy\n- Zero-trust network model\n- Secrets via environment variables or vault\n- Input validation at every boundary\n- Parameterized queries only\n- OWASP Top 10 compliance\n- Regular dependency audits\n',
+ },
+ {
+ path: '.claude/rules/operations.md',
+ content: '# Operations\n- Structured JSON logging\n- Health check endpoints for all services\n- Graceful shutdown handling\n- Circuit breaker patterns for external deps\n- SLO-driven alerting\n',
+ },
+ {
+ path: '.claude/rules/git.md',
+ content: '# Git Policy\n- Conventional commits (feat:, fix:, chore:, docs:)\n- No force push to main/master\n- Squash merge for feature branches\n- PR description must include testing notes\n',
+ },
+ ],
+}
+
+// ============================================================================
+// PROFILE 13: senior-beginner
+// ============================================================================
+const seniorBeginner: Profile = {
+ slug: 'senior-beginner',
+ ageGroup: 'senior',
+ skillLevel: 'beginner',
+ displayName: 'Senior Beginner',
+ persona: 'A warm, patient helper who deeply respects your wisdom and life experience. Explains technology in the simplest possible terms with familiar everyday analogies. Never rushes. Extra large text and high contrast for comfortable reading.',
+ ui: {
+ fontSize: 20,
+ fontScale: 1.4,
+ motionLevel: 'minimal',
+ contrast: 'high',
+ touchTargetMin: 56,
+ layoutDensity: 'spacious',
+ theme: 'light',
+ },
+ template: {
+ slug: 'senior-beginner',
+ verbosity: 'maximum',
+ safety: 'maximum',
+ targetLines: [120, 160],
+ content: `# {{projectName}} — CLAUDE.md
+
+## Who I Am
+I'm your friendly technology helper. I explain everything in simple, everyday language. I never rush, and I always make sure you're comfortable before moving forward.
+
+## What We're Working On
+{{projectDescription}}
+
+## The Tools We Use
+{{techStack}}
+
+## My Promises To You
+- I will ALWAYS explain things in plain, simple language
+- I will NEVER use technical jargon without explaining it
+- I will go at YOUR pace — there is no rush
+- I will break everything into very small, easy steps
+- I will show you what will happen BEFORE I do anything
+- I will ask for your permission before making any changes
+- I will celebrate every step forward — you're doing great!
+
+## How We Work Together
+1. You tell me what you'd like to do
+2. I explain the plan in simple terms
+3. I show you each step before I take it
+4. I explain what happened after each step
+5. I check that you're comfortable before continuing
+6. At the end, I summarize everything we did
+
+## Your Safety Is My Priority
+- I NEVER make changes without asking you first
+- I NEVER delete anything without your clear approval
+- I always keep backup copies of your work
+- I explain any risks in simple terms before proceeding
+- If something goes wrong, we can always undo it together
+
+## Important Reminders
+- There is no such thing as a silly question
+- If something doesn't make sense, just ask me to explain differently
+- We can stop and take a break anytime
+- Making mistakes is completely normal and okay
+- You bring valuable life experience to this — trust yourself!
+`,
+ },
+ skillBundle: [
+ { name: 'Plain Language Explainer', description: 'Explains all technology in simple everyday language with familiar analogies' },
+ ],
+ mcpServers: [
+ {
+ name: 'filesystem',
+ package: '@anthropic-ai/mcp-server-filesystem',
+ installCmd: 'npx @anthropic-ai/mcp-server-filesystem /path/to/project',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'memory',
+ package: '@anthropic-ai/mcp-server-memory',
+ installCmd: 'npx @anthropic-ai/mcp-server-memory',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ ],
+ hooks: {
+ PreToolUse: [
+ {
+ matcher: 'Bash',
+ hooks: [{ type: 'command', command: 'echo "I am about to run a command. I will explain what it does first."' }],
+ },
+ {
+ matcher: 'Write',
+ hooks: [{ type: 'command', command: 'echo "I am about to create or change a file. Let me explain what this means."' }],
+ },
+ {
+ matcher: 'Edit',
+ hooks: [{ type: 'command', command: 'echo "I am about to make a small change to a file. Let me explain."' }],
+ },
+ ],
+ Stop: [
+ {
+ matcher: '.*',
+ hooks: [{ type: 'command', command: 'echo "Our session is finished. You did wonderful work today!"' }],
+ },
+ ],
+ },
+ subagents: [],
+ learningPath: [
+ { title: 'Khan Academy — Intro to Computing', url: 'https://www.khanacademy.org/computing/computer-programming', type: 'course', level: 'beginner' },
+ { title: 'Code.org — Learn the Basics', url: 'https://code.org/', type: 'course', level: 'beginner' },
+ ],
+ rulesFiles: [],
+}
+
+// ============================================================================
+// PROFILE 14: senior-intermediate
+// ============================================================================
+const seniorIntermediate: Profile = {
+ slug: 'senior-intermediate',
+ ageGroup: 'senior',
+ skillLevel: 'intermediate',
+ displayName: 'Senior Intermediate',
+ persona: 'A respectful, clear-speaking colleague who values your experience and knowledge. Explains new concepts thoroughly while respecting what you already know. Prioritizes readability and clarity in everything.',
+ ui: {
+ fontSize: 18,
+ fontScale: 1.25,
+ motionLevel: 'minimal',
+ contrast: 'high',
+ touchTargetMin: 52,
+ layoutDensity: 'comfortable',
+ theme: 'light',
+ },
+ template: {
+ slug: 'senior-intermediate',
+ verbosity: 'medium-high',
+ safety: 'high',
+ targetLines: [55, 85],
+ content: `# {{projectName}} — CLAUDE.md
+
+## Persona
+Respectful colleague. Clear communication. Values your experience and knowledge.
+
+## Project
+{{projectDescription}}
+
+## Stack
+{{techStack}}
+
+## Guidelines
+- Explain new concepts clearly; respect what you already know
+- Prioritize code readability and clarity
+- Suggest best practices with clear reasoning
+- Confirm before any destructive operations
+- Provide context for design choices
+- Use well-structured, readable communication
+
+## Workflow
+1. Discuss the goal together
+2. Plan the approach with clear steps
+3. Build with explanations for new concepts
+4. Test and verify results
+5. Document what was accomplished
+
+## Safety
+- Always confirm before destructive commands
+- Explain side effects clearly
+- Maintain clean version control
+- Keep sensitive data secure
+`,
+ },
+ skillBundle: [
+ { name: 'Code Review', description: 'Reviews code with clear, constructive feedback' },
+ { name: 'Test Writing', description: 'Writes clear, well-organized tests' },
+ { name: 'Refactoring', description: 'Improves code structure for readability and maintainability' },
+ { name: 'Database Design', description: 'Creates clean schemas and efficient queries' },
+ ],
+ mcpServers: [
+ {
+ name: 'filesystem',
+ package: '@anthropic-ai/mcp-server-filesystem',
+ installCmd: 'npx @anthropic-ai/mcp-server-filesystem /path/to/project',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'github',
+ package: '@anthropic-ai/mcp-server-github',
+ installCmd: 'npx @anthropic-ai/mcp-server-github',
+ requiresKey: true,
+ trustLevel: 'official',
+ },
+ {
+ name: 'postgres',
+ package: '@anthropic-ai/mcp-server-postgres',
+ installCmd: 'npx @anthropic-ai/mcp-server-postgres postgresql://localhost/mydb',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'memory',
+ package: '@anthropic-ai/mcp-server-memory',
+ installCmd: 'npx @anthropic-ai/mcp-server-memory',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ ],
+ hooks: {
+ PreToolUse: [
+ {
+ matcher: 'Bash',
+ hooks: [{ type: 'command', command: 'echo "Running a shell command — please review"' }],
+ },
+ ],
+ },
+ subagents: [
+ { name: 'test-runner', model: 'haiku', tools: ['Bash', 'Read'] },
+ ],
+ learningPath: [
+ { title: 'The Odin Project — Full Stack JavaScript', url: 'https://www.theodinproject.com/paths/full-stack-javascript', type: 'course', level: 'intermediate' },
+ { title: 'PostgreSQL Tutorial', url: 'https://www.postgresqltutorial.com/', type: 'docs', level: 'intermediate' },
+ { title: 'MDN Web Docs — JavaScript Guide', url: 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide', type: 'docs', level: 'intermediate' },
+ ],
+ rulesFiles: [
+ {
+ path: '.claude/rules/readability.md',
+ content: '# Readability\n- Prioritize clear, readable code over clever code\n- Use descriptive names for everything\n- Add comments for any non-obvious logic\n- Keep functions short and focused\n- Organize files logically\n',
+ },
+ ],
+}
+
+// ============================================================================
+// PROFILE 15: senior-expert
+// ============================================================================
+const seniorExpert: Profile = {
+ slug: 'senior-expert',
+ ageGroup: 'senior',
+ skillLevel: 'expert',
+ displayName: 'Senior Expert',
+ persona: 'A respected peer with deep mutual respect. Direct and efficient communication with enhanced readability. Focuses on system design, reliability, and well-documented architectures.',
+ ui: {
+ fontSize: 17,
+ fontScale: 1.15,
+ motionLevel: 'minimal',
+ contrast: 'high',
+ touchTargetMin: 48,
+ layoutDensity: 'comfortable',
+ theme: 'auto',
+ },
+ template: {
+ slug: 'senior-expert',
+ verbosity: 'low',
+ safety: 'policy-driven',
+ targetLines: [30, 55],
+ content: `# {{projectName}} — CLAUDE.md
+
+## Persona
+Respected peer. Direct, efficient. System design and reliability focus. Clear documentation.
+
+## Project
+{{projectDescription}}
+
+## Stack
+{{techStack}}
+
+## Principles
+- Production-ready by default
+- Design for failure and graceful recovery
+- Observability: structured logging, metrics, tracing
+- Security: defense in depth, zero trust
+- Performance: measure first, optimize second
+
+## Policy
+- Follow .claude/rules/ for project-specific standards
+- Lint and test before every commit
+- Comprehensive error handling required
+- Parameterized queries only — no raw SQL
+- Secrets via env vars or secret manager
+`,
+ },
+ skillBundle: [
+ { name: 'System Architecture', description: 'Designs distributed systems with fault tolerance and horizontal scalability' },
+ { name: 'Performance Optimization', description: 'Profiling, benchmarking, and targeted performance improvements' },
+ { name: 'Security Audit', description: 'Threat modeling, vulnerability assessment, and security hardening' },
+ { name: 'DevOps Integration', description: 'CI/CD pipelines, infrastructure as code, blue-green deployments' },
+ { name: 'Database Optimization', description: 'Query optimization, indexing, replication, and partitioning' },
+ { name: 'Incident Response', description: 'Root cause analysis, runbook creation, post-mortem facilitation' },
+ ],
+ mcpServers: [
+ {
+ name: 'filesystem',
+ package: '@anthropic-ai/mcp-server-filesystem',
+ installCmd: 'npx @anthropic-ai/mcp-server-filesystem /path/to/project',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'github',
+ package: '@anthropic-ai/mcp-server-github',
+ installCmd: 'npx @anthropic-ai/mcp-server-github',
+ requiresKey: true,
+ trustLevel: 'official',
+ },
+ {
+ name: 'postgres',
+ package: '@anthropic-ai/mcp-server-postgres',
+ installCmd: 'npx @anthropic-ai/mcp-server-postgres postgresql://localhost/mydb',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'puppeteer',
+ package: '@anthropic-ai/mcp-server-puppeteer',
+ installCmd: 'npx @anthropic-ai/mcp-server-puppeteer',
+ requiresKey: false,
+ trustLevel: 'official',
+ },
+ {
+ name: 'sentry',
+ package: '@sentry/mcp-server',
+ installCmd: 'npx @sentry/mcp-server',
+ requiresKey: true,
+ trustLevel: 'verified',
+ },
+ {
+ name: 'linear',
+ package: '@anthropic-ai/mcp-server-linear',
+ installCmd: 'npx @anthropic-ai/mcp-server-linear',
+ requiresKey: true,
+ trustLevel: 'official',
+ },
+ {
+ name: 'docker',
+ package: 'mcp-server-docker',
+ installCmd: 'npx mcp-server-docker',
+ requiresKey: false,
+ trustLevel: 'community',
+ },
+ {
+ name: 'kubernetes',
+ package: 'mcp-server-kubernetes',
+ installCmd: 'npx mcp-server-kubernetes',
+ requiresKey: false,
+ trustLevel: 'community',
+ },
+ {
+ name: 'cloudflare',
+ package: '@anthropic-ai/mcp-server-cloudflare',
+ installCmd: 'npx @anthropic-ai/mcp-server-cloudflare',
+ requiresKey: true,
+ trustLevel: 'official',
+ },
+ {
+ name: 'slack',
+ package: '@anthropic-ai/mcp-server-slack',
+ installCmd: 'npx @anthropic-ai/mcp-server-slack',
+ requiresKey: true,
+ trustLevel: 'official',
+ },
+ {
+ name: 'datadog',
+ package: 'mcp-server-datadog',
+ installCmd: 'npx mcp-server-datadog',
+ requiresKey: true,
+ trustLevel: 'community',
+ },
+ {
+ name: 'grafana',
+ package: 'mcp-server-grafana',
+ installCmd: 'npx mcp-server-grafana',
+ requiresKey: true,
+ trustLevel: 'community',
+ },
+ {
+ name: 'terraform',
+ package: 'mcp-server-terraform',
+ installCmd: 'npx mcp-server-terraform',
+ requiresKey: false,
+ trustLevel: 'community',
+ },
+ ],
+ hooks: {
+ PreToolUse: [
+ {
+ matcher: 'Bash',
+ hooks: [{ type: 'command', command: 'echo "$TOOL_INPUT" | grep -qE "rm -rf|DROP TABLE|TRUNCATE|reset --hard|push --force" && echo "BLOCKED: Destructive command requires explicit approval" && exit 1 || true' }],
+ },
+ ],
+ PostToolUse: [
+ {
+ matcher: 'Write',
+ hooks: [{ type: 'command', command: 'echo "File changed — run lint and tests before committing"' }],
+ },
+ ],
+ Stop: [
+ {
+ matcher: '.*',
+ hooks: [{ type: 'command', command: 'echo "Session ended — verify changes are committed and tested"' }],
+ },
+ ],
+ },
+ subagents: [
+ { name: 'test-runner', model: 'haiku', tools: ['Bash', 'Read'] },
+ { name: 'lint-checker', model: 'haiku', tools: ['Bash', 'Read'] },
+ { name: 'security-scanner', model: 'sonnet', tools: ['Bash', 'Read', 'Glob'] },
+ { name: 'perf-profiler', model: 'sonnet', tools: ['Bash', 'Read'] },
+ ],
+ learningPath: [
+ { title: 'Designing Data-Intensive Applications', url: 'https://dataintensive.net/', type: 'article', level: 'expert' },
+ { title: 'Site Reliability Engineering — Google', url: 'https://sre.google/sre-book/table-of-contents/', type: 'docs', level: 'expert' },
+ { title: 'The Architecture of Open Source Applications', url: 'https://aosabook.org/en/', type: 'article', level: 'expert' },
+ ],
+ rulesFiles: [
+ {
+ path: '.claude/rules/architecture.md',
+ content: '# Architecture\n- Hexagonal architecture for core services\n- Event-driven communication between bounded contexts\n- CQRS where read/write patterns diverge significantly\n- Design for failure: circuit breakers, bulkheads, timeouts\n',
+ },
+ {
+ path: '.claude/rules/security.md',
+ content: '# Security Policy\n- Zero-trust network model\n- All secrets via environment variables or vault\n- Input validation at every boundary\n- Parameterized queries only — no string concatenation\n- OWASP Top 10 compliance required\n- Regular dependency audits\n',
+ },
+ {
+ path: '.claude/rules/operations.md',
+ content: '# Operations\n- Structured JSON logging (no console.log in production)\n- Health check endpoints for all services\n- Graceful shutdown handling\n- Circuit breaker patterns for external dependencies\n- SLO-driven alerting\n',
+ },
+ {
+ path: '.claude/rules/git.md',
+ content: '# Git Policy\n- Conventional commits (feat:, fix:, chore:, docs:)\n- No force push to main/master\n- Squash merge for feature branches\n- PR description must include testing notes\n',
+ },
+ ],
+}
+
+// ============================================================================
+// EXPORTS
+// ============================================================================
+export const PROFILES: Profile[] = [
+ teenBeginner,
+ teenIntermediate,
+ teenExpert,
+ youngAdultBeginner,
+ youngAdultIntermediate,
+ youngAdultExpert,
+ professionalBeginner,
+ professionalIntermediate,
+ professionalExpert,
+ preSeniorBeginner,
+ preSeniorIntermediate,
+ preSeniorExpert,
+ seniorBeginner,
+ seniorIntermediate,
+ seniorExpert,
+]
+
+export function getProfile(slug: string): Profile | undefined {
+ return PROFILES.find(p => p.slug === slug)
+}
+
+export function getProfilesByAge(age: AgeGroup): Profile[] {
+ return PROFILES.filter(p => p.ageGroup === age)
+}
+
+export function getProfilesBySkill(skill: SkillLevel): Profile[] {
+ return PROFILES.filter(p => p.skillLevel === skill)
+}
diff --git a/src/lib/profiles/index.ts b/src/lib/profiles/index.ts
new file mode 100644
index 0000000..63e5835
--- /dev/null
+++ b/src/lib/profiles/index.ts
@@ -0,0 +1,3 @@
+export * from './types'
+export * from './data'
+export * from './templates'
diff --git a/src/lib/profiles/templates.ts b/src/lib/profiles/templates.ts
new file mode 100644
index 0000000..c443069
--- /dev/null
+++ b/src/lib/profiles/templates.ts
@@ -0,0 +1,8 @@
+import Mustache from 'mustache'
+
+// Disable HTML escaping — we're generating markdown, not HTML
+Mustache.escape = (text: string) => text
+
+export function renderTemplate(template: string, vars: Record<string, string>): string {
+ return Mustache.render(template, vars)
+}
diff --git a/src/lib/profiles/types.ts b/src/lib/profiles/types.ts
new file mode 100644
index 0000000..bbebfd0
--- /dev/null
+++ b/src/lib/profiles/types.ts
@@ -0,0 +1,79 @@
+export type AgeGroup = 'teen' | 'young_adult' | 'professional' | 'pre_senior' | 'senior'
+export type SkillLevel = 'beginner' | 'intermediate' | 'expert'
+export type MotionLevel = 'minimal' | 'reduced' | 'normal' | 'full'
+export type ContrastLevel = 'standard' | 'enhanced' | 'high'
+export type LayoutDensity = 'compact' | 'comfortable' | 'spacious'
+export type ThemeMode = 'light' | 'dark' | 'auto'
+
+export interface UIConfig {
+ fontSize: number
+ fontScale: number
+ motionLevel: MotionLevel
+ contrast: ContrastLevel
+ touchTargetMin: number
+ layoutDensity: LayoutDensity
+ theme: ThemeMode
+}
+
+export interface TemplateConfig {
+ slug: string
+ verbosity: 'minimal' | 'low' | 'medium' | 'medium-high' | 'high' | 'maximum'
+ safety: 'standard' | 'high' | 'maximum' | 'policy-driven'
+ targetLines: [number, number]
+ content: string
+}
+
+export interface LearningResource {
+ title: string
+ url: string
+ type: 'video' | 'course' | 'article' | 'repo' | 'docs'
+ level: SkillLevel
+}
+
+export interface SkillRef {
+ name: string
+ description: string
+}
+
+export interface MCPServerRef {
+ name: string
+ package: string
+ installCmd: string
+ requiresKey: boolean
+ trustLevel: 'official' | 'verified' | 'community'
+}
+
+export interface SubagentRef {
+ name: string
+ model: 'haiku' | 'sonnet' | 'opus' | 'inherit'
+ tools: string[]
+}
+
+export interface HookRule {
+ matcher: string
+ hooks: { type: 'command'; command: string }[]
+}
+
+export interface HookConfig {
+ PreToolUse?: HookRule[]
+ PostToolUse?: HookRule[]
+ Stop?: HookRule[]
+ SessionStart?: HookRule[]
+ PreCompact?: HookRule[]
+}
+
+export interface Profile {
+ slug: string
+ ageGroup: AgeGroup
+ skillLevel: SkillLevel
+ displayName: string
+ persona: string
+ ui: UIConfig
+ template: TemplateConfig
+ skillBundle: SkillRef[]
+ mcpServers: MCPServerRef[]
+ hooks: HookConfig
+ subagents: SubagentRef[]
+ learningPath: LearningResource[]
+ rulesFiles: { path: string; content: string }[]
+}
diff --git a/src/lib/store.ts b/src/lib/store.ts
new file mode 100644
index 0000000..ff827d9
--- /dev/null
+++ b/src/lib/store.ts
@@ -0,0 +1,125 @@
+'use client'
+
+import { create } from 'zustand'
+import type { Profile } from '@/lib/profiles/types'
+import type { AnalysisResult, Suggestion } from '@/lib/analyzer/types'
+
+interface MassUpdateFile {
+ id: string
+ name: string
+ content: string
+ type: 'claude_md' | 'rule' | 'skill' | 'hook'
+ analysis: AnalysisResult | null
+ lastAnalyzed: string | null
+}
+
+interface ClawCoderStore {
+ // Profile selection
+ selectedProfile: Profile | null
+ setProfile: (p: Profile) => void
+
+ // Onboarding answers
+ onboardingAnswers: Record<string, string>
+ setAnswer: (key: string, value: string) => void
+
+ // Step navigation
+ currentStep: number
+ setStep: (n: number) => void
+
+ // Analysis
+ analysisResult: AnalysisResult | null
+ setAnalysis: (a: AnalysisResult | null) => void
+
+ // Suggestions
+ selectedSuggestions: Set<string>
+ toggleSuggestion: (id: string) => void
+ selectAllSuggestions: (suggestions: Suggestion[]) => void
+ clearSuggestions: () => void
+
+ // Generated content
+ generatedContent: string
+ setGeneratedContent: (c: string) => void
+
+ // Loading state
+ isLoading: boolean
+ setLoading: (b: boolean) => void
+
+ // Mass update files
+ massFiles: MassUpdateFile[]
+ addMassFiles: (files: MassUpdateFile[]) => void
+ removeMassFile: (id: string) => void
+ updateMassFile: (id: string, updates: Partial<MassUpdateFile>) => void
+ clearMassFiles: () => void
+ selectedMassFileIds: Set<string>
+ toggleMassFileSelection: (id: string) => void
+ selectAllMassFiles: () => void
+ clearMassFileSelection: () => void
+}
+
+export const useStore = create<ClawCoderStore>((set) => ({
+ selectedProfile: null,
+ setProfile: (p) => set({ selectedProfile: p }),
+
+ onboardingAnswers: {},
+ setAnswer: (key, value) =>
+ set((s) => ({
+ onboardingAnswers: { ...s.onboardingAnswers, [key]: value },
+ })),
+
+ currentStep: 0,
+ setStep: (n) => set({ currentStep: n }),
+
+ analysisResult: null,
+ setAnalysis: (a) => set({ analysisResult: a }),
+
+ selectedSuggestions: new Set(),
+ toggleSuggestion: (id) =>
+ set((s) => {
+ const next = new Set(s.selectedSuggestions)
+ if (next.has(id)) {
+ next.delete(id)
+ } else {
+ next.add(id)
+ }
+ return { selectedSuggestions: next }
+ }),
+ selectAllSuggestions: (sugs) =>
+ set({ selectedSuggestions: new Set(sugs.map((s) => s.id)) }),
+ clearSuggestions: () => set({ selectedSuggestions: new Set() }),
+
+ generatedContent: '',
+ setGeneratedContent: (c) => set({ generatedContent: c }),
+
+ isLoading: false,
+ setLoading: (b) => set({ isLoading: b }),
+
+ massFiles: [],
+ addMassFiles: (files) =>
+ set((s) => ({ massFiles: [...s.massFiles, ...files] })),
+ removeMassFile: (id) =>
+ set((s) => ({ massFiles: s.massFiles.filter((f) => f.id !== id) })),
+ updateMassFile: (id, updates) =>
+ set((s) => ({
+ massFiles: s.massFiles.map((f) =>
+ f.id === id ? { ...f, ...updates } : f
+ ),
+ })),
+ clearMassFiles: () => set({ massFiles: [], selectedMassFileIds: new Set() }),
+
+ selectedMassFileIds: new Set(),
+ toggleMassFileSelection: (id) =>
+ set((s) => {
+ const next = new Set(s.selectedMassFileIds)
+ if (next.has(id)) {
+ next.delete(id)
+ } else {
+ next.add(id)
+ }
+ return { selectedMassFileIds: next }
+ }),
+ selectAllMassFiles: () =>
+ set((s) => ({
+ selectedMassFileIds: new Set(s.massFiles.map((f) => f.id)),
+ })),
+ clearMassFileSelection: () => set({ selectedMassFileIds: new Set() }),
+}))
diff --git a/src/middleware.ts b/src/middleware.ts
new file mode 100644
index 0000000..e272735
--- /dev/null
+++ b/src/middleware.ts
@@ -0,0 +1,53 @@
+import { NextResponse } from 'next/server'
+import type { NextRequest } from 'next/server'
+
+const AUTH_USER = 'Steve'
+const AUTH_PASS = 'DWSecure2024!'
+const SESSION_COOKIE = 'clawcoder_session'
+const SESSION_MAX_AGE = 30 * 24 * 60 * 60 * 1000 // 30 days
+
+const publicPaths = [
+ '/login',
+ '/api/',
+ '/_next',
+ '/favicon.ico',
+]
+
+export function middleware(request: NextRequest) {
+ const { pathname } = request.nextUrl
+
+ // Allow public paths
+ if (publicPaths.some(path => pathname.startsWith(path))) {
+ return NextResponse.next()
+ }
+
+ // Check Basic Auth header (for API/curl access)
+ const authHeader = request.headers.get('authorization')
+ if (authHeader?.startsWith('Basic ')) {
+ const decoded = Buffer.from(authHeader.slice(6), 'base64').toString()
+ const [user, pass] = decoded.split(':')
+ if (user === AUTH_USER && pass === AUTH_PASS) {
+ return NextResponse.next()
+ }
+ }
+
+ // Check session cookie (for browser access)
+ const session = request.cookies.get(SESSION_COOKIE)?.value
+ if (session) {
+ try {
+ const data = JSON.parse(Buffer.from(session, 'base64url').toString())
+ if (data.authenticated && (Date.now() - data.loginTime) < SESSION_MAX_AGE) {
+ return NextResponse.next()
+ }
+ } catch {}
+ }
+
+ // No valid auth — redirect to login
+ const loginUrl = new URL('/login', request.url)
+ loginUrl.searchParams.set('redirect', pathname)
+ return NextResponse.redirect(loginUrl)
+}
+
+export const config = {
+ matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'],
+}
diff --git a/src/tests/lib/generator.test.ts b/src/tests/lib/generator.test.ts
new file mode 100644
index 0000000..1af9e79
--- /dev/null
+++ b/src/tests/lib/generator.test.ts
@@ -0,0 +1,167 @@
+import { describe, it, expect } from 'vitest'
+import { PROFILES, getProfile } from '@/lib/profiles/data'
+import { generateClaudeMd, type GeneratorVars } from '@/lib/generator/claude-md'
+import { generateRulesFiles } from '@/lib/generator/rules'
+import { generateSettingsJson, serializeSettingsJson } from '@/lib/generator/hooks'
+import { generateBundle } from '@/lib/generator/bundle'
+
+const testVars: GeneratorVars = {
+ projectName: 'TestApp',
+ projectDescription: 'A sample test application for unit testing.',
+ techStack: 'Next.js, TypeScript, PostgreSQL',
+}
+
+describe('Template Rendering', () => {
+ it('renders templates without leftover {{}} placeholders', () => {
+ for (const profile of PROFILES) {
+ const rendered = generateClaudeMd(profile, testVars)
+ const leftoverPlaceholders = rendered.match(/\{\{[^}]+\}\}/g)
+ expect(
+ leftoverPlaceholders,
+ `${profile.slug} has leftover placeholders: ${leftoverPlaceholders}`
+ ).toBeNull()
+ }
+ })
+
+ it('includes the project name in rendered output', () => {
+ for (const profile of PROFILES) {
+ const rendered = generateClaudeMd(profile, testVars)
+ expect(
+ rendered,
+ `${profile.slug} should contain project name`
+ ).toContain('TestApp')
+ }
+ })
+
+ it('includes the project description in rendered output', () => {
+ for (const profile of PROFILES) {
+ const rendered = generateClaudeMd(profile, testVars)
+ expect(
+ rendered,
+ `${profile.slug} should contain project description`
+ ).toContain('A sample test application')
+ }
+ })
+
+ it('includes the tech stack in rendered output', () => {
+ for (const profile of PROFILES) {
+ const rendered = generateClaudeMd(profile, testVars)
+ expect(
+ rendered,
+ `${profile.slug} should contain tech stack`
+ ).toContain('Next.js, TypeScript, PostgreSQL')
+ }
+ })
+})
+
+describe('Verbosity Progression', () => {
+ it('expert template output is shorter than beginner for same age group', () => {
+ const ageGroups = ['teen', 'young_adult', 'professional', 'pre_senior', 'senior'] as const
+
+ for (const age of ageGroups) {
+ const beginner = PROFILES.find(p => p.ageGroup === age && p.skillLevel === 'beginner')!
+ const expert = PROFILES.find(p => p.ageGroup === age && p.skillLevel === 'expert')!
+
+ const beginnerOutput = generateClaudeMd(beginner, testVars)
+ const expertOutput = generateClaudeMd(expert, testVars)
+
+ expect(
+ expertOutput.length,
+ `${age} expert output (${expertOutput.length} chars) should be shorter than beginner (${beginnerOutput.length} chars)`
+ ).toBeLessThan(beginnerOutput.length)
+ }
+ })
+})
+
+describe('Rules Generation', () => {
+ it('generates rules for intermediate+ profiles that have them', () => {
+ // Intermediate and expert profiles should generally have rules files
+ const profilesWithRules = PROFILES.filter(p => p.rulesFiles.length > 0)
+ expect(profilesWithRules.length).toBeGreaterThan(0)
+
+ for (const profile of profilesWithRules) {
+ const rules = generateRulesFiles(profile)
+ expect(rules.length).toBeGreaterThan(0)
+
+ for (const rule of rules) {
+ expect(rule.path).toBeTruthy()
+ expect(rule.content).toBeTruthy()
+ expect(rule.path).toContain('.claude/rules/')
+ }
+ }
+ })
+
+ it('beginner profiles have no rules files (or very few)', () => {
+ const beginners = PROFILES.filter(p => p.skillLevel === 'beginner')
+ for (const profile of beginners) {
+ const rules = generateRulesFiles(profile)
+ // Beginners should have 0 rules files
+ expect(
+ rules.length,
+ `${profile.slug} (beginner) should have 0 rules files`
+ ).toBe(0)
+ }
+ })
+})
+
+describe('Hooks Generation', () => {
+ it('generates valid settings JSON', () => {
+ for (const profile of PROFILES) {
+ const settings = generateSettingsJson(profile)
+ const json = serializeSettingsJson(settings)
+
+ // Should be valid JSON
+ expect(() => JSON.parse(json)).not.toThrow()
+ }
+ })
+
+ it('expert profiles with destructive command hooks have proper quoting', () => {
+ const expertProfiles = PROFILES.filter(p => p.skillLevel === 'expert')
+
+ for (const profile of expertProfiles) {
+ if (profile.hooks.PreToolUse) {
+ for (const rule of profile.hooks.PreToolUse) {
+ for (const hook of rule.hooks) {
+ // If the hook references TOOL_INPUT, it should use quoted form
+ if (hook.command.includes('TOOL_INPUT')) {
+ expect(
+ hook.command,
+ `${profile.slug} hook should use quoted "$TOOL_INPUT"`
+ ).toContain('"$TOOL_INPUT"')
+ }
+ }
+ }
+ }
+ }
+ })
+})
+
+describe('Bundle Generation', () => {
+ it('generates a complete bundle for every profile', () => {
+ for (const profile of PROFILES) {
+ const bundle = generateBundle(profile, testVars)
+
+ expect(bundle.claudeMd, `${profile.slug}: claudeMd`).toBeTruthy()
+ expect(bundle.settingsJson, `${profile.slug}: settingsJson`).toBeTruthy()
+ expect(bundle.profile.slug, `${profile.slug}: profile.slug`).toBe(profile.slug)
+ expect(bundle.profile.displayName, `${profile.slug}: profile.displayName`).toBeTruthy()
+ expect(Array.isArray(bundle.rulesFiles), `${profile.slug}: rulesFiles`).toBe(true)
+
+ // claudeMd should not have leftover placeholders
+ expect(bundle.claudeMd.match(/\{\{[^}]+\}\}/g)).toBeNull()
+
+ // settingsJson should be valid JSON
+ expect(() => JSON.parse(bundle.settingsJson)).not.toThrow()
+ }
+ })
+
+ it('preserves profile metadata in the bundle', () => {
+ const profile = getProfile('professional-expert')!
+ const bundle = generateBundle(profile, testVars)
+
+ expect(bundle.profile.slug).toBe('professional-expert')
+ expect(bundle.profile.ageGroup).toBe('professional')
+ expect(bundle.profile.skillLevel).toBe('expert')
+ expect(bundle.profile.displayName).toBe('Professional Expert')
+ })
+})
diff --git a/src/tests/lib/profiles.test.ts b/src/tests/lib/profiles.test.ts
new file mode 100644
index 0000000..a702801
--- /dev/null
+++ b/src/tests/lib/profiles.test.ts
@@ -0,0 +1,140 @@
+import { describe, it, expect } from 'vitest'
+import { PROFILES, getProfile, getProfilesByAge, getProfilesBySkill } from '@/lib/profiles/data'
+import type { AgeGroup, SkillLevel } from '@/lib/profiles/types'
+
+describe('Profile Data', () => {
+ it('has exactly 15 profiles', () => {
+ expect(PROFILES).toHaveLength(15)
+ })
+
+ it('covers all 5x3 matrix cells (5 age groups x 3 skill levels)', () => {
+ const ageGroups: AgeGroup[] = ['teen', 'young_adult', 'professional', 'pre_senior', 'senior']
+ const skillLevels: SkillLevel[] = ['beginner', 'intermediate', 'expert']
+
+ for (const age of ageGroups) {
+ for (const skill of skillLevels) {
+ const match = PROFILES.find(p => p.ageGroup === age && p.skillLevel === skill)
+ expect(match, `Missing profile for ${age}-${skill}`).toBeDefined()
+ }
+ }
+ })
+
+ it('has unique slugs for every profile', () => {
+ const slugs = PROFILES.map(p => p.slug)
+ const uniqueSlugs = new Set(slugs)
+ expect(uniqueSlugs.size).toBe(15)
+ })
+
+ it('has required fields on every profile', () => {
+ for (const profile of PROFILES) {
+ expect(profile.slug, `${profile.slug}: slug`).toBeTruthy()
+ expect(profile.ageGroup, `${profile.slug}: ageGroup`).toBeTruthy()
+ expect(profile.skillLevel, `${profile.slug}: skillLevel`).toBeTruthy()
+ expect(profile.displayName, `${profile.slug}: displayName`).toBeTruthy()
+ expect(profile.persona, `${profile.slug}: persona`).toBeTruthy()
+ expect(profile.ui, `${profile.slug}: ui`).toBeDefined()
+ expect(profile.template, `${profile.slug}: template`).toBeDefined()
+ expect(profile.template.content, `${profile.slug}: template.content`).toBeTruthy()
+ expect(Array.isArray(profile.skillBundle), `${profile.slug}: skillBundle`).toBe(true)
+ expect(Array.isArray(profile.mcpServers), `${profile.slug}: mcpServers`).toBe(true)
+ expect(profile.hooks, `${profile.slug}: hooks`).toBeDefined()
+ expect(Array.isArray(profile.subagents), `${profile.slug}: subagents`).toBe(true)
+ expect(Array.isArray(profile.learningPath), `${profile.slug}: learningPath`).toBe(true)
+ expect(Array.isArray(profile.rulesFiles), `${profile.slug}: rulesFiles`).toBe(true)
+ }
+ })
+
+ describe('Font size progression', () => {
+ it('seniors have larger fonts than teens at the same skill level', () => {
+ const skillLevels: SkillLevel[] = ['beginner', 'intermediate', 'expert']
+
+ for (const skill of skillLevels) {
+ const teen = PROFILES.find(p => p.ageGroup === 'teen' && p.skillLevel === skill)!
+ const senior = PROFILES.find(p => p.ageGroup === 'senior' && p.skillLevel === skill)!
+
+ expect(
+ senior.ui.fontSize,
+ `Senior ${skill} font (${senior.ui.fontSize}) should be > teen ${skill} font (${teen.ui.fontSize})`
+ ).toBeGreaterThan(teen.ui.fontSize)
+ }
+ })
+ })
+
+ describe('MCP count progression', () => {
+ it('experts have more MCP servers than beginners within the same age group', () => {
+ const ageGroups: AgeGroup[] = ['teen', 'young_adult', 'professional', 'pre_senior', 'senior']
+
+ for (const age of ageGroups) {
+ const beginner = PROFILES.find(p => p.ageGroup === age && p.skillLevel === 'beginner')!
+ const expert = PROFILES.find(p => p.ageGroup === age && p.skillLevel === 'expert')!
+
+ expect(
+ expert.mcpServers.length,
+ `${age} expert MCPs (${expert.mcpServers.length}) should be >= ${age} beginner MCPs (${beginner.mcpServers.length})`
+ ).toBeGreaterThanOrEqual(beginner.mcpServers.length)
+ }
+ })
+ })
+
+ describe('Touch target minimums', () => {
+ it('no profile has touch targets below 44px', () => {
+ for (const profile of PROFILES) {
+ expect(
+ profile.ui.touchTargetMin,
+ `${profile.slug} has touchTargetMin ${profile.ui.touchTargetMin} which is below 44`
+ ).toBeGreaterThanOrEqual(44)
+ }
+ })
+ })
+
+ describe('Senior expert contrast', () => {
+ it('senior-expert has "high" contrast (not "enhanced")', () => {
+ const seniorExpert = getProfile('senior-expert')
+ expect(seniorExpert).toBeDefined()
+ expect(seniorExpert!.ui.contrast).toBe('high')
+ })
+ })
+})
+
+describe('getProfile', () => {
+ it('returns a profile by slug', () => {
+ const p = getProfile('teen-beginner')
+ expect(p).toBeDefined()
+ expect(p!.slug).toBe('teen-beginner')
+ expect(p!.ageGroup).toBe('teen')
+ expect(p!.skillLevel).toBe('beginner')
+ })
+
+ it('returns undefined for unknown slug', () => {
+ const p = getProfile('nonexistent-profile')
+ expect(p).toBeUndefined()
+ })
+})
+
+describe('getProfilesByAge', () => {
+ it('returns 3 profiles for each age group', () => {
+ const ageGroups: AgeGroup[] = ['teen', 'young_adult', 'professional', 'pre_senior', 'senior']
+
+ for (const age of ageGroups) {
+ const profiles = getProfilesByAge(age)
+ expect(profiles, `${age} should have 3 profiles`).toHaveLength(3)
+ for (const p of profiles) {
+ expect(p.ageGroup).toBe(age)
+ }
+ }
+ })
+})
+
+describe('getProfilesBySkill', () => {
+ it('returns 5 profiles for each skill level', () => {
+ const skillLevels: SkillLevel[] = ['beginner', 'intermediate', 'expert']
+
+ for (const skill of skillLevels) {
+ const profiles = getProfilesBySkill(skill)
+ expect(profiles, `${skill} should have 5 profiles`).toHaveLength(5)
+ for (const p of profiles) {
+ expect(p.skillLevel).toBe(skill)
+ }
+ }
+ })
+})
diff --git a/src/tests/lib/scorer.test.ts b/src/tests/lib/scorer.test.ts
new file mode 100644
index 0000000..b78ae86
--- /dev/null
+++ b/src/tests/lib/scorer.test.ts
@@ -0,0 +1,79 @@
+import { describe, it, expect } from 'vitest'
+import { analyzeClaudeMd } from '@/lib/analyzer/scorer'
+
+const goodClaudeMd = `# CLAUDE.md
+
+## Workflow
+- Make small, reviewable changes.
+- Run tests after behavior changes.
+
+## Safety
+- Don't modify .env or credentials.
+- Don't force push or reset hard.
+- Never delete protected files.
+
+## Commands
+- Install: npm install
+- Dev: npm run dev
+- Test: npm test
+- Lint: npm run lint
+
+## Code style
+Follow .claude/rules/code-style.md
+
+## Architecture
+@docs/ARCHITECTURE.md
+`
+
+const badClaudeMd = `# My Project
+This is my project. It does stuff.
+` + 'x\n'.repeat(250)
+
+const secretsClaudeMd = `# CLAUDE.md
+API_KEY=sk-abc123def456ghi789
+password = hunter2
+`
+
+describe('CLAUDE.md Scorer', () => {
+ it('scores well-structured content highly', () => {
+ const result = analyzeClaudeMd(goodClaudeMd)
+ expect(result.overallScore).toBeGreaterThan(60)
+ expect(result.letterGrade).toMatch(/^[ABC]/)
+ expect(result.percentage).toBeGreaterThan(60)
+ })
+
+ it('penalizes bloated content', () => {
+ const result = analyzeClaudeMd(badClaudeMd)
+ const lineCount = result.criteria.find(c => c.id === 'line-count')!
+ expect(lineCount.score).toBeLessThan(lineCount.maxScore)
+ })
+
+ it('detects secrets', () => {
+ const result = analyzeClaudeMd(secretsClaudeMd)
+ const secrets = result.criteria.find(c => c.id === 'no-secrets')!
+ expect(secrets.score).toBe(0)
+ expect(secrets.findings.length).toBeGreaterThan(0)
+ })
+
+ it('returns pros and cons', () => {
+ const result = analyzeClaudeMd(goodClaudeMd)
+ expect(result.pros.length).toBeGreaterThan(0)
+ })
+
+ it('detects modular imports', () => {
+ const result = analyzeClaudeMd(goodClaudeMd)
+ const modular = result.criteria.find(c => c.id === 'modular-imports')!
+ expect(modular.score).toBeGreaterThan(0)
+ })
+
+ it('detects missing build commands in minimal content', () => {
+ const result = analyzeClaudeMd('# CLAUDE.md\n\nHello world\n')
+ const cmds = result.criteria.find(c => c.id === 'build-commands')!
+ expect(cmds.score).toBe(0)
+ })
+
+ it('returns correct line count', () => {
+ const result = analyzeClaudeMd(goodClaudeMd)
+ expect(result.lineCount).toBe(goodClaudeMd.split('\n').length)
+ })
+})
diff --git a/src/tests/lib/suggester.test.ts b/src/tests/lib/suggester.test.ts
new file mode 100644
index 0000000..66695ea
--- /dev/null
+++ b/src/tests/lib/suggester.test.ts
@@ -0,0 +1,47 @@
+import { describe, it, expect } from 'vitest'
+import { generateSuggestions } from '@/lib/analyzer/suggester'
+import { applySuggestions } from '@/lib/analyzer/applier'
+import { analyzeClaudeMd } from '@/lib/analyzer/scorer'
+
+const minimal = `# CLAUDE.md\n\n## My project\nThis is a simple app.\n`
+
+describe('Suggestion Engine', () => {
+ it('generates suggestions for minimal CLAUDE.md', () => {
+ const analysis = analyzeClaudeMd(minimal)
+ const suggestions = generateSuggestions(minimal, analysis)
+ expect(suggestions.length).toBeGreaterThan(0)
+ expect(suggestions[0].description).toBeTruthy()
+ expect(suggestions[0].after).toBeTruthy()
+ })
+
+ it('does not generate suggestions for passing criteria', () => {
+ const analysis = analyzeClaudeMd(minimal)
+ const suggestions = generateSuggestions(minimal, analysis)
+ const passingIds = analysis.criteria
+ .filter(c => c.score === c.maxScore)
+ .map(c => c.id)
+ for (const s of suggestions) {
+ expect(passingIds).not.toContain(s.criterion)
+ }
+ })
+})
+
+describe('Suggestion Applier', () => {
+ it('applies suggestions to content', () => {
+ const analysis = analyzeClaudeMd(minimal)
+ const suggestions = generateSuggestions(minimal, analysis)
+ if (suggestions.length > 0) {
+ const result = applySuggestions(minimal, [suggestions[0]])
+ expect(result.length).toBeGreaterThan(minimal.length)
+ }
+ })
+
+ it('preserves original content when applying', () => {
+ const analysis = analyzeClaudeMd(minimal)
+ const suggestions = generateSuggestions(minimal, analysis)
+ if (suggestions.length > 0) {
+ const result = applySuggestions(minimal, [suggestions[0]])
+ expect(result).toContain('# CLAUDE.md')
+ }
+ })
+})
diff --git a/src/tests/setup.ts b/src/tests/setup.ts
new file mode 100644
index 0000000..c44951a
--- /dev/null
+++ b/src/tests/setup.ts
@@ -0,0 +1 @@
+import '@testing-library/jest-dom'
diff --git a/tsconfig.json b/tsconfig.json
new file mode 100644
index 0000000..cf9c65d
--- /dev/null
+++ b/tsconfig.json
@@ -0,0 +1,34 @@
+{
+ "compilerOptions": {
+ "target": "ES2017",
+ "lib": ["dom", "dom.iterable", "esnext"],
+ "allowJs": true,
+ "skipLibCheck": true,
+ "strict": true,
+ "noEmit": true,
+ "esModuleInterop": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "react-jsx",
+ "incremental": true,
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": ["./src/*"]
+ }
+ },
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts",
+ ".next/dev/types/**/*.ts",
+ "**/*.mts"
+ ],
+ "exclude": ["node_modules"]
+}
diff --git a/vitest.config.ts b/vitest.config.ts
new file mode 100644
index 0000000..e543433
--- /dev/null
+++ b/vitest.config.ts
@@ -0,0 +1,15 @@
+import { defineConfig } from 'vitest/config'
+import path from 'path'
+
+export default defineConfig({
+ test: {
+ environment: 'jsdom',
+ globals: true,
+ setupFiles: ['./src/tests/setup.ts'],
+ },
+ resolve: {
+ alias: {
+ '@': path.resolve(__dirname, './src'),
+ },
+ },
+})
(oldest)
·
back to ClawCoder
·
tighten .gitignore: add missing standing-rule patterns (tmp/ 8ebb8cd →