launchframe 0.3.1 → 0.4.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 (95) hide show
  1. package/.amazonq/cli-agents/clone-website.json +9 -0
  2. package/.amazonq/cli-agents/launchframe.json +9 -0
  3. package/.amazonq/rules/project.md +158 -0
  4. package/{template/.augment → .augment}/commands/clone-website.md +35 -112
  5. package/.augment/commands/launchframe.md +46 -0
  6. package/.claude/skills/clone-website/SKILL.md +487 -0
  7. package/.claude/skills/launchframe/SKILL.md +45 -0
  8. package/.clinerules +158 -0
  9. package/.codex/skills/clone-website/SKILL.md +487 -0
  10. package/.codex/skills/launchframe/SKILL.md +45 -0
  11. package/{template/.continue → .continue}/commands/clone-website.md +35 -112
  12. package/.continue/commands/launchframe.md +47 -0
  13. package/.continue/rules/project.md +162 -0
  14. package/{template/.cursor → .cursor}/commands/clone-website.md +35 -112
  15. package/.cursor/commands/launchframe.md +42 -0
  16. package/.cursor/rules/project.mdc +7 -0
  17. package/{template/.gemini → .gemini}/commands/clone-website.toml +35 -112
  18. package/.gemini/commands/launchframe.toml +48 -0
  19. package/.github/copilot-instructions.md +158 -0
  20. package/.github/skills/clone-website/SKILL.md +487 -0
  21. package/.github/skills/launchframe/SKILL.md +45 -0
  22. package/.gitignore +49 -0
  23. package/{template/.opencode → .opencode}/commands/clone-website.md +35 -112
  24. package/.opencode/commands/launchframe.md +45 -0
  25. package/.windsurf/workflows/clone-website.md +484 -0
  26. package/.windsurf/workflows/launchframe.md +42 -0
  27. package/AGENTS.md +66 -0
  28. package/README.md +149 -31
  29. package/bin/launchframe.mjs +348 -315
  30. package/docs/research/INSPECTION_GUIDE.md +90 -0
  31. package/package.json +73 -26
  32. package/scripts/sync-skills.mjs +124 -0
  33. package/{template/src → src}/app/globals.css +1 -93
  34. package/{template/src → src}/app/layout.tsx +16 -5
  35. package/src/app/page.tsx +40 -0
  36. package/src/lib/launchframe-config.ts +8 -0
  37. package/template/.amazonq/cli-agents/clone-website.json +0 -9
  38. package/template/.amazonq/rules/project.md +0 -281
  39. package/template/.claude/skills/clone-website/SKILL.md +0 -564
  40. package/template/.claude/skills/marketing-social-proof-motion/SKILL.md +0 -47
  41. package/template/.clinerules +0 -285
  42. package/template/.codex/skills/clone-website/SKILL.md +0 -564
  43. package/template/.continue/rules/project.md +0 -285
  44. package/template/.cursor/commands/marketing-social-proof-motion.md +0 -42
  45. package/template/.cursor/rules/project.mdc +0 -22
  46. package/template/.github/copilot-instructions.md +0 -281
  47. package/template/.github/skills/clone-website/SKILL.md +0 -564
  48. package/template/.nvmrc +0 -1
  49. package/template/.windsurf/workflows/clone-website.md +0 -561
  50. package/template/AGENTS.md +0 -160
  51. package/template/LICENSE +0 -21
  52. package/template/README.md +0 -121
  53. package/template/START_HERE.md +0 -15
  54. package/template/docs/design-references/playwright-example.com-1440px.png +0 -0
  55. package/template/docs/design-references/playwright-example.com-390px.png +0 -0
  56. package/template/docs/research/INSPECTION_GUIDE.md +0 -124
  57. package/template/launchframe.config.json +0 -14
  58. package/template/package-lock.json +0 -9873
  59. package/template/package.json +0 -54
  60. package/template/scripts/.gitkeep +0 -0
  61. package/template/scripts/recon-playwright.mjs +0 -396
  62. package/template/scripts/sync-skills.mjs +0 -111
  63. package/template/src/app/page.tsx +0 -5
  64. package/template/src/components/marketing/scribewise-landing.tsx +0 -34
  65. package/template/src/hooks/.gitkeep +0 -0
  66. package/template/src/types/.gitkeep +0 -0
  67. /package/{template/.aider.conf.yml → .aider.conf.yml} +0 -0
  68. /package/{template/.dockerignore → .dockerignore} +0 -0
  69. /package/{template/.gitattributes → .gitattributes} +0 -0
  70. /package/{template/.github → .github}/ISSUE_TEMPLATE/bug_report.yml +0 -0
  71. /package/{template/.github → .github}/ISSUE_TEMPLATE/config.yml +0 -0
  72. /package/{template/.github → .github}/ISSUE_TEMPLATE/feature_request.yml +0 -0
  73. /package/{template/.github → .github}/PULL_REQUEST_TEMPLATE.md +0 -0
  74. /package/{template/.github → .github}/copilot-setup-steps.yml +0 -0
  75. /package/{template/.github → .github}/workflows/ci.yml +0 -0
  76. /package/{template/.windsurfrules → .windsurfrules} +0 -0
  77. /package/{template/CLAUDE.md → CLAUDE.md} +0 -0
  78. /package/{template/Dockerfile → Dockerfile} +0 -0
  79. /package/{template/Dockerfile.dev → Dockerfile.dev} +0 -0
  80. /package/{template/GEMINI.md → GEMINI.md} +0 -0
  81. /package/{template/components.json → components.json} +0 -0
  82. /package/{template/docker-compose.yml → docker-compose.yml} +0 -0
  83. /package/{template/docs → docs}/design-references/.gitkeep +0 -0
  84. /package/{template/docs → docs}/design-references/comparison.png +0 -0
  85. /package/{template/eslint.config.mjs → eslint.config.mjs} +0 -0
  86. /package/{template/next.config.ts → next.config.ts} +0 -0
  87. /package/{template/postcss.config.mjs → postcss.config.mjs} +0 -0
  88. /package/{template/public/images → scripts}/.gitkeep +0 -0
  89. /package/{template/scripts → scripts}/sync-agent-rules.sh +0 -0
  90. /package/{template/src → src}/app/favicon.ico +0 -0
  91. /package/{template/src → src}/components/ui/button.tsx +0 -0
  92. /package/{template/public/seo → src/hooks}/.gitkeep +0 -0
  93. /package/{template/src → src}/lib/utils.ts +0 -0
  94. /package/{template/public/videos → src/types}/.gitkeep +0 -0
  95. /package/{template/tsconfig.json → tsconfig.json} +0 -0
@@ -1,54 +0,0 @@
1
- {
2
- "name": "ai-website-cloner-template",
3
- "version": "0.3.1",
4
- "private": true,
5
- "description": "clone any website into a clean, modern Next.js codebase using AI coding agents",
6
- "license": "MIT",
7
- "keywords": [
8
- "claude-code",
9
- "website-clone",
10
- "reverse-engineering",
11
- "nextjs",
12
- "ai",
13
- "template",
14
- "tailwindcss",
15
- "shadcn-ui"
16
- ],
17
- "engines": {
18
- "node": ">=24"
19
- },
20
- "scripts": {
21
- "dev": "next dev",
22
- "build": "next build",
23
- "start": "next start",
24
- "lint": "eslint",
25
- "typecheck": "tsc --noEmit",
26
- "check": "npm run lint && npm run typecheck && npm run build",
27
- "recon": "node scripts/recon-playwright.mjs",
28
- "recon:headed": "node scripts/recon-playwright.mjs --headed"
29
- },
30
- "dependencies": {
31
- "@base-ui/react": "^1.3.0",
32
- "class-variance-authority": "^0.7.1",
33
- "clsx": "^2.1.1",
34
- "framer-motion": "^12.4.0",
35
- "lucide-react": "^1.6.0",
36
- "next": "16.2.1",
37
- "react": "19.2.4",
38
- "react-dom": "19.2.4",
39
- "shadcn": "^4.1.0",
40
- "tailwind-merge": "^3.5.0",
41
- "tw-animate-css": "^1.4.0"
42
- },
43
- "devDependencies": {
44
- "@tailwindcss/postcss": "^4",
45
- "@types/node": "^24",
46
- "@types/react": "^19",
47
- "@types/react-dom": "^19",
48
- "eslint": "^9",
49
- "eslint-config-next": "16.2.1",
50
- "playwright": "^1.49.1",
51
- "tailwindcss": "^4",
52
- "typescript": "^5"
53
- }
54
- }
File without changes
@@ -1,396 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Playwright recon when Browser / Chrome DevTools MCP is unavailable or broken.
4
- *
5
- * Reads URL from launchframe.config.json (or --url). Writes:
6
- * - docs/design-references/playwright-<host>-<w>px.png (full page)
7
- * - docs/research/computed-snapshot.json (styles + merged asset inventory + bot-wall hint)
8
- * - docs/research/MEDIA_MANIFEST.md (table of discovered media URLs)
9
- *
10
- * Usage:
11
- * npm run recon
12
- * npm run recon:headed
13
- * node scripts/recon-playwright.mjs --url https://example.com --headed
14
- *
15
- * First time: npx playwright install chromium
16
- */
17
-
18
- import { readFileSync, writeFileSync, mkdirSync, existsSync } from "node:fs";
19
- import { dirname, join } from "node:path";
20
- import { fileURLToPath } from "node:url";
21
-
22
- const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
23
-
24
- function parseArgs(argv) {
25
- const out = { url: null, headed: false, channel: process.env.PW_CHANNEL || undefined };
26
- for (let i = 0; i < argv.length; i++) {
27
- const a = argv[i];
28
- if (a === "--headed" || a === "-H") out.headed = true;
29
- else if (a === "--url" || a === "-u") out.url = argv[++i];
30
- else if (a.startsWith("--url=")) out.url = a.slice(6);
31
- else if (a === "--channel" || a === "-c") out.channel = argv[++i] || undefined;
32
- }
33
- return out;
34
- }
35
-
36
- function readLaunchframeUrl() {
37
- const p = join(ROOT, "launchframe.config.json");
38
- if (!existsSync(p)) return null;
39
- try {
40
- const j = JSON.parse(readFileSync(p, "utf8"));
41
- return j.url || null;
42
- } catch {
43
- return null;
44
- }
45
- }
46
-
47
- function safeHost(url) {
48
- try {
49
- return new URL(url).hostname.replace(/[^a-z0-9.-]+/gi, "_");
50
- } catch {
51
- return "unknown-host";
52
- }
53
- }
54
-
55
- /**
56
- * Scroll the full document depth slowly so lazy-loaded media and below-fold DOM resolve.
57
- * Repeats until scrollHeight stabilizes (handles incremental infinite layouts up to max rounds).
58
- */
59
- async function scrollFullPage(page) {
60
- await page.evaluate(async () => {
61
- const delay = (ms) => new Promise((r) => setTimeout(r, ms));
62
- const stepSize = () => Math.max(1, Math.floor(window.innerHeight * 0.85));
63
-
64
- let unchangedStreak = 0;
65
- let prevTotal = -1;
66
-
67
- for (let round = 0; round < 12; round++) {
68
- let y = 0;
69
- let limit = document.documentElement.scrollHeight;
70
-
71
- while (y < limit) {
72
- y = Math.min(y + stepSize(), limit);
73
- window.scrollTo(0, y);
74
- await delay(130);
75
- limit = document.documentElement.scrollHeight;
76
- }
77
-
78
- window.scrollTo(0, document.documentElement.scrollHeight);
79
- await delay(420);
80
-
81
- const h = document.documentElement.scrollHeight;
82
-
83
- window.scrollTo(0, 0);
84
- await delay(220);
85
-
86
- if (h === prevTotal) unchangedStreak++;
87
- else unchangedStreak = 0;
88
- prevTotal = h;
89
-
90
- if (unchangedStreak >= 2) break;
91
- }
92
- });
93
- }
94
-
95
- function mergeAssetInventory(base, extra) {
96
- if (!extra) return base;
97
- const imgKey = (i) => i.src || "";
98
- const seenImg = new Set(base.images.map(imgKey));
99
- const images = [...base.images];
100
- for (const i of extra.images) {
101
- const k = imgKey(i);
102
- if (k && !seenImg.has(k)) {
103
- seenImg.add(k);
104
- images.push(i);
105
- }
106
- }
107
-
108
- const vidKey = (v) => `${v.src || ""}|${v.poster || ""}`;
109
- const seenVid = new Set(base.videos.map(vidKey));
110
- const videos = [...base.videos];
111
- for (const v of extra.videos) {
112
- const k = vidKey(v);
113
- if ((v.src || v.poster) && !seenVid.has(k)) {
114
- seenVid.add(k);
115
- videos.push(v);
116
- }
117
- }
118
-
119
- const seenBg = new Set(base.backgroundImages.map((b) => b.url));
120
- const backgroundImages = [...base.backgroundImages];
121
- for (const b of extra.backgroundImages) {
122
- if (b.url && !seenBg.has(b.url)) {
123
- seenBg.add(b.url);
124
- backgroundImages.push(b);
125
- }
126
- }
127
-
128
- return {
129
- images,
130
- videos,
131
- backgroundImages,
132
- svgCount: Math.max(base.svgCount ?? 0, extra.svgCount ?? 0),
133
- };
134
- }
135
-
136
- async function gatherPageData(page) {
137
- return page.evaluate(() => {
138
- const title = document.title || "";
139
- const text = (document.body?.innerText || "").slice(0, 80000);
140
- const likelyBotWall =
141
- /just a moment/i.test(title) ||
142
- /checking your browser/i.test(text) ||
143
- /cf-browser-verification/i.test(document.documentElement?.innerHTML || "") ||
144
- document.querySelector("#challenge-running, .cf-browser-verification, #cf-challenge-running") !== null;
145
-
146
- const imgs = [...document.querySelectorAll("img")].map((img) => ({
147
- src: img.currentSrc || img.src || "",
148
- srcset: img.srcset || "",
149
- alt: img.alt || "",
150
- w: img.naturalWidth,
151
- h: img.naturalHeight,
152
- }));
153
-
154
- const videos = [...document.querySelectorAll("video")].map((v) => ({
155
- src: v.currentSrc || v.src || "",
156
- poster: v.poster || "",
157
- autoplay: v.autoplay,
158
- loop: v.loop,
159
- muted: v.muted,
160
- }));
161
-
162
- const bgUrls = [];
163
- const seen = new Set();
164
- for (const el of document.querySelectorAll("*")) {
165
- if (bgUrls.length > 400) break;
166
- const bg = getComputedStyle(el).backgroundImage;
167
- if (!bg || bg === "none") continue;
168
- const m = /url\(["']?([^"')]+)["']?\)/.exec(bg);
169
- if (m && m[1] && !seen.has(m[1])) {
170
- seen.add(m[1]);
171
- bgUrls.push({ url: m[1], tag: el.tagName.toLowerCase() });
172
- }
173
- }
174
-
175
- const props = [
176
- "fontSize",
177
- "fontWeight",
178
- "fontFamily",
179
- "lineHeight",
180
- "letterSpacing",
181
- "color",
182
- "backgroundColor",
183
- "padding",
184
- "margin",
185
- "maxWidth",
186
- "display",
187
- "gap",
188
- "borderRadius",
189
- "boxShadow",
190
- ];
191
-
192
- function pickStyles(el) {
193
- if (!el) return null;
194
- const cs = getComputedStyle(el);
195
- const o = {};
196
- for (const p of props) o[p] = cs[p];
197
- return o;
198
- }
199
-
200
- const landmarks = {
201
- html: pickStyles(document.documentElement),
202
- body: pickStyles(document.body),
203
- h1: pickStyles(document.querySelector("h1")),
204
- header: pickStyles(document.querySelector("header")),
205
- main: pickStyles(document.querySelector("main")),
206
- firstSection: pickStyles(document.querySelector("main section, section")),
207
- };
208
-
209
- return {
210
- title,
211
- likelyBotWall,
212
- textSample: text.slice(0, 24000),
213
- assetInventory: {
214
- images: imgs.filter((i) => i.src),
215
- videos,
216
- backgroundImages: bgUrls,
217
- svgCount: document.querySelectorAll("svg").length,
218
- },
219
- computedLandmarks: landmarks,
220
- };
221
- });
222
- }
223
-
224
- function writeMediaManifest(host, data, limitationsPath) {
225
- const { images, videos, backgroundImages } = data.assetInventory;
226
- const lines = [
227
- "# MEDIA_MANIFEST (Playwright)",
228
- "",
229
- `Host: **${host}**`,
230
- "",
231
- "## Images (`<img>`)",
232
- "",
233
- "| src | alt | natural |",
234
- "|-----|-----|---------|",
235
- ];
236
- for (const i of images.slice(0, 200)) {
237
- const s = i.src.replace(/\|/g, "\\|");
238
- lines.push(`| ${s} | ${(i.alt || "").replace(/\|/g, "\\|")} | ${i.w}×${i.h} |`);
239
- }
240
- if (images.length > 200) lines.push(`\n_…and ${images.length - 200} more_\n`);
241
-
242
- lines.push("", "## Video", "", "| src | poster |", "|-----|--------|");
243
- for (const v of videos) {
244
- lines.push(`| ${(v.src || "").replace(/\|/g, "\\|")} | ${(v.poster || "").replace(/\|/g, "\\|")} |`);
245
- }
246
-
247
- lines.push("", "## Background images (sample)", "", "| url |", "|-----|");
248
- for (const b of backgroundImages.slice(0, 150)) {
249
- lines.push(`| ${b.url.replace(/\|/g, "\\|")} |`);
250
- }
251
-
252
- lines.push("", "---", "", `_Generated by scripts/recon-playwright.mjs. Download with scripts/download-assets.mjs when ready._`);
253
- if (limitationsPath) lines.push("", `See also: **${limitationsPath}**`);
254
-
255
- writeFileSync(join(ROOT, "docs", "research", "MEDIA_MANIFEST.md"), lines.join("\n") + "\n", "utf8");
256
- }
257
-
258
- function writeLimitations(host, url, finalUrl, data) {
259
- const p = join(ROOT, "docs", "research", "EXTRACTION_LIMITATIONS.md");
260
- const lines = [
261
- "# Extraction limitations",
262
- "",
263
- `- **Requested URL:** ${url}`,
264
- `- **Final URL:** ${finalUrl}`,
265
- `- **Host:** ${host}`,
266
- `- **Captured at:** ${new Date().toISOString()}`,
267
- `- **Tool:** Playwright (\`npm run recon\`)`,
268
- "",
269
- ];
270
- if (data.likelyBotWall) {
271
- lines.push(
272
- "## Bot / challenge page suspected",
273
- "",
274
- "Title or DOM matches common WAF/interstitial patterns. **Computed styles and screenshots may not reflect the real marketing page.** Prefer \`npm run recon:headed\` with real Chrome, or open the site manually and export assets.",
275
- ""
276
- );
277
- } else {
278
- lines.push(
279
- "## Notes",
280
- "",
281
- "Snapshot-derived data is from Playwright Chromium, not interactive MCP. Re-run after layout shifts; verify against a real browser for pixel QA.",
282
- ""
283
- );
284
- }
285
- writeFileSync(p, lines.join("\n"), "utf8");
286
- return "docs/research/EXTRACTION_LIMITATIONS.md";
287
- }
288
-
289
- async function main() {
290
- const args = parseArgs(process.argv.slice(2));
291
- const url = args.url || readLaunchframeUrl();
292
- if (!url) {
293
- console.error("No URL: set launchframe.config.json or pass --url https://...");
294
- process.exit(1);
295
- }
296
-
297
- let chromium;
298
- try {
299
- ({ chromium } = await import("playwright"));
300
- } catch {
301
- console.error("Install Playwright: npm i -D playwright && npx playwright install chromium");
302
- process.exit(1);
303
- }
304
-
305
- const host = safeHost(url);
306
- mkdirSync(join(ROOT, "docs", "design-references"), { recursive: true });
307
- mkdirSync(join(ROOT, "docs", "research"), { recursive: true });
308
-
309
- const browser = await chromium.launch({
310
- headless: !args.headed,
311
- channel: args.channel,
312
- });
313
-
314
- const context = await browser.newContext({
315
- userAgent:
316
- "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
317
- locale: "en-US",
318
- });
319
-
320
- const page = await context.newPage();
321
-
322
- const snapshot = {
323
- fetchedAt: new Date().toISOString(),
324
- requestedUrl: url,
325
- finalUrl: null,
326
- viewports: {},
327
- title: null,
328
- likelyBotWall: false,
329
- assetInventory: null,
330
- computedLandmarks: null,
331
- textSample: null,
332
- };
333
-
334
- try {
335
- await page.goto(url, { waitUntil: "domcontentloaded", timeout: 90_000 });
336
- await new Promise((r) => setTimeout(r, 2500));
337
- snapshot.finalUrl = page.url();
338
-
339
- let mergedInventory = null;
340
- let desktopLandmarks = null;
341
-
342
- for (const w of [1440, 390]) {
343
- await page.setViewportSize({ width: w, height: w === 1440 ? 900 : 844 });
344
- await scrollFullPage(page);
345
-
346
- const shotPath = join(ROOT, "docs", "design-references", `playwright-${host}-${w}px.png`);
347
- await page.screenshot({ path: shotPath, fullPage: true });
348
- snapshot.viewports[w] = { screenshot: `docs/design-references/playwright-${host}-${w}px.png` };
349
-
350
- const data = await gatherPageData(page);
351
-
352
- if (w === 1440) {
353
- snapshot.title = data.title;
354
- snapshot.likelyBotWall = data.likelyBotWall;
355
- mergedInventory = data.assetInventory;
356
- desktopLandmarks = data.computedLandmarks;
357
- snapshot.textSample = data.textSample;
358
- } else {
359
- mergedInventory = mergeAssetInventory(mergedInventory, data.assetInventory);
360
- }
361
- }
362
-
363
- snapshot.assetInventory = mergedInventory;
364
- snapshot.computedLandmarks = desktopLandmarks;
365
- } finally {
366
- await browser.close();
367
- }
368
-
369
- const limRel = writeLimitations(host, url, snapshot.finalUrl, {
370
- likelyBotWall: snapshot.likelyBotWall,
371
- });
372
-
373
- writeFileSync(
374
- join(ROOT, "docs", "research", "computed-snapshot.json"),
375
- JSON.stringify(snapshot, null, 2) + "\n",
376
- "utf8"
377
- );
378
-
379
- if (snapshot.assetInventory) {
380
- writeMediaManifest(host, { assetInventory: snapshot.assetInventory }, limRel);
381
- }
382
-
383
- console.log("Playwright recon finished.");
384
- console.log(` Screenshots: docs/design-references/playwright-${host}-1440px.png (+ 390px)`);
385
- console.log(` Snapshot: docs/research/computed-snapshot.json`);
386
- console.log(` Media list: docs/research/MEDIA_MANIFEST.md`);
387
- console.log(` Limits: ${limRel}`);
388
- if (snapshot.likelyBotWall) {
389
- console.warn("\n⚠ Possible bot wall / challenge page — check EXTRACTION_LIMITATIONS.md and try: npm run recon:headed\n");
390
- }
391
- }
392
-
393
- main().catch((e) => {
394
- console.error(e);
395
- process.exit(1);
396
- });
@@ -1,111 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * Generates clone-website command/skill files for all supported AI coding platforms.
5
- * Source of truth: .claude/skills/clone-website/SKILL.md
6
- *
7
- * Usage: node scripts/sync-skills.mjs
8
- */
9
-
10
- import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
11
- import { dirname, join } from 'node:path';
12
- import { fileURLToPath } from 'node:url';
13
-
14
- const ROOT = join(dirname(fileURLToPath(import.meta.url)), '..');
15
- const SOURCE = join(ROOT, '.claude', 'skills', 'clone-website', 'SKILL.md');
16
-
17
- // --- Parse source skill ---
18
-
19
- let raw;
20
- try {
21
- raw = readFileSync(SOURCE, 'utf8').replace(/\r\n/g, '\n');
22
- } catch {
23
- console.error(`Error: Source skill not found at .claude/skills/clone-website/SKILL.md`);
24
- process.exit(1);
25
- }
26
-
27
- const match = raw.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
28
- if (!match) {
29
- console.error('Error: Could not parse SKILL.md frontmatter');
30
- process.exit(1);
31
- }
32
-
33
- const body = match[2];
34
- const shortDesc = 'Reverse-engineer and clone any website as a pixel-perfect replica';
35
-
36
- // --- Helpers ---
37
-
38
- function write(relPath, content) {
39
- const full = join(ROOT, relPath);
40
- mkdirSync(dirname(full), { recursive: true });
41
- writeFileSync(full, content, 'utf8');
42
- console.log(` \u2713 ${relPath}`);
43
- }
44
-
45
- const HEADER =
46
- '<!-- AUTO-GENERATED from .claude/skills/clone-website/SKILL.md \u2014 do not edit directly.\n' +
47
- ' Run `node scripts/sync-skills.mjs` to regenerate. -->\n\n';
48
-
49
- const noArgs = (text) => text.replace(/\$ARGUMENTS/g, 'the target URL provided by the user');
50
-
51
- // --- Generate ---
52
-
53
- console.log('Syncing clone-website skill to all platforms...');
54
- console.log(` Source: .claude/skills/clone-website/SKILL.md\n`);
55
-
56
- // 1. Codex CLI — same SKILL.md format, same $ARGUMENTS syntax
57
- write('.codex/skills/clone-website/SKILL.md', raw);
58
-
59
- // 2. GitHub Copilot — same SKILL.md format
60
- write('.github/skills/clone-website/SKILL.md', raw);
61
-
62
- // 3. Cursor — plain markdown, no argument substitution support
63
- write('.cursor/commands/clone-website.md', HEADER + noArgs(body));
64
-
65
- // 4. Windsurf — markdown workflow
66
- write('.windsurf/workflows/clone-website.md', HEADER + noArgs(body));
67
-
68
- // 5. Gemini CLI — TOML format, {{args}} for arguments
69
- const geminiBody = body.replace(/\$ARGUMENTS/g, '{{args}}');
70
- write(
71
- '.gemini/commands/clone-website.toml',
72
- `# AUTO-GENERATED from .claude/skills/clone-website/SKILL.md\n` +
73
- `# Run \`node scripts/sync-skills.mjs\` to regenerate.\n\n` +
74
- `description = "${shortDesc}"\n\n` +
75
- `[prompt]\ntext = '''\n${geminiBody}\n'''\n`
76
- );
77
-
78
- // 6. OpenCode — markdown + YAML frontmatter, $ARGUMENTS works natively
79
- write(
80
- '.opencode/commands/clone-website.md',
81
- `---\ndescription: "${shortDesc}"\n---\n${HEADER}${body}`
82
- );
83
-
84
- // 7. Augment Code — markdown + YAML frontmatter
85
- write(
86
- '.augment/commands/clone-website.md',
87
- `---\ndescription: "${shortDesc}"\nargument-hint: "<url>"\n---\n${HEADER}${body}`
88
- );
89
-
90
- // 8. Continue — prompt file with invokable: true
91
- write(
92
- '.continue/commands/clone-website.md',
93
- `---\nname: clone-website\ndescription: "${shortDesc}"\ninvokable: true\n---\n${HEADER}${body}`
94
- );
95
-
96
- // 9. Amazon Q — JSON agent definition
97
- write(
98
- '.amazonq/cli-agents/clone-website.json',
99
- JSON.stringify(
100
- {
101
- name: 'clone-website',
102
- description: shortDesc,
103
- prompt: noArgs(body),
104
- fileContext: ['AGENTS.md', 'docs/research/**'],
105
- },
106
- null,
107
- 2
108
- ) + '\n'
109
- );
110
-
111
- console.log('\nDone! 9 platform command files generated from source skill.');
@@ -1,5 +0,0 @@
1
- import { ScribewiseLanding } from "@/components/marketing/scribewise-landing";
2
-
3
- export default function Home() {
4
- return <ScribewiseLanding />;
5
- }
@@ -1,34 +0,0 @@
1
- import Link from "next/link";
2
-
3
- import { Button } from "@/components/ui/button";
4
-
5
- export function ScribewiseLanding() {
6
- return (
7
- <main className="relative flex flex-1 flex-col items-center justify-center gap-8 overflow-hidden px-6 py-24 text-center">
8
- <div
9
- aria-hidden
10
- className="pointer-events-none absolute inset-0 -z-10 bg-[radial-gradient(ellipse_80%_50%_at_50%_-20%,color-mix(in_oklab,var(--primary)_28%,transparent),transparent)]"
11
- />
12
- <p className="text-muted-foreground text-xs font-medium tracking-[0.2em] uppercase">
13
- Launchframe template
14
- </p>
15
- <div className="max-w-2xl space-y-4">
16
- <h1 className="text-balance font-semibold text-4xl tracking-tight sm:text-5xl">
17
- Ready for `/clone-website`
18
- </h1>
19
- <p className="text-muted-foreground text-balance text-lg">
20
- Add your target URL and SaaS idea via <code className="text-foreground">launchframe.config.json</code>,
21
- run recon if needed, then let your agent ship the full landing.
22
- </p>
23
- </div>
24
- <div className="flex flex-wrap items-center justify-center gap-3">
25
- <Button nativeButton={false} render={<Link href="https://github.com/evangruhlkey/launchframe" />}>
26
- Launchframe docs
27
- </Button>
28
- <Button variant="outline" nativeButton={false} render={<Link href="/" />}>
29
- Replace this page
30
- </Button>
31
- </div>
32
- </main>
33
- );
34
- }
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes