A high-performance web service that converts JavaScript-heavy documentation sites into clean, LLM-optimized Markdown. Built specifically to solve the problem of AI agents being unable to parse modern documentation sites that rely heavily on client-side rendering.
Why llm.codes exists: Modern AI agents like Claude Code struggle with JavaScript-heavy documentation sites, particularly Apple's developer docs. This tool bridges that gap by converting dynamic content into clean, parseable Markdown that AI agents can actually use.
📖 Read the full story: How llm.codes Transforms Developer Documentation for AI Agents
Modern documentation sites (especially Apple's) use heavy JavaScript rendering that makes content invisible to AI agents. llm.codes solves this by:
🚀 Try it now at llm.codes
Experience the tool instantly without any setup required.
git clone https://github.com/amantus-ai/llm-codes.git
cd llm-codes
pnpm install
.env.local file:cp .env.local.example .env.local
.env.local:# Default hosted provider
SCRAPE_PROVIDER=firecrawl
FIRECRAWL_API_KEY=your_api_key_here
# Self-hosted alternative
# SCRAPE_PROVIDER=playwright
# Run once: pnpm exec playwright install chromium
# Optional - Redis Cache (Recommended for production)
UPSTASH_REDIS_REST_URL=https://your-redis-instance.upstash.io
UPSTASH_REDIS_REST_TOKEN=your_redis_token_here
# Optional - Cache Admin
CACHE_ADMIN_KEY=your_secure_admin_key_here
pnpm run dev
The easiest way to deploy is using Vercel:
FIRECRAWL_API_KEY environment variableSCRAPE_PROVIDER: firecrawl by default, or playwright for self-hosted Node/Docker deploymentsFIRECRAWL_API_KEY: Your Firecrawl API key (required when SCRAPE_PROVIDER=firecrawl)UPSTASH_REDIS_REST_URL: Your Upstash Redis URL (optional)UPSTASH_REDIS_REST_TOKEN: Your Upstash Redis token (optional)CACHE_ADMIN_KEY: Admin key for cache endpoints (optional)Click "Learn more" to see the supported URL patterns
Configure Options (click "Show Options"):
Use Deep Crawl Mode: Use Firecrawl's crawl API with the selected depth and page limit. Turn this off when using SCRAPE_PROVIDER=playwright.
Process: Click "Process Documentation" and grant notification permissions if prompted
Monitor Progress:
Browser notifications alert you when complete
Download: View statistics and download your clean Markdown file
llm.codes uses intelligent pattern matching to support most documentation sites automatically. Rather than maintaining a list of thousands of individual sites, we use regex patterns to match common documentation URL structures.
We support documentation sites that match these patterns:
docs.*, developer.*, learn.*, etc.)docs.python.org, developer.apple.com, learn.microsoft.comPattern: Any subdomain like docs, developer, dev, learn, help, api, guide, wiki, or devcenter
Documentation Paths (/docs, /guide, /learn, etc.)
angular.io/docs, redis.io/docs, react.dev/learnPattern: URLs ending with paths like /docs, /documentation, /api-docs, /guides, /learn, /help, /stable, or /latest
Programming Language Sites (*js.org, *lang.org, etc.)
vuejs.org, kotlinlang.org, ruby-doc.orgPattern: Domains ending with js, lang, py, or -doc followed by .org or .com
GitHub Pages (*.github.io)
A small number of popular documentation sites don't follow standard patterns and are explicitly supported:
swiftpackageindex.com)flask.palletsprojects.com)mui.com/material-ui)pip.pypa.io/en/stable)www.php.net/docs.php)docs.rs)zulip.com/api)www.nutrient.io/api)sdk.play.date)js-cdn.music.apple.com/musickit)react-spring.io)valtio.pmnd.rs)Most documentation sites are automatically supported! If your site follows standard documentation URL patterns (like having /docs in the path or docs. as a subdomain), it should work without any changes.
If you find a documentation site that isn't supported, please open an issue and we'll either adjust our patterns or add it as an exception.
| Option | Description | Default | Range |
|---|---|---|---|
| Crawl Depth | How many levels deep to follow links | 2 | 0-5 |
| Max URLs | Maximum number of URLs to process | 200 | 1-2000 |
| Batch Size | URLs processed concurrently | 10 | N/A |
| Cache Duration | How long results are cached | 30 days | N/A |
/api/scrapeThe core API endpoint that handles documentation conversion.
Request Flow:
SCRAPE_PROVIDER=playwright launches self-hosted Chromium.Request Body:
{
"url": "https://developer.apple.com/documentation/swiftui",
"action": "scrape"
}
Response:
{
"success": true,
"data": {
"markdown": "# SwiftUI Documentation\n\n..."
},
"cached": false
}
Error Handling:
llm-codes/
├── src/
│ ├── app/
│ │ ├── api/
│ │ │ ├── scrape/ # Single-page scrape endpoint
│ │ │ ├── crawl/ # Firecrawl crawl endpoints
│ │ │ └── cache/ # Cache stats endpoint
│ │ ├── globals.css # Global styles & Tailwind
│ │ ├── layout.tsx # Root layout
│ │ ├── page.tsx # Main page component
│ │ └── icon.tsx # Dynamic favicon
│ ├── constants.ts # Configuration constants
│ ├── utils/ # Utility functions
│ │ ├── code-extraction.ts # Code-block extraction
│ │ ├── content-processing.ts # Content cleaning logic
│ │ ├── file-utils.ts # File handling
│ │ ├── result-processing.ts # Final output shaping
│ │ ├── url-utils.ts # URL validation & handling
│ │ └── __tests__/ # Utility tests
│ ├── lib/ # Scrape providers, cache, retry, and HTTP clients
│ └── test/
│ └── setup.ts # Test configuration
├── public/
│ ├── logo.png # App icon
│ └── manifest.json # PWA manifest
├── next.config.js # Next.js configuration
├── postcss.config.js # PostCSS with Tailwind v4
├── tsconfig.json # TypeScript configuration
├── vitest.config.ts # Vitest test configuration
├── spec.md # Detailed specification
└── package.json # Dependencies
# Run all tests once
pnpm run test:run
# Run tests with UI
pnpm run test:ui
# Run tests with coverage
pnpm run test:coverage
# Type checking
pnpm run type-check
# Full local gate
pnpm run verify
# Live Firecrawl mode smoke without a local browser
pnpm run verify:modes:live
Tests cover:
Contributions are welcome! Please feel free to submit a Pull Request.
git checkout -b feature/AmazingFeature)git commit -m 'Add some AmazingFeature')git push origin feature/AmazingFeature)The app includes a 30-day cache to minimize API calls. If you're hitting rate limits:
For production use, we recommend setting up Redis cache:
$ claude mcp add llm-codes \
-- python -m otcore.mcp_server <graph>