launchframe 0.2.0 → 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 -183
- package/bin/launchframe.mjs +234 -30
- package/package.json +52 -67
- 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 -548
- 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
|
@@ -1,270 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Emits research artifacts under each extract run, aligned in layout and intent
|
|
3
|
-
* with the AI Website Cloner template (topology, behaviors bible, per-section
|
|
4
|
-
* specs). Values are derived from Landingfram's automated crawl where available;
|
|
5
|
-
* interactive sweeps still require manual Browser MCP work for full fidelity.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
9
|
-
import { dirname, join } from "node:path";
|
|
10
|
-
|
|
11
|
-
import type { ExtractionRun, SectionLayout, SiteLayout } from "./types.js";
|
|
12
|
-
import { hostSlug } from "./host-slug.js";
|
|
13
|
-
|
|
14
|
-
export function emitClonerResearch(
|
|
15
|
-
run: ExtractionRun,
|
|
16
|
-
layoutsByHost: Map<string, SiteLayout>,
|
|
17
|
-
): string[] {
|
|
18
|
-
const written: string[] = [];
|
|
19
|
-
const root = join(run.outputDir, "docs", "research");
|
|
20
|
-
mkdirSync(root, { recursive: true });
|
|
21
|
-
|
|
22
|
-
written.push(
|
|
23
|
-
writeFile(
|
|
24
|
-
join(root, "README.md"),
|
|
25
|
-
emitResearchReadme(run, layoutsByHost.size > 0),
|
|
26
|
-
),
|
|
27
|
-
);
|
|
28
|
-
|
|
29
|
-
for (const cap of run.captures) {
|
|
30
|
-
if (cap.status !== "ok") continue;
|
|
31
|
-
const layout = layoutsByHost.get(cap.host);
|
|
32
|
-
const hostDir = join(root, hostSlug(cap.host));
|
|
33
|
-
mkdirSync(join(hostDir, "components"), { recursive: true });
|
|
34
|
-
|
|
35
|
-
written.push(
|
|
36
|
-
writeFile(join(hostDir, "PAGE_TOPOLOGY.md"), emitTopology(cap.url, cap.host, layout)),
|
|
37
|
-
);
|
|
38
|
-
written.push(
|
|
39
|
-
writeFile(
|
|
40
|
-
join(hostDir, "BEHAVIORS.md"),
|
|
41
|
-
emitBehaviors(cap.url, cap.host, run, layout),
|
|
42
|
-
),
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
if (layout) {
|
|
46
|
-
for (const section of layout.sections) {
|
|
47
|
-
const name = `${section.id}-${section.role}`.replace(/[^a-z0-9-]+/gi, "-");
|
|
48
|
-
written.push(
|
|
49
|
-
writeFile(
|
|
50
|
-
join(hostDir, "components", `${name}.spec.md`),
|
|
51
|
-
emitSectionSpec(cap.host, layout, section),
|
|
52
|
-
),
|
|
53
|
-
);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return written;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function writeFile(path: string, body: string): string {
|
|
62
|
-
mkdirSync(dirname(path), { recursive: true });
|
|
63
|
-
writeFileSync(path, body, "utf8");
|
|
64
|
-
return path;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function automatedJsonAppend(outputDir: string, host: string): string {
|
|
68
|
-
const slug = hostSlug(host);
|
|
69
|
-
const base = join(outputDir, "docs", "design-references", slug);
|
|
70
|
-
const probe = join(base, "header-scroll-probe.json");
|
|
71
|
-
const hints = join(base, "interaction-hints.json");
|
|
72
|
-
const chunks: string[] = [];
|
|
73
|
-
if (existsSync(probe)) {
|
|
74
|
-
chunks.push("\n### Embedded: header-scroll-probe.json\n\n```json\n");
|
|
75
|
-
chunks.push(readFileSync(probe, "utf8").trimEnd());
|
|
76
|
-
chunks.push("\n```\n");
|
|
77
|
-
}
|
|
78
|
-
if (existsSync(hints)) {
|
|
79
|
-
chunks.push("\n### Embedded: interaction-hints.json\n\n```json\n");
|
|
80
|
-
chunks.push(readFileSync(hints, "utf8").trimEnd());
|
|
81
|
-
chunks.push("\n```\n");
|
|
82
|
-
}
|
|
83
|
-
const manifest = join(outputDir, "downloaded_assets", slug, "manifest.json");
|
|
84
|
-
if (existsSync(manifest)) {
|
|
85
|
-
chunks.push(
|
|
86
|
-
`\n### Asset downloads\n\nLocal manifest: \`downloaded_assets/${slug}/manifest.json\`\n`,
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
return chunks.join("");
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
function emitResearchReadme(run: ExtractionRun, hasLayouts: boolean): string {
|
|
93
|
-
const idea =
|
|
94
|
-
run.saasIdea?.trim() ||
|
|
95
|
-
"_No SaaS idea passed. Re-run with:_ `npx launchframe <url> \"your idea\"` _or_ `--idea \"...\"`.";
|
|
96
|
-
|
|
97
|
-
const layoutNote = hasLayouts
|
|
98
|
-
? ""
|
|
99
|
-
: "\n**Note:** No `SiteLayout` was available for any host (DOM crawl may have failed). Topology files are stubbed; rely on `reference/<host>/` + tokens.\n";
|
|
100
|
-
|
|
101
|
-
return `# Research artifacts
|
|
102
|
-
|
|
103
|
-
This folder mirrors the **artifact roles** of [ai-website-cloner-template](https://github.com/JCodesMore/ai-website-cloner-template) (\`PAGE_TOPOLOGY.md\`, \`BEHAVIORS.md\`, \`components/*.spec.md\`) so coding agents can follow the same **recon → specs → build** cadence.
|
|
104
|
-
|
|
105
|
-
**Run:** \`${run.runId}\`
|
|
106
|
-
**Output:** \`${run.outputDir.replace(/\\/g, "/")}\`
|
|
107
|
-
|
|
108
|
-
## Operator SaaS idea
|
|
109
|
-
|
|
110
|
-
${idea}
|
|
111
|
-
|
|
112
|
-
## Per-host paths
|
|
113
|
-
|
|
114
|
-
For each captured host: \`<host>/PAGE_TOPOLOGY.md\`, \`<host>/BEHAVIORS.md\`, \`<host>/components/*.spec.md\`
|
|
115
|
-
|
|
116
|
-
Screenshots for visual QA live in \`../design-references/<host-slug>/\` (multi-viewport + scroll sweep). Downloaded binaries (when enabled): \`../downloaded_assets/<host-slug>/manifest.json\`.
|
|
117
|
-
${layoutNote}`;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
function emitTopology(url: string, host: string, layout: SiteLayout | undefined): string {
|
|
121
|
-
const lines: string[] = [
|
|
122
|
-
`# Page topology`,
|
|
123
|
-
``,
|
|
124
|
-
`- **URL:** ${url}`,
|
|
125
|
-
`- **Host:** ${host}`,
|
|
126
|
-
`- **Generated by:** Landingfram automated crawl (primary viewport + optional responsive passes under \`docs/design-references/\`)`,
|
|
127
|
-
``,
|
|
128
|
-
`## Section order (top → bottom)`,
|
|
129
|
-
``,
|
|
130
|
-
];
|
|
131
|
-
|
|
132
|
-
if (!layout?.sections.length) {
|
|
133
|
-
lines.push(
|
|
134
|
-
`_No section model — see \`reference/${host}/structure-outline.txt\` and \`dom-structure.json\`._`,
|
|
135
|
-
);
|
|
136
|
-
return lines.join("\n");
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
for (const s of layout.sections) {
|
|
140
|
-
const [x, y, w, h] = s.bbox;
|
|
141
|
-
lines.push(
|
|
142
|
-
`### ${s.id} — ${s.role}`,
|
|
143
|
-
`- **Composition:** ${s.composition}`,
|
|
144
|
-
`- **Density:** ${s.density}`,
|
|
145
|
-
`- **BBox (normalized):** x=${x.toFixed(3)}, y=${y.toFixed(3)}, w=${w.toFixed(3)}, h=${h.toFixed(3)}`,
|
|
146
|
-
`- **Slots:** ${s.slots.map((sl) => `${sl.kind}×${sl.count}`).join(", ") || "_none_"}`,
|
|
147
|
-
`- **Notes:** ${s.notes.join("; ") || "—"}`,
|
|
148
|
-
``,
|
|
149
|
-
);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
lines.push(
|
|
153
|
-
`## Fixed / sticky overlays`,
|
|
154
|
-
``,
|
|
155
|
-
`_Not inferred automatically. Inspect \`reference/${host}/page.html\` or re-scan with Browser MCP per [clone-website skill](https://github.com/JCodesMore/ai-website-cloner-template/blob/master/.claude/skills/clone-website/SKILL.md)._`,
|
|
156
|
-
``,
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
return lines.join("\n");
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function emitBehaviors(
|
|
163
|
-
url: string,
|
|
164
|
-
host: string,
|
|
165
|
-
run: ExtractionRun,
|
|
166
|
-
layout: SiteLayout | undefined,
|
|
167
|
-
): string {
|
|
168
|
-
const vpLine = layout?.viewport
|
|
169
|
-
? `${layout.viewport.width}×${layout.viewport.height}px`
|
|
170
|
-
: "See crawl viewport in run.json / raw/*.layout.json";
|
|
171
|
-
|
|
172
|
-
return `# Behaviors bible
|
|
173
|
-
|
|
174
|
-
- **URL:** ${url}
|
|
175
|
-
- **Capture viewport:** ${vpLine}
|
|
176
|
-
|
|
177
|
-
## What Landingfram captured automatically
|
|
178
|
-
|
|
179
|
-
- Single desktop pass in headless Chromium with **reduced motion** forced for deterministic screenshots.
|
|
180
|
-
- **Automated multi-viewport + scroll sweep** PNGs under \`docs/design-references/${hostSlug(host)}/\` (desktop/tablet/mobile + scroll frames).
|
|
181
|
-
- **Header scroll probe** (\`header-scroll-probe.json\`) and **interaction hints** (\`interaction-hints.json\`) — check for scroll-driven chrome, Lenis-like wrappers, scroll-snap.
|
|
182
|
-
- Computed-style tokens and a typed **section scaffold** when DOM crawl succeeds (\`raw/${host}.layout.json\`, \`mirror/${host}/page.tsx\`).
|
|
183
|
-
${automatedJsonAppend(run.outputDir, host)}
|
|
184
|
-
|
|
185
|
-
## What still needs a manual interaction sweep
|
|
186
|
-
|
|
187
|
-
Use Browser MCP (or the upstream **clone-website** workflow) to fill gaps — match their checklist:
|
|
188
|
-
|
|
189
|
-
1. **Scroll sweep** — header shrink, scroll-driven tabs, scroll-snap, sticky sidebars, Lenis / Locomotive markers.
|
|
190
|
-
2. **Click sweep** — modals, dropdowns, tab panels; extract **every** tab state.
|
|
191
|
-
3. **Hover sweep** — transitions (duration + easing), not just before/after colors.
|
|
192
|
-
4. **Responsive sweep** — 1440 / 768 / 390 viewports with breakpoint notes.
|
|
193
|
-
|
|
194
|
-
Document findings below as you discover them.
|
|
195
|
-
|
|
196
|
-
## Recorded behaviors (manual)
|
|
197
|
-
|
|
198
|
-
| Area | Interaction model | Trigger | State A | State B | Transition |
|
|
199
|
-
|------|---------------------|---------|---------|---------|------------|
|
|
200
|
-
| _(empty)_ | | | | | |
|
|
201
|
-
|
|
202
|
-
---
|
|
203
|
-
|
|
204
|
-
## Operator SaaS idea
|
|
205
|
-
|
|
206
|
-
${run.saasIdea?.trim() || "_Pass `--idea` or a second positional string when invoking landingfram._"}
|
|
207
|
-
|
|
208
|
-
When rebuilding UI, preserve **interaction models** from this table (once filled); rewrite **copy** to match the SaaS idea without impersonating the reference brand.
|
|
209
|
-
`;
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
function emitSectionSpec(host: string, layout: SiteLayout, s: SectionLayout): string {
|
|
213
|
-
const mirrorHint = `mirror/${host}/page.tsx — section marker starts with \`${s.id}:\` inside data-mirror-section`;
|
|
214
|
-
const slotsMd = s.slots.map((sl) => `- **${sl.kind}** × ${sl.count}`).join("\n");
|
|
215
|
-
|
|
216
|
-
return `# Section specification — ${s.id}
|
|
217
|
-
|
|
218
|
-
## Overview
|
|
219
|
-
|
|
220
|
-
- **Source URL:** ${layout.url}
|
|
221
|
-
- **Mirror:** \`${mirrorHint}\`
|
|
222
|
-
- **Interaction model:** _unknown — infer via scroll-first sweep per clone-website skill; default treated as **static** for this automated extract._
|
|
223
|
-
|
|
224
|
-
## Section summary
|
|
225
|
-
|
|
226
|
-
- **Role:** ${s.role}
|
|
227
|
-
- **Composition:** ${s.composition}
|
|
228
|
-
- **Density:** ${s.density}
|
|
229
|
-
|
|
230
|
-
## Layout hints (from crawl)
|
|
231
|
-
|
|
232
|
-
### Container style hints
|
|
233
|
-
|
|
234
|
-
- **Background:** ${s.styles.backgroundHex ?? "—"}
|
|
235
|
-
- **Foreground:** ${s.styles.foregroundHex ?? "—"}
|
|
236
|
-
- **Padding block:** top ${s.styles.paddingTopPx ?? "—"}px / bottom ${s.styles.paddingBottomPx ?? "—"}px
|
|
237
|
-
|
|
238
|
-
### Site tokens (page-level)
|
|
239
|
-
|
|
240
|
-
- **Body font:** ${layout.tokens.bodyFontFamily}
|
|
241
|
-
- **Heading font:** ${layout.tokens.headingFontFamily}
|
|
242
|
-
- **Background / foreground:** ${layout.tokens.backgroundHex} / ${layout.tokens.foregroundHex}
|
|
243
|
-
- **Primary:** ${layout.tokens.primaryHex}
|
|
244
|
-
- **Radius:** ${layout.tokens.radiusPx}px
|
|
245
|
-
|
|
246
|
-
## Slots (mirror placeholders)
|
|
247
|
-
|
|
248
|
-
${slotsMd || "_None_"}
|
|
249
|
-
|
|
250
|
-
Fill each \`<TextSlot>\` / \`<MediaSlot>\` using **verbatim** strings from \`reference/${host}/visible-text.txt\` only when you own rights or are authorized; otherwise substitute original copy aligned with the operator SaaS idea.
|
|
251
|
-
|
|
252
|
-
## States & behaviors
|
|
253
|
-
|
|
254
|
-
_N/A for automated single-state crawl — add rows after MCP extraction._
|
|
255
|
-
|
|
256
|
-
## Assets
|
|
257
|
-
|
|
258
|
-
- Crawl index: \`reference/${host}/media.json\` (respect licensing).
|
|
259
|
-
- When downloads ran (no \`--no-download\`): \`downloaded_assets/${hostSlug(host)}/manifest.json\` maps remote URLs to \`files/*\`.
|
|
260
|
-
|
|
261
|
-
## Responsive behavior
|
|
262
|
-
|
|
263
|
-
Automated PNGs live under \`docs/design-references/${hostSlug(host)}/\` (tablet 768×900, mobile 390×844, scroll sweep). Refine breakpoints using those artifacts alongside the mirror scaffold.
|
|
264
|
-
|
|
265
|
-
## Text content
|
|
266
|
-
|
|
267
|
-
See \`reference/${host}/visible-text.json\` / \`.txt\`. Map strings into slots by section order and landmark roles.
|
|
268
|
-
|
|
269
|
-
`;
|
|
270
|
-
}
|