launchframe 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +143 -183
  2. package/bin/launchframe.mjs +234 -30
  3. package/package.json +52 -67
  4. package/template/.aider.conf.yml +3 -0
  5. package/template/.amazonq/cli-agents/clone-website.json +9 -0
  6. package/template/.amazonq/rules/project.md +156 -0
  7. package/template/.augment/commands/clone-website.md +516 -0
  8. package/template/.claude/skills/clone-website/SKILL.md +515 -0
  9. package/template/.clinerules +156 -0
  10. package/template/.codex/skills/clone-website/SKILL.md +515 -0
  11. package/template/.continue/commands/clone-website.md +517 -0
  12. package/template/.continue/rules/project.md +160 -0
  13. package/template/.cursor/commands/clone-website.md +512 -0
  14. package/template/.cursor/rules/project.mdc +7 -0
  15. package/template/.dockerignore +60 -0
  16. package/template/.gemini/commands/clone-website.toml +518 -0
  17. package/template/.gitattributes +9 -0
  18. package/template/.github/ISSUE_TEMPLATE/bug_report.yml +86 -0
  19. package/template/.github/ISSUE_TEMPLATE/config.yml +5 -0
  20. package/template/.github/ISSUE_TEMPLATE/feature_request.yml +50 -0
  21. package/template/.github/PULL_REQUEST_TEMPLATE.md +19 -0
  22. package/template/.github/copilot-instructions.md +156 -0
  23. package/template/.github/copilot-setup-steps.yml +3 -0
  24. package/template/.github/skills/clone-website/SKILL.md +515 -0
  25. package/template/.github/workflows/ci.yml +36 -0
  26. package/template/.nvmrc +1 -0
  27. package/template/.opencode/commands/clone-website.md +515 -0
  28. package/template/.windsurf/workflows/clone-website.md +512 -0
  29. package/template/.windsurfrules +2 -0
  30. package/template/AGENTS.md +74 -0
  31. package/template/CHANGELOG.md +80 -0
  32. package/template/CLAUDE.md +1 -0
  33. package/template/Dockerfile +114 -0
  34. package/template/Dockerfile.dev +15 -0
  35. package/template/GEMINI.md +1 -0
  36. package/template/README.md +129 -0
  37. package/template/components.json +25 -0
  38. package/template/docker-compose.yml +53 -0
  39. package/template/docs/design-references/.gitkeep +0 -0
  40. package/template/docs/design-references/comparison.png +0 -0
  41. package/template/docs/research/INSPECTION_GUIDE.md +80 -0
  42. package/template/eslint.config.mjs +18 -0
  43. package/template/next.config.ts +8 -0
  44. package/template/package.json +59 -0
  45. package/template/postcss.config.mjs +7 -0
  46. package/template/public/images/.gitkeep +0 -0
  47. package/template/public/seo/.gitkeep +0 -0
  48. package/template/public/videos/.gitkeep +0 -0
  49. package/template/scripts/.gitkeep +0 -0
  50. package/template/scripts/sync-agent-rules.sh +88 -0
  51. package/template/scripts/sync-skills.mjs +111 -0
  52. package/template/src/app/favicon.ico +0 -0
  53. package/template/src/app/globals.css +130 -0
  54. package/template/src/app/layout.tsx +33 -0
  55. package/template/src/app/page.tsx +9 -0
  56. package/template/src/components/ui/button.tsx +60 -0
  57. package/template/src/hooks/.gitkeep +0 -0
  58. package/template/src/lib/utils.ts +6 -0
  59. package/template/src/types/.gitkeep +0 -0
  60. package/template/tsconfig.json +34 -0
  61. package/packages/extract/automated-clone-pass.ts +0 -353
  62. package/packages/extract/browser-extract.ts +0 -237
  63. package/packages/extract/cloner-research-emit.ts +0 -270
  64. package/packages/extract/dom-crawler.ts +0 -521
  65. package/packages/extract/emit.ts +0 -553
  66. package/packages/extract/extract.ts +0 -548
  67. package/packages/extract/host-slug.ts +0 -5
  68. package/packages/extract/mirror-emit.ts +0 -620
  69. package/packages/extract/package.json +0 -13
  70. package/packages/extract/reference-dump.ts +0 -431
  71. package/packages/extract/synthesize.ts +0 -551
  72. package/packages/extract/types.ts +0 -316
package/README.md CHANGED
@@ -1,183 +1,143 @@
1
- # Landingfram
2
-
3
- A reusable workflow for reverse-engineering live marketing sites into a **shadcn-style token theme**, **reference dumps** (DOM, copy index, media index), and a **layout-mirror** React scaffold optimized for AI coding agents (same orchestration model as [ai-website-cloner-template](https://github.com/JCodesMore/ai-website-cloner-template)).
4
-
5
- **Recommended:** [Claude Code](https://docs.anthropic.com/en/docs/claude-code) or **Cursor** for multi-phase builds — but Landingfram output works with any agent that can read Markdown and TypeScript.
6
-
7
- Point the CLI at a URL, run **`npx launchframe@latest <url> "<SaaS idea>"`**, then drive the same **spec → parallel builders → merge → QA** rhythm. In Cursor, use the **Clone Website** command (`.cursor/commands/clone-website.md`); in Claude Code / Codex, use the **`clone-website`** skill. Those workflows merge [ai-website-cloner-template](https://github.com/JCodesMore/ai-website-cloner-template) discipline with **automated** Playwright recon. The same npm package also exposes a **`landingfram`** bin shim for older scripts.
8
-
9
- ## Quick start (any folder)
10
-
11
- **One time per machine** (Chromium for Playwright):
12
-
13
- ```bash
14
- npx playwright install chromium
15
- ```
16
-
17
- **Every extraction** (writes to **`./output/<runId>/`** in the current working directory):
18
-
19
- ```bash
20
- cd path/to/your-app-or-empty-folder
21
- npx launchframe@latest https://example.com "Your SaaS idea"
22
- ```
23
-
24
- Pass multiple URLs for a combined token synthesis (see CLI reference below). Your **SaaS idea** string is stored in `run.json`, `FOR_AI.md`, and `docs/research/`.
25
-
26
- **Then hand output to your agent:** attach **`FOR_AI.md`**, **`reference/<host>/`** (especially **`dom-structure.json`**), **`mirror/<host>/`**, **`tokens.json`**, and **`docs/design-references/<host-slug>/`**. For the full builder orchestration checklist, open **`AGENTS.md`** or run the **Clone Website** Cursor command.
27
-
28
- ## Supported platforms
29
-
30
- | Agent | Status |
31
- | ----- | ------ |
32
- | [Claude Code](https://docs.anthropic.com/en/docs/claude-code) | Recommended — align with upstream template |
33
- | [Codex CLI](https://github.com/openai/codex) | Supported — `.codex/skills/clone-website/` |
34
- | [OpenCode](https://opencode.ai/) | Supported |
35
- | [GitHub Copilot](https://github.com/features/copilot) | Supported — `AGENTS.md` → `npm run sync:agents` |
36
- | [Cursor](https://cursor.com/) | Supported`.cursor/commands/clone-website.md` |
37
- | [Windsurf](https://codeium.com/windsurf) | Supported |
38
- | [Gemini CLI](https://github.com/google-gemini/gemini-cli) | Supported (`GEMINI.md`) |
39
- | [Cline](https://github.com/cline/cline) | Supported |
40
- | [Roo Code](https://github.com/RooCodeInc/Roo-Code) | Supported |
41
- | [Continue](https://continue.dev/) | Supported |
42
- | [Amazon Q](https://aws.amazon.com/q/developer/) | Supported |
43
- | [Augment Code](https://www.augmentcode.com/) | Supported |
44
- | [Aider](https://aider.chat/) | Supported |
45
- | | Any agent that reads `AGENTS.md` |
46
-
47
- ## Prerequisites
48
-
49
- - [Node.js](https://nodejs.org/) **20+**
50
- - Chromium via Playwright (`npx playwright install chromium`)
51
- - **From a git clone of this monorepo:** initialize the upstream template submodule with `git submodule update --init --recursive` (or clone with `git clone --recurse-submodules …`) so `vendor/ai-website-cloner-template` is available for reference.
52
-
53
- ## Tech stack
54
-
55
- - **Extract:** Node.js, Playwright, TypeScript
56
- - **Published CLI:** `npx launchframe` → `packages/extract/` (`landingfram` is a bin alias on install)
57
- - **Studio (optional):** Next.js App Router in `apps/studio/` (**Next.js 16**, **React 19**, Tailwind CSS, **Lucide** via `@framework/blocks` / direct imports — aligned with [ai-website-cloner-template](https://github.com/JCodesMore/ai-website-cloner-template); studio Tailwind is v3 today while their README cites **v4**; emitted run artifacts remain drop-in CSS/token bundles.)
58
- - **Mirror output:** React, Framer Motion, Phosphor (generated `page.tsx`)
59
- - **Tokens:** `tailwind.config.ts`, `globals.css`, `tokens.json`, `REPORT.md`
60
-
61
- ## Parity with [ai-website-cloner-template](https://github.com/JCodesMore/ai-website-cloner-template)
62
-
63
- Landingfram mirrors that template’s **phases and artifact roles**, not its single-repo layout:
64
-
65
- | Aspect | What they do | What Landingfram does (same intent) |
66
- | -------- | ------------------ | ------------------------------------- |
67
- | **Phase 1 recon** | `/clone-website` drives screenshots, tokens, interaction sweep | **`npx launchframe`** performs automated recon (Playwright) into a run folder; optional Browser MCP passes match their sweep discipline (**Clone Website** in Cursor / **`clone-website`** skill elsewhere ≈ orchestration checklist) |
68
- | **Repo shape** | One Next.js app (`src/app`, `components`, …) | **Monorepo:** CLI (`packages/extract`) + optional **`apps/studio`** + **`packages/blocks`**; published CLI ships separately |
69
- | **Where artifacts live** | `docs/research/`, `docs/design-references/`, `public/` | Same tree **inside** `./output/<runId>/` (gitignored by default). To match their repo layout, run with **`--out path/to/your-next-app/extraction-<slug>`** or merge **`docs/`** + **`downloaded_assets/`** into your app’s **`docs/`** and **`public/images`** (and **`public/videos`**, **`public/seo`**) when integrating |
70
- | **Stack** | Next 16, React 19, Tailwind v4, Lucide | **Next 16 + React 19 + Lucide** in studio/blocks; Tailwind **v3** in-studio until upgraded to v4 |
71
-
72
- ## How it works
73
-
74
- Same multi-phase pipeline as [ai-website-cloner-template](https://github.com/JCodesMore/ai-website-cloner-template) (`vendor/ai-website-cloner-template` in this repo):
75
-
76
- 1. **Reconnaissance** — screenshots, design token extraction, interaction sweep (scroll, click, hover, responsive). **Launchfram:** run **`npx launchframe@latest <url> "<SaaS idea>"`** first for an automated baseline into **`./output/<runId>/`**; agents still use browser MCP for the mandatory sweep where needed (see **`.cursor/commands/clone-website.md`**).
77
- 2. **Foundation**updates fonts, colors, globals, downloads all assets (merge emitted `globals.css` / `tailwind.config.ts` / `tokens.json` from the run when present).
78
- 3. **Component specs** detailed spec files (`docs/research/components/`) with exact computed CSS values, states, behaviors, and content.
79
- 4. **Parallel build** — dispatches builder agents in git worktrees, one per section/component.
80
- 5. **Assembly & QA** — merges worktrees, wires up the page, runs visual diff against the original.
81
-
82
- Each builder agent receives the full component specification **inline** — exact `getComputedStyle()` values, interaction models, multi-state content, responsive breakpoints, and asset paths. Authority order: **`FOR_AI.md`** and **`AGENTS.md`**.
83
-
84
- ## Use cases
85
-
86
- - Seeding a **design system** from sites you’re allowed to analyze
87
- - **Layout research** with a typed mirror and DOM reference
88
- - **Rebuilding** properties you own when only the public site remains
89
-
90
- ## Not intended for
91
-
92
- - **Impersonation or phishing**
93
- - **Passing off** another brand’s identity, trademarks, or licensed media
94
- - **Violating robots.txt or site terms** — Landingfram checks robots by default (`--no-robots` is discouraged)
95
-
96
- ## Output layout
97
-
98
- ```txt
99
- output/<runId>/
100
- ├── FOR_AI.md
101
- ├── tokens.json
102
- ├── tailwind.config.ts
103
- ├── globals.css
104
- ├── theme-preview.tsx
105
- ├── REPORT.md
106
- ├── run.json
107
- ├── screenshots/
108
- ├── raw/
109
- ├── reference/
110
- │ └── <host>/
111
- │ ├── dom-structure.json
112
- │ ├── page.html
113
- │ ├── visible-text.txt / .json
114
- │ ├── media.json
115
- │ └── FOR_AI_REFERENCE.md
116
- ├── mirror/
117
- │ └── <host>/
118
- │ ├── page.tsx
119
- │ └── MIRROR_NOTES.md
120
- ├── docs/
121
- │ ├── design-references/<host-slug>/ # PNGs, probes, hints
122
- │ └── research/<host-slug>/ # PAGE_TOPOLOGY, BEHAVIORS, components/*.spec.md
123
- └── downloaded_assets/<host-slug>/ # when enabled
124
- ```
125
-
126
- ## CLI reference
127
-
128
- ```bash
129
- npx launchframe@latest <url> [<url> ...] ["Your SaaS idea"] [options]
130
- ```
131
-
132
- | Flag | Default | Notes |
133
- | ---- | ------- | ----- |
134
- | `--out <dir>` | `./output/<runId>` | Run directory |
135
- | `--name <slug>` | _(unset)_ | Appends slug to run id |
136
- | `--no-robots` | off | Skip robots.txt check |
137
- | `--rate <n>` | `15` | Per-domain requests per minute |
138
- | `--width <px>` | `1440` | Viewport width |
139
- | `--height <px>` | `900` | Viewport height |
140
-
141
- ```bash
142
- npx launchframe@latest https://example.com "AI billing copilot" --name acme
143
- ```
144
-
145
- ## Run inside this repo (contributors)
146
-
147
- ```bash
148
- git clone https://github.com/evangruhlkey/launchframe
149
- cd launchframe
150
- npm install
151
- npx playwright install chromium
152
- npm run extract -- https://example.com "Your SaaS idea"
153
- ```
154
-
155
- ```bash
156
- npm run studio # Next.js studio
157
- npm run studio:build # Production build
158
- npm run typecheck
159
- npm run sync:agents # Regenerate Copilot / Cline / Continue / Amazon Q stubs from AGENTS.md
160
- ```
161
-
162
- ### AI agents in this repo
163
-
164
- - **`AGENTS.md`** is the **single source of truth** for agent instructions; **`docs/research/INSPECTION_GUIDE.md`** is inlined when you run **`npm run sync:agents`**.
165
- - **Cursor:** `.cursor/rules/project.mdc` points at `AGENTS.md`; use **Clone Website** for the full cloner-template-style checklist.
166
- - Edit **`AGENTS.md`**, then run **`npm run sync:agents`**.
167
-
168
- ### Updating agent copies (parity with upstream template)
169
-
170
- | What | Source of truth (this repo) | Sync / notes |
171
- | ---- | --------------------------- | ------------ |
172
- | Project instructions | `AGENTS.md` | `npm run sync:agents` |
173
- | `/clone-website` skill text | `vendor/ai-website-cloner-template/.claude/skills/clone-website/SKILL.md` | Copy into `.claude/`, `.codex/`, `.github/skills/`; re-apply **Pre-Flight step 0** (Launchfram). Cursor command: `vendor/.../.cursor/commands/clone-website.md` → `.cursor/commands/` (same step 0). |
174
- | Inspection checklist | `vendor/.../docs/research/INSPECTION_GUIDE.md` | Merged in `docs/research/INSPECTION_GUIDE.md` with a **Landingfram** mapping section. |
175
-
176
- ## What this is not
177
-
178
- - **Not the origin site’s bundle** — output is synthesized tokens, reference artifacts, and a **slot-based mirror**, not a dump of proprietary components.
179
- - **Not legal clearance** — you are responsible for compliance; see **`FOR_AI.md`** and **`AGENTS.md`**.
180
-
181
- ## License
182
-
183
- MIT. See [`LICENSE`](./LICENSE).
1
+ # Launchframe
2
+
3
+ > **Scaffold a SaaS-ready Next.js codebase from any URLin one command.**
4
+
5
+ ```bash
6
+ npx launchframe@latest <url> "<saas idea>"
7
+ ```
8
+
9
+ Launchframe drops an [AI-cloner template](https://github.com/JCodesMore/ai-website-cloner-template) into your filesystem, pre-wired with the URL you want to clone and the SaaS you want to build. Open the project in your AI agent of choice ([Claude Code](https://docs.anthropic.com/en/docs/claude-code), [Cursor](https://cursor.com/), [Codex CLI](https://github.com/openai/codex), [Gemini CLI](https://github.com/google-gemini/gemini-cli), etc.), run `/clone-website`, and the agent will reverse-engineer the target site pixel-perfectly, then re-skin every line of copy and every brand mark for your SaaS idea.
10
+
11
+ ## Why Launchframe
12
+
13
+ Cloning a website is a solved problem if you have a great AI agent and a great template. What's missing is **a one-command entrypoint that wires those together with your specific intent**.
14
+
15
+ - ✅ Pick a real, beautiful, production-tested landing page
16
+ - ✅ Pick the SaaS you want to ship
17
+ - Get a buildable Next.js codebase in seconds, ready for your AI agent to clone + rebrand
18
+
19
+ You spend your time on product, not on translating Figma boxes into Tailwind.
20
+
21
+ ## Quick Start
22
+
23
+ ```bash
24
+ # 1) Scaffold a project
25
+ npx launchframe@latest https://linear.app "AI-powered customer feedback platform"
26
+
27
+ # 2) Hop in and install
28
+ cd launchframe-app
29
+ npm install
30
+
31
+ # 3) Open in your AI agent and run the skill
32
+ # (Claude Code recommended: `claude --chrome`)
33
+ /clone-website
34
+ ```
35
+
36
+ The skill reads `launchframe.config.json` from the scaffolded project no need to repeat the URL or idea.
37
+
38
+ ## What gets generated
39
+
40
+ ```
41
+ launchframe-app/
42
+ ├─ launchframe.config.json ← url + saas idea (the directive)
43
+ ├─ AGENTS.md ← agent instructions (read by every supported agent)
44
+ ├─ .claude/ Claude Code skill: /clone-website
45
+ ├─ .cursor/ ← Cursor command: /clone-website
46
+ ├─ .codex/ .gemini/ .opencode/ .windsurf/ .github/ .augment/
47
+ │ .continue/ .amazonq/ ← every other agent gets the same skill, format-adapted
48
+ ├─ src/ ← Next.js 16 + shadcn/ui + Tailwind v4 scaffold
49
+ ├─ public/ becomes populated by the cloner with real assets
50
+ ├─ docs/research/ ← extraction artifacts the cloner writes during the run
51
+ └─ scripts/ ← asset download + sync scripts
52
+ ```
53
+
54
+ ## CLI Reference
55
+
56
+ ```
57
+ npx launchframe@latest <url> "<saas idea>" [options]
58
+
59
+ Arguments:
60
+ <url> URL of the site you want to clone (e.g. https://linear.app)
61
+ <saas idea> One-line description of the SaaS you're building
62
+
63
+ Options:
64
+ --dir <name> Target directory (default: launchframe-app)
65
+ --force Overwrite the target if it already exists
66
+ --help, -h Show this message
67
+ --version, -v Show the version
68
+ ```
69
+
70
+ ### Examples
71
+
72
+ ```bash
73
+ npx launchframe@latest https://linear.app "AI-powered customer feedback platform"
74
+
75
+ npx launchframe@latest https://vercel.com "DevOps platform for ML teams" --dir my-startup
76
+
77
+ # Hostname-onlylaunchframe will prepend https://
78
+ npx launchframe@latest stripe.com "B2B billing for AI agent companies"
79
+ ```
80
+
81
+ ## Supported AI Agents
82
+
83
+ | Agent | Status |
84
+ | ------------------------------------------------------------- | -------------------------- |
85
+ | [Claude Code](https://docs.anthropic.com/en/docs/claude-code) | **Recommended** — Opus 4.7 |
86
+ | [Codex CLI](https://github.com/openai/codex) | Supported |
87
+ | [Cursor](https://cursor.com/) | Supported |
88
+ | [Gemini CLI](https://github.com/google-gemini/gemini-cli) | Supported |
89
+ | [GitHub Copilot](https://github.com/features/copilot) | Supported |
90
+ | [OpenCode](https://opencode.ai/) | Supported |
91
+ | [Windsurf](https://codeium.com/windsurf) | Supported |
92
+ | [Cline](https://github.com/cline/cline) / Roo Code | Supported |
93
+ | [Continue](https://continue.dev/) | Supported |
94
+ | [Amazon Q](https://aws.amazon.com/q/developer/) | Supported |
95
+ | [Augment Code](https://www.augmentcode.com/) | Supported |
96
+ | [Aider](https://aider.chat/) | Supported |
97
+
98
+ Every supported agent receives the same `/clone-website` skill — it's auto-synced from a single source-of-truth file in the template.
99
+
100
+ ## How `/clone-website` Works
101
+
102
+ A multi-phase pipeline runs inside your AI agent. Browser automation MCP (Chrome MCP / Playwright MCP / Browserbase MCP) is required.
103
+
104
+ 1. **Reconnaissance** — full-page screenshots at desktop + mobile, design-token extraction, mandatory scroll/click/hover/responsive sweep
105
+ 2. **Foundation** — updates fonts, colors, globals.css; downloads all assets; extracts SVG icons
106
+ 3. **Component Specs** — writes detailed `.spec.md` files for every section with exact `getComputedStyle()` values
107
+ 4. **Parallel Build** — dispatches builder agents in git worktrees, one per section/component
108
+ 5. **SaaS Rebrand Pass** — swaps product name, headlines, feature copy, CTAs, and brand marks to match `launchframe.config.json#idea`. Spacing, color, typography, animations stay 1:1
109
+ 6. **Assembly + Visual QA** — merges worktrees, wires `src/app/page.tsx`, runs side-by-side diff against the original
110
+
111
+ Each builder agent gets the full component spec inline. No guessing.
112
+
113
+ ## Editing the Config Mid-Project
114
+
115
+ `launchframe.config.json` is the contract between you and the skill. Change either field any time and re-invoke `/clone-website`:
116
+
117
+ ```json
118
+ {
119
+ "url": "https://stripe.com",
120
+ "idea": "Usage-based billing for AI agent startups"
121
+ }
122
+ ```
123
+
124
+ ## Built On
125
+
126
+ - The amazing [`ai-website-cloner-template`](https://github.com/JCodesMore/ai-website-cloner-template) by [@JCodesMore](https://github.com/JCodesMore) — Launchframe vendors and extends this as its payload
127
+ - [Next.js 16](https://nextjs.org/), [React 19](https://react.dev/), [shadcn/ui](https://ui.shadcn.com/), [Tailwind CSS v4](https://tailwindcss.com/)
128
+
129
+ ## Prerequisites
130
+
131
+ - [Node.js](https://nodejs.org/) 18+ to run the CLI
132
+ - [Node.js](https://nodejs.org/) 24+ inside the scaffolded project (for Next.js 16)
133
+ - An AI coding agent with browser automation MCP
134
+
135
+ ## Not Intended For
136
+
137
+ - Phishing or impersonation
138
+ - Passing off someone else's design as your own
139
+ - Violating terms of service (some sites prohibit scraping or reproduction — check first)
140
+
141
+ ## License
142
+
143
+ MIT — see [LICENSE](./LICENSE).
@@ -1,46 +1,250 @@
1
1
  #!/usr/bin/env node
2
+ // @ts-check
2
3
  /**
3
- * CLI entry for `npx launchframe@latest` (npm package name) and the `landingfram`
4
- * bin shim on install. Spawns the TypeScript extract pipeline with the same Node
5
- * that installed this package. Output defaults to `./output/<runId>/` in the
6
- * *current* working directory (where the user ran the command), not inside the
7
- * package install path.
4
+ * launchframe scaffold an AI-cloner project pointed at any URL + SaaS idea.
5
+ *
6
+ * Usage:
7
+ * npx launchframe@latest <url> "<saas idea>" [--dir <name>] [--force]
8
+ *
9
+ * Behavior:
10
+ * 1. Validates the URL and SaaS-idea string.
11
+ * 2. Copies the bundled `template/` payload (the ai-website-cloner-template
12
+ * pre-configured for launchframe) into `./<name>` (default `launchframe-app`).
13
+ * 3. Writes `launchframe.config.json` into the new project so the bundled
14
+ * `/clone-website` skill knows which URL to clone and how to re-skin it
15
+ * for your SaaS idea.
16
+ * 4. Prints next steps for hooking up an AI agent (Claude Code, Cursor,
17
+ * Codex CLI, Gemini CLI, etc.).
8
18
  */
9
19
 
10
- import { spawnSync } from "node:child_process";
20
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
11
21
  import { createRequire } from "node:module";
12
- import { dirname, join } from "node:path";
22
+ import { dirname, isAbsolute, join, resolve } from "node:path";
13
23
  import { fileURLToPath } from "node:url";
14
24
 
15
25
  const __filename = fileURLToPath(import.meta.url);
16
26
  const __dirname = dirname(__filename);
17
- const pkgRoot = join(__dirname, "..");
18
- const pkgJsonPath = join(pkgRoot, "package.json");
27
+ const pkgRoot = resolve(__dirname, "..");
28
+ const templateDir = join(pkgRoot, "template");
29
+ const require = createRequire(import.meta.url);
30
+ const pkgJson = require(join(pkgRoot, "package.json"));
19
31
 
20
- const require = createRequire(pkgJsonPath);
32
+ const COLORS = {
33
+ reset: "\x1b[0m",
34
+ bold: "\x1b[1m",
35
+ dim: "\x1b[2m",
36
+ red: "\x1b[31m",
37
+ green: "\x1b[32m",
38
+ yellow: "\x1b[33m",
39
+ cyan: "\x1b[36m",
40
+ };
41
+ const supportsColor = process.stdout.isTTY && !process.env.NO_COLOR;
42
+ const c = (color, s) => (supportsColor ? COLORS[color] + s + COLORS.reset : s);
21
43
 
22
- let tsxCli;
23
- try {
24
- const tsxPkg = require.resolve("tsx/package.json", { paths: [pkgRoot] });
25
- tsxCli = join(dirname(tsxPkg), "dist", "cli.mjs");
26
- } catch {
27
- console.error(
28
- "launchframe: could not resolve the `tsx` runtime. Re-install with: npm install -g launchframe",
44
+ function printHelp() {
45
+ console.log(`
46
+ ${c("bold", "launchframe")} ${c("dim", `v${pkgJson.version}`)}
47
+ Scaffold an AI-cloner project pointed at any URL + SaaS idea.
48
+
49
+ ${c("bold", "Usage:")}
50
+ npx launchframe@latest <url> "<saas idea>" [options]
51
+
52
+ ${c("bold", "Arguments:")}
53
+ <url> URL of the site you want to clone (e.g. https://linear.app)
54
+ <saas idea> One-line description of the SaaS you're building
55
+ (used to re-skin copy/branding after the visual clone)
56
+
57
+ ${c("bold", "Options:")}
58
+ --dir <name> Target directory name (default: launchframe-app)
59
+ --force Overwrite the target directory if it already exists
60
+ --help, -h Show this message
61
+ --version, -v Show the launchframe version
62
+
63
+ ${c("bold", "Example:")}
64
+ npx launchframe@latest https://linear.app "AI-powered customer feedback platform"
65
+ npx launchframe@latest https://vercel.com "DevOps platform for ML teams" --dir my-startup
66
+ `);
67
+ }
68
+
69
+ function exitErr(msg, code = 1) {
70
+ console.error(c("red", `\nlaunchframe: ${msg}`));
71
+ console.error(c("dim", "Run `npx launchframe --help` for usage.\n"));
72
+ process.exit(code);
73
+ }
74
+
75
+ function parseArgs(argv) {
76
+ const positional = [];
77
+ const opts = { dir: "launchframe-app", force: false, help: false, version: false };
78
+ for (let i = 0; i < argv.length; i++) {
79
+ const a = argv[i];
80
+ if (a === "--help" || a === "-h") opts.help = true;
81
+ else if (a === "--version" || a === "-v") opts.version = true;
82
+ else if (a === "--force" || a === "-f") opts.force = true;
83
+ else if (a === "--dir" || a === "-d") {
84
+ const next = argv[++i];
85
+ if (!next || next.startsWith("-")) exitErr("`--dir` requires a value");
86
+ opts.dir = next;
87
+ } else if (a.startsWith("--dir=")) {
88
+ opts.dir = a.slice("--dir=".length);
89
+ } else if (a.startsWith("-")) {
90
+ exitErr(`unknown option: ${a}`);
91
+ } else {
92
+ positional.push(a);
93
+ }
94
+ }
95
+ return { positional, opts };
96
+ }
97
+
98
+ function normalizeUrl(raw) {
99
+ let candidate = raw.trim();
100
+ if (!/^https?:\/\//i.test(candidate)) candidate = `https://${candidate}`;
101
+ try {
102
+ const u = new URL(candidate);
103
+ if (!u.hostname || !u.hostname.includes(".")) throw new Error("missing hostname");
104
+ return u.toString();
105
+ } catch {
106
+ exitErr(`invalid URL: ${raw}`);
107
+ }
108
+ }
109
+
110
+ function isDirEmpty(dir) {
111
+ try {
112
+ return readdirSync(dir).length === 0;
113
+ } catch {
114
+ return true;
115
+ }
116
+ }
117
+
118
+ function rewritePackageJson(targetDir, projectName, url, idea) {
119
+ const pkgPath = join(targetDir, "package.json");
120
+ if (!existsSync(pkgPath)) return;
121
+ let pkg;
122
+ try {
123
+ pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
124
+ } catch {
125
+ return;
126
+ }
127
+ pkg.name = projectName.toLowerCase().replace(/[^a-z0-9-_]/g, "-").slice(0, 64) || "launchframe-app";
128
+ pkg.version = "0.1.0";
129
+ pkg.private = true;
130
+ pkg.description = `Launchframe project — clone of ${url} reframed as: ${idea}`;
131
+ delete pkg.author;
132
+ delete pkg.homepage;
133
+ delete pkg.repository;
134
+ delete pkg.bugs;
135
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
136
+ }
137
+
138
+ function writeLaunchframeConfig(targetDir, url, idea) {
139
+ const cfg = {
140
+ $schema: "https://launchframe.dev/schema/launchframe.config.json",
141
+ url,
142
+ idea,
143
+ createdAt: new Date().toISOString(),
144
+ launchframeVersion: pkgJson.version,
145
+ notes: [
146
+ "The /clone-website skill reads this file at the start of every run.",
147
+ "`url` is the visual source-of-truth (clone its layout, spacing, tokens, motion).",
148
+ "`idea` is the rebranding directive applied AFTER the pixel-perfect clone.",
149
+ "Edit either field and re-invoke the skill to re-run.",
150
+ ],
151
+ };
152
+ writeFileSync(
153
+ join(targetDir, "launchframe.config.json"),
154
+ JSON.stringify(cfg, null, 2) + "\n",
155
+ "utf8"
29
156
  );
30
- process.exit(1);
31
157
  }
32
158
 
33
- const extractScript = join(pkgRoot, "packages", "extract", "extract.ts");
159
+ function nextSteps(projectDir, projectName, url, idea) {
160
+ const rel = `./${projectName}`;
161
+ console.log(`
162
+ ${c("green", "\u2713")} Scaffolded ${c("bold", projectName)} from launchframe template.
34
163
 
35
- const result = spawnSync(
36
- process.execPath,
37
- [tsxCli, extractScript, ...process.argv.slice(2)],
38
- {
39
- cwd: process.cwd(),
40
- stdio: "inherit",
41
- env: process.env,
42
- shell: false,
43
- },
44
- );
164
+ ${c("dim", "Target URL:")} ${c("cyan", url)}
165
+ ${c("dim", "SaaS idea:")} ${c("cyan", idea)}
166
+ ${c("dim", "Location:")} ${projectDir}
45
167
 
46
- process.exit(result.status === null ? 1 : result.status);
168
+ ${c("bold", "Next steps:")}
169
+ 1. ${c("cyan", `cd ${rel}`)}
170
+ 2. ${c("cyan", "npm install")}
171
+ 3. Open the folder in your AI coding agent of choice.
172
+ ${c("dim", "(Claude Code recommended — `claude --chrome` — but Cursor, Codex CLI,")}
173
+ ${c("dim", " Gemini CLI, Copilot, Windsurf, Cline, Continue, etc. all work.)")}
174
+ 4. Run the skill: ${c("cyan", "/clone-website")}
175
+ ${c("dim", "It reads launchframe.config.json for the URL + idea automatically.")}
176
+
177
+ ${c("dim", "Edit launchframe.config.json any time to change target URL or SaaS idea.")}
178
+ `);
179
+ }
180
+
181
+ function main() {
182
+ const { positional, opts } = parseArgs(process.argv.slice(2));
183
+ if (opts.help) return printHelp();
184
+ if (opts.version) return console.log(pkgJson.version);
185
+
186
+ if (positional.length < 2) {
187
+ if (positional.length === 0) {
188
+ console.error(c("red", "\nlaunchframe: missing <url> and <saas idea>"));
189
+ } else {
190
+ console.error(c("red", '\nlaunchframe: missing <saas idea> (wrap it in quotes: "...")'));
191
+ }
192
+ printHelp();
193
+ process.exit(1);
194
+ }
195
+ if (positional.length > 2) {
196
+ exitErr(
197
+ `too many positional arguments. Wrap your SaaS idea in quotes: ` +
198
+ `\`npx launchframe ${positional[0]} "${positional.slice(1).join(" ")}"\``
199
+ );
200
+ }
201
+
202
+ const url = normalizeUrl(positional[0]);
203
+ const idea = positional[1].trim();
204
+ if (!idea) exitErr("SaaS idea cannot be empty");
205
+
206
+ const dirName = opts.dir;
207
+ const targetDir = isAbsolute(dirName) ? dirName : resolve(process.cwd(), dirName);
208
+
209
+ if (existsSync(targetDir) && !isDirEmpty(targetDir) && !opts.force) {
210
+ exitErr(
211
+ `target directory \`${dirName}\` already exists and is not empty.\n` +
212
+ `Pass \`--force\` to overwrite, or use \`--dir <name>\` to choose a different folder.`
213
+ );
214
+ }
215
+
216
+ if (!existsSync(templateDir)) {
217
+ exitErr(
218
+ `bundled template not found at \`${templateDir}\`.\n` +
219
+ `This usually means the launchframe package was installed incompletely. ` +
220
+ `Try: \`npm install -g launchframe@latest\` or re-run \`npx launchframe@latest ...\`.`
221
+ );
222
+ }
223
+
224
+ mkdirSync(targetDir, { recursive: true });
225
+
226
+ console.log(c("dim", `\nCopying template \u2192 ${targetDir} ...`));
227
+ cpSync(templateDir, targetDir, {
228
+ recursive: true,
229
+ force: opts.force,
230
+ filter: (src) => {
231
+ const lower = src.toLowerCase();
232
+ if (lower.endsWith(`${"\\"}node_modules`) || lower.endsWith("/node_modules")) return false;
233
+ if (lower.endsWith(`${"\\"}.next`) || lower.endsWith("/.next")) return false;
234
+ if (lower.endsWith("package-lock.json")) return false;
235
+ return true;
236
+ },
237
+ });
238
+
239
+ rewritePackageJson(targetDir, dirName, url, idea);
240
+ writeLaunchframeConfig(targetDir, url, idea);
241
+
242
+ nextSteps(targetDir, dirName, url, idea);
243
+ }
244
+
245
+ try {
246
+ main();
247
+ } catch (err) {
248
+ console.error(c("red", `\nlaunchframe: ${err?.message || err}\n`));
249
+ process.exit(1);
250
+ }