# Website Clone Skill

[![npm version](https://img.shields.io/npm/v/website-clone-skill.svg)](https://www.npmjs.com/package/website-clone-skill)
[![npm version](https://img.shields.io/npm/v/create-website-clone.svg)](https://www.npmjs.com/package/create-website-clone)
[![npm version](https://img.shields.io/npm/v/website-clone-plugin.svg)](https://www.npmjs.com/package/website-clone-plugin)

A skill for Claude Code, Codex CLI, opencode, Cursor, and Antigravity that enables cloning any website into a Next.js App Router project.

## Packages

| Package | Version | Description |
|---------|---------|-------------|
| [`website-clone-skill`](https://www.npmjs.com/package/website-clone-skill) | 1.0.3 ✅ | **Recommended** - CLI installer with latest fixes |
| [`create-website-clone`](https://www.npmjs.com/package/create-website-clone) | 1.3.1 ⚠️ | Legacy CLI installer (outdated) |
| [`website-clone-plugin`](https://www.npmjs.com/package/website-clone-plugin) | 1.0.2 | Core plugin with Playwright/Firecrawl utilities |

> **Note:** `website-clone-skill` is the actively maintained package with the latest fixes including:
> - Node.js 21+ localStorage error fix
> - Automatic CSS `url()` path rewriting for fonts/assets

## Quick Install (Recommended)

```bash
# Recommended (latest fixes)
npx website-clone-skill

# Legacy (may have issues on Node.js 21+)
npx create-website-clone
```

This will:
1. Ask which editor to install for (Claude Code, Codex CLI, opencode, Cursor, Antigravity)
2. Copy skill files to the correct location
3. Run `npm install` for dependencies
4. Install Playwright Chromium browser
5. Prompt for your Firecrawl API key and save it to your config file (and shell config when possible)

That's it! Restart your editor and ask: "Clone https://example.com"

---

## Architecture

```
User: "Clone https://example.com"
          │
          ▼
┌─────────────────────────────────────────────────────────────┐
│                     Claude (You)                             │
│                                                              │
│  0. scaffoldProject() ─► Next.js + shadcn boilerplate        │
│  1. renderPageWithExtraction() ─► Playwright renders HTML    │
│  2. scrapePage() ──────► Firecrawl + download assets         │
│  3. Analyze HTML + screenshot ─► Identify sections           │
│  4. Generate TSX ──────► Write React components              │
│  5. Write files ───────► Save to output directory            │
│                                                              │
└─────────────────────────────────────────────────────────────┘
```

**Key insight**: Claude IS the LLM. The skill provides:
- Playwright for rendering (handles SPAs, JavaScript)
- Firecrawl for clean HTML extraction
- Asset downloading utilities
- File writing utilities

Claude handles:
- Analyzing the DOM structure
- Identifying visual sections
- Generating TSX components
- Writing Tailwind CSS

## Manual Installation

<details>
<summary><b>Manual install (if npx doesn't work)</b></summary>

### Claude Code

```bash
git clone https://github.com/improdead/website-cloning-skills.git
cp -r website-cloning-skills/skill ~/.claude/skills/website-clone
cd ~/.claude/skills/website-clone && npm install && npx playwright install chromium
```

### opencode

```bash
git clone https://github.com/improdead/website-cloning-skills.git
cp -r website-cloning-skills/skill ~/.config/opencode/skills/website-clone
cd ~/.config/opencode/skills/website-clone && npm install && npx playwright install chromium
```

### Codex CLI

```bash
git clone https://github.com/improdead/website-cloning-skills.git
cp -r website-cloning-skills/skill ~/.codex/skills/website-clone
cd ~/.codex/skills/website-clone && npm install && npx playwright install chromium
```

### Cursor

```bash
git clone https://github.com/improdead/website-cloning-skills.git
mkdir -p .cursor/rules
cp website-cloning-skills/skill/SKILL.md .cursor/rules/website-clone.md
cp -r website-cloning-skills/skill ./website-clone-skill
cd website-clone-skill && npm install && npx playwright install chromium
```

### Antigravity (workspace)

```bash
git clone https://github.com/improdead/website-cloning-skills.git
cp -r website-cloning-skills/skill .agent/skills/website-clone
cd .agent/skills/website-clone && npm install && npx playwright install chromium
```

### Antigravity (global)

```bash
git clone https://github.com/improdead/website-cloning-skills.git
mkdir -p ~/.gemini/antigravity/global_skills
cp -r website-cloning-skills/skill ~/.gemini/antigravity/global_skills/website-clone
cd ~/.gemini/antigravity/global_skills/website-clone && npm install && npx playwright install chromium
```

</details>

## Uninstall

```bash
# Claude Code
rm -rf ~/.claude/skills/website-clone

# opencode
rm -rf ~/.config/opencode/skills/website-clone

# Codex CLI
rm -rf ~/.codex/skills/website-clone

# Cursor
rm -f .cursor/rules/website-clone.md
rm -rf ./website-clone-skill

# Antigravity (workspace)
rm -rf .agent/skills/website-clone

# Antigravity (global)
rm -rf ~/.gemini/antigravity/global_skills/website-clone
```

## Firecrawl API Key

If you skipped the API key prompt during install, set it manually:

```bash
# macOS/Linux
export FIRECRAWL_API_KEY="fc_your_key"

# Save to config file used by the skill
mkdir -p ~/.config/website-clone-skill
echo 'FIRECRAWL_API_KEY="fc_your_key"' >> ~/.config/website-clone-skill/.env

# Add to shell config for persistence
echo 'export FIRECRAWL_API_KEY="fc_your_key"' >> ~/.zshrc

# Windows
setx FIRECRAWL_API_KEY "fc_your_key"
# Or add to: %APPDATA%\website-clone-skill\.env
```

Get your key at https://firecrawl.dev

## Quickstart

1. Run `npx website-clone-skill` and follow the prompts
2. Restart your editor
3. Ask: "Clone https://example.com"
4. Run the clone:

```bash
cd output
npm install
npm run dev
```

## Usage

### Claude Code / opencode

- Trigger the skill with `/website-clone` or “Clone https://example.com”.
- Follow the step-by-step guides in `workflow/` (start at `step0-scaffold.md`).
- The skill writes a Next.js project to your chosen output directory.

### Cursor

- Paste `SKILL.md` into `.cursor/rules/website-clone.md` (see Installation).
- Ask: “Clone https://example.com” or “Use website-clone skill”.
- If Cursor doesn’t auto-open linked files, open the referenced `workflow/` or `rules/` file manually.

### Antigravity

- Start a conversation and mention the skill: “Use the website-clone skill to clone https://example.com”.
- Antigravity will load `SKILL.md` and follow linked files in `workflow/` and `rules/`.

### Programmatic usage

See the **Example Usage** section below for a full script.

## How It Works

When a user asks to clone a website, Claude:

0. **Scaffolds** a Next.js 15 + Tailwind 4 + shadcn/ui project
1. **Renders** the page using Playwright (captures JavaScript-rendered content)
2. **Scrapes** assets and HTML using Firecrawl
3. **Analyzes** the HTML + screenshot to identify sections
4. **Generates** TSX components for each section
5. **Writes** files to the output directory

## Plugin API

### `renderPage(options)`

Renders a page with Playwright and captures HTML, screenshot, and network requests.

```ts
import {renderPage} from './plugin';

const result = await renderPage({
  url: 'https://example.com',
  viewport: {width: 1440, height: 900},
  waitUntil: 'networkidle',
  extraWaitMs: 2000,
});

// result.html - Full rendered HTML
// result.screenshot - Buffer with PNG screenshot
// result.requests - Array of network requests
```

### `scrapePage(options)`

Scrapes assets using Firecrawl and downloads images/fonts.

```ts
import FirecrawlApp from '@mendable/firecrawl-js';
import {scrapePage, createProjectStructure, getFirecrawlApiKey} from './plugin';

const firecrawl = new FirecrawlApp({apiKey: getFirecrawlApiKey()});

await createProjectStructure('./output');

const result = await scrapePage({
  url: 'https://example.com',
  renderResult: renderResult,
  firecrawlClient: firecrawl,
  outputDir: './output',
});

// result.assets - Downloaded assets with local paths
// result.html - Playwright HTML
// result.firecrawlHtml - Firecrawl clean HTML
```

### `getFirecrawlApiKey()`

Loads the Firecrawl API key from the environment or the skill config file.

```ts
import {getFirecrawlApiKey} from './plugin';

const apiKey = getFirecrawlApiKey();
```

### `writeFile(path, content)`

Writes a file to disk, creating directories as needed.

```ts
import {writeFile} from './plugin';

await writeFile('./output/src/components/hero.tsx', heroTsxCode);
```

### `saveScreenshot(buffer, path)`

Saves a screenshot buffer to a file.

```ts
import {saveScreenshot} from './plugin';

await saveScreenshot(result.screenshot, './output/screenshot.png');
```

### `renderPageWithExtraction(options)`

Enhanced render that automatically extracts fonts and colors.

```ts
import {renderPageWithExtraction} from './plugin';

const result = await renderPageWithExtraction({
  url: 'https://example.com',
  viewport: {width: 1440, height: 900},
});

// All standard render results plus:
// result.fonts.googleFontsImports - Google Fonts URLs found
// result.fonts.fontFamilies - Font family names used
// result.colors.hex - All hex colors found
// result.colors.rgb - All RGB colors found
// result.colors.cssVariables - CSS custom properties for colors
// result.stylesheets - All CSS content captured
```

### `extractComputedStyles(options)`

Extract exact computed CSS values from rendered elements.

```ts
import {extractComputedStyles} from './plugin';

const styles = await extractComputedStyles({
  url: 'https://example.com',
  selectors: ['h1', '.hero-button', 'nav a'],
  properties: ['color', 'background-color', 'font-family', 'font-size'],
});

// Returns: [{ selector: 'h1', styles: { 'font-size': '160px', 'color': 'rgb(60, 47, 18)' } }]
```

### `extractFonts(html, css?)`

Extract font information from HTML/CSS content.

```ts
import {extractFonts} from './plugin';

const fonts = extractFonts(htmlContent, cssContent);
// fonts.googleFontsImports - Google Fonts import URLs
// fonts.fontFamilies - Font family names (filtered)
// fonts.fontFaceDeclarations - @font-face rules
```

### `extractColors(content)`

Extract all color values from content.

```ts
import {extractColors} from './plugin';

const colors = extractColors(htmlContent + cssContent);
// colors.hex - Hex colors like #F5F3ED
// colors.rgb - RGB/RGBA colors
// colors.hsl - HSL/HSLA colors
// colors.cssVariables - CSS custom properties that contain colors
```

### `compareScreenshots(original, clone, diffPath?)`

Compare two screenshots for visual similarity.

```ts
import {compareScreenshots} from './plugin';

const comparison = await compareScreenshots(
  originalBuffer,
  cloneBuffer,
  './screenshots/diff.png'
);

// comparison.similarity - 0 to 1, where 1 is identical
// comparison.diffPixels - Number of different pixels
// comparison.totalPixels - Total pixels compared
// comparison.diffImagePath - Path to diff image (if generated)
```

**Note**: Requires optional `pixelmatch` and `pngjs` packages.

### `generateThemeBlock(options)`

Generate Tailwind v4 @theme block from extracted design tokens.

```ts
import {generateThemeBlock} from './plugin';

const themeBlock = generateThemeBlock({
  fonts: extractedFonts,
  colors: extractedColors,
});
// Returns CSS @theme block for globals.css
```

### `generateGoogleFontsUrl(fonts)`

Generate a Google Fonts import URL.

```ts
import {generateGoogleFontsUrl} from './plugin';

const url = generateGoogleFontsUrl([
  {name: 'Inter', weights: [400, 500, 600, 700]},
  {name: 'Caveat', weights: [400, 700]},
]);
// Returns: 'https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Caveat:wght@400;700&display=swap'
```

### `scaffoldProject(outputDir, templateDir)`

Copy boilerplate files to create a complete Next.js 15 + shadcn/ui project.

```ts
import {scaffoldProject, createProjectStructure} from './plugin';

await createProjectStructure('./output');
await scaffoldProject('./output', './templates');
// Copies: package.json, next.config.ts, tsconfig.json, components.json, etc.
```

### `installShadcnComponents(projectDir, components)`

Install additional shadcn/ui components using the CLI.

```ts
import {installShadcnComponents} from './plugin';

await installShadcnComponents('./output', [
  'button', 'card', 'dialog', 'dropdown-menu', 'input'
]);
```

### `generateAgentsMd(options)`

Generate an AGENTS.md file documenting the cloned project.

```ts
import {generateAgentsMd, writeFile} from './plugin';

const agentsMd = generateAgentsMd({
  sourceUrl: 'https://example.com',
  siteName: 'Example Site',
  sections: ['Navigation', 'Hero', 'Features', 'Footer'],
});

await writeFile('./output/AGENTS.md', agentsMd);
```

## Example Usage

```ts
import FirecrawlApp from '@mendable/firecrawl-js';
import {
  createProjectStructure,
  scaffoldProject,
  renderPageWithExtraction,
  scrapePage,
  saveScreenshot,
  generateAgentsMd,
  writeFile,
  getFirecrawlApiKey,
} from './plugin';

const outputDir = './output';
const templateDir = './templates'; // Use absolute path if running elsewhere
const sourceUrl = 'https://example.com';

// 0. Scaffold Next.js + shadcn/ui
await createProjectStructure(outputDir);
await scaffoldProject(outputDir, templateDir);

// 1. Render with extraction
const render = await renderPageWithExtraction({url: sourceUrl});
await saveScreenshot(render.screenshot, `${outputDir}/screenshots/original.png`);

// 2. Scrape assets
const firecrawl = new FirecrawlApp({apiKey: getFirecrawlApiKey()});
const scrape = await scrapePage({
  url: sourceUrl,
  renderResult: render,
  firecrawlClient: firecrawl,
  outputDir,
});

// 3. Analyze HTML + screenshot (Claude does this)
// ... analyze scrape.html to find sections ...
// ... generate TSX for each section ...

// 4. Write project files
await writeFile(`${outputDir}/src/app/page.tsx`, pageTsx);
await writeFile(`${outputDir}/src/app/globals.css`, globalsCss);
await writeFile(`${outputDir}/src/components/sections/hero.tsx`, heroTsx);

// 5. Generate AGENTS.md
const agentsMd = generateAgentsMd({
  sourceUrl,
  siteName: 'Example Site',
  sections: ['Navigation', 'Hero', 'Footer'],
});
await writeFile(`${outputDir}/AGENTS.md`, agentsMd);
```

## Output Structure

```
output/
├── package.json
├── next.config.ts
├── tsconfig.json
├── components.json
├── postcss.config.mjs
├── eslint.config.mjs
├── .gitignore
├── public/
│   ├── assets/
│   └── fonts/
├── src/
│   ├── app/
│   │   ├── layout.tsx
│   │   ├── globals.css
│   │   └── page.tsx
│   ├── components/
│   │   ├── ui/
│   │   │   └── button.tsx
│   │   └── sections/
│   │       ├── navigation.tsx
│   │       ├── hero.tsx
│   │       └── footer.tsx
│   └── lib/
│       └── utils.ts
├── screenshots/
│   └── original.png
└── AGENTS.md
```

## Running the Clone

```bash
cd output
npm install
npm run dev
```

Open http://localhost:3000 to view the clone.

## No Anthropic API Key Needed

This skill runs inside Claude Code/opencode, so **Claude IS the AI**. The skill provides utilities for:
- Rendering pages (Playwright)
- Scraping content (Firecrawl)
- Downloading assets
- Writing files

Claude uses its own intelligence to:
- Analyze the HTML structure
- Identify visual sections
- Generate clean TSX components
- Apply appropriate Tailwind classes
