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.
- package/README.md +143 -175
- package/bin/launchframe.mjs +234 -30
- package/package.json +52 -65
- package/template/.aider.conf.yml +3 -0
- package/template/.amazonq/cli-agents/clone-website.json +9 -0
- package/template/.amazonq/rules/project.md +156 -0
- package/template/.augment/commands/clone-website.md +516 -0
- package/template/.claude/skills/clone-website/SKILL.md +515 -0
- package/template/.clinerules +156 -0
- package/template/.codex/skills/clone-website/SKILL.md +515 -0
- package/template/.continue/commands/clone-website.md +517 -0
- package/template/.continue/rules/project.md +160 -0
- package/template/.cursor/commands/clone-website.md +512 -0
- package/template/.cursor/rules/project.mdc +7 -0
- package/template/.dockerignore +60 -0
- package/template/.gemini/commands/clone-website.toml +518 -0
- package/template/.gitattributes +9 -0
- package/template/.github/ISSUE_TEMPLATE/bug_report.yml +86 -0
- package/template/.github/ISSUE_TEMPLATE/config.yml +5 -0
- package/template/.github/ISSUE_TEMPLATE/feature_request.yml +50 -0
- package/template/.github/PULL_REQUEST_TEMPLATE.md +19 -0
- package/template/.github/copilot-instructions.md +156 -0
- package/template/.github/copilot-setup-steps.yml +3 -0
- package/template/.github/skills/clone-website/SKILL.md +515 -0
- package/template/.github/workflows/ci.yml +36 -0
- package/template/.nvmrc +1 -0
- package/template/.opencode/commands/clone-website.md +515 -0
- package/template/.windsurf/workflows/clone-website.md +512 -0
- package/template/.windsurfrules +2 -0
- package/template/AGENTS.md +74 -0
- package/template/CHANGELOG.md +80 -0
- package/template/CLAUDE.md +1 -0
- package/template/Dockerfile +114 -0
- package/template/Dockerfile.dev +15 -0
- package/template/GEMINI.md +1 -0
- package/template/README.md +129 -0
- package/template/components.json +25 -0
- package/template/docker-compose.yml +53 -0
- package/template/docs/design-references/.gitkeep +0 -0
- package/template/docs/design-references/comparison.png +0 -0
- package/template/docs/research/INSPECTION_GUIDE.md +80 -0
- package/template/eslint.config.mjs +18 -0
- package/template/next.config.ts +8 -0
- package/template/package.json +59 -0
- package/template/postcss.config.mjs +7 -0
- package/template/public/images/.gitkeep +0 -0
- package/template/public/seo/.gitkeep +0 -0
- package/template/public/videos/.gitkeep +0 -0
- package/template/scripts/.gitkeep +0 -0
- package/template/scripts/sync-agent-rules.sh +88 -0
- package/template/scripts/sync-skills.mjs +111 -0
- package/template/src/app/favicon.ico +0 -0
- package/template/src/app/globals.css +130 -0
- package/template/src/app/layout.tsx +33 -0
- package/template/src/app/page.tsx +9 -0
- package/template/src/components/ui/button.tsx +60 -0
- package/template/src/hooks/.gitkeep +0 -0
- package/template/src/lib/utils.ts +6 -0
- package/template/src/types/.gitkeep +0 -0
- package/template/tsconfig.json +34 -0
- package/packages/extract/automated-clone-pass.ts +0 -353
- package/packages/extract/browser-extract.ts +0 -237
- package/packages/extract/cloner-research-emit.ts +0 -270
- package/packages/extract/dom-crawler.ts +0 -521
- package/packages/extract/emit.ts +0 -553
- package/packages/extract/extract.ts +0 -547
- package/packages/extract/host-slug.ts +0 -5
- package/packages/extract/mirror-emit.ts +0 -620
- package/packages/extract/package.json +0 -13
- package/packages/extract/reference-dump.ts +0 -431
- package/packages/extract/synthesize.ts +0 -551
- package/packages/extract/types.ts +0 -316
package/README.md
CHANGED
|
@@ -1,175 +1,143 @@
|
|
|
1
|
-
# Launchframe
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
##
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
- **
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
```
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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 URL — in 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).
|
package/bin/launchframe.mjs
CHANGED
|
@@ -1,46 +1,250 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
// @ts-check
|
|
2
3
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
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 {
|
|
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 =
|
|
18
|
-
const
|
|
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
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
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
|
+
}
|