launchframe 0.1.13 → 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 -175
  2. package/bin/launchframe.mjs +234 -30
  3. package/package.json +52 -65
  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 -547
  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,175 +1,143 @@
1
- # Launchframe
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.
4
-
5
- **Recommended:** [Claude Code](https://docs.anthropic.com/en/docs/claude-code) or **Cursor** for multi-phase builds — but Launchframe output works with any agent that can read Markdown and TypeScript.
6
-
7
- Point the CLI at a URL, run **`npx launchframe@latest`**, then drive the same **spec → parallel builders → merge → QA** rhythm popularized by [JCodesMore/ai-website-cloner-template](https://github.com/JCodesMore/ai-website-cloner-template). In Cursor, use the **Clone Website** command (`.cursor/commands/clone-website.md`), which matches that template’s pipeline with Launchframe handling automated recon.
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 |
33
- | [Cursor](https://cursor.com/) | Supported — `.cursor/commands/clone-website.md` |
34
- | [GitHub Copilot](https://github.com/features/copilot) | Supported — `AGENTS.md` → `npm run sync:agents` |
35
- | [Gemini CLI](https://github.com/google-gemini/gemini-cli) | Supported (`GEMINI.md`) |
36
- | [Cline](https://github.com/cline/cline) | Supported |
37
- | [Continue](https://continue.dev/) | Supported |
38
- | [Amazon Q](https://aws.amazon.com/q/developer/) | Supported |
39
- | … | Any agent that reads `AGENTS.md` |
40
-
41
- ## Prerequisites
42
-
43
- - [Node.js](https://nodejs.org/) **20+**
44
- - Chromium via Playwright (`npx playwright install chromium`)
45
-
46
- ## Tech stack
47
-
48
- - **Extract:** Node.js, Playwright, TypeScript
49
- - **Published CLI:** `npx launchframe` `packages/extract/`
50
- - **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.)
51
- - **Mirror output:** React, Framer Motion, Phosphor (generated `page.tsx`)
52
- - **Tokens:** `tailwind.config.ts`, `globals.css`, `tokens.json`, `REPORT.md`
53
-
54
- ## Parity with [ai-website-cloner-template](https://github.com/JCodesMore/ai-website-cloner-template)
55
-
56
- Launchframe mirrors that template’s **phases and artifact roles**, not its single-repo layout:
57
-
58
- | Aspect | What they do | What Launchframe does (same intent) |
59
- | -------- | ------------------ | ------------------------------------- |
60
- | **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 ≈ orchestration checklist) |
61
- | **Repo shape** | One Next.js app (`src/app`, `components`, …) | **Monorepo:** CLI (`packages/extract`) + optional **`apps/studio`** + **`packages/blocks`**; published CLI ships separately |
62
- | **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 |
63
- | **Stack** | Next 16, React 19, Tailwind v4, Lucide | **Next 16 + React 19 + Lucide** in studio/blocks; Tailwind **v3** in-studio until upgraded to v4 |
64
-
65
- ## How it works
66
-
67
- This repo combines **Launchframe automated extraction** with the **AI Website Cloner**-style agent workflow:
68
-
69
- 1. **Automated recon** — headless Chromium capture, computed-style aggregation, reference dump (`dom-structure.json`, `visible-text.*`, `media.json`), design-reference PNGs and probes under `docs/design-references/`, and starter topology/spec files under `docs/research/`.
70
- 2. **Foundation** — synthesized shadcn-compatible theme files at the run root; merge into your Next app.
71
- 3. **Component specs** — per-section `.spec.md` files (extend Launchframe’s emitted stubs) with behaviors, states, and assets.
72
- 4. **Parallel build** — agents in worktrees/branches per section or subcomponent.
73
- 5. **Assembly & QA** wire the target route (from mirror structure), visual diff against references.
74
-
75
- Each builder should receive **inline** spec content, not “go read the repo.” Authority order for structure is in **`FOR_AI.md`** and **`AGENTS.md`**.
76
-
77
- ## Use cases
78
-
79
- - Seeding a **design system** from sites you’re allowed to analyze
80
- - **Layout research** with a typed mirror and DOM reference
81
- - **Rebuilding** properties you own when only the public site remains
82
-
83
- ## Not intended for
84
-
85
- - **Impersonation or phishing**
86
- - **Passing off** another brand’s identity, trademarks, or licensed media
87
- - **Violating robots.txt or site terms** — Launchframe checks robots by default (`--no-robots` is discouraged)
88
-
89
- ## Output layout
90
-
91
- ```txt
92
- output/<runId>/
93
- ├── FOR_AI.md
94
- ├── tokens.json
95
- ├── tailwind.config.ts
96
- ├── globals.css
97
- ├── theme-preview.tsx
98
- ├── REPORT.md
99
- ├── run.json
100
- ├── screenshots/
101
- ├── raw/
102
- ├── reference/
103
- │ └── <host>/
104
- │ ├── dom-structure.json
105
- │ ├── page.html
106
- │ ├── visible-text.txt / .json
107
- │ ├── media.json
108
- │ └── FOR_AI_REFERENCE.md
109
- ├── mirror/
110
- │ └── <host>/
111
- │ ├── page.tsx
112
- │ └── MIRROR_NOTES.md
113
- ├── docs/
114
- │ ├── design-references/<host-slug>/ # PNGs, probes, hints
115
- │ └── research/<host-slug>/ # PAGE_TOPOLOGY, BEHAVIORS, components/*.spec.md
116
- └── downloaded_assets/<host-slug>/ # when enabled
117
- ```
118
-
119
- ## CLI reference
120
-
121
- ```bash
122
- npx launchframe@latest <url> [<url> ...] ["Your SaaS idea"] [options]
123
- ```
124
-
125
- | Flag | Default | Notes |
126
- | ---- | ------- | ----- |
127
- | `--out <dir>` | `./output/<runId>` | Run directory |
128
- | `--name <slug>` | _(unset)_ | Appends slug to run id |
129
- | `--no-robots` | off | Skip robots.txt check |
130
- | `--rate <n>` | `15` | Per-domain requests per minute |
131
- | `--width <px>` | `1440` | Viewport width |
132
- | `--height <px>` | `900` | Viewport height |
133
-
134
- ```bash
135
- npx launchframe@latest https://example.com "AI billing copilot" --name acme
136
- ```
137
-
138
- ## Run inside this repo (contributors)
139
-
140
- ```bash
141
- git clone https://github.com/evangruhlkey/launchframe
142
- cd launchframe
143
- npm install
144
- npx playwright install chromium
145
- npm run extract -- https://example.com "Your SaaS idea"
146
- ```
147
-
148
- ```bash
149
- npm run studio # Next.js studio
150
- npm run studio:build # Production build
151
- npm run typecheck
152
- npm run sync:agents # Regenerate Copilot / Cline / Continue / Amazon Q stubs from AGENTS.md
153
- ```
154
-
155
- ### AI agents in this repo
156
-
157
- - **`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`**.
158
- - **Cursor:** `.cursor/rules/project.mdc` points at `AGENTS.md`; use **Clone Website** for the full cloner-template-style checklist.
159
- - Edit **`AGENTS.md`**, then run **`npm run sync:agents`**.
160
-
161
- ### Updating agent copies from one file
162
-
163
- | What | Source of truth | Sync command |
164
- | ---- | ----------------- | ------------ |
165
- | Project instructions | `AGENTS.md` | `npm run sync:agents` |
166
- | Cursor multi-phase pipeline | `.cursor/commands/clone-website.md` | (edit in repo — no codegen) |
167
-
168
- ## What this is not
169
-
170
- - **Not the origin site’s bundle** — output is synthesized tokens, reference artifacts, and a **slot-based mirror**, not a dump of proprietary components.
171
- - **Not legal clearance** — you are responsible for compliance; see **`FOR_AI.md`** and **`AGENTS.md`**.
172
-
173
- ## License
174
-
175
- 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-only — launchframe 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` / `npm exec launchframe`.
4
- * Spawns the TypeScript extract pipeline with the same Node that installed
5
- * this package. Output defaults to `./output/<runId>/` in the *current*
6
- * 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: 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
+ }