launchframe 0.2.0 → 0.2.2

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 (73) hide show
  1. package/README.md +144 -183
  2. package/bin/launchframe.mjs +261 -28
  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 +161 -0
  7. package/template/.augment/commands/clone-website.md +518 -0
  8. package/template/.claude/skills/clone-website/SKILL.md +517 -0
  9. package/template/.clinerules +161 -0
  10. package/template/.codex/skills/clone-website/SKILL.md +517 -0
  11. package/template/.continue/commands/clone-website.md +519 -0
  12. package/template/.continue/rules/project.md +165 -0
  13. package/template/.cursor/commands/clone-website.md +514 -0
  14. package/template/.cursor/rules/project.mdc +20 -0
  15. package/template/.dockerignore +60 -0
  16. package/template/.gemini/commands/clone-website.toml +520 -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 +161 -0
  23. package/template/.github/copilot-setup-steps.yml +3 -0
  24. package/template/.github/skills/clone-website/SKILL.md +517 -0
  25. package/template/.github/workflows/ci.yml +36 -0
  26. package/template/.nvmrc +1 -0
  27. package/template/.opencode/commands/clone-website.md +517 -0
  28. package/template/.windsurf/workflows/clone-website.md +514 -0
  29. package/template/.windsurfrules +2 -0
  30. package/template/AGENTS.md +79 -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 +118 -0
  37. package/template/START_HERE.md +15 -0
  38. package/template/components.json +25 -0
  39. package/template/docker-compose.yml +53 -0
  40. package/template/docs/design-references/.gitkeep +0 -0
  41. package/template/docs/design-references/comparison.png +0 -0
  42. package/template/docs/research/INSPECTION_GUIDE.md +80 -0
  43. package/template/eslint.config.mjs +18 -0
  44. package/template/next.config.ts +8 -0
  45. package/template/package.json +59 -0
  46. package/template/postcss.config.mjs +7 -0
  47. package/template/public/images/.gitkeep +0 -0
  48. package/template/public/seo/.gitkeep +0 -0
  49. package/template/public/videos/.gitkeep +0 -0
  50. package/template/scripts/.gitkeep +0 -0
  51. package/template/scripts/sync-agent-rules.sh +88 -0
  52. package/template/scripts/sync-skills.mjs +111 -0
  53. package/template/src/app/favicon.ico +0 -0
  54. package/template/src/app/globals.css +130 -0
  55. package/template/src/app/layout.tsx +33 -0
  56. package/template/src/app/page.tsx +9 -0
  57. package/template/src/components/ui/button.tsx +60 -0
  58. package/template/src/hooks/.gitkeep +0 -0
  59. package/template/src/lib/utils.ts +6 -0
  60. package/template/src/types/.gitkeep +0 -0
  61. package/template/tsconfig.json +34 -0
  62. package/packages/extract/automated-clone-pass.ts +0 -353
  63. package/packages/extract/browser-extract.ts +0 -237
  64. package/packages/extract/cloner-research-emit.ts +0 -270
  65. package/packages/extract/dom-crawler.ts +0 -521
  66. package/packages/extract/emit.ts +0 -553
  67. package/packages/extract/extract.ts +0 -548
  68. package/packages/extract/host-slug.ts +0 -5
  69. package/packages/extract/mirror-emit.ts +0 -620
  70. package/packages/extract/package.json +0 -13
  71. package/packages/extract/reference-dump.ts +0 -431
  72. package/packages/extract/synthesize.ts +0 -551
  73. package/packages/extract/types.ts +0 -316
package/README.md CHANGED
@@ -1,183 +1,144 @@
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. It **runs `npm install` for you**. Open the new folder in [Cursor](https://cursor.com/) (or any AI editor) and tell your AI **Build it** — they'll follow `launchframe.config.json` + `AGENTS.md` and run the full clone + rebrand pipeline (same as `/clone-website`).
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
+ npx launchframe@latest https://linear.app "AI-powered customer feedback platform"
25
+ ```
26
+
27
+ Then open **`launchframe-app`** in Cursor and chat: **Build it.**
28
+
29
+ That's the whole loop: no manual `npm install`, no pasting the URL again — the CLI installs deps and writes `launchframe.config.json`. Cursor rules treat **Build it** as “run the full clone + rebrand workflow.”
30
+
31
+ Optional: **`/clone-website`** in Cursor, or **`--skip-install`** if you only want the files (CI / debugging).
32
+
33
+ ```bash
34
+ npx launchframe@latest https://linear.app "My idea" --skip-install
35
+ ```
36
+
37
+ ## What gets generated
38
+
39
+ ```
40
+ launchframe-app/
41
+ ├─ START_HERE.md ← "open Cursor, say Build it"
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
+ --skip-install Skip npm install (for CI / debugging only)
67
+ --help, -h Show this message
68
+ --version, -v Show the version
69
+ ```
70
+
71
+ ### Examples
72
+
73
+ ```bash
74
+ npx launchframe@latest https://linear.app "AI-powered customer feedback platform"
75
+
76
+ npx launchframe@latest https://vercel.com "DevOps platform for ML teams" --dir my-startup
77
+
78
+ # Hostname-onlylaunchframe will prepend https://
79
+ npx launchframe@latest stripe.com "B2B billing for AI agent companies"
80
+ ```
81
+
82
+ ## Supported AI Agents
83
+
84
+ | Agent | Status |
85
+ | ------------------------------------------------------------- | -------------------------- |
86
+ | [Claude Code](https://docs.anthropic.com/en/docs/claude-code) | **Recommended** Opus 4.7 |
87
+ | [Codex CLI](https://github.com/openai/codex) | Supported |
88
+ | [Cursor](https://cursor.com/) | Supported |
89
+ | [Gemini CLI](https://github.com/google-gemini/gemini-cli) | Supported |
90
+ | [GitHub Copilot](https://github.com/features/copilot) | Supported |
91
+ | [OpenCode](https://opencode.ai/) | Supported |
92
+ | [Windsurf](https://codeium.com/windsurf) | Supported |
93
+ | [Cline](https://github.com/cline/cline) / Roo Code | Supported |
94
+ | [Continue](https://continue.dev/) | Supported |
95
+ | [Amazon Q](https://aws.amazon.com/q/developer/) | Supported |
96
+ | [Augment Code](https://www.augmentcode.com/) | Supported |
97
+ | [Aider](https://aider.chat/) | Supported |
98
+
99
+ Every supported agent receives the same `/clone-website` skill — it's auto-synced from a single source-of-truth file in the template.
100
+
101
+ ## How `/clone-website` Works
102
+
103
+ A multi-phase pipeline runs inside your AI agent. Browser automation MCP (Chrome MCP / Playwright MCP / Browserbase MCP) is required.
104
+
105
+ 1. **Reconnaissance** — full-page screenshots at desktop + mobile, design-token extraction, mandatory scroll/click/hover/responsive sweep
106
+ 2. **Foundation** — updates fonts, colors, globals.css; downloads all assets; extracts SVG icons
107
+ 3. **Component Specs** — writes detailed `.spec.md` files for every section with exact `getComputedStyle()` values
108
+ 4. **Parallel Build** — dispatches builder agents in git worktrees, one per section/component
109
+ 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
110
+ 6. **Assembly + Visual QA** — merges worktrees, wires `src/app/page.tsx`, runs side-by-side diff against the original
111
+
112
+ Each builder agent gets the full component spec inline. No guessing.
113
+
114
+ ## Editing the Config Mid-Project
115
+
116
+ `launchframe.config.json` is the contract between you and the skill. Change either field any time and re-invoke `/clone-website`:
117
+
118
+ ```json
119
+ {
120
+ "url": "https://stripe.com",
121
+ "idea": "Usage-based billing for AI agent startups"
122
+ }
123
+ ```
124
+
125
+ ## Built On
126
+
127
+ - 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
128
+ - [Next.js 16](https://nextjs.org/), [React 19](https://react.dev/), [shadcn/ui](https://ui.shadcn.com/), [Tailwind CSS v4](https://tailwindcss.com/)
129
+
130
+ ## Prerequisites
131
+
132
+ - [Node.js](https://nodejs.org/) 18+ to run the CLI
133
+ - [Node.js](https://nodejs.org/) 24+ inside the scaffolded project (for Next.js 16)
134
+ - An AI coding agent with browser automation MCP
135
+
136
+ ## Not Intended For
137
+
138
+ - Phishing or impersonation
139
+ - Passing off someone else's design as your own
140
+ - Violating terms of service (some sites prohibit scraping or reproduction — check first)
141
+
142
+ ## License
143
+
144
+ MIT — see [LICENSE](./LICENSE).
@@ -1,46 +1,279 @@
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. Runs `npm install` in the new project (so the user does not have to).
17
+ * 5. Prints one line: open the folder in Cursor and tell the AI **Build it**.
8
18
  */
9
19
 
10
20
  import { spawnSync } from "node:child_process";
21
+ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
11
22
  import { createRequire } from "node:module";
12
- import { dirname, join } from "node:path";
23
+ import { dirname, isAbsolute, join, resolve } from "node:path";
13
24
  import { fileURLToPath } from "node:url";
14
25
 
15
26
  const __filename = fileURLToPath(import.meta.url);
16
27
  const __dirname = dirname(__filename);
17
- const pkgRoot = join(__dirname, "..");
18
- const pkgJsonPath = join(pkgRoot, "package.json");
28
+ const pkgRoot = resolve(__dirname, "..");
29
+ const templateDir = join(pkgRoot, "template");
30
+ const require = createRequire(import.meta.url);
31
+ const pkgJson = require(join(pkgRoot, "package.json"));
19
32
 
20
- const require = createRequire(pkgJsonPath);
33
+ const COLORS = {
34
+ reset: "\x1b[0m",
35
+ bold: "\x1b[1m",
36
+ dim: "\x1b[2m",
37
+ red: "\x1b[31m",
38
+ green: "\x1b[32m",
39
+ yellow: "\x1b[33m",
40
+ cyan: "\x1b[36m",
41
+ };
42
+ const supportsColor = process.stdout.isTTY && !process.env.NO_COLOR;
43
+ const c = (color, s) => (supportsColor ? COLORS[color] + s + COLORS.reset : s);
21
44
 
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",
29
- );
30
- process.exit(1);
45
+ function printHelp() {
46
+ console.log(`
47
+ ${c("bold", "launchframe")} ${c("dim", `v${pkgJson.version}`)}
48
+ Scaffold an AI-cloner project pointed at any URL + SaaS idea.
49
+
50
+ ${c("bold", "Usage:")}
51
+ npx launchframe@latest <url> "<saas idea>" [options]
52
+
53
+ ${c("bold", "Arguments:")}
54
+ <url> URL of the site you want to clone (e.g. https://linear.app)
55
+ <saas idea> One-line description of the SaaS you're building
56
+ (used to re-skin copy/branding after the visual clone)
57
+
58
+ ${c("bold", "Options:")}
59
+ --dir <name> Target directory name (default: launchframe-app)
60
+ --force Overwrite the target directory if it already exists
61
+ --skip-install Skip npm install after scaffold (faster for CI / debugging)
62
+ --help, -h Show this message
63
+ --version, -v Show the launchframe version
64
+
65
+ ${c("bold", "Example:")}
66
+ npx launchframe@latest https://linear.app "AI-powered customer feedback platform"
67
+ npx launchframe@latest https://vercel.com "DevOps platform for ML teams" --dir my-startup
68
+ npx launchframe@latest https://stripe.com "Billing for AI agents" --skip-install
69
+ `);
70
+ }
71
+
72
+ function exitErr(msg, code = 1) {
73
+ console.error(c("red", `\nlaunchframe: ${msg}`));
74
+ console.error(c("dim", "Run `npx launchframe --help` for usage.\n"));
75
+ process.exit(code);
76
+ }
77
+
78
+ function parseArgs(argv) {
79
+ const positional = [];
80
+ const opts = {
81
+ dir: "launchframe-app",
82
+ force: false,
83
+ skipInstall: false,
84
+ help: false,
85
+ version: false,
86
+ };
87
+ for (let i = 0; i < argv.length; i++) {
88
+ const a = argv[i];
89
+ if (a === "--help" || a === "-h") opts.help = true;
90
+ else if (a === "--version" || a === "-v") opts.version = true;
91
+ else if (a === "--force" || a === "-f") opts.force = true;
92
+ else if (a === "--skip-install") opts.skipInstall = true;
93
+ else if (a === "--dir" || a === "-d") {
94
+ const next = argv[++i];
95
+ if (!next || next.startsWith("-")) exitErr("`--dir` requires a value");
96
+ opts.dir = next;
97
+ } else if (a.startsWith("--dir=")) {
98
+ opts.dir = a.slice("--dir=".length);
99
+ } else if (a.startsWith("-")) {
100
+ exitErr(`unknown option: ${a}`);
101
+ } else {
102
+ positional.push(a);
103
+ }
104
+ }
105
+ return { positional, opts };
106
+ }
107
+
108
+ function normalizeUrl(raw) {
109
+ let candidate = raw.trim();
110
+ if (!/^https?:\/\//i.test(candidate)) candidate = `https://${candidate}`;
111
+ try {
112
+ const u = new URL(candidate);
113
+ if (!u.hostname || !u.hostname.includes(".")) throw new Error("missing hostname");
114
+ return u.toString();
115
+ } catch {
116
+ exitErr(`invalid URL: ${raw}`);
117
+ }
118
+ }
119
+
120
+ function isDirEmpty(dir) {
121
+ try {
122
+ return readdirSync(dir).length === 0;
123
+ } catch {
124
+ return true;
125
+ }
31
126
  }
32
127
 
33
- const extractScript = join(pkgRoot, "packages", "extract", "extract.ts");
128
+ function rewritePackageJson(targetDir, projectName, url, idea) {
129
+ const pkgPath = join(targetDir, "package.json");
130
+ if (!existsSync(pkgPath)) return;
131
+ let pkg;
132
+ try {
133
+ pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
134
+ } catch {
135
+ return;
136
+ }
137
+ pkg.name = projectName.toLowerCase().replace(/[^a-z0-9-_]/g, "-").slice(0, 64) || "launchframe-app";
138
+ pkg.version = "0.1.0";
139
+ pkg.private = true;
140
+ pkg.description = `Launchframe project — clone of ${url} reframed as: ${idea}`;
141
+ delete pkg.author;
142
+ delete pkg.homepage;
143
+ delete pkg.repository;
144
+ delete pkg.bugs;
145
+ writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
146
+ }
34
147
 
35
- const result = spawnSync(
36
- process.execPath,
37
- [tsxCli, extractScript, ...process.argv.slice(2)],
38
- {
39
- cwd: process.cwd(),
148
+ function writeLaunchframeConfig(targetDir, url, idea) {
149
+ const cfg = {
150
+ $schema: "https://launchframe.dev/schema/launchframe.config.json",
151
+ url,
152
+ idea,
153
+ createdAt: new Date().toISOString(),
154
+ launchframeVersion: pkgJson.version,
155
+ notes: [
156
+ "The /clone-website skill reads this file at the start of every run.",
157
+ "After scaffold: open this folder in Cursor (or your AI editor) and say **Build it** — same workflow.",
158
+ "`url` is the visual source-of-truth (clone its layout, spacing, tokens, motion).",
159
+ "`idea` is the rebranding directive applied AFTER the pixel-perfect clone.",
160
+ "Edit either field and re-invoke the skill to re-run.",
161
+ ],
162
+ };
163
+ writeFileSync(
164
+ join(targetDir, "launchframe.config.json"),
165
+ JSON.stringify(cfg, null, 2) + "\n",
166
+ "utf8"
167
+ );
168
+ }
169
+
170
+ function runNpmInstall(targetDir) {
171
+ console.log(c("dim", "\nRunning npm install (this may take a minute)...\n"));
172
+ const result = spawnSync("npm", ["install", "--no-fund", "--no-audit"], {
173
+ cwd: targetDir,
40
174
  stdio: "inherit",
175
+ shell: true,
41
176
  env: process.env,
42
- shell: false,
43
- },
44
- );
177
+ });
178
+ if (result.status !== 0) {
179
+ console.error(
180
+ c("yellow", "\nlaunchframe: npm install exited with an error. ") +
181
+ c("dim", `Fix the issue and run \`npm install\` inside the project folder, or re-run with \`--force\`.\n`)
182
+ );
183
+ process.exit(result.status === null ? 1 : result.status);
184
+ }
185
+ console.log(c("green", "\n\u2713 npm install finished.\n"));
186
+ }
45
187
 
46
- process.exit(result.status === null ? 1 : result.status);
188
+ function nextSteps(projectDir, projectName, url, idea) {
189
+ const rel = `./${projectName}`;
190
+ console.log(`
191
+ ${c("green", "\u2713")} Done — ${c("bold", projectName)} is ready.
192
+
193
+ ${c("dim", "Target URL:")} ${c("cyan", url)}
194
+ ${c("dim", "SaaS idea:")} ${c("cyan", idea)}
195
+ ${c("dim", "Folder:")} ${projectDir}
196
+
197
+ ${c("bold", "All you do next:")}
198
+ 1. Open ${c("cyan", rel)} in ${c("bold", "Cursor")} ${c("dim", "(File \u2192 Open Folder)")}
199
+ 2. In chat, say: ${c("bold", "Build it")}
200
+ ${c("dim", "Your AI reads launchframe.config.json + AGENTS.md and runs the full clone + rebrand workflow.")}
201
+ ${c("dim", "You can also type ") + c("cyan", "/clone-website") + c("dim", " if you prefer.")}
202
+
203
+ ${c("dim", "Other editors: same folder — say Build it, or run the /clone-website skill for your tool.")}
204
+ ${c("dim", "Edit launchframe.config.json anytime to change URL or SaaS idea.")}
205
+ `);
206
+ }
207
+
208
+ function main() {
209
+ const { positional, opts } = parseArgs(process.argv.slice(2));
210
+ if (opts.help) return printHelp();
211
+ if (opts.version) return console.log(pkgJson.version);
212
+
213
+ if (positional.length < 2) {
214
+ if (positional.length === 0) {
215
+ console.error(c("red", "\nlaunchframe: missing <url> and <saas idea>"));
216
+ } else {
217
+ console.error(c("red", '\nlaunchframe: missing <saas idea> (wrap it in quotes: "...")'));
218
+ }
219
+ printHelp();
220
+ process.exit(1);
221
+ }
222
+ if (positional.length > 2) {
223
+ exitErr(
224
+ `too many positional arguments. Wrap your SaaS idea in quotes: ` +
225
+ `\`npx launchframe ${positional[0]} "${positional.slice(1).join(" ")}"\``
226
+ );
227
+ }
228
+
229
+ const url = normalizeUrl(positional[0]);
230
+ const idea = positional[1].trim();
231
+ if (!idea) exitErr("SaaS idea cannot be empty");
232
+
233
+ const dirName = opts.dir;
234
+ const targetDir = isAbsolute(dirName) ? dirName : resolve(process.cwd(), dirName);
235
+
236
+ if (existsSync(targetDir) && !isDirEmpty(targetDir) && !opts.force) {
237
+ exitErr(
238
+ `target directory \`${dirName}\` already exists and is not empty.\n` +
239
+ `Pass \`--force\` to overwrite, or use \`--dir <name>\` to choose a different folder.`
240
+ );
241
+ }
242
+
243
+ if (!existsSync(templateDir)) {
244
+ exitErr(
245
+ `bundled template not found at \`${templateDir}\`.\n` +
246
+ `This usually means the launchframe package was installed incompletely. ` +
247
+ `Try: \`npm install -g launchframe@latest\` or re-run \`npx launchframe@latest ...\`.`
248
+ );
249
+ }
250
+
251
+ mkdirSync(targetDir, { recursive: true });
252
+
253
+ console.log(c("dim", `\nCopying template \u2192 ${targetDir} ...`));
254
+ cpSync(templateDir, targetDir, {
255
+ recursive: true,
256
+ force: opts.force,
257
+ filter: (src) => {
258
+ const lower = src.toLowerCase();
259
+ if (lower.endsWith(`${"\\"}node_modules`) || lower.endsWith("/node_modules")) return false;
260
+ if (lower.endsWith(`${"\\"}.next`) || lower.endsWith("/.next")) return false;
261
+ if (lower.endsWith("package-lock.json")) return false;
262
+ return true;
263
+ },
264
+ });
265
+
266
+ rewritePackageJson(targetDir, dirName, url, idea);
267
+ writeLaunchframeConfig(targetDir, url, idea);
268
+
269
+ if (!opts.skipInstall) runNpmInstall(targetDir);
270
+
271
+ nextSteps(targetDir, dirName, url, idea);
272
+ }
273
+
274
+ try {
275
+ main();
276
+ } catch (err) {
277
+ console.error(c("red", `\nlaunchframe: ${err?.message || err}\n`));
278
+ process.exit(1);
279
+ }