launchframe 0.4.12 → 0.4.14

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.
@@ -7,6 +7,8 @@
7
7
 
8
8
  Each executor appends results to **`docs/research/LAUNCHFRAME_VERIFICATION.md`** using the **output contract** at the bottom.
9
9
 
10
+ These prompts check **visual & motion equivalence within measured tolerances** plus **clean human-editable React source** — they do **NOT** check class-string equality or byte-equal DOM identity against the reference's compiled bundle. The captures under `docs/research/<host>/page-inspection/` are reference material, not the implementation.
11
+
10
12
  ---
11
13
 
12
14
  ## Prompt — Pass 1 — Raster media & icons
@@ -17,32 +19,47 @@ You are a **readonly verification subagent** for a Next.js clone. Repository roo
17
19
 
18
20
  1. **Narrative slots:** Inventory every reference marketing/lifestyle/card/hero **image role** described in `docs/research/` specs vs **committed files** under `public/` (e.g. `public/images/...`) actually referenced from `src/`. **FAIL** if the reference showed a photo/panel thumbnail and this clone relies on placeholders, empty `src`, or bare gradients only.
19
21
  2. List every **raster** and **video poster** and every **authored SVG / component used as an icon** referenced from `src/` (`app/`, `components/`). Confirm files exist and paths resolve.
20
- 3. Compare presentation to specs: `picture` / `source` behavior, `sizes` / responsive behavior, `object-fit` / `object-position`, dimensions / aspect-ratio, parent overflow and radius, `background-image` and pseudo-elements.
21
- 4. For SVGs/icons: `viewBox`, strokes, fills, `currentColor`, sprite usage must match specs flag opportunistic Lucide substitutions unless the spec explicitly allowed them. Flag wrong crops, missing layers, or lazy `next/image` `fill` misuse.
22
+ 3. Compare presentation to specs: `picture` / `source` behavior **or** documented `next/image` `sizes` / `srcSet` mapping, `object-fit` / `object-position`, dimensions / aspect-ratio, parent overflow and radius, `background-image` and pseudo-elements.
23
+ 4. For SVGs/icons: `viewBox`, strokes, fills, `currentColor`, sprite usage must match specs and live under **semantic component names** in `src/components/icons.tsx` (`SearchIcon`, `ChevronDownIcon`, `WordmarkLogo`, …). Flag opportunistic Lucide substitutions unless the spec explicitly allowed them. Flag wrong crops, missing layers, or lazy `next/image` `fill` misuse.
22
24
 
23
25
  Return the **output contract** below.
24
26
 
25
27
  ---
26
28
 
27
- ## Prompt — Pass 2 — HTML / DOM structure
29
+ ## Prompt — Pass 2 — Semantic structure & code clarity
28
30
 
29
31
  You are a **readonly verification subagent** for a Next.js clone.
30
32
 
31
33
  **Rubric — execute fully:**
32
34
 
33
- Diff **PAGE_TOPOLOGY.md** (or equivalent topology in `docs/research/`) plus component specs against the React tree: **section order**, **wrapper count**, **sibling order**, scroll/sticky containers. Any flattened structure that changes stacking or scroll is **FAIL** until fixed.
35
+ 1. Walk `docs/research/PAGE_TOPOLOGY.md` (or equivalent topology in `docs/research/`) plus component specs and confirm the React tree under `src/app/` + `src/components/` has the **same visible section order**, **scroll/sticky containers**, and **z-index stacking**. Wrapper-count drift is acceptable as long as **computed layout, scroll, and stacking** match at 1440 / 768 / 390 flattening redundant `<div>`s for code clarity is good, not a regression.
36
+ 2. **Semantic naming check.** Every section component file under `src/components/` should be named for the role it plays (`SiteHeader.tsx`, `Hero.tsx`, `FeatureGrid.tsx`, `PricingTable.tsx`, `TestimonialsCarousel.tsx`, `SiteFooter.tsx`, …). **FAIL** on `Section1.tsx`, `Block2.tsx`, or transliterations of hashed CSS-module names.
37
+ 3. **Source cleanliness check.** Grep `src/` (`.tsx`, `.ts`, `.css`, `.module.css`) for any of the following — each hit is a **FAIL** until rewritten:
38
+ - Hashed class strings shaped like `module__[a-zA-Z0-9]{4,}`, `__[a-f0-9]{6,}`, `[a-z]+-module__[a-z0-9]+`, or imports of `*.module.css` files whose names look auto-generated rather than meaningful.
39
+ - Inline `style="..."` strings copied verbatim from `document.html` (especially with `--var:` custom properties carrying compiled-bundle values).
40
+ - Single `className` attributes longer than ~120 chars built from clearly minified bundle output rather than hand-authored Tailwind.
41
+ - Large blocks of pasted minified HTML/JSX that no human would author by hand.
42
+ 4. **Semantic HTML.** Confirm `<header>`, `<nav>`, `<main>`, `<section>`, `<footer>`, `<button>`, `<a>` are used where appropriate. Excessive `<div className="…">` soup that could be a landmark element is a soft FAIL with a fix suggestion.
34
43
 
35
44
  Return the **output contract** below.
36
45
 
37
46
  ---
38
47
 
39
- ## Prompt — Pass 3 — CSS parity
48
+ ## Prompt — Pass 3 — Visual & CSS parity (within tolerance)
40
49
 
41
50
  You are a **readonly verification subagent** for a Next.js clone.
42
51
 
43
52
  **Rubric — execute fully:**
44
53
 
45
- Spot-check **hero, nav, first fold, footer** (and any section flagged risky in specs) against component CSS: tokens in `src/app/globals.css`, arbitrary Tailwind vs measured px from specs, **`@keyframes`** presence where required. Run **`npm run lint`** and **`npm run typecheck`**; failures = **FAIL** until green.
54
+ 1. **Side-by-side screenshots.** Compare clone vs reference at **1440px**, **768px**, and **390px** (use Chrome MCP / Playwright if available, or describe the diff procedure if not). Document any visible mismatch.
55
+ 2. **Tolerances:** layout dimensions within **±2px**, colors and typography exact, spacing rhythm consistent. Larger gaps are bugs.
56
+ 3. **Token & utility audit.** Spot-check **hero, nav, first fold, footer** (and any spec-flagged risky section) against component CSS:
57
+ - Tokens defined in `src/app/globals.css` (`:root`, `.dark`) match the reference palette and typography scale.
58
+ - Tailwind utilities (including arbitrary values like `max-w-[872px]`, `gap-[18px]`) compile to the measured px from specs.
59
+ - `@keyframes` blocks the reference uses are present in `globals.css` (or a hand-authored module) — not hand-waved.
60
+ 4. **Lint + typecheck.** Run `npm run lint` and `npm run typecheck`; failures = **FAIL** until green.
61
+
62
+ Do **NOT** check for class-string equality with the reference's compiled CSS, hashed module names, or DOM-byte identity. Tailwind utility classes that produce equivalent styles to the reference are correct.
46
63
 
47
64
  Return the **output contract** below.
48
65
 
@@ -54,7 +71,14 @@ You are a **readonly verification subagent** for a Next.js clone.
54
71
 
55
72
  **Rubric — execute fully:**
56
73
 
57
- Re-walk **`docs/research/BEHAVIORS.md`** and the motion audit JSON under `## Motion audit (Chrome MCP)`: headers, carousels, scroll-driven UI, smooth-scroll libraries. Phase 5 motion QA must be **confirmed** against the reference behavior described in research — not assumed.
74
+ Re-walk **`docs/research/BEHAVIORS.md`** and the motion audit JSON under `## Motion audit (Chrome MCP)`: headers, carousels, scroll-driven UI, smooth-scroll libraries, hover transitions.
75
+
76
+ 1. Durations within **±20ms** of the reference; easing curves equivalent (cubic-bezier values preserved or mathematically equivalent).
77
+ 2. `@keyframes` blocks ported into `globals.css` (or a documented module) and referenced from Tailwind `animate-*` utilities or CSS class hooks — not invented from adjectives.
78
+ 3. Scroll/intersection thresholds preserved (e.g. nav floats at the same scroll position; carousel ticks on the same interval).
79
+ 4. Reduced-motion handling matches the reference (if reference disables motion under `prefers-reduced-motion: reduce`, clone must too).
80
+
81
+ Phase 5 motion QA must be **confirmed** against the reference behavior described in research — not assumed.
58
82
 
59
83
  Return the **output contract** below.
60
84
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "launchframe",
3
- "version": "0.4.12",
3
+ "version": "0.4.14",
4
4
  "private": false,
5
5
  "description": "Scaffold a Next.js app from a reference URL plus your SaaS idea — AI-ready website cloning",
6
6
  "author": "JCodesMore",
@@ -73,7 +73,8 @@
73
73
  "lint": "eslint",
74
74
  "typecheck": "tsc --noEmit",
75
75
  "check": "npm run lint && npm run typecheck && npm run build",
76
- "inspect:page": "node scripts/page-inspection-dump.mjs"
76
+ "inspect:page": "node scripts/page-inspection-dump.mjs",
77
+ "mirror:snapshot-assets": "node scripts/mirror-snapshot-assets.mjs"
77
78
  },
78
79
  "dependencies": {
79
80
  "@base-ui/react": "^1.3.0",
@@ -0,0 +1,257 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Mirror root-relative assets referenced by a captured document.html into `public/`.
4
+ *
5
+ * Why this exists:
6
+ * /launchframe and /snapshot use `npm run inspect:page` to capture a frozen copy
7
+ * of a live URL (document.html, body-outer.html, network *.css, motion-summary.json,
8
+ * capture-meta.json). When the captured HTML/CSS references **root-relative** paths
9
+ * like /_next/static/..., /assets/..., or /static/..., those paths resolve against
10
+ * localhost:3000 in dev — not the original origin — so the iframe (or a stand-in
11
+ * reference page) breaks unless we sideload those bytes locally.
12
+ *
13
+ * This script reads the **latest** capture under
14
+ * docs/research/<host>/page-inspection/<stamp>/ (preferred), or
15
+ * docs/research/page-captures/<host>-<stamp>/ (default fallback)
16
+ * for the host of LAUNCHFRAME_SOURCE_URL, scans the document.html + every CSS
17
+ * file for root-relative URLs, and downloads each one to `public/<same-path>`.
18
+ *
19
+ * What it is NOT:
20
+ * - It is NOT the implementation. Mirrored bundles are runtime fixtures so offline
21
+ * preview of `document.html` (or generated screenshots) match the live site.
22
+ * The hand-authored components under `src/components/**` are the deliverable.
23
+ * - It does not rewrite document.html or any source file.
24
+ * - It does not handle absolute https:// CDN URLs (the browser still fetches those
25
+ * normally; CDNs typically allow cross-origin loads). Use the existing inspection
26
+ * CSS files for offline CSS replay if you need it.
27
+ *
28
+ * Usage:
29
+ * npm run mirror:snapshot-assets
30
+ * node scripts/mirror-snapshot-assets.mjs --base https://www.example.com
31
+ * node scripts/mirror-snapshot-assets.mjs --capture docs/research/www.example.com/page-inspection/www.example.com-2026-05-15T21-49-07-887Z
32
+ * node scripts/mirror-snapshot-assets.mjs --paths /_next /assets /static /fonts
33
+ *
34
+ * Options:
35
+ * --base <url> Origin to fetch from. Defaults to LAUNCHFRAME_SOURCE_URL
36
+ * in src/lib/launchframe-config.ts.
37
+ * --capture <dir> Specific inspection capture folder. Defaults to the most
38
+ * recent capture under docs/research/<host>/page-inspection/
39
+ * (or docs/research/page-captures/<host>-*).
40
+ * --paths <list> Space- or comma-separated allowlist of root prefixes to
41
+ * mirror. Defaults to: /_next /assets /static /fonts /images.
42
+ * --concurrency <n> Parallel downloads (default 6).
43
+ * --dry-run Print the URLs that would be downloaded without writing.
44
+ */
45
+
46
+ import { mkdir, readFile, readdir, stat, writeFile } from "node:fs/promises";
47
+ import { existsSync } from "node:fs";
48
+ import { dirname, join, resolve } from "node:path";
49
+ import { fileURLToPath } from "node:url";
50
+
51
+ const __dirname = dirname(fileURLToPath(import.meta.url));
52
+ const REPO_ROOT = resolve(__dirname, "..");
53
+
54
+ function parseArgs(argv) {
55
+ const flags = new Set();
56
+ /** @type {Record<string, string>} */
57
+ const opts = {};
58
+ for (let i = 2; i < argv.length; i++) {
59
+ const a = argv[i];
60
+ if (!a.startsWith("--")) continue;
61
+ const key = a.slice(2);
62
+ if (key === "dry-run") {
63
+ flags.add(key);
64
+ continue;
65
+ }
66
+ const next = argv[i + 1];
67
+ if (!next || next.startsWith("--")) {
68
+ console.error(`Missing value for --${key}`);
69
+ process.exit(1);
70
+ }
71
+ opts[key] = next;
72
+ i++;
73
+ }
74
+ return { flags, opts };
75
+ }
76
+
77
+ async function readSourceUrlFromConfig() {
78
+ const configPath = join(REPO_ROOT, "src/lib/launchframe-config.ts");
79
+ if (!existsSync(configPath)) return null;
80
+ const text = await readFile(configPath, "utf8");
81
+ const m = text.match(/LAUNCHFRAME_SOURCE_URL\s*=\s*["'`]([^"'`]+)["'`]/);
82
+ return m ? m[1] : null;
83
+ }
84
+
85
+ async function findLatestCapture(host) {
86
+ const candidates = [];
87
+ const a = join(REPO_ROOT, "docs/research", host, "page-inspection");
88
+ const b = join(REPO_ROOT, "docs/research/page-captures");
89
+ if (existsSync(a)) {
90
+ for (const name of await readdir(a)) {
91
+ const full = join(a, name);
92
+ const s = await stat(full);
93
+ if (s.isDirectory()) candidates.push({ full, mtimeMs: s.mtimeMs });
94
+ }
95
+ }
96
+ if (existsSync(b)) {
97
+ for (const name of await readdir(b)) {
98
+ if (!name.startsWith(`${host}-`)) continue;
99
+ const full = join(b, name);
100
+ const s = await stat(full);
101
+ if (s.isDirectory()) candidates.push({ full, mtimeMs: s.mtimeMs });
102
+ }
103
+ }
104
+ candidates.sort((x, y) => y.mtimeMs - x.mtimeMs);
105
+ return candidates[0]?.full ?? null;
106
+ }
107
+
108
+ function collectRootRelativePaths(text, allowedPrefixes) {
109
+ /** @type {Set<string>} */
110
+ const found = new Set();
111
+ const re = /(?:["'(])(\/[A-Za-z0-9_./~?@\-+%=#]+)/g;
112
+ let m;
113
+ while ((m = re.exec(text)) !== null) {
114
+ const path = m[1].split("?")[0].split("#")[0];
115
+ if (path.startsWith("//")) continue;
116
+ if (!allowedPrefixes.some((p) => path.startsWith(p + "/") || path === p)) continue;
117
+ found.add(path);
118
+ }
119
+ return [...found];
120
+ }
121
+
122
+ async function downloadOne(baseUrl, path, dryRun) {
123
+ const url = new URL(path, baseUrl).toString();
124
+ const target = join(REPO_ROOT, "public", path.replace(/^\//, ""));
125
+ if (existsSync(target)) {
126
+ return { url, target, status: "skipped (exists)" };
127
+ }
128
+ if (dryRun) {
129
+ return { url, target, status: "would download" };
130
+ }
131
+ const res = await fetch(url, {
132
+ headers: {
133
+ "user-agent":
134
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 LaunchFrameMirror/1",
135
+ },
136
+ redirect: "follow",
137
+ });
138
+ if (!res.ok) {
139
+ return { url, target, status: `fail ${res.status}` };
140
+ }
141
+ const buf = Buffer.from(await res.arrayBuffer());
142
+ await mkdir(dirname(target), { recursive: true });
143
+ await writeFile(target, buf);
144
+ return { url, target, status: `ok ${buf.byteLength}b` };
145
+ }
146
+
147
+ async function runWithConcurrency(items, n, fn) {
148
+ const results = [];
149
+ let i = 0;
150
+ const workers = Array.from({ length: Math.max(1, n) }, async () => {
151
+ while (i < items.length) {
152
+ const idx = i++;
153
+ try {
154
+ results[idx] = await fn(items[idx]);
155
+ } catch (e) {
156
+ results[idx] = { error: String(e), item: items[idx] };
157
+ }
158
+ }
159
+ });
160
+ await Promise.all(workers);
161
+ return results;
162
+ }
163
+
164
+ async function main() {
165
+ const { flags, opts } = parseArgs(process.argv);
166
+
167
+ let baseUrlRaw = opts.base ?? (await readSourceUrlFromConfig());
168
+ if (!baseUrlRaw) {
169
+ console.error(
170
+ "Could not determine base URL. Pass --base <url> or set LAUNCHFRAME_SOURCE_URL in src/lib/launchframe-config.ts.",
171
+ );
172
+ process.exit(1);
173
+ }
174
+ let baseUrl;
175
+ try {
176
+ baseUrl = new URL(baseUrlRaw);
177
+ } catch {
178
+ console.error(`Invalid --base URL: ${baseUrlRaw}`);
179
+ process.exit(1);
180
+ }
181
+
182
+ let captureDir = opts.capture
183
+ ? resolve(process.cwd(), opts.capture)
184
+ : await findLatestCapture(baseUrl.hostname);
185
+ if (!captureDir || !existsSync(captureDir)) {
186
+ console.error(
187
+ `No inspection capture found. Run \`npm run inspect:page -- "${baseUrl.toString()}" --scroll-full --out-parent "docs/research/${baseUrl.hostname}/page-inspection"\` first, or pass --capture <dir>.`,
188
+ );
189
+ process.exit(1);
190
+ }
191
+
192
+ const allowed = (opts.paths ?? "/_next /assets /static /fonts /images")
193
+ .split(/[\s,]+/)
194
+ .filter(Boolean)
195
+ .map((p) => (p.startsWith("/") ? p : `/${p}`));
196
+
197
+ const concurrency = Number(opts.concurrency ?? "6");
198
+
199
+ const documentHtmlPath = join(captureDir, "document.html");
200
+ const cssFiles = (await readdir(captureDir)).filter((n) => n.endsWith(".css"));
201
+
202
+ const aggregated = [];
203
+ if (existsSync(documentHtmlPath)) {
204
+ aggregated.push(await readFile(documentHtmlPath, "utf8"));
205
+ }
206
+ for (const f of cssFiles) {
207
+ aggregated.push(await readFile(join(captureDir, f), "utf8"));
208
+ }
209
+ const inlineStylesPath = join(captureDir, "inline-styles.json");
210
+ if (existsSync(inlineStylesPath)) {
211
+ aggregated.push(await readFile(inlineStylesPath, "utf8"));
212
+ }
213
+
214
+ const paths = collectRootRelativePaths(aggregated.join("\n"), allowed);
215
+ if (paths.length === 0) {
216
+ console.log(
217
+ `No root-relative paths under ${allowed.join(", ")} were found in ${captureDir}. Nothing to mirror.`,
218
+ );
219
+ return;
220
+ }
221
+
222
+ console.log(
223
+ `Mirroring ${paths.length} root-relative assets from ${baseUrl.origin} to public/ ...`,
224
+ );
225
+ if (flags.has("dry-run")) {
226
+ console.log("(dry-run mode — no files will be written)");
227
+ }
228
+
229
+ const results = await runWithConcurrency(paths, concurrency, (p) =>
230
+ downloadOne(baseUrl.origin, p, flags.has("dry-run")),
231
+ );
232
+
233
+ let ok = 0;
234
+ let skipped = 0;
235
+ let failed = 0;
236
+ for (const r of results) {
237
+ if (!r) continue;
238
+ if ("error" in r) {
239
+ failed++;
240
+ console.warn(` ! ${r.item} → ${r.error}`);
241
+ continue;
242
+ }
243
+ if (r.status.startsWith("ok")) ok++;
244
+ else if (r.status.startsWith("skipped") || r.status === "would download") skipped++;
245
+ else failed++;
246
+ console.log(` ${r.status.padEnd(20)} ${r.url}`);
247
+ }
248
+
249
+ console.log(
250
+ `\nDone. ${ok} downloaded, ${skipped} skipped/dry, ${failed} failed.\nMirrored bundles are runtime fixtures, not source. Edit components under src/.`,
251
+ );
252
+ }
253
+
254
+ main().catch((e) => {
255
+ console.error(e);
256
+ process.exit(1);
257
+ });
@@ -1,88 +1,88 @@
1
- #!/usr/bin/env bash
2
- #
3
- # sync-agent-rules.sh — Generate AI agent config files from AGENTS.md
4
- #
5
- # AGENTS.md is the single source of truth. This script creates copies
6
- # for agents that don't read AGENTS.md natively (Cline, Continue,
7
- # Amazon Q, GitHub Copilot Chat).
8
- #
9
- # Usage:
10
- # bash scripts/sync-agent-rules.sh
11
- #
12
- # Agents that DON'T need generated files (they read AGENTS.md natively):
13
- # Codex CLI, OpenCode, Cursor, Windsurf, Copilot Coding Agent,
14
- # Roo Code, Aider, Augment Code
15
- #
16
- # Agents with their own thin pointer files (created manually):
17
- # Claude Code → CLAUDE.md (@AGENTS.md import)
18
- # Gemini CLI → GEMINI.md (@AGENTS.md import)
19
- # Cursor → .cursor/rules/project.mdc (pointer)
20
- # Windsurf → .windsurfrules (pointer)
21
- # Aider → .aider.conf.yml (read: [AGENTS.md])
22
-
23
- set -euo pipefail
24
-
25
- REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
26
- SOURCE="$REPO_ROOT/AGENTS.md"
27
-
28
- if [[ ! -f "$SOURCE" ]]; then
29
- echo "Error: AGENTS.md not found at $SOURCE" >&2
30
- exit 1
31
- fi
32
-
33
- # Resolve @file imports (Claude Code syntax) into inline content.
34
- # Lines like "@docs/research/INSPECTION_GUIDE.md" become the file's contents.
35
- resolve_imports() {
36
- while IFS= read -r line || [[ -n "$line" ]]; do
37
- line="${line%$'\r'}"
38
- if [[ "$line" =~ ^@(.+)$ ]]; then
39
- local import_path="${BASH_REMATCH[1]}"
40
- local resolved="$REPO_ROOT/$import_path"
41
- if [[ -f "$resolved" ]]; then
42
- cat "$resolved"
43
- echo ""
44
- else
45
- echo "<!-- Import not found: $import_path -->"
46
- fi
47
- else
48
- echo "$line"
49
- fi
50
- done < "$SOURCE"
51
- }
52
-
53
- RESOLVED_CONTENT="$(resolve_imports)"
54
-
55
- HEADER="<!-- AUTO-GENERATED from AGENTS.md — do not edit directly.
56
- Run \`bash scripts/sync-agent-rules.sh\` to regenerate. -->"
57
-
58
- # Helper: write a generated file with header
59
- write_file() {
60
- local target="$1"
61
- local content="$2"
62
- mkdir -p "$(dirname "$target")"
63
- printf '%s\n\n%s\n' "$HEADER" "$content" > "$target"
64
- echo " ✓ $target"
65
- }
66
-
67
- echo "Syncing agent rules from AGENTS.md..."
68
-
69
- # GitHub Copilot Chat — .github/copilot-instructions.md
70
- write_file "$REPO_ROOT/.github/copilot-instructions.md" "$RESOLVED_CONTENT"
71
-
72
- # Cline / Roo Code — .clinerules
73
- write_file "$REPO_ROOT/.clinerules" "$RESOLVED_CONTENT"
74
-
75
- # Continue — .continue/rules/project.md
76
- CONTINUE_FRONTMATTER="---
77
- description: Project conventions for AI Website Clone Template
78
- alwaysApply: true
79
- ---"
80
- write_file "$REPO_ROOT/.continue/rules/project.md" "$CONTINUE_FRONTMATTER
81
- $RESOLVED_CONTENT"
82
-
83
- # Amazon Q Developer — .amazonq/rules/project.md
84
- write_file "$REPO_ROOT/.amazonq/rules/project.md" "$RESOLVED_CONTENT"
85
-
86
- echo ""
87
- echo "Done. Generated files are committed to the repo but sourced from AGENTS.md."
88
- echo "Edit AGENTS.md, then re-run this script to update all agent configs."
1
+ #!/usr/bin/env bash
2
+ #
3
+ # sync-agent-rules.sh — Generate AI agent config files from AGENTS.md
4
+ #
5
+ # AGENTS.md is the single source of truth. This script creates copies
6
+ # for agents that don't read AGENTS.md natively (Cline, Continue,
7
+ # Amazon Q, GitHub Copilot Chat).
8
+ #
9
+ # Usage:
10
+ # bash scripts/sync-agent-rules.sh
11
+ #
12
+ # Agents that DON'T need generated files (they read AGENTS.md natively):
13
+ # Codex CLI, OpenCode, Cursor, Windsurf, Copilot Coding Agent,
14
+ # Roo Code, Aider, Augment Code
15
+ #
16
+ # Agents with their own thin pointer files (created manually):
17
+ # Claude Code → CLAUDE.md (@AGENTS.md import)
18
+ # Gemini CLI → GEMINI.md (@AGENTS.md import)
19
+ # Cursor → .cursor/rules/project.mdc (pointer)
20
+ # Windsurf → .windsurfrules (pointer)
21
+ # Aider → .aider.conf.yml (read: [AGENTS.md])
22
+
23
+ set -euo pipefail
24
+
25
+ REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
26
+ SOURCE="$REPO_ROOT/AGENTS.md"
27
+
28
+ if [[ ! -f "$SOURCE" ]]; then
29
+ echo "Error: AGENTS.md not found at $SOURCE" >&2
30
+ exit 1
31
+ fi
32
+
33
+ # Resolve @file imports (Claude Code syntax) into inline content.
34
+ # Lines like "@docs/research/INSPECTION_GUIDE.md" become the file's contents.
35
+ resolve_imports() {
36
+ while IFS= read -r line || [[ -n "$line" ]]; do
37
+ line="${line%$'\r'}"
38
+ if [[ "$line" =~ ^@(.+)$ ]]; then
39
+ local import_path="${BASH_REMATCH[1]}"
40
+ local resolved="$REPO_ROOT/$import_path"
41
+ if [[ -f "$resolved" ]]; then
42
+ cat "$resolved"
43
+ echo ""
44
+ else
45
+ echo "<!-- Import not found: $import_path -->"
46
+ fi
47
+ else
48
+ echo "$line"
49
+ fi
50
+ done < "$SOURCE"
51
+ }
52
+
53
+ RESOLVED_CONTENT="$(resolve_imports)"
54
+
55
+ HEADER="<!-- AUTO-GENERATED from AGENTS.md — do not edit directly.
56
+ Run \`bash scripts/sync-agent-rules.sh\` to regenerate. -->"
57
+
58
+ # Helper: write a generated file with header
59
+ write_file() {
60
+ local target="$1"
61
+ local content="$2"
62
+ mkdir -p "$(dirname "$target")"
63
+ printf '%s\n\n%s\n' "$HEADER" "$content" > "$target"
64
+ echo " ✓ $target"
65
+ }
66
+
67
+ echo "Syncing agent rules from AGENTS.md..."
68
+
69
+ # GitHub Copilot Chat — .github/copilot-instructions.md
70
+ write_file "$REPO_ROOT/.github/copilot-instructions.md" "$RESOLVED_CONTENT"
71
+
72
+ # Cline / Roo Code — .clinerules
73
+ write_file "$REPO_ROOT/.clinerules" "$RESOLVED_CONTENT"
74
+
75
+ # Continue — .continue/rules/project.md
76
+ CONTINUE_FRONTMATTER="---
77
+ description: Project conventions for AI Website Clone Template
78
+ alwaysApply: true
79
+ ---"
80
+ write_file "$REPO_ROOT/.continue/rules/project.md" "$CONTINUE_FRONTMATTER
81
+ $RESOLVED_CONTENT"
82
+
83
+ # Amazon Q Developer — .amazonq/rules/project.md
84
+ write_file "$REPO_ROOT/.amazonq/rules/project.md" "$RESOLVED_CONTENT"
85
+
86
+ echo ""
87
+ echo "Done. Generated files are committed to the repo but sourced from AGENTS.md."
88
+ echo "Edit AGENTS.md, then re-run this script to update all agent configs."
package/src/app/page.tsx CHANGED
@@ -1,40 +1,9 @@
1
- import Link from "next/link";
2
-
3
- import {
4
- LAUNCHFRAME_SAAS_IDEA,
5
- LAUNCHFRAME_SOURCE_URL,
6
- } from "@/lib/launchframe-config";
7
-
8
1
  export default function Home() {
9
2
  return (
10
- <main className="flex min-h-screen flex-col items-center justify-center gap-8 px-6 py-16">
11
- <div className="mx-auto max-w-2xl space-y-6 text-center">
12
- <p className="text-xs font-semibold uppercase tracking-[0.2em] text-muted-foreground">
13
- SaaS idea
14
- </p>
15
- <h1 className="text-balance text-3xl font-semibold tracking-tight text-foreground sm:text-4xl whitespace-pre-wrap">
16
- {LAUNCHFRAME_SAAS_IDEA}
17
- </h1>
18
- <p className="text-pretty text-sm text-muted-foreground sm:text-base">
19
- Visual reference (clone target):{" "}
20
- <Link
21
- href={LAUNCHFRAME_SOURCE_URL}
22
- className="font-medium text-foreground underline underline-offset-4"
23
- target="_blank"
24
- rel="noreferrer noopener"
25
- >
26
- {LAUNCHFRAME_SOURCE_URL}
27
- </Link>
28
- </p>
29
- <p className="text-pretty text-sm text-muted-foreground">
30
- Run{" "}
31
- <code className="rounded-md bg-muted px-2 py-1 font-mono text-foreground">
32
- /launchframe {LAUNCHFRAME_SOURCE_URL} &quot;…your saas idea…&quot;
33
- </code>{" "}
34
- with your AI agent to rebuild this layout from the reference site while keeping the SaaS
35
- positioning above.
36
- </p>
37
- </div>
38
- </main>
3
+ <iframe
4
+ src="/captured/example-com-document.html"
5
+ title="Captured reference page"
6
+ className="fixed inset-0 h-dvh w-dvw border-0"
7
+ />
39
8
  );
40
9
  }