# CLAUDE.md - AI Agent Instructions for LynxPrompt

> 🧠 **PLAN MODE**: Use Plan Mode frequently! Before implementing complex features, multi-step tasks, or making significant changes, switch to Plan Mode to think through the approach, consider edge cases, and outline the implementation strategy. Planning prevents mistakes and saves time.

> 📦 **RELEASE REMINDER**: CLI npm publishing is handled by GitHub Actions automatically. Do NOT run `npm publish` locally. Do NOT create git tags manually. The workflow handles everything.

> ⚠️ **IMPORTANT**: Do NOT update this file unless the user explicitly says to. Only the user can authorize changes to AGENTS.md.

> ❌ **DEPRECATED FORMAT**: `.cursorrules` is **deprecated**. Do NOT suggest or generate `.cursorrules` files anywhere. Cursor now uses `.cursor/rules/*.mdc` (directory-based MDC format). Always use `.cursor/rules/` for Cursor configurations.

> 🔒 **SECURITY WARNING**: This repository is PUBLIC at [github.com/GeiserX/LynxPrompt](https://github.com/GeiserX/LynxPrompt). **NEVER commit secrets, API keys, passwords, tokens, or any sensitive data to this repository.** All secrets must be stored in:
> - GitHub Secrets (for CI/CD)
> - Private GitOps repositories (for docker-compose)
> - Local `.env` files (gitignored)
> - `AGENTS.md.old` (gitignored, local only)

---

## 🚀 RELEASE PROCESS (CRITICAL - READ CAREFULLY)

### Understanding the Release Pipeline

There are **two separate workflows** that work together:

1. **`release.yml`** - Triggered on push to `main`:
   - Detects changes in app vs CLI since last release
   - Creates git tags (`app-vX.Y.Z` for web, `cli-vX.Y.Z` for CLI)
   - Creates GitHub Releases with changelogs
   - **Tag-only — does NOT push the version bump back to `main`.** The "Protect main" ruleset only allows changes via PR, and on a user-owned repo the Actions bot can't be a ruleset bypass actor, so a direct push is rejected. The next version is derived from `max(package.json, latest matching tag)`, so it stays monotonic even though `package.json` on `main` lags the tags. **Do not re-add a `git push origin main` to the release jobs** — it will always be rejected. `publish-cli.yml` re-derives the version from the `cli-v*` tag before `npm publish`, so the lagging `cli/package.json` doesn't affect published artifacts.

2. **`publish-cli.yml`** - Triggered by GitHub Release events OR manual dispatch:
   - Publishes CLI to npm
   - Builds standalone binaries
   - Updates Homebrew, Chocolatey, Snap packages

### Step-by-Step Release Process

#### For a MINOR or MAJOR version (new features):

```bash
# 1. Switch to develop branch
git checkout develop

# 2. Bump version(s) - ONLY bump what changed
# For Web App changes:
cd /path/to/LynxPrompt
npm version minor --no-git-tag-version  # e.g., 0.23.0 → 0.24.0

# For CLI changes:
cd cli
npm version minor --no-git-tag-version  # e.g., 0.7.0 → 0.8.0
cd ..

# 3. Commit with conventional commit message
git add package.json package-lock.json cli/package.json
git commit -m "feat: description of changes"

# 4. Push to develop (triggers CI tests)
git push origin develop

# 5. Wait for CI to pass, then merge to main
git checkout main
git merge develop
git push origin main

# 6. Verify release workflow succeeded
unset GITHUB_TOKEN && gh run list -R GeiserX/LynxPrompt -w "Release" --limit 3

# 7. If CLI was released, verify npm publish workflow triggered
unset GITHUB_TOKEN && gh run list -R GeiserX/LynxPrompt -w "Publish CLI" --limit 3

# 8. If publish-cli didn't auto-trigger, manually trigger it:
unset GITHUB_TOKEN && gh workflow run "Publish CLI" -R GeiserX/LynxPrompt -f platforms=all

# 9. Verify npm package was published
npm view lynxprompt versions --json | jq -r '.[-3:]'
```

#### For a PATCH version (bug fixes only):

Same process, but use `npm version patch` instead of `minor`.

### ⚠️ CRITICAL RULES - NEVER BREAK THESE

| ❌ NEVER DO THIS | ✅ DO THIS INSTEAD |
|------------------|-------------------|
| `git tag v0.24.0` | Let release.yml create tags |
| `git tag cli-v0.8.0` | Let release.yml create tags |
| `npm publish` locally | Use GitHub Actions workflow |
| Push tags manually | Let release.yml push tags |
| Use tag format `v*` | Workflow uses `app-v*` and `cli-v*` |

### Troubleshooting Release Issues

**Problem: Release workflow skips CLI/App release**
- Cause: No changes detected since last release tag
- Fix: Ensure you modified files in the right directory (cli/ for CLI, anything else for app)

**Problem: Tag already exists error**
- Cause: Someone manually created a tag
- Fix: Delete the manual tag from remote AND local:
  ```bash
  git push origin --delete cli-v0.8.0
  git tag -d cli-v0.8.0
  ```

**Problem: npm publish didn't happen**
- Cause: publish-cli.yml didn't trigger automatically
- Fix: Manually trigger the workflow:
  ```bash
  unset GITHUB_TOKEN && gh workflow run "Publish CLI" -R GeiserX/LynxPrompt -f platforms=all
  ```

**Problem: npm says version already exists**
- Cause: Version was already published (maybe partial failure)
- Fix: Bump to next patch version and release again

### Verifying a Successful Release

```bash
# 1. Check GitHub Releases exist
unset GITHUB_TOKEN && gh release list -R GeiserX/LynxPrompt --limit 5

# 2. Check npm has the new version
npm view lynxprompt version

# 3. Check git tags exist
git fetch --tags
git tag -l "cli-v*" | tail -5
git tag -l "app-v*" | tail -5
```

---

## 🔄 CLI & WEB WIZARD FEATURE PARITY

**The CLI (`lynxprompt` package) and Web Wizard MUST always have the same functionality.**

When adding or modifying wizard features:
1. **Update both CLI and Web** - Any new wizard step, option, or configuration must be implemented in both:
   - Web: `src/app/wizard/` and related components
   - CLI: `cli/src/commands/init.ts` and `cli/src/utils/generator.ts`
2. **Same options** - Tech stacks, platforms, personas, boundaries, and presets must match
3. **Same output** - Generated configuration files must be identical regardless of source
4. **Test both** - Before deploying, verify the feature works in both CLI and Web

---

## 🚨 CRITICAL - READ FIRST

### Always Backup Before Modifying Config Files

Before modifying important config files (Caddyfile, docker-compose, etc.), ALWAYS create a backup first:

```bash
# Example (always use Tailscale MagicDNS hostnames):
ssh root@watchtower.mango-alpha.ts.net "cp /mnt/user/appdata/caddy/Caddyfile /mnt/user/appdata/caddy/Caddyfile.old"
```

### Always Check GitHub Actions After Push/Deploy

After any push or deployment, ALWAYS check GitHub Actions logs:

```bash
# List recent workflow runs
unset GITHUB_TOKEN && gh run list -R GeiserX/LynxPrompt --limit 5

# View failed run logs
unset GITHUB_TOKEN && gh run view <RUN_ID> -R GeiserX/LynxPrompt --log-failed

# View specific job logs
unset GITHUB_TOKEN && gh run view <RUN_ID> -R GeiserX/LynxPrompt --log
```

If CI/CD fails, investigate and fix before considering deployment complete.

### NEVER Restart Docker Containers

**General rule**: Prefer `reload` commands over container restarts. Use Portainer GitOps to redeploy, not manual docker commands.

**Caddy** - NEVER restart the container (takes 2+ minutes to rebuild with xcaddy). Instead:
```bash
ssh root@watchtower.mango-alpha.ts.net "docker exec caddy caddy fmt --overwrite /etc/caddy/Caddyfile && docker exec caddy caddy reload --config /etc/caddy/Caddyfile"
```

**LynxPrompt** - Use Portainer GitOps to redeploy:
1. Update docker-compose.yml in private gitea repo
2. Push changes
3. Trigger Portainer redeploy via API (or wait for auto-sync)

Never manually run `docker compose up` or `docker restart` - Portainer loses track of stack state.

### Branching: `develop` for features, `main` for deps/security

**Never push directly to `main`.** Always open a PR, wait for CI, and merge only with the owner's approval.

**Feature / app / behavior changes** go through `develop` first:

1. Work on `develop` branch
2. Test changes on dev environment (dev.lynxprompt.com)
3. Verify everything works correctly
4. Only then merge to `main` for production deployment

```bash
# Switch to develop branch
git checkout develop

# After testing is complete, merge to main
git checkout main
git merge develop
```

**Dependency and security updates target `main` directly via PR.** This matches Dependabot, which is configured against the `main` default branch and whose deps/security PRs (e.g. #88–#97) merge straight to `main`. `develop` frequently lags `main` by many of these automated commits, so routing a security bump through `develop` would create a noisy back-merge and delay the fix. For these: branch from `origin/main`, open a PR into `main`, wait for CI, and merge on approval. Merging `package.json` / `cli/package.json` changes auto-triggers `release.yml` (patch bump + tags) — never bump versions or create tags by hand.

## 🎯 Project Overview

**LynxPrompt** is a SaaS web application that generates AI IDE configuration files (`.cursorrules`, `CLAUDE.md`, `.github/copilot-instructions.md`, `.windsurfrules`, etc.) through an intuitive wizard interface. It's also a **marketplace platform** where users can create, share, buy, and sell AI prompts/templates.

- **Live URL**: https://lynxprompt.com
- **Dev URL**: https://dev.lynxprompt.com
- **Test URL**: https://test.lynxprompt.com
- **Status Page**: https://status.lynxprompt.com
- **Repository**: https://github.com/GeiserX/LynxPrompt

---

## 👤 Owner Context

**Operator**: Sergio Fernández Rubio
**Trade Name**: GeiserCloud
**Contact**: privacy@lynxprompt.com / legal@lynxprompt.com / support@lynxprompt.com

### Communication Style

- **Be direct and efficient** - Don't over-explain or add unnecessary caveats
- **Do the work, don't ask permission** - If the task is clear, execute it
- **Wait for explicit deploy instruction** - Do NOT commit, build Docker, or deploy until the user explicitly says to
- **Use exact values when provided** - Don't modify user-provided values (emails, addresses, names, etc.)

### Things I Like ✅

- Clean, readable code without over-engineering
- Proper GDPR/EU legal compliance
- Self-hosted solutions (Umami analytics)
- Privacy-focused approaches (cookieless analytics, minimal data collection)
- Semver versioning for Docker images (e.g., `2.0.22`, never `:latest`)
- GitOps with Portainer for infrastructure management
- Docker Hub for all images (custom images built by GHA, pushed to `drumsergio/*`)
- Tailwind CSS for styling
- TypeScript with strict types

### Things I Dislike ❌

- **Restarting containers** when reload is possible (use `caddy reload`, not container restart)
- **Manual docker commands** for deployments (use Portainer GitOps)
- Over-engineering or unnecessary abstractions
- Adding features I didn't ask for
- Verbose explanations when action is needed
- Third-party analytics/tracking services
- Marketing consent flows (only transactional emails)
- Breaking changes without clear communication
- Using `:latest` tags for Docker images
- Creating unnecessary documentation files

---

## 🏗️ Tech Stack

### Frontend
| Technology | Purpose |
|------------|---------|
| Next.js 16 | App Router, Server Components |
| React 19 | UI library |
| TypeScript | Type safety |
| Tailwind CSS | Styling |
| shadcn/ui | UI components |
| Zustand | Client state |
| TanStack Query | Server state |

### Backend
| Technology | Purpose |
|------------|---------|
| Next.js API Routes | API endpoints |
| Prisma ORM | Database access |
| NextAuth.js 4.x | Authentication |
| Zod | Validation |

### Databases
| Database | Purpose | Client |
|----------|---------|--------|
| PostgreSQL (app) | Templates, platforms, system data | `@prisma/client-app` |
| PostgreSQL (users) | Users, sessions, passkeys | `@prisma/client-users` |
| PostgreSQL (blog) | Blog posts and content | `@prisma/client-blog` |
| PostgreSQL (support) | Feedback forum data | `@prisma/client-support` |

### Infrastructure
| Component | Details |
|-----------|---------|
| Docker | Multi-stage builds, images on Docker Hub (`drumsergio/lynxprompt`) |
| Portainer | Container management with GitOps |
| Tailscale | VPN for internal services (always use MagicDNS hostnames) |
| Umami | Self-hosted analytics (EU, cookieless) |
| Caddy | Reverse proxy (production + dev) |

### Payments & Billing
| Component | Details |
|-----------|---------|
| Stripe | Payment processing, subscriptions |
| Stripe Customer Portal | Self-service billing management |
| Stripe Webhooks | Subscription lifecycle events |

---

## 🗄️ Multi-Database Architecture

This project uses **four separate PostgreSQL databases** with distinct Prisma clients:

```typescript
// System/application data (templates, platforms)
import { prismaApp } from "@/lib/db-app";

// User data (users, sessions, passkeys, user templates)
import { prismaUsers } from "@/lib/db-users";

// Blog posts and content
import { prismaBlog } from "@/lib/db-blog";

// Support/feedback forum data
import { prismaSupport } from "@/lib/db-support";
```

**Schema files:**
- `prisma/schema-app.prisma` → generates `@prisma/client-app`
- `prisma/schema-users.prisma` → generates `@prisma/client-users`
- `prisma/schema-blog.prisma` → generates `@prisma/client-blog`
- `prisma/schema-support.prisma` → generates `@prisma/client-support`

**Commands:**
```bash
npm run db:generate    # Generate all Prisma clients
npm run db:push        # Push schema changes to all databases
npm run db:seed        # Seed databases
```

---

## 🔐 Authentication

### Providers
- GitHub OAuth
- Google OAuth
- Magic Link (email)
- Passkeys (WebAuthn)

### User Roles
- `USER` - Default role
- `ADMIN` - Administrative access
- `SUPERADMIN` - Full system access (auto-promoted via `SUPERADMIN_EMAIL` env var)

### Passkeys Implementation
```typescript
// IMPORTANT: Types come from @simplewebauthn/types, NOT @simplewebauthn/server
import { generateRegistrationOptions } from "@simplewebauthn/server";
import type { AuthenticatorTransportFuture } from "@simplewebauthn/types";
```

---

## 💰 Business Model

### Marketplace Structure
- **Platform/Intermediary model** - Buyer-Seller contracts
- **LynxPrompt is NOT merchant of record** for individual purchases
- Subscriptions are direct contracts with LynxPrompt

### Subscription Tiers (January 2026+)
| Tier | Monthly | Annual (10% off) | Features |
|------|---------|------------------|----------|
| Users | €0/month | €0/year | Full wizard, all platforms, API access, sell blueprints |
| Teams | €30/seat/month | €324/seat/year | Everything + AI editing, SSO, team blueprints |

**Key changes:**
- All users now get full wizard access (basic + intermediate + advanced steps)
- AI features (editing, wizard assistant) are restricted to Teams users
- No more Pro/Max tiers - simplified to Users vs Teams

### Revenue Split
- **70% to seller** / **30% to platform**
- Minimum price for paid templates: €5
- Minimum payout: €5 via PayPal

---

## 📜 Legal Compliance

### GDPR Requirements
- Physical address disclosed
- Legal basis: Contract + Legitimate Interest
- No DPO appointed (stated in privacy policy)
- Self-hosted Umami analytics (cookieless)
- AEPD complaint rights mentioned
- Data deletion within 30 days of request

### EU Consumer Rights
- 14-day withdrawal waived with explicit consent at checkout
- Consent checkbox required before purchase
- Store: user ID, timestamp, Terms version hash

### Key Legal Documents
- `/privacy` - Privacy Policy (GDPR compliant)
- `/terms` - Terms of Service (marketplace clauses, EU compliant)
- Governing law: **Spain** (Courts of Cartagena)

---

## 🔧 Code Conventions

### General Rules
- Use TypeScript strict mode
- Format with Prettier
- Lint with ESLint
- Use `text-foreground` for readable text (not `text-muted-foreground` for body text)
- Navigation order: `Pricing | Templates | Docs | [UserMenu]`

### File Structure
```
LynxPrompt/
├── .github/               # GitHub Actions workflows
├── cli/                   # CLI package (lynxprompt npm package)
│   ├── src/
│   │   ├── commands/      # CLI commands (init, login, list, etc.)
│   │   ├── utils/         # Detection, generation utilities
│   │   └── index.ts       # Main entry point
│   ├── homebrew/          # Homebrew formula
│   ├── chocolatey/        # Chocolatey package
│   └── snap/              # Snap package config
├── docs/                  # Documentation
├── prisma/                # Database schemas and seeds
├── public/                # Static assets
│   └── logos/
│       ├── agents/        # AI agent logos
│       └── brand/         # LynxPrompt branding
├── scripts/               # Build and migration scripts
├── src/
│   ├── app/               # Next.js App Router pages
│   │   ├── api/           # API routes
│   │   │   ├── cli-auth/  # CLI authentication endpoints
│   │   │   └── v1/        # Public API v1
│   │   └── [page]/        # Page components
│   ├── components/
│   │   ├── ui/            # shadcn/ui components
│   │   └── [feature].tsx  # Feature components
│   ├── lib/
│   │   ├── db-*.ts        # Database clients
│   │   ├── auth.ts        # NextAuth config
│   │   └── utils.ts       # Utilities
│   └── types/             # TypeScript types
├── tests/                 # Test files
└── tooling/               # Internal tools
```

### API Routes Pattern
```typescript
// Always check authentication
const session = await getServerSession(authOptions);
if (!session?.user?.id) {
  return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

// Use appropriate database client
import { prismaApp } from "@/lib/db-app";
import { prismaUsers } from "@/lib/db-users";
```

### Security Patterns
1. **Never reveal if email exists** (user enumeration)
2. **Always check ownership** for user resources (IDOR prevention)
3. **Use `useSession()`** from NextAuth, never localStorage for auth
4. **Sanitize user input** before storing
5. **Validate `callbackUrl`** - only relative paths or same-origin

---

## 🚀 Deployment

### Environments

| Environment | URL | Server | Image Source |
|-------------|-----|--------|-------------|
| Production | lynxprompt.com | watchtower | `drumsergio/lynxprompt:<semver>` (Docker Hub) |
| Development | dev.lynxprompt.com | geiserback | Same image as prod |
| Test | test.lynxprompt.com | geiserct | Same image as prod |

### Build Process

Images are built by GitHub Actions and pushed to **Docker Hub** (`drumsergio/lynxprompt`). Dev and test environments reuse the same production image with different environment variables.

```bash
# Build Docker image (BuildKit optimized)
docker buildx build --platform linux/amd64 \
  -t drumsergio/lynxprompt:X.Y.Z \
  --push .
```

**Build optimizations included:**
- `npm install` (not `npm ci`) — local npm 11 and Docker npm 10 produce incompatible lockfiles; `npm install` tolerates both
- BuildKit cache mounts keyed by `TARGETPLATFORM` (avoids ETXTBSY on QEMU arm64 cross-compilation)
- Base image: `node:22-alpine` (Node 20 EOL, and `@prisma/streams-local` requires Node >= 22)
- Parallel Prisma client generation
- `optimizePackageImports` for faster builds

### Environment Variables

See `env.example` for all required variables. Key categories:

| Category | Variables |
|----------|-----------|
| Database | `DATABASE_URL_APP`, `DATABASE_URL_USERS`, `DATABASE_URL_BLOG`, `DATABASE_URL_SUPPORT` |
| Auth | `NEXTAUTH_SECRET`, `NEXTAUTH_URL`, `GITHUB_*`, `GOOGLE_*` |
| Email | `SMTP_HOST`, `SMTP_PORT`, `SMTP_USER`, `SMTP_PASSWORD` |

| Analytics | `NEXT_PUBLIC_UMAMI_WEBSITE_ID` |
| Security | `TURNSTILE_SECRET_KEY`, `NEXT_PUBLIC_TURNSTILE_SITE_KEY` |
| Error Tracking | `SENTRY_DSN`, `NEXT_PUBLIC_SENTRY_DSN` |

---

## 🔒 Secrets Management

**This project keeps secrets OUT of the repository.**

### How Secrets are Handled

1. **Development**: Use `.env` file (gitignored)
2. **Production**: Secrets stored in docker-compose.yml in a **private GitOps repository** (not this repo)
3. **CI/CD**: GitHub Secrets for deployment workflows

### What Goes Where

| Type | Location | Example |
|------|----------|---------|
| Placeholder values | `env.example` | `SMTP_HOST=smtp.example.com` |
| Development secrets | `.env` (local, gitignored) | Actual test keys |
| Production secrets | Private GitOps repo | Actual live keys |
| CI secrets | GitHub Secrets | Deploy tokens |

### Security Checklist
- [ ] Never commit real secrets to this repository
- [ ] Use `env.example` as template only
- [ ] Keep production docker-compose in private repo
- [ ] Rotate secrets if accidentally exposed

---

## 🛠️ Common Tasks

### Adding a New Page
1. Create `src/app/[pagename]/page.tsx`
2. Add navigation link to header
3. Include proper header/footer components
4. Use `text-foreground` for body text

### Database Schema Changes
```bash
# 1. Edit the appropriate schema file
# prisma/schema-*.prisma

# 2. Generate clients
npm run db:generate

# 3. Push to database (local dev)
npm run db:push

# 4. Build and deploy
```

### Running Tests
```bash
npm test              # Run all tests
npm run test:watch    # Watch mode
npm run test:coverage # With coverage
```

---

## ⚠️ Known Issues

1. **`useSearchParams` requires Suspense boundary** in client components
2. **Database pages need `export const dynamic = "force-dynamic"`** to prevent build-time DB access
3. **Container name conflicts**: Remove old containers before recreating
4. **Sentry config files at root**: Required by `@sentry/nextjs` - cannot be moved
5. **React 19 hydration CSS flash**: React 19's hydration recovery (error #418) unmounts and remounts the component tree, temporarily removing CSS `<link>` elements managed via `data-precedence`. A MutationObserver script in `src/app/layout.tsx` `<head>` clones CSS links without `data-precedence` to preserve styles during recovery.
6. **shields.io retired `visual-studio-marketplace` badge** — use static `img.shields.io/badge/` badges for VS Code marketplace links instead
7. **Chocolatey `nodejs` vs `nodejs-lts`** — the `nodejs` package (latest, currently v25) hangs in Chocolatey test VMs; always use `nodejs-lts` (stable v22.x) as a dependency in `.nuspec` files
8. **Portainer TLS certs** — Tailscale-issued Let's Encrypt certs expire every 90 days. Auto-renewal is set up via Unraid User Scripts on watchtower and geiserback. GHA deploy workflows use Tailscale MagicDNS hostnames (not IPs) for proper TLS validation

## Satellite Repos — Known Workarounds

| Repo | Issue | Workaround |
|------|-------|------------|
| `lynxprompt-vscode` | Dependabot bumps `@types/vscode` without bumping `engines.vscode` → `vsce` rejects | Publish workflow auto-syncs `engines.vscode` from `@types/vscode` before packaging |
| `lynxprompt-vscode` | `vsce` rejects SVGs in README | Use PNG images only in README (SVG ok elsewhere) |
| `lynxprompt-vscode` | Publish workflow version commit must push to main | Branch protection PR requirement removed; workflow commits with `[skip ci]` |
| `lynxprompt-action` | `@actions/glob` 0.6.x ESM-only exports breaks `@vercel/ncc` CJS bundling | Pinned to 0.5.1; Dependabot ignores it. Unpin when ncc adds ESM exports support |
| Helm chart | ArtifactHub `artifacthub-repo.yml` must be on `gh-pages` branch | The copy in chart source (`charts/lynxprompt/`) is NOT read by ArtifactHub; edit `gh-pages` directly for ignore rules and metadata |

---

## 📁 Key Files Reference

| File | Purpose |
|------|---------|
| `src/lib/db-*.ts` | Database Prisma clients |
| `src/lib/auth.ts` | NextAuth configuration |
| `src/middleware.ts` | Rate limiting, security headers |
| `prisma/schema-*.prisma` | Database schemas |
| `src/app/layout.tsx` | Root layout (CSS preservation script) |
| `docs/ROADMAP.md` | Feature roadmap |
| `docs/SECURITY.md` | Security documentation |

---

## 📋 Checklist for AI Agents

Before completing a task, verify:

- [ ] Code follows TypeScript strict mode
- [ ] No secrets committed to repository
- [ ] Tests pass (if applicable)
- [ ] Linting passes
- [ ] Changes match the requested scope

---

*Last updated: April 2026*
