launchframe 0.2.6 → 0.3.0
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/LICENSE +1 -1
- package/README.md +76 -102
- package/launchframe.config.json +14 -0
- package/package.json +54 -52
- package/bin/launchframe.mjs +0 -315
- package/template/README.md +0 -121
- package/template/package-lock.json +0 -8795
- package/template/package.json +0 -63
- /package/{template/.aider.conf.yml → .aider.conf.yml} +0 -0
- /package/{template/.amazonq → .amazonq}/cli-agents/clone-website.json +0 -0
- /package/{template/.amazonq → .amazonq}/rules/project.md +0 -0
- /package/{template/.augment → .augment}/commands/clone-website.md +0 -0
- /package/{template/.claude → .claude}/skills/clone-website/SKILL.md +0 -0
- /package/{template/.claude → .claude}/skills/marketing-social-proof-motion/SKILL.md +0 -0
- /package/{template/.clinerules → .clinerules} +0 -0
- /package/{template/.codex → .codex}/skills/clone-website/SKILL.md +0 -0
- /package/{template/.continue → .continue}/commands/clone-website.md +0 -0
- /package/{template/.continue → .continue}/rules/project.md +0 -0
- /package/{template/.cursor → .cursor}/commands/clone-website.md +0 -0
- /package/{template/.cursor → .cursor}/commands/marketing-social-proof-motion.md +0 -0
- /package/{template/.cursor → .cursor}/rules/project.mdc +0 -0
- /package/{template/.dockerignore → .dockerignore} +0 -0
- /package/{template/.gemini → .gemini}/commands/clone-website.toml +0 -0
- /package/{template/.gitattributes → .gitattributes} +0 -0
- /package/{template/.github → .github}/ISSUE_TEMPLATE/bug_report.yml +0 -0
- /package/{template/.github → .github}/ISSUE_TEMPLATE/config.yml +0 -0
- /package/{template/.github → .github}/ISSUE_TEMPLATE/feature_request.yml +0 -0
- /package/{template/.github → .github}/PULL_REQUEST_TEMPLATE.md +0 -0
- /package/{template/.github → .github}/copilot-instructions.md +0 -0
- /package/{template/.github → .github}/copilot-setup-steps.yml +0 -0
- /package/{template/.github → .github}/skills/clone-website/SKILL.md +0 -0
- /package/{template/.github → .github}/workflows/ci.yml +0 -0
- /package/{template/.nvmrc → .nvmrc} +0 -0
- /package/{template/.opencode → .opencode}/commands/clone-website.md +0 -0
- /package/{template/.windsurf → .windsurf}/workflows/clone-website.md +0 -0
- /package/{template/.windsurfrules → .windsurfrules} +0 -0
- /package/{template/AGENTS.md → AGENTS.md} +0 -0
- /package/{template/CHANGELOG.md → CHANGELOG.md} +0 -0
- /package/{template/CLAUDE.md → CLAUDE.md} +0 -0
- /package/{template/Dockerfile → Dockerfile} +0 -0
- /package/{template/Dockerfile.dev → Dockerfile.dev} +0 -0
- /package/{template/GEMINI.md → GEMINI.md} +0 -0
- /package/{template/START_HERE.md → START_HERE.md} +0 -0
- /package/{template/components.json → components.json} +0 -0
- /package/{template/docker-compose.yml → docker-compose.yml} +0 -0
- /package/{template/docs → docs}/design-references/.gitkeep +0 -0
- /package/{template/docs → docs}/design-references/comparison.png +0 -0
- /package/{template/docs → docs}/design-references/playwright-example.com-1440px.png +0 -0
- /package/{template/docs → docs}/design-references/playwright-example.com-390px.png +0 -0
- /package/{template/docs → docs}/research/INSPECTION_GUIDE.md +0 -0
- /package/{template/eslint.config.mjs → eslint.config.mjs} +0 -0
- /package/{template/next.config.ts → next.config.ts} +0 -0
- /package/{template/postcss.config.mjs → postcss.config.mjs} +0 -0
- /package/{template/public → public}/images/.gitkeep +0 -0
- /package/{template/public → public}/seo/.gitkeep +0 -0
- /package/{template/public → public}/videos/.gitkeep +0 -0
- /package/{template/scripts → scripts}/.gitkeep +0 -0
- /package/{template/scripts → scripts}/recon-playwright.mjs +0 -0
- /package/{template/scripts → scripts}/sync-agent-rules.sh +0 -0
- /package/{template/scripts → scripts}/sync-skills.mjs +0 -0
- /package/{template/src → src}/app/favicon.ico +0 -0
- /package/{template/src → src}/app/globals.css +0 -0
- /package/{template/src → src}/app/layout.tsx +0 -0
- /package/{template/src → src}/app/page.tsx +0 -0
- /package/{template/src → src}/components/marketing/scribewise-landing.tsx +0 -0
- /package/{template/src → src}/components/ui/button.tsx +0 -0
- /package/{template/src → src}/hooks/.gitkeep +0 -0
- /package/{template/src → src}/lib/utils.ts +0 -0
- /package/{template/src → src}/types/.gitkeep +0 -0
- /package/{template/tsconfig.json → tsconfig.json} +0 -0
package/bin/launchframe.mjs
DELETED
|
@@ -1,315 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
// @ts-check
|
|
3
|
-
/**
|
|
4
|
-
* launchframe — scaffold an AI-cloner project pointed at any URL + SaaS idea.
|
|
5
|
-
*
|
|
6
|
-
* Usage:
|
|
7
|
-
* npx launchframe@latest <url> "<saas idea>" [--dir <path>] [--force] [--skip-install]
|
|
8
|
-
*
|
|
9
|
-
* Behavior:
|
|
10
|
-
* 1. Validates the URL and SaaS-idea string.
|
|
11
|
-
* 2. Copies the bundled `template/` payload into the project root (current
|
|
12
|
-
* directory by default) so dotfolders like `.cursor` and `.claude` live at
|
|
13
|
-
* the workspace root when you open that folder in your editor. Use
|
|
14
|
-
* `--dir <name>` for a subdirectory if you prefer.
|
|
15
|
-
* 3. Writes `launchframe.config.json` into the new project so the bundled
|
|
16
|
-
* `/clone-website` skill knows which URL to clone and how to re-skin it
|
|
17
|
-
* for your SaaS idea.
|
|
18
|
-
* 4. Runs `npm install` in the new project (so the user does not have to).
|
|
19
|
-
* 5. Prints one line: open the folder in Cursor and tell the AI **Build it**.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import { spawnSync } from "node:child_process";
|
|
23
|
-
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
24
|
-
import { createRequire } from "node:module";
|
|
25
|
-
import { dirname, basename, isAbsolute, join, resolve } from "node:path";
|
|
26
|
-
import { fileURLToPath } from "node:url";
|
|
27
|
-
|
|
28
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
29
|
-
const __dirname = dirname(__filename);
|
|
30
|
-
const pkgRoot = resolve(__dirname, "..");
|
|
31
|
-
const templateDir = join(pkgRoot, "template");
|
|
32
|
-
const require = createRequire(import.meta.url);
|
|
33
|
-
const pkgJson = require(join(pkgRoot, "package.json"));
|
|
34
|
-
|
|
35
|
-
const COLORS = {
|
|
36
|
-
reset: "\x1b[0m",
|
|
37
|
-
bold: "\x1b[1m",
|
|
38
|
-
dim: "\x1b[2m",
|
|
39
|
-
red: "\x1b[31m",
|
|
40
|
-
green: "\x1b[32m",
|
|
41
|
-
yellow: "\x1b[33m",
|
|
42
|
-
cyan: "\x1b[36m",
|
|
43
|
-
};
|
|
44
|
-
const supportsColor = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
45
|
-
const c = (color, s) => (supportsColor ? COLORS[color] + s + COLORS.reset : s);
|
|
46
|
-
|
|
47
|
-
function printHelp() {
|
|
48
|
-
console.log(`
|
|
49
|
-
${c("bold", "launchframe")} ${c("dim", `v${pkgJson.version}`)}
|
|
50
|
-
Scaffold an AI-cloner project pointed at any URL + SaaS idea.
|
|
51
|
-
|
|
52
|
-
${c("bold", "Usage:")}
|
|
53
|
-
npx launchframe@latest <url> "<saas idea>" [options]
|
|
54
|
-
|
|
55
|
-
${c("bold", "Arguments:")}
|
|
56
|
-
<url> URL of the site you want to clone (e.g. https://linear.app)
|
|
57
|
-
<saas idea> One-line description of the SaaS you're building
|
|
58
|
-
(used to re-skin copy/branding after the visual clone)
|
|
59
|
-
|
|
60
|
-
${c("bold", "Options:")}
|
|
61
|
-
--dir <path> Output folder (default: . — current directory / project root)
|
|
62
|
-
--force Overwrite merging files into a non-empty directory (use with care)
|
|
63
|
-
--skip-install Skip npm install after scaffold (faster for CI / debugging)
|
|
64
|
-
--help, -h Show this message
|
|
65
|
-
--version, -v Show the launchframe version
|
|
66
|
-
|
|
67
|
-
${c("bold", "Example:")}
|
|
68
|
-
npx launchframe@latest https://linear.app "AI-powered customer feedback platform"
|
|
69
|
-
# From an empty folder (or git init only), files land in . so .cursor/ works at workspace root
|
|
70
|
-
npx launchframe@latest https://vercel.com "DevOps for ML" --dir launchframe-app
|
|
71
|
-
npx launchframe@latest https://stripe.com "Billing for AI agents" --skip-install
|
|
72
|
-
`);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
function exitErr(msg, code = 1) {
|
|
76
|
-
console.error(c("red", `\nlaunchframe: ${msg}`));
|
|
77
|
-
console.error(c("dim", "Run `npx launchframe --help` for usage.\n"));
|
|
78
|
-
process.exit(code);
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
function parseArgs(argv) {
|
|
82
|
-
const positional = [];
|
|
83
|
-
const opts = {
|
|
84
|
-
dir: ".",
|
|
85
|
-
force: false,
|
|
86
|
-
skipInstall: false,
|
|
87
|
-
help: false,
|
|
88
|
-
version: false,
|
|
89
|
-
};
|
|
90
|
-
for (let i = 0; i < argv.length; i++) {
|
|
91
|
-
const a = argv[i];
|
|
92
|
-
if (a === "--help" || a === "-h") opts.help = true;
|
|
93
|
-
else if (a === "--version" || a === "-v") opts.version = true;
|
|
94
|
-
else if (a === "--force" || a === "-f") opts.force = true;
|
|
95
|
-
else if (a === "--skip-install") opts.skipInstall = true;
|
|
96
|
-
else if (a === "--dir" || a === "-d") {
|
|
97
|
-
const next = argv[++i];
|
|
98
|
-
if (!next || next.startsWith("-")) exitErr("`--dir` requires a value");
|
|
99
|
-
opts.dir = next;
|
|
100
|
-
} else if (a.startsWith("--dir=")) {
|
|
101
|
-
opts.dir = a.slice("--dir=".length);
|
|
102
|
-
} else if (a.startsWith("-")) {
|
|
103
|
-
exitErr(`unknown option: ${a}`);
|
|
104
|
-
} else {
|
|
105
|
-
positional.push(a);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
return { positional, opts };
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
function normalizeUrl(raw) {
|
|
112
|
-
let candidate = raw.trim();
|
|
113
|
-
if (!/^https?:\/\//i.test(candidate)) candidate = `https://${candidate}`;
|
|
114
|
-
try {
|
|
115
|
-
const u = new URL(candidate);
|
|
116
|
-
if (!u.hostname || !u.hostname.includes(".")) throw new Error("missing hostname");
|
|
117
|
-
return u.toString();
|
|
118
|
-
} catch {
|
|
119
|
-
exitErr(`invalid URL: ${raw}`);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function isDirEmpty(dir) {
|
|
124
|
-
try {
|
|
125
|
-
return readdirSync(dir).length === 0;
|
|
126
|
-
} catch {
|
|
127
|
-
return true;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
/** True if the folder is empty or only has git/bootstrap noise (so we can scaffold at project root). */
|
|
132
|
-
function isScaffoldRootUsable(dir) {
|
|
133
|
-
let names;
|
|
134
|
-
try {
|
|
135
|
-
names = readdirSync(dir);
|
|
136
|
-
} catch {
|
|
137
|
-
return true;
|
|
138
|
-
}
|
|
139
|
-
if (names.length === 0) return true;
|
|
140
|
-
const allowedOnly = new Set([".git", ".gitignore", ".gitattributes"]);
|
|
141
|
-
return names.every((n) => allowedOnly.has(n));
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
function npmPackageSlug(dirName, targetDir) {
|
|
145
|
-
const raw =
|
|
146
|
-
dirName === "." || dirName === "./"
|
|
147
|
-
? basename(targetDir) || "launchframe-app"
|
|
148
|
-
: dirName;
|
|
149
|
-
const slug = raw
|
|
150
|
-
.toLowerCase()
|
|
151
|
-
.replace(/[^a-z0-9-_]/g, "-")
|
|
152
|
-
.replace(/-+/g, "-")
|
|
153
|
-
.replace(/^-|-$/g, "")
|
|
154
|
-
.slice(0, 64);
|
|
155
|
-
return slug || "launchframe-app";
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function rewritePackageJson(targetDir, packageSlug, url, idea) {
|
|
159
|
-
const pkgPath = join(targetDir, "package.json");
|
|
160
|
-
if (!existsSync(pkgPath)) return;
|
|
161
|
-
let pkg;
|
|
162
|
-
try {
|
|
163
|
-
pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
164
|
-
} catch {
|
|
165
|
-
return;
|
|
166
|
-
}
|
|
167
|
-
pkg.name = packageSlug;
|
|
168
|
-
pkg.version = "0.1.0";
|
|
169
|
-
pkg.private = true;
|
|
170
|
-
pkg.description = `Launchframe project — clone of ${url} reframed as: ${idea}`;
|
|
171
|
-
delete pkg.author;
|
|
172
|
-
delete pkg.homepage;
|
|
173
|
-
delete pkg.repository;
|
|
174
|
-
delete pkg.bugs;
|
|
175
|
-
writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n", "utf8");
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
function writeLaunchframeConfig(targetDir, url, idea) {
|
|
179
|
-
const cfg = {
|
|
180
|
-
$schema: "https://launchframe.dev/schema/launchframe.config.json",
|
|
181
|
-
url,
|
|
182
|
-
idea,
|
|
183
|
-
createdAt: new Date().toISOString(),
|
|
184
|
-
launchframeVersion: pkgJson.version,
|
|
185
|
-
notes: [
|
|
186
|
-
"The /clone-website skill reads this file at the start of every run.",
|
|
187
|
-
"After scaffold: open this folder in Cursor (or your AI editor) and say **Build it** — same workflow.",
|
|
188
|
-
"`url` is the visual source-of-truth (clone its layout, spacing, tokens, motion).",
|
|
189
|
-
"`idea` is the rebranding directive applied AFTER the pixel-perfect clone.",
|
|
190
|
-
"Edit either field and re-invoke the skill to re-run.",
|
|
191
|
-
],
|
|
192
|
-
};
|
|
193
|
-
writeFileSync(
|
|
194
|
-
join(targetDir, "launchframe.config.json"),
|
|
195
|
-
JSON.stringify(cfg, null, 2) + "\n",
|
|
196
|
-
"utf8"
|
|
197
|
-
);
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function runNpmInstall(targetDir) {
|
|
201
|
-
console.log(c("dim", "\nRunning npm install (this may take a minute)...\n"));
|
|
202
|
-
const result = spawnSync("npm", ["install", "--no-fund", "--no-audit"], {
|
|
203
|
-
cwd: targetDir,
|
|
204
|
-
stdio: "inherit",
|
|
205
|
-
shell: true,
|
|
206
|
-
env: process.env,
|
|
207
|
-
});
|
|
208
|
-
if (result.status !== 0) {
|
|
209
|
-
console.error(
|
|
210
|
-
c("yellow", "\nlaunchframe: npm install exited with an error. ") +
|
|
211
|
-
c("dim", `Fix the issue and run \`npm install\` inside the project folder, or re-run with \`--force\`.\n`)
|
|
212
|
-
);
|
|
213
|
-
process.exit(result.status === null ? 1 : result.status);
|
|
214
|
-
}
|
|
215
|
-
console.log(c("green", "\n\u2713 npm install finished.\n"));
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
function nextSteps(projectDir, dirLabel, openHint, url, idea) {
|
|
219
|
-
console.log(`
|
|
220
|
-
${c("green", "\u2713")} Done — ${c("bold", dirLabel)} is ready.
|
|
221
|
-
|
|
222
|
-
${c("dim", "Target URL:")} ${c("cyan", url)}
|
|
223
|
-
${c("dim", "SaaS idea:")} ${c("cyan", idea)}
|
|
224
|
-
${c("dim", "Folder:")} ${projectDir}
|
|
225
|
-
|
|
226
|
-
${c("bold", "All you do next:")}
|
|
227
|
-
1. Open ${c("cyan", openHint)} in ${c("bold", "Cursor")} ${c("dim", "(File \u2192 Open Folder)")}
|
|
228
|
-
2. In chat, say: ${c("bold", "Build it")}
|
|
229
|
-
${c("dim", "Your AI reads launchframe.config.json + AGENTS.md and runs the full clone + rebrand workflow.")}
|
|
230
|
-
${c("dim", "You can also type ") + c("cyan", "/clone-website") + c("dim", " if you prefer.")}
|
|
231
|
-
|
|
232
|
-
${c("dim", "Dotfolders (.cursor, .claude, …) are at this project root so rules and skills apply when you open this folder.")}
|
|
233
|
-
${c("dim", "Other editors: same folder — say Build it, or run the /clone-website skill for your tool.")}
|
|
234
|
-
${c("dim", "Edit launchframe.config.json anytime to change URL or SaaS idea.")}
|
|
235
|
-
`);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
function main() {
|
|
239
|
-
const { positional, opts } = parseArgs(process.argv.slice(2));
|
|
240
|
-
if (opts.help) return printHelp();
|
|
241
|
-
if (opts.version) return console.log(pkgJson.version);
|
|
242
|
-
|
|
243
|
-
if (positional.length < 2) {
|
|
244
|
-
if (positional.length === 0) {
|
|
245
|
-
console.error(c("red", "\nlaunchframe: missing <url> and <saas idea>"));
|
|
246
|
-
} else {
|
|
247
|
-
console.error(c("red", '\nlaunchframe: missing <saas idea> (wrap it in quotes: "...")'));
|
|
248
|
-
}
|
|
249
|
-
printHelp();
|
|
250
|
-
process.exit(1);
|
|
251
|
-
}
|
|
252
|
-
if (positional.length > 2) {
|
|
253
|
-
exitErr(
|
|
254
|
-
`too many positional arguments. Wrap your SaaS idea in quotes: ` +
|
|
255
|
-
`\`npx launchframe ${positional[0]} "${positional.slice(1).join(" ")}"\``
|
|
256
|
-
);
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
const url = normalizeUrl(positional[0]);
|
|
260
|
-
const idea = positional[1].trim();
|
|
261
|
-
if (!idea) exitErr("SaaS idea cannot be empty");
|
|
262
|
-
|
|
263
|
-
const dirNameRaw = opts.dir;
|
|
264
|
-
const targetDir = isAbsolute(dirNameRaw) ? dirNameRaw : resolve(process.cwd(), dirNameRaw);
|
|
265
|
-
|
|
266
|
-
const isRootDot = dirNameRaw === "." || dirNameRaw === "./";
|
|
267
|
-
const usable = isDirEmpty(targetDir) || isScaffoldRootUsable(targetDir);
|
|
268
|
-
if (existsSync(targetDir) && !usable && !opts.force) {
|
|
269
|
-
exitErr(
|
|
270
|
-
`target directory \`${dirNameRaw}\` is not empty.\n` +
|
|
271
|
-
`Create an empty folder (or \`git init\` only), run again from there, or pass \`--force\` to merge files, ` +
|
|
272
|
-
`or use \`--dir launchframe-app\` to scaffold into a subdirectory.`
|
|
273
|
-
);
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
if (!existsSync(templateDir)) {
|
|
277
|
-
exitErr(
|
|
278
|
-
`bundled template not found at \`${templateDir}\`.\n` +
|
|
279
|
-
`This usually means the launchframe package was installed incompletely. ` +
|
|
280
|
-
`Try: \`npm install -g launchframe@latest\` or re-run \`npx launchframe@latest ...\`.`
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
mkdirSync(targetDir, { recursive: true });
|
|
285
|
-
|
|
286
|
-
console.log(c("dim", `\nCopying template \u2192 ${targetDir} ...`));
|
|
287
|
-
cpSync(templateDir, targetDir, {
|
|
288
|
-
recursive: true,
|
|
289
|
-
force: opts.force,
|
|
290
|
-
filter: (src) => {
|
|
291
|
-
const lower = src.toLowerCase();
|
|
292
|
-
if (lower.endsWith(`${"\\"}node_modules`) || lower.endsWith("/node_modules")) return false;
|
|
293
|
-
if (lower.endsWith(`${"\\"}.next`) || lower.endsWith("/.next")) return false;
|
|
294
|
-
if (lower.endsWith("package-lock.json")) return false;
|
|
295
|
-
return true;
|
|
296
|
-
},
|
|
297
|
-
});
|
|
298
|
-
|
|
299
|
-
const packageSlug = npmPackageSlug(dirNameRaw, targetDir);
|
|
300
|
-
rewritePackageJson(targetDir, packageSlug, url, idea);
|
|
301
|
-
writeLaunchframeConfig(targetDir, url, idea);
|
|
302
|
-
|
|
303
|
-
if (!opts.skipInstall) runNpmInstall(targetDir);
|
|
304
|
-
|
|
305
|
-
const dirLabel = isRootDot ? "this folder (project root)" : dirNameRaw;
|
|
306
|
-
const openHint = isRootDot ? "this folder" : `./${dirNameRaw}`;
|
|
307
|
-
nextSteps(targetDir, dirLabel, openHint, url, idea);
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
try {
|
|
311
|
-
main();
|
|
312
|
-
} catch (err) {
|
|
313
|
-
console.error(c("red", `\nlaunchframe: ${err?.message || err}\n`));
|
|
314
|
-
process.exit(1);
|
|
315
|
-
}
|
package/template/README.md
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
# Launchframe Project
|
|
2
|
-
|
|
3
|
-
This project was scaffolded by **[Launchframe](https://github.com/evangruhlkey/launchframe)** — an AI-powered website cloner + SaaS rebrander.
|
|
4
|
-
|
|
5
|
-
```bash
|
|
6
|
-
npx launchframe@latest <url> "<saas idea>"
|
|
7
|
-
```
|
|
8
|
-
|
|
9
|
-
Dependencies were installed for you. Files were written to **this directory** (project root) so **`.cursor`**, **`.claude`**, etc. work when you open this folder in your editor. Config lives in **`launchframe.config.json`** (`url` + SaaS `idea`).
|
|
10
|
-
|
|
11
|
-
---
|
|
12
|
-
|
|
13
|
-
## Quick start (two steps)
|
|
14
|
-
|
|
15
|
-
1. **Open this folder** in [Cursor](https://cursor.com/) — the directory that **contains** `.cursor/` (not a parent folder).
|
|
16
|
-
2. In chat, say: **Build it.**
|
|
17
|
-
|
|
18
|
-
Your AI reads [`launchframe.config.json`](./launchframe.config.json) and [`AGENTS.md`](./AGENTS.md) and runs the full clone + rebrand pipeline (same as **`/clone-website`**).
|
|
19
|
-
|
|
20
|
-
Rather read a postcard? See [`START_HERE.md`](./START_HERE.md).
|
|
21
|
-
|
|
22
|
-
## What `/clone-website` does
|
|
23
|
-
|
|
24
|
-
A multi-phase pipeline runs inside your AI agent:
|
|
25
|
-
|
|
26
|
-
1. **Reconnaissance** — screenshots, design-token extraction, **image/video inventory** (`MEDIA_MANIFEST.md`), interaction sweep (scroll, click, hover, responsive)
|
|
27
|
-
2. **Foundation** — fonts, globals, **`framer-motion`**, **download images & videos** to `public/` before most UI build
|
|
28
|
-
3. **Component Specs** — writes detailed spec files (`docs/research/components/`) with exact CSS, **local media paths**, and **Motion** (CSS vs Framer)
|
|
29
|
-
4. **Parallel Build** — dispatches builder agents in git worktrees, one per section
|
|
30
|
-
5. **SaaS Rebrand Pass** — swaps product name, headlines, feature copy, CTAs, and brand marks to match `launchframe.config.json#idea`. Visuals stay 1:1.
|
|
31
|
-
6. **Assembly & Visual QA** — merges worktrees, wires up the page, runs visual diff against the original
|
|
32
|
-
|
|
33
|
-
Each builder agent receives the full component spec inline — exact `getComputedStyle()` values, interaction models, multi-state content, responsive breakpoints, asset paths. No guessing.
|
|
34
|
-
|
|
35
|
-
## Supported AI Agents
|
|
36
|
-
|
|
37
|
-
| Agent | Status |
|
|
38
|
-
| ------------------------------------------------------------- | -------------------------- |
|
|
39
|
-
| [Claude Code](https://docs.anthropic.com/en/docs/claude-code) | **Recommended** — Opus 4.7 |
|
|
40
|
-
| [Codex CLI](https://github.com/openai/codex) | Supported |
|
|
41
|
-
| [OpenCode](https://opencode.ai/) | Supported |
|
|
42
|
-
| [GitHub Copilot](https://github.com/features/copilot) | Supported |
|
|
43
|
-
| [Cursor](https://cursor.com/) | Supported |
|
|
44
|
-
| [Windsurf](https://codeium.com/windsurf) | Supported |
|
|
45
|
-
| [Gemini CLI](https://github.com/google-gemini/gemini-cli) | Supported |
|
|
46
|
-
| [Cline](https://github.com/cline/cline) | Supported |
|
|
47
|
-
| [Roo Code](https://github.com/RooCodeInc/Roo-Code) | Supported |
|
|
48
|
-
| [Continue](https://continue.dev/) | Supported |
|
|
49
|
-
| [Amazon Q](https://aws.amazon.com/q/developer/) | Supported |
|
|
50
|
-
| [Augment Code](https://www.augmentcode.com/) | Supported |
|
|
51
|
-
| [Aider](https://aider.chat/) | Supported |
|
|
52
|
-
|
|
53
|
-
## Tech Stack
|
|
54
|
-
|
|
55
|
-
- **Next.js 16** — App Router, React 19, TypeScript strict
|
|
56
|
-
- **shadcn/ui** — Radix primitives + Tailwind CSS v4
|
|
57
|
-
- **Tailwind CSS v4** — oklch design tokens
|
|
58
|
-
- **Framer Motion** — default for non-trivial marketing animation (scroll reveals, staggers, layout); see `AGENTS.md`
|
|
59
|
-
- **Lucide React** — default icons (replaced by extracted SVGs during cloning)
|
|
60
|
-
- **Media** — targets download to `public/images/` & `public/videos/` per `docs/research/MEDIA_MANIFEST.md` (see `INSPECTION_GUIDE.md`)
|
|
61
|
-
|
|
62
|
-
## Prerequisites
|
|
63
|
-
|
|
64
|
-
- [Node.js](https://nodejs.org/) 24+
|
|
65
|
-
- An AI coding agent with browser automation MCP **or** [Playwright](https://playwright.dev/) for `npm run recon` when MCP is unavailable
|
|
66
|
-
- **First-time Playwright browsers:** after `npm install`, run `npx playwright install chromium`
|
|
67
|
-
|
|
68
|
-
## Project Structure
|
|
69
|
-
|
|
70
|
-
```
|
|
71
|
-
src/
|
|
72
|
-
app/ # Next.js routes
|
|
73
|
-
components/ # React components
|
|
74
|
-
ui/ # shadcn/ui primitives
|
|
75
|
-
icons.tsx # Extracted SVG icons
|
|
76
|
-
lib/utils.ts # cn() utility
|
|
77
|
-
types/ # TypeScript interfaces
|
|
78
|
-
hooks/ # Custom React hooks
|
|
79
|
-
public/
|
|
80
|
-
images/ # Downloaded images from target
|
|
81
|
-
videos/ # Downloaded videos from target
|
|
82
|
-
seo/ # Favicons, OG images
|
|
83
|
-
docs/
|
|
84
|
-
research/ # Extraction output, component specs, REBRAND.md
|
|
85
|
-
design-references/ # Screenshots
|
|
86
|
-
scripts/
|
|
87
|
-
sync-agent-rules.sh # Regenerate agent instruction files
|
|
88
|
-
sync-skills.mjs # Regenerate /clone-website for all platforms
|
|
89
|
-
launchframe.config.json # ← URL + SaaS idea (single source of truth)
|
|
90
|
-
AGENTS.md # Agent instructions (single source of truth)
|
|
91
|
-
START_HERE.md # "Open Cursor → say Build it"
|
|
92
|
-
CLAUDE.md # Claude Code config (imports AGENTS.md)
|
|
93
|
-
GEMINI.md # Gemini CLI config (imports AGENTS.md)
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
## Commands
|
|
97
|
-
|
|
98
|
-
```bash
|
|
99
|
-
npm run dev # Start dev server
|
|
100
|
-
npm run build # Production build
|
|
101
|
-
npm run lint # ESLint check
|
|
102
|
-
npm run typecheck # TypeScript check
|
|
103
|
-
npm run check # Run lint + typecheck + build
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
## Use Cases
|
|
107
|
-
|
|
108
|
-
- **Launch a SaaS faster** — start from a proven landing page, not a blank canvas
|
|
109
|
-
- **Platform migration** — rebuild a site you own from WordPress/Webflow/Squarespace into a modern Next.js codebase
|
|
110
|
-
- **Lost source code** — your site is live but the repo is gone; get the code back in a modern format
|
|
111
|
-
- **Learning** — deconstruct how production sites achieve specific layouts, animations, and responsive behavior
|
|
112
|
-
|
|
113
|
-
## Not Intended For
|
|
114
|
-
|
|
115
|
-
- **Phishing or impersonation** — this project must not be used for deceptive purposes, impersonation, or any activity that breaks the law
|
|
116
|
-
- **Passing off someone's design as your own** — logos, brand assets, and original copy belong to their owners
|
|
117
|
-
- **Violating terms of service** — some sites prohibit scraping or reproduction. Check first
|
|
118
|
-
|
|
119
|
-
## License
|
|
120
|
-
|
|
121
|
-
MIT
|