cinematic-web 0.1.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 +21 -0
- package/assets/CLAUDE.md +150 -0
- package/assets/presets/3d-immersive/README.md +30 -0
- package/assets/presets/3d-immersive/preset.json +45 -0
- package/assets/presets/antigravity-lift/README.md +24 -0
- package/assets/presets/antigravity-lift/preset.json +38 -0
- package/assets/presets/brutalist-signal/README.md +24 -0
- package/assets/presets/brutalist-signal/preset.json +36 -0
- package/assets/presets/midnight-luxe/README.md +24 -0
- package/assets/presets/midnight-luxe/preset.json +36 -0
- package/assets/presets/organic-tech/README.md +24 -0
- package/assets/presets/organic-tech/preset.json +37 -0
- package/assets/presets/vapor-clinic/README.md +24 -0
- package/assets/presets/vapor-clinic/preset.json +36 -0
- package/assets/prompts/product-development/Guided-MVP-Concept.md +67 -0
- package/assets/prompts/product-development/Guided-MVP.md +65 -0
- package/assets/prompts/product-development/Guided-PRD-Creation.md +51 -0
- package/assets/prompts/product-development/Guided-Test-Plan.md +57 -0
- package/assets/prompts/product-development/Guided-UX-User-Flow.md +93 -0
- package/assets/prompts/product-development/README.md +21 -0
- package/assets/prompts/product-development/v0-design-prompt.md +107 -0
- package/assets/templates/base-react/index.html +18 -0
- package/assets/templates/base-react/package.json +26 -0
- package/assets/templates/base-react/postcss.config.js +6 -0
- package/assets/templates/base-react/src/App.jsx +33 -0
- package/assets/templates/base-react/src/index.css +90 -0
- package/assets/templates/base-react/src/main.jsx +10 -0
- package/assets/templates/base-react/src/sections/Features.jsx +238 -0
- package/assets/templates/base-react/src/sections/Footer.jsx +120 -0
- package/assets/templates/base-react/src/sections/Hero.jsx +96 -0
- package/assets/templates/base-react/src/sections/Navbar.jsx +119 -0
- package/assets/templates/base-react/src/sections/Philosophy.jsx +67 -0
- package/assets/templates/base-react/src/sections/Pricing.jsx +135 -0
- package/assets/templates/base-react/src/sections/Protocol.jsx +123 -0
- package/assets/templates/base-react/tailwind.config.js +26 -0
- package/assets/templates/base-react/vite.config.js +6 -0
- package/assets/templates/three-fiber/eslint.config.js +21 -0
- package/assets/templates/three-fiber/index.html +16 -0
- package/assets/templates/three-fiber/package.json +36 -0
- package/assets/templates/three-fiber/postcss.config.js +6 -0
- package/assets/templates/three-fiber/src/App.jsx +61 -0
- package/assets/templates/three-fiber/src/components/CameraRig.jsx +42 -0
- package/assets/templates/three-fiber/src/components/NetworkGraph.jsx +120 -0
- package/assets/templates/three-fiber/src/components/ParticleSystem.jsx +77 -0
- package/assets/templates/three-fiber/src/components/Scene.jsx +39 -0
- package/assets/templates/three-fiber/src/components/SocialProofBillboards.jsx +56 -0
- package/assets/templates/three-fiber/src/context/SceneContext.jsx +21 -0
- package/assets/templates/three-fiber/src/index.css +37 -0
- package/assets/templates/three-fiber/src/main.jsx +21 -0
- package/assets/templates/three-fiber/src/sections/CTA.jsx +41 -0
- package/assets/templates/three-fiber/src/sections/Hero.jsx +66 -0
- package/assets/templates/three-fiber/src/sections/HowItWorks.jsx +55 -0
- package/assets/templates/three-fiber/src/sections/Navbar.jsx +40 -0
- package/assets/templates/three-fiber/src/sections/SocialProof.jsx +50 -0
- package/assets/templates/three-fiber/src/sections/TheOldWay.jsx +28 -0
- package/assets/templates/three-fiber/src/sections/ValueProps.jsx +50 -0
- package/assets/templates/three-fiber/tailwind.config.js +21 -0
- package/assets/templates/three-fiber/vite.config.js +7 -0
- package/dist/cli.js +539 -0
- package/dist/cli.js.map +1 -0
- package/package.json +44 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,539 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli.ts
|
|
4
|
+
import { Command } from "commander";
|
|
5
|
+
|
|
6
|
+
// src/commands/create.ts
|
|
7
|
+
import { existsSync as existsSync2, mkdirSync } from "fs";
|
|
8
|
+
import { resolve as resolve3, join as join2 } from "path";
|
|
9
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
10
|
+
import * as p from "@clack/prompts";
|
|
11
|
+
import pc from "picocolors";
|
|
12
|
+
import { listPresets, getPreset } from "cinematic-core";
|
|
13
|
+
|
|
14
|
+
// src/spec.ts
|
|
15
|
+
import { readFileSync, writeFileSync, existsSync } from "fs";
|
|
16
|
+
import { resolve } from "path";
|
|
17
|
+
import { ProjectSpecSchema } from "cinematic-core";
|
|
18
|
+
var SPEC_FILENAME = "cinematic.json";
|
|
19
|
+
function loadSpec(dir = process.cwd()) {
|
|
20
|
+
const path = resolve(dir, SPEC_FILENAME);
|
|
21
|
+
if (!existsSync(path)) return null;
|
|
22
|
+
try {
|
|
23
|
+
const raw = readFileSync(path, "utf-8");
|
|
24
|
+
const parsed = ProjectSpecSchema.safeParse(JSON.parse(raw));
|
|
25
|
+
return parsed.success ? parsed.data : null;
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
function saveSpec(spec, dir = process.cwd()) {
|
|
31
|
+
const path = resolve(dir, SPEC_FILENAME);
|
|
32
|
+
writeFileSync(path, JSON.stringify(spec, null, 2) + "\n", "utf-8");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// src/scaffold.ts
|
|
36
|
+
import fsExtra from "fs-extra";
|
|
37
|
+
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2, readdirSync } from "fs";
|
|
38
|
+
import { join, resolve as resolve2, relative } from "path";
|
|
39
|
+
import { fileURLToPath } from "url";
|
|
40
|
+
|
|
41
|
+
// src/tokens.ts
|
|
42
|
+
import { tokenReplace } from "cinematic-core";
|
|
43
|
+
function buildTokenMap(spec, preset) {
|
|
44
|
+
const brandSlug = spec.brand.toLowerCase().replace(/[^a-z0-9]+/g, "-");
|
|
45
|
+
return {
|
|
46
|
+
brand: spec.brand,
|
|
47
|
+
"brand-slug": brandSlug,
|
|
48
|
+
purpose: spec.purpose,
|
|
49
|
+
cta: spec.cta,
|
|
50
|
+
notes: spec.notes,
|
|
51
|
+
presetId: preset.id,
|
|
52
|
+
// Hero lines — filled from preset pattern with brand content
|
|
53
|
+
heroLine1: spec.purpose.split(" ").slice(0, 4).join(" "),
|
|
54
|
+
heroLine2: spec.brand,
|
|
55
|
+
// Value props
|
|
56
|
+
valueProp0: spec.valueProps[0],
|
|
57
|
+
valueProp1: spec.valueProps[1],
|
|
58
|
+
valueProp2: spec.valueProps[2],
|
|
59
|
+
// Manifesto — generated from purpose
|
|
60
|
+
manifesto: `${spec.purpose} We believe the future belongs to those who move with precision, clarity, and intention.`,
|
|
61
|
+
// Protocol steps — generated from value props
|
|
62
|
+
protocolStep0Title: `Understand ${spec.valueProps[0]}`,
|
|
63
|
+
protocolStep0Desc: `We begin with a deep analysis of your current state \u2014 mapping every signal, every gap, every opportunity hidden in your data.`,
|
|
64
|
+
protocolStep1Title: `Activate ${spec.valueProps[1]}`,
|
|
65
|
+
protocolStep1Desc: `Our system processes your inputs and delivers a tailored protocol built specifically for your context and objectives.`,
|
|
66
|
+
protocolStep2Title: `Deliver ${spec.valueProps[2]}`,
|
|
67
|
+
protocolStep2Desc: `Results are delivered in real time with full transparency \u2014 measurable, verifiable, and ready to act on.`,
|
|
68
|
+
// Per-preset curated Unsplash photo ID (falls back to organic-tech default)
|
|
69
|
+
unsplashPhotoId: preset.unsplashPhotoId ?? "1518770660439-4636190af475",
|
|
70
|
+
// Preset tokens (flat)
|
|
71
|
+
"preset.name": preset.name,
|
|
72
|
+
"preset.id": preset.id,
|
|
73
|
+
googleFonts: preset.googleFonts,
|
|
74
|
+
// Palette
|
|
75
|
+
"palette.primary": preset.palette.primary,
|
|
76
|
+
"palette.accent": preset.palette.accent,
|
|
77
|
+
"palette.background": preset.palette.background,
|
|
78
|
+
"palette.dark": preset.palette.dark,
|
|
79
|
+
// Typography
|
|
80
|
+
"typography.heading0": preset.typography.heading[0] ?? "",
|
|
81
|
+
"typography.heading1": preset.typography.heading[1] ?? "",
|
|
82
|
+
"typography.drama": preset.typography.drama ?? "",
|
|
83
|
+
"typography.data": preset.typography.data ?? ""
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
function applyTokens(content, spec, preset) {
|
|
87
|
+
return tokenReplace(content, buildTokenMap(spec, preset));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// src/scaffold.ts
|
|
91
|
+
var { copySync } = fsExtra;
|
|
92
|
+
var ASSETS_DIR = resolve2(fileURLToPath(import.meta.url), "..", "..", "assets");
|
|
93
|
+
var TEXT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
94
|
+
".html",
|
|
95
|
+
".css",
|
|
96
|
+
".js",
|
|
97
|
+
".jsx",
|
|
98
|
+
".ts",
|
|
99
|
+
".tsx",
|
|
100
|
+
".json",
|
|
101
|
+
".md",
|
|
102
|
+
".txt",
|
|
103
|
+
".env.example"
|
|
104
|
+
]);
|
|
105
|
+
async function scaffoldProject(outputDir, spec, preset) {
|
|
106
|
+
const templateName = preset.template === "three-fiber" ? "three-fiber" : "base-react";
|
|
107
|
+
const templateDir = join(ASSETS_DIR, "templates", templateName);
|
|
108
|
+
copySync(templateDir, outputDir, {
|
|
109
|
+
overwrite: false,
|
|
110
|
+
filter: (src) => {
|
|
111
|
+
const rel = relative(templateDir, src);
|
|
112
|
+
return !rel.startsWith("node_modules") && !rel.startsWith("dist");
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
replaceTokensInDir(outputDir, spec, preset);
|
|
116
|
+
}
|
|
117
|
+
function replaceTokensInDir(dir, spec, preset) {
|
|
118
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
119
|
+
for (const entry of entries) {
|
|
120
|
+
const fullPath = join(dir, entry.name);
|
|
121
|
+
if (entry.isDirectory()) {
|
|
122
|
+
replaceTokensInDir(fullPath, spec, preset);
|
|
123
|
+
} else if (entry.isFile()) {
|
|
124
|
+
const ext = "." + (entry.name.split(".").pop() ?? "");
|
|
125
|
+
if (!TEXT_EXTENSIONS.has(ext)) continue;
|
|
126
|
+
try {
|
|
127
|
+
const original = readFileSync2(fullPath, "utf-8");
|
|
128
|
+
const replaced = applyTokens(original, spec, preset);
|
|
129
|
+
if (replaced !== original) {
|
|
130
|
+
writeFileSync2(fullPath, replaced, "utf-8");
|
|
131
|
+
}
|
|
132
|
+
} catch {
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/commands/create.ts
|
|
139
|
+
var ASSETS_DIR2 = resolve3(fileURLToPath2(import.meta.url), "..", "..", "assets");
|
|
140
|
+
var PRESETS_DIR = join2(ASSETS_DIR2, "presets");
|
|
141
|
+
async function runCreate(name) {
|
|
142
|
+
p.intro(`${pc.bgCyan(pc.black(" cinematic-web "))} ${pc.dim("create")}`);
|
|
143
|
+
let projectName = name;
|
|
144
|
+
if (!projectName) {
|
|
145
|
+
const input = await p.text({
|
|
146
|
+
message: "Project name",
|
|
147
|
+
placeholder: "my-landing-page",
|
|
148
|
+
validate: (v) => v.trim().length === 0 ? "Project name is required" : void 0
|
|
149
|
+
});
|
|
150
|
+
if (p.isCancel(input)) {
|
|
151
|
+
p.cancel("Cancelled.");
|
|
152
|
+
process.exit(0);
|
|
153
|
+
}
|
|
154
|
+
projectName = input.trim();
|
|
155
|
+
}
|
|
156
|
+
const outputDir = resolve3(process.cwd(), projectName);
|
|
157
|
+
if (existsSync2(outputDir)) {
|
|
158
|
+
p.cancel(`Directory ${pc.cyan(projectName)} already exists.`);
|
|
159
|
+
process.exit(1);
|
|
160
|
+
}
|
|
161
|
+
const existingSpec = loadSpec(process.cwd());
|
|
162
|
+
const presets = listPresets(PRESETS_DIR);
|
|
163
|
+
if (presets.length === 0) {
|
|
164
|
+
p.cancel("No presets found. Make sure you're running from the cinematic-web repo root or install the package.");
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
const brand = await p.text({
|
|
168
|
+
message: "Brand name and one-line purpose?",
|
|
169
|
+
placeholder: "Nura Health \u2014 precision longevity medicine",
|
|
170
|
+
initialValue: existingSpec ? `${existingSpec.brand} \u2014 ${existingSpec.purpose}` : "",
|
|
171
|
+
validate: (v) => v.trim().length === 0 ? "Required" : void 0
|
|
172
|
+
});
|
|
173
|
+
if (p.isCancel(brand)) {
|
|
174
|
+
p.cancel("Cancelled.");
|
|
175
|
+
process.exit(0);
|
|
176
|
+
}
|
|
177
|
+
const brandStr = brand;
|
|
178
|
+
const dashIdx = brandStr.indexOf(" \u2014 ");
|
|
179
|
+
const parsedBrand = dashIdx > -1 ? brandStr.slice(0, dashIdx).trim() : brandStr.trim();
|
|
180
|
+
const parsedPurpose = dashIdx > -1 ? brandStr.slice(dashIdx + 3).trim() : "";
|
|
181
|
+
let purpose = parsedPurpose;
|
|
182
|
+
if (!purpose) {
|
|
183
|
+
const purposeInput = await p.text({
|
|
184
|
+
message: "One-line purpose?",
|
|
185
|
+
placeholder: "Precision longevity medicine powered by biological data.",
|
|
186
|
+
validate: (v) => v.trim().length === 0 ? "Required" : void 0
|
|
187
|
+
});
|
|
188
|
+
if (p.isCancel(purposeInput)) {
|
|
189
|
+
p.cancel("Cancelled.");
|
|
190
|
+
process.exit(0);
|
|
191
|
+
}
|
|
192
|
+
purpose = purposeInput.trim();
|
|
193
|
+
}
|
|
194
|
+
const presetChoice = await p.select({
|
|
195
|
+
message: "Pick an aesthetic direction",
|
|
196
|
+
options: presets.map((preset2) => ({
|
|
197
|
+
value: preset2.id,
|
|
198
|
+
label: `${preset2.name} \u2014 ${preset2.label}`,
|
|
199
|
+
hint: preset2.identity
|
|
200
|
+
})),
|
|
201
|
+
initialValue: existingSpec?.presetId ?? presets[0]?.id
|
|
202
|
+
});
|
|
203
|
+
if (p.isCancel(presetChoice)) {
|
|
204
|
+
p.cancel("Cancelled.");
|
|
205
|
+
process.exit(0);
|
|
206
|
+
}
|
|
207
|
+
const vp0 = await p.text({
|
|
208
|
+
message: "Value proposition 1 of 3",
|
|
209
|
+
placeholder: "Telemetry",
|
|
210
|
+
initialValue: existingSpec?.valueProps[0] ?? "",
|
|
211
|
+
validate: (v) => v.trim().length === 0 ? "Required" : void 0
|
|
212
|
+
});
|
|
213
|
+
if (p.isCancel(vp0)) {
|
|
214
|
+
p.cancel("Cancelled.");
|
|
215
|
+
process.exit(0);
|
|
216
|
+
}
|
|
217
|
+
const vp1 = await p.text({
|
|
218
|
+
message: "Value proposition 2 of 3",
|
|
219
|
+
placeholder: "Protocol",
|
|
220
|
+
initialValue: existingSpec?.valueProps[1] ?? "",
|
|
221
|
+
validate: (v) => v.trim().length === 0 ? "Required" : void 0
|
|
222
|
+
});
|
|
223
|
+
if (p.isCancel(vp1)) {
|
|
224
|
+
p.cancel("Cancelled.");
|
|
225
|
+
process.exit(0);
|
|
226
|
+
}
|
|
227
|
+
const vp2 = await p.text({
|
|
228
|
+
message: "Value proposition 3 of 3",
|
|
229
|
+
placeholder: "Outcomes",
|
|
230
|
+
initialValue: existingSpec?.valueProps[2] ?? "",
|
|
231
|
+
validate: (v) => v.trim().length === 0 ? "Required" : void 0
|
|
232
|
+
});
|
|
233
|
+
if (p.isCancel(vp2)) {
|
|
234
|
+
p.cancel("Cancelled.");
|
|
235
|
+
process.exit(0);
|
|
236
|
+
}
|
|
237
|
+
const cta = await p.text({
|
|
238
|
+
message: "Primary CTA text?",
|
|
239
|
+
placeholder: "Book a consultation",
|
|
240
|
+
initialValue: existingSpec?.cta ?? "",
|
|
241
|
+
validate: (v) => v.trim().length === 0 ? "Required" : void 0
|
|
242
|
+
});
|
|
243
|
+
if (p.isCancel(cta)) {
|
|
244
|
+
p.cancel("Cancelled.");
|
|
245
|
+
process.exit(0);
|
|
246
|
+
}
|
|
247
|
+
const notes = await p.text({
|
|
248
|
+
message: "Special requests or custom features? (optional)",
|
|
249
|
+
placeholder: "Add a 3D hero, custom animations...",
|
|
250
|
+
initialValue: existingSpec?.notes ?? ""
|
|
251
|
+
});
|
|
252
|
+
if (p.isCancel(notes)) {
|
|
253
|
+
p.cancel("Cancelled.");
|
|
254
|
+
process.exit(0);
|
|
255
|
+
}
|
|
256
|
+
const spec = {
|
|
257
|
+
brand: parsedBrand,
|
|
258
|
+
purpose,
|
|
259
|
+
presetId: presetChoice,
|
|
260
|
+
valueProps: [
|
|
261
|
+
vp0.trim(),
|
|
262
|
+
vp1.trim(),
|
|
263
|
+
vp2.trim()
|
|
264
|
+
],
|
|
265
|
+
cta: cta.trim(),
|
|
266
|
+
notes: (notes ?? "").trim()
|
|
267
|
+
};
|
|
268
|
+
const preset = getPreset(spec.presetId, PRESETS_DIR);
|
|
269
|
+
if (!preset) {
|
|
270
|
+
p.cancel(`Preset "${spec.presetId}" not found.`);
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
273
|
+
const spinner3 = p.spinner();
|
|
274
|
+
spinner3.start(`Scaffolding ${pc.cyan(parsedBrand)} with ${pc.cyan(preset.name)}...`);
|
|
275
|
+
mkdirSync(outputDir, { recursive: true });
|
|
276
|
+
try {
|
|
277
|
+
await scaffoldProject(outputDir, spec, preset);
|
|
278
|
+
saveSpec(spec, outputDir);
|
|
279
|
+
spinner3.stop(`${pc.green("\u2713")} Scaffolded ${pc.cyan(projectName)}`);
|
|
280
|
+
} catch (err) {
|
|
281
|
+
spinner3.stop(`${pc.red("\u2717")} Scaffold failed`);
|
|
282
|
+
throw err;
|
|
283
|
+
}
|
|
284
|
+
p.note(
|
|
285
|
+
[
|
|
286
|
+
`cd ${projectName}`,
|
|
287
|
+
"npm install",
|
|
288
|
+
"npm run dev"
|
|
289
|
+
].join("\n"),
|
|
290
|
+
"Next steps"
|
|
291
|
+
);
|
|
292
|
+
if (preset.id === "3d-immersive") {
|
|
293
|
+
p.note(
|
|
294
|
+
`You selected the 3D Immersive preset.
|
|
295
|
+
Full build instructions: ${pc.cyan("prompts/3d-immersive/3d-website-builder.md")}`,
|
|
296
|
+
"3D Notes"
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
p.outro(`${pc.green("Done!")} Your cinematic site is ready at ${pc.cyan(projectName)}/`);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// src/commands/think.ts
|
|
303
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
304
|
+
import { resolve as resolve4, join as join3 } from "path";
|
|
305
|
+
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
306
|
+
import * as p2 from "@clack/prompts";
|
|
307
|
+
import pc2 from "picocolors";
|
|
308
|
+
var ASSETS_DIR3 = resolve4(fileURLToPath3(import.meta.url), "..", "..", "assets");
|
|
309
|
+
var PROMPTS_DIR = join3(ASSETS_DIR3, "prompts", "product-development");
|
|
310
|
+
function fillTemplate(templatePath, session) {
|
|
311
|
+
if (!existsSync3(templatePath)) return "";
|
|
312
|
+
const raw = readFileSync3(templatePath, "utf-8");
|
|
313
|
+
const braindumpBlock = `
|
|
314
|
+
Brand: ${session.brand}
|
|
315
|
+
Purpose: ${session.purpose}
|
|
316
|
+
${session.valueProps ? `Value Propositions: ${session.valueProps.join(", ")}` : ""}
|
|
317
|
+
${session.cta ? `CTA: ${session.cta}` : ""}
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
${session.braindump}
|
|
322
|
+
`.trim();
|
|
323
|
+
return raw.replace(
|
|
324
|
+
/\[\s*\*\*<<< PASTE YOUR RAW NOTES.*?>>>\*\*\s*\]/s,
|
|
325
|
+
braindumpBlock
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
function stubDoc(title, session) {
|
|
329
|
+
return `# ${title}
|
|
330
|
+
|
|
331
|
+
**Brand:** ${session.brand}
|
|
332
|
+
**Purpose:** ${session.purpose}
|
|
333
|
+
|
|
334
|
+
> This document was generated by \`cinematic-web think\`.
|
|
335
|
+
> Paste this file's full contents into your LLM of choice to get started.
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
${session.braindump}
|
|
340
|
+
`;
|
|
341
|
+
}
|
|
342
|
+
async function runThink() {
|
|
343
|
+
p2.intro(`${pc2.bgCyan(pc2.black(" cinematic-web "))} ${pc2.dim("think")}`);
|
|
344
|
+
const cwd = process.cwd();
|
|
345
|
+
const existingSpec = loadSpec(cwd);
|
|
346
|
+
p2.note(
|
|
347
|
+
[
|
|
348
|
+
"The `think` command walks you through the product-development pipeline.",
|
|
349
|
+
"It fills your brain dump into 5 prompt templates and writes them to docs/.",
|
|
350
|
+
"Then paste each doc into Claude, GPT, or your LLM of choice to generate the artefacts."
|
|
351
|
+
].join("\n"),
|
|
352
|
+
"How this works"
|
|
353
|
+
);
|
|
354
|
+
const brandInput = await p2.text({
|
|
355
|
+
message: "Brand name and one-line purpose?",
|
|
356
|
+
placeholder: "Nura Health \u2014 precision longevity medicine",
|
|
357
|
+
initialValue: existingSpec ? `${existingSpec.brand} \u2014 ${existingSpec.purpose}` : "",
|
|
358
|
+
validate: (v) => v.trim().length === 0 ? "Required" : void 0
|
|
359
|
+
});
|
|
360
|
+
if (p2.isCancel(brandInput)) {
|
|
361
|
+
p2.cancel("Cancelled.");
|
|
362
|
+
process.exit(0);
|
|
363
|
+
}
|
|
364
|
+
const brandStr = brandInput;
|
|
365
|
+
const dashIdx = brandStr.indexOf(" \u2014 ");
|
|
366
|
+
const brand = dashIdx > -1 ? brandStr.slice(0, dashIdx).trim() : brandStr.trim();
|
|
367
|
+
let purpose = dashIdx > -1 ? brandStr.slice(dashIdx + 3).trim() : "";
|
|
368
|
+
if (!purpose) {
|
|
369
|
+
const purposeInput = await p2.text({
|
|
370
|
+
message: "One-line purpose?",
|
|
371
|
+
placeholder: "Precision longevity medicine powered by biological data.",
|
|
372
|
+
validate: (v) => v.trim().length === 0 ? "Required" : void 0
|
|
373
|
+
});
|
|
374
|
+
if (p2.isCancel(purposeInput)) {
|
|
375
|
+
p2.cancel("Cancelled.");
|
|
376
|
+
process.exit(0);
|
|
377
|
+
}
|
|
378
|
+
purpose = purposeInput.trim();
|
|
379
|
+
}
|
|
380
|
+
p2.note(
|
|
381
|
+
"Paste everything you know about this product \u2014 goals, features, competitors, users, constraints.\nNo need to structure it. Stream of consciousness is fine.",
|
|
382
|
+
"Brain dump"
|
|
383
|
+
);
|
|
384
|
+
const braindumpInput = await p2.text({
|
|
385
|
+
message: "Paste your brain dump",
|
|
386
|
+
placeholder: "We want to build a platform that...",
|
|
387
|
+
validate: (v) => v.trim().length < 20 ? "Give me more to work with (20+ chars)" : void 0
|
|
388
|
+
});
|
|
389
|
+
if (p2.isCancel(braindumpInput)) {
|
|
390
|
+
p2.cancel("Cancelled.");
|
|
391
|
+
process.exit(0);
|
|
392
|
+
}
|
|
393
|
+
const session = {
|
|
394
|
+
brand,
|
|
395
|
+
purpose,
|
|
396
|
+
braindump: braindumpInput.trim()
|
|
397
|
+
};
|
|
398
|
+
const wantValueProps = await p2.confirm({
|
|
399
|
+
message: "Do you know your 3 value propositions? (You can skip for now)",
|
|
400
|
+
initialValue: !!existingSpec?.valueProps
|
|
401
|
+
});
|
|
402
|
+
if (!p2.isCancel(wantValueProps) && wantValueProps) {
|
|
403
|
+
const vp0 = await p2.text({
|
|
404
|
+
message: "Value prop 1",
|
|
405
|
+
placeholder: "Telemetry",
|
|
406
|
+
initialValue: existingSpec?.valueProps[0] ?? ""
|
|
407
|
+
});
|
|
408
|
+
const vp1 = await p2.text({
|
|
409
|
+
message: "Value prop 2",
|
|
410
|
+
placeholder: "Protocol",
|
|
411
|
+
initialValue: existingSpec?.valueProps[1] ?? ""
|
|
412
|
+
});
|
|
413
|
+
const vp2 = await p2.text({
|
|
414
|
+
message: "Value prop 3",
|
|
415
|
+
placeholder: "Outcomes",
|
|
416
|
+
initialValue: existingSpec?.valueProps[2] ?? ""
|
|
417
|
+
});
|
|
418
|
+
if (!p2.isCancel(vp0) && !p2.isCancel(vp1) && !p2.isCancel(vp2)) {
|
|
419
|
+
session.valueProps = [
|
|
420
|
+
vp0.trim() || "Value Prop 1",
|
|
421
|
+
vp1.trim() || "Value Prop 2",
|
|
422
|
+
vp2.trim() || "Value Prop 3"
|
|
423
|
+
];
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
const ctaInput = await p2.text({
|
|
427
|
+
message: "Primary CTA? (optional \u2014 press Enter to skip)",
|
|
428
|
+
placeholder: "Book a consultation",
|
|
429
|
+
initialValue: existingSpec?.cta ?? ""
|
|
430
|
+
});
|
|
431
|
+
if (!p2.isCancel(ctaInput) && ctaInput.trim()) {
|
|
432
|
+
session.cta = ctaInput.trim();
|
|
433
|
+
}
|
|
434
|
+
const spinner3 = p2.spinner();
|
|
435
|
+
spinner3.start("Generating prompt templates in docs/\u2026");
|
|
436
|
+
const docsDir = join3(cwd, "docs");
|
|
437
|
+
mkdirSync2(docsDir, { recursive: true });
|
|
438
|
+
const files = [
|
|
439
|
+
{ templateFile: "Guided-PRD-Creation.md", outputFile: "PRD.md", title: "Product Requirements Document" },
|
|
440
|
+
{ templateFile: "Guided-UX-User-Flow.md", outputFile: "UX.md", title: "UX & User Flow" },
|
|
441
|
+
{ templateFile: "Guided-MVP-Concept.md", outputFile: "MVP-Concept.md", title: "MVP Concept" },
|
|
442
|
+
{ templateFile: "Guided-MVP.md", outputFile: "MVP.md", title: "MVP Plan" },
|
|
443
|
+
{ templateFile: "Guided-Test-Plan.md", outputFile: "Test-Plan.md", title: "Test Plan" }
|
|
444
|
+
];
|
|
445
|
+
const written = [];
|
|
446
|
+
for (const { templateFile, outputFile, title } of files) {
|
|
447
|
+
const templatePath = join3(PROMPTS_DIR, templateFile);
|
|
448
|
+
const outputPath = join3(docsDir, outputFile);
|
|
449
|
+
const content = existsSync3(templatePath) ? fillTemplate(templatePath, session) : stubDoc(title, session);
|
|
450
|
+
writeFileSync3(outputPath, content, "utf-8");
|
|
451
|
+
written.push(`docs/${outputFile}`);
|
|
452
|
+
}
|
|
453
|
+
const partialSpec = {
|
|
454
|
+
brand,
|
|
455
|
+
purpose,
|
|
456
|
+
presetId: existingSpec?.presetId ?? "organic-tech",
|
|
457
|
+
valueProps: session.valueProps ?? existingSpec?.valueProps ?? ["Value 1", "Value 2", "Value 3"],
|
|
458
|
+
cta: session.cta ?? existingSpec?.cta ?? "Get started",
|
|
459
|
+
notes: existingSpec?.notes ?? "",
|
|
460
|
+
prd: { path: "./docs/PRD.md" },
|
|
461
|
+
ux: { path: "./docs/UX.md" },
|
|
462
|
+
mvp: { path: "./docs/MVP.md" }
|
|
463
|
+
};
|
|
464
|
+
saveSpec(partialSpec, cwd);
|
|
465
|
+
spinner3.stop(`${pc2.green("\u2713")} Generated ${written.length} prompt templates`);
|
|
466
|
+
p2.note(
|
|
467
|
+
written.map((f) => `${pc2.cyan(f)}`).join("\n"),
|
|
468
|
+
"Files written"
|
|
469
|
+
);
|
|
470
|
+
p2.note(
|
|
471
|
+
[
|
|
472
|
+
`1. Open each doc in ${pc2.cyan("docs/")} with your LLM (Claude, GPT, Gemini, etc.)`,
|
|
473
|
+
"2. The brain dump is pre-filled \u2014 just send the doc as your first message.",
|
|
474
|
+
"3. Work through PRD \u2192 UX \u2192 MVP \u2192 Test Plan in order.",
|
|
475
|
+
`4. When ready to build: ${pc2.cyan("cinematic-web create")} will pre-fill from ${pc2.cyan("cinematic.json")}`
|
|
476
|
+
].join("\n"),
|
|
477
|
+
"Next steps"
|
|
478
|
+
);
|
|
479
|
+
p2.outro(`${pc2.green("Done!")} Your product docs are in ${pc2.cyan("docs/")}`);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// src/commands/init.ts
|
|
483
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
484
|
+
import { resolve as resolve5, join as join4 } from "path";
|
|
485
|
+
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
486
|
+
import * as p3 from "@clack/prompts";
|
|
487
|
+
import pc3 from "picocolors";
|
|
488
|
+
var ASSETS_DIR4 = resolve5(fileURLToPath4(import.meta.url), "..", "..", "assets");
|
|
489
|
+
var SOURCE_CLAUDE_MD = join4(ASSETS_DIR4, "CLAUDE.md");
|
|
490
|
+
async function runInit() {
|
|
491
|
+
p3.intro(`${pc3.bgCyan(pc3.black(" cinematic-web "))} ${pc3.dim("init")}`);
|
|
492
|
+
const cwd = process.cwd();
|
|
493
|
+
const dest = join4(cwd, "CLAUDE.md");
|
|
494
|
+
if (!existsSync4(SOURCE_CLAUDE_MD)) {
|
|
495
|
+
p3.cancel(`Source CLAUDE.md not found at ${pc3.cyan(SOURCE_CLAUDE_MD)}
|
|
496
|
+
Are you running from inside the cinematic-web repo?`);
|
|
497
|
+
process.exit(1);
|
|
498
|
+
}
|
|
499
|
+
if (existsSync4(dest)) {
|
|
500
|
+
const overwrite = await p3.confirm({
|
|
501
|
+
message: `${pc3.cyan("CLAUDE.md")} already exists in this directory. Overwrite?`,
|
|
502
|
+
initialValue: false
|
|
503
|
+
});
|
|
504
|
+
if (p3.isCancel(overwrite) || !overwrite) {
|
|
505
|
+
p3.cancel("Aborted \u2014 existing CLAUDE.md preserved.");
|
|
506
|
+
process.exit(0);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
const content = readFileSync4(SOURCE_CLAUDE_MD, "utf-8");
|
|
510
|
+
writeFileSync4(dest, content, "utf-8");
|
|
511
|
+
p3.note(
|
|
512
|
+
[
|
|
513
|
+
`${pc3.green("\u2713")} Wrote CLAUDE.md to ${pc3.cyan(cwd)}`,
|
|
514
|
+
"",
|
|
515
|
+
"Load it into Claude Code with:",
|
|
516
|
+
` ${pc3.cyan("claude")} \u2014 CLAUDE.md auto-loads when you open this directory`,
|
|
517
|
+
"",
|
|
518
|
+
"Or reference it explicitly:",
|
|
519
|
+
` ${pc3.cyan("/load CLAUDE.md")} inside a Claude session`
|
|
520
|
+
].join("\n"),
|
|
521
|
+
"CLAUDE.md installed"
|
|
522
|
+
);
|
|
523
|
+
p3.outro(`${pc3.green("Done!")} Open this directory in Claude Code to start building cinematically.`);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// src/cli.ts
|
|
527
|
+
var program = new Command();
|
|
528
|
+
program.name("cinematic-web").description("Build cinematic landing pages with premium design presets").version("0.1.0");
|
|
529
|
+
program.command("create [name]").description("Scaffold a new cinematic landing page project").action(async (name) => {
|
|
530
|
+
await runCreate(name);
|
|
531
|
+
});
|
|
532
|
+
program.command("think").description("Walk through the product-development pipeline (PRD \u2192 UX \u2192 MVP)").action(async () => {
|
|
533
|
+
await runThink();
|
|
534
|
+
});
|
|
535
|
+
program.command("init").description("Write CLAUDE.md into the current directory (for Claude Code users)").action(async () => {
|
|
536
|
+
await runInit();
|
|
537
|
+
});
|
|
538
|
+
program.parse();
|
|
539
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/commands/create.ts","../src/spec.ts","../src/scaffold.ts","../src/tokens.ts","../src/commands/think.ts","../src/commands/init.ts"],"sourcesContent":["import { Command } from 'commander'\nimport { runCreate } from './commands/create.js'\nimport { runThink } from './commands/think.js'\nimport { runInit } from './commands/init.js'\n\nconst program = new Command()\n\nprogram\n .name('cinematic-web')\n .description('Build cinematic landing pages with premium design presets')\n .version('0.1.0')\n\nprogram\n .command('create [name]')\n .description('Scaffold a new cinematic landing page project')\n .action(async (name?: string) => {\n await runCreate(name)\n })\n\nprogram\n .command('think')\n .description('Walk through the product-development pipeline (PRD → UX → MVP)')\n .action(async () => {\n await runThink()\n })\n\nprogram\n .command('init')\n .description('Write CLAUDE.md into the current directory (for Claude Code users)')\n .action(async () => {\n await runInit()\n })\n\nprogram.parse()\n","import { existsSync, mkdirSync } from 'node:fs'\nimport { resolve, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport * as p from '@clack/prompts'\nimport pc from 'picocolors'\nimport { listPresets, getPreset } from 'cinematic-core'\nimport type { ProjectSpec } from 'cinematic-core'\nimport { loadSpec, saveSpec } from '../spec.js'\nimport { scaffoldProject } from '../scaffold.js'\n\n// Assets dir: dist/cli.js → packages/cli/ → assets/ (copied at build time by copy-assets.mjs)\nconst ASSETS_DIR = resolve(fileURLToPath(import.meta.url), '..', '..', 'assets')\nconst PRESETS_DIR = join(ASSETS_DIR, 'presets')\n\nexport async function runCreate(name?: string): Promise<void> {\n p.intro(`${pc.bgCyan(pc.black(' cinematic-web '))} ${pc.dim('create')}`)\n\n // --- Determine project directory ---\n let projectName = name\n if (!projectName) {\n const input = await p.text({\n message: 'Project name',\n placeholder: 'my-landing-page',\n validate: (v) => (v.trim().length === 0 ? 'Project name is required' : undefined),\n })\n if (p.isCancel(input)) { p.cancel('Cancelled.'); process.exit(0) }\n projectName = (input as string).trim()\n }\n\n const outputDir = resolve(process.cwd(), projectName)\n if (existsSync(outputDir)) {\n p.cancel(`Directory ${pc.cyan(projectName)} already exists.`)\n process.exit(1)\n }\n\n // --- Check for existing cinematic.json (pre-filled by `think`) ---\n const existingSpec = loadSpec(process.cwd())\n const presets = listPresets(PRESETS_DIR)\n\n if (presets.length === 0) {\n p.cancel('No presets found. Make sure you\\'re running from the cinematic-web repo root or install the package.')\n process.exit(1)\n }\n\n // --- 6 questions ---\n const brand = await p.text({\n message: 'Brand name and one-line purpose?',\n placeholder: 'Nura Health — precision longevity medicine',\n initialValue: existingSpec ? `${existingSpec.brand} — ${existingSpec.purpose}` : '',\n validate: (v) => (v.trim().length === 0 ? 'Required' : undefined),\n })\n if (p.isCancel(brand)) { p.cancel('Cancelled.'); process.exit(0) }\n\n // Parse \"Brand — purpose\" or just brand name\n const brandStr = brand as string\n const dashIdx = brandStr.indexOf(' — ')\n const parsedBrand = dashIdx > -1 ? brandStr.slice(0, dashIdx).trim() : brandStr.trim()\n const parsedPurpose = dashIdx > -1 ? brandStr.slice(dashIdx + 3).trim() : ''\n\n let purpose = parsedPurpose\n if (!purpose) {\n const purposeInput = await p.text({\n message: 'One-line purpose?',\n placeholder: 'Precision longevity medicine powered by biological data.',\n validate: (v) => (v.trim().length === 0 ? 'Required' : undefined),\n })\n if (p.isCancel(purposeInput)) { p.cancel('Cancelled.'); process.exit(0) }\n purpose = (purposeInput as string).trim()\n }\n\n const presetChoice = await p.select({\n message: 'Pick an aesthetic direction',\n options: presets.map((preset) => ({\n value: preset.id,\n label: `${preset.name} — ${preset.label}`,\n hint: preset.identity,\n })),\n initialValue: existingSpec?.presetId ?? presets[0]?.id,\n })\n if (p.isCancel(presetChoice)) { p.cancel('Cancelled.'); process.exit(0) }\n\n const vp0 = await p.text({\n message: 'Value proposition 1 of 3',\n placeholder: 'Telemetry',\n initialValue: existingSpec?.valueProps[0] ?? '',\n validate: (v) => (v.trim().length === 0 ? 'Required' : undefined),\n })\n if (p.isCancel(vp0)) { p.cancel('Cancelled.'); process.exit(0) }\n\n const vp1 = await p.text({\n message: 'Value proposition 2 of 3',\n placeholder: 'Protocol',\n initialValue: existingSpec?.valueProps[1] ?? '',\n validate: (v) => (v.trim().length === 0 ? 'Required' : undefined),\n })\n if (p.isCancel(vp1)) { p.cancel('Cancelled.'); process.exit(0) }\n\n const vp2 = await p.text({\n message: 'Value proposition 3 of 3',\n placeholder: 'Outcomes',\n initialValue: existingSpec?.valueProps[2] ?? '',\n validate: (v) => (v.trim().length === 0 ? 'Required' : undefined),\n })\n if (p.isCancel(vp2)) { p.cancel('Cancelled.'); process.exit(0) }\n\n const cta = await p.text({\n message: 'Primary CTA text?',\n placeholder: 'Book a consultation',\n initialValue: existingSpec?.cta ?? '',\n validate: (v) => (v.trim().length === 0 ? 'Required' : undefined),\n })\n if (p.isCancel(cta)) { p.cancel('Cancelled.'); process.exit(0) }\n\n const notes = await p.text({\n message: 'Special requests or custom features? (optional)',\n placeholder: 'Add a 3D hero, custom animations...',\n initialValue: existingSpec?.notes ?? '',\n })\n if (p.isCancel(notes)) { p.cancel('Cancelled.'); process.exit(0) }\n\n // --- Build spec ---\n const spec: ProjectSpec = {\n brand: parsedBrand,\n purpose,\n presetId: presetChoice as string,\n valueProps: [\n (vp0 as string).trim(),\n (vp1 as string).trim(),\n (vp2 as string).trim(),\n ],\n cta: (cta as string).trim(),\n notes: ((notes as string) ?? '').trim(),\n }\n\n const preset = getPreset(spec.presetId, PRESETS_DIR)\n if (!preset) {\n p.cancel(`Preset \"${spec.presetId}\" not found.`)\n process.exit(1)\n }\n\n // --- Scaffold ---\n const spinner = p.spinner()\n spinner.start(`Scaffolding ${pc.cyan(parsedBrand)} with ${pc.cyan(preset.name)}...`)\n\n mkdirSync(outputDir, { recursive: true })\n\n try {\n await scaffoldProject(outputDir, spec, preset)\n // Write cinematic.json into the new project\n saveSpec(spec, outputDir)\n spinner.stop(`${pc.green('✓')} Scaffolded ${pc.cyan(projectName)}`)\n } catch (err) {\n spinner.stop(`${pc.red('✗')} Scaffold failed`)\n throw err\n }\n\n // --- Done ---\n p.note(\n [\n `cd ${projectName}`,\n 'npm install',\n 'npm run dev',\n ].join('\\n'),\n 'Next steps'\n )\n\n if (preset.id === '3d-immersive') {\n p.note(\n `You selected the 3D Immersive preset.\\nFull build instructions: ${pc.cyan('prompts/3d-immersive/3d-website-builder.md')}`,\n '3D Notes'\n )\n }\n\n p.outro(`${pc.green('Done!')} Your cinematic site is ready at ${pc.cyan(projectName)}/`)\n}\n","import { readFileSync, writeFileSync, existsSync } from 'node:fs'\nimport { resolve } from 'node:path'\nimport { ProjectSpecSchema } from 'cinematic-core'\nimport type { ProjectSpec } from 'cinematic-core'\n\nconst SPEC_FILENAME = 'cinematic.json'\n\n/**\n * Load a ProjectSpec from cinematic.json in the given directory.\n * Returns null if the file doesn't exist or fails to parse.\n */\nexport function loadSpec(dir: string = process.cwd()): ProjectSpec | null {\n const path = resolve(dir, SPEC_FILENAME)\n if (!existsSync(path)) return null\n try {\n const raw = readFileSync(path, 'utf-8')\n const parsed = ProjectSpecSchema.safeParse(JSON.parse(raw))\n return parsed.success ? parsed.data : null\n } catch {\n return null\n }\n}\n\n/**\n * Write a ProjectSpec to cinematic.json in the given directory.\n */\nexport function saveSpec(spec: ProjectSpec, dir: string = process.cwd()): void {\n const path = resolve(dir, SPEC_FILENAME)\n writeFileSync(path, JSON.stringify(spec, null, 2) + '\\n', 'utf-8')\n}\n","import fsExtra from 'fs-extra'\nconst { copySync } = fsExtra\nimport { readFileSync, writeFileSync, readdirSync } from 'node:fs'\nimport { join, resolve, relative } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport { applyTokens } from './tokens.js'\nimport type { ProjectSpec, Preset } from 'cinematic-core'\n\n// Assets dir: packages/cli/dist/cli.js → packages/cli/ → assets/\n// Works both in the monorepo (packages/cli/assets/) and as an installed npm package\n// (node_modules/cinematic-web/assets/). Assets are copied at build time by copy-assets.mjs.\nconst ASSETS_DIR = resolve(fileURLToPath(import.meta.url), '..', '..', 'assets')\n\n// File extensions that support token replacement\nconst TEXT_EXTENSIONS = new Set([\n '.html', '.css', '.js', '.jsx', '.ts', '.tsx',\n '.json', '.md', '.txt', '.env.example',\n])\n\n/**\n * Scaffold a new project directory from a template.\n * 1. Copy template files recursively\n * 2. Apply {{token}} replacement to all text files\n */\nexport async function scaffoldProject(\n outputDir: string,\n spec: ProjectSpec,\n preset: Preset\n): Promise<void> {\n const templateName = preset.template === 'three-fiber' ? 'three-fiber' : 'base-react'\n const templateDir = join(ASSETS_DIR, 'templates', templateName)\n\n // Copy all template files to output\n copySync(templateDir, outputDir, {\n overwrite: false,\n filter: (src) => {\n const rel = relative(templateDir, src)\n return !rel.startsWith('node_modules') && !rel.startsWith('dist')\n },\n })\n\n // Token-replace all text files\n replaceTokensInDir(outputDir, spec, preset)\n}\n\nfunction replaceTokensInDir(dir: string, spec: ProjectSpec, preset: Preset): void {\n const entries = readdirSync(dir, { withFileTypes: true })\n for (const entry of entries) {\n const fullPath = join(dir, entry.name)\n if (entry.isDirectory()) {\n replaceTokensInDir(fullPath, spec, preset)\n } else if (entry.isFile()) {\n const ext = '.' + (entry.name.split('.').pop() ?? '')\n if (!TEXT_EXTENSIONS.has(ext)) continue\n try {\n const original = readFileSync(fullPath, 'utf-8')\n const replaced = applyTokens(original, spec, preset)\n if (replaced !== original) {\n writeFileSync(fullPath, replaced, 'utf-8')\n }\n } catch {\n // Skip binary files that slipped through extension check\n }\n }\n }\n}\n","import { tokenReplace } from 'cinematic-core'\nimport type { ProjectSpec, Preset } from 'cinematic-core'\n\n/**\n * Build the full token map by merging spec + preset tokens.\n * Used to replace {{...}} placeholders in template files.\n */\nexport function buildTokenMap(spec: ProjectSpec, preset: Preset): Record<string, unknown> {\n const brandSlug = spec.brand.toLowerCase().replace(/[^a-z0-9]+/g, '-')\n\n return {\n brand: spec.brand,\n 'brand-slug': brandSlug,\n purpose: spec.purpose,\n cta: spec.cta,\n notes: spec.notes,\n presetId: preset.id,\n\n // Hero lines — filled from preset pattern with brand content\n heroLine1: spec.purpose.split(' ').slice(0, 4).join(' '),\n heroLine2: spec.brand,\n\n // Value props\n valueProp0: spec.valueProps[0],\n valueProp1: spec.valueProps[1],\n valueProp2: spec.valueProps[2],\n\n // Manifesto — generated from purpose\n manifesto: `${spec.purpose} We believe the future belongs to those who move with precision, clarity, and intention.`,\n\n // Protocol steps — generated from value props\n protocolStep0Title: `Understand ${spec.valueProps[0]}`,\n protocolStep0Desc: `We begin with a deep analysis of your current state — mapping every signal, every gap, every opportunity hidden in your data.`,\n protocolStep1Title: `Activate ${spec.valueProps[1]}`,\n protocolStep1Desc: `Our system processes your inputs and delivers a tailored protocol built specifically for your context and objectives.`,\n protocolStep2Title: `Deliver ${spec.valueProps[2]}`,\n protocolStep2Desc: `Results are delivered in real time with full transparency — measurable, verifiable, and ready to act on.`,\n\n // Per-preset curated Unsplash photo ID (falls back to organic-tech default)\n unsplashPhotoId: preset.unsplashPhotoId ?? '1518770660439-4636190af475',\n\n // Preset tokens (flat)\n 'preset.name': preset.name,\n 'preset.id': preset.id,\n googleFonts: preset.googleFonts,\n\n // Palette\n 'palette.primary': preset.palette.primary,\n 'palette.accent': preset.palette.accent,\n 'palette.background': preset.palette.background,\n 'palette.dark': preset.palette.dark,\n\n // Typography\n 'typography.heading0': preset.typography.heading[0] ?? '',\n 'typography.heading1': preset.typography.heading[1] ?? '',\n 'typography.drama': preset.typography.drama ?? '',\n 'typography.data': preset.typography.data ?? '',\n }\n}\n\n/**\n * Apply token replacement to a string using spec + preset tokens.\n */\nexport function applyTokens(\n content: string,\n spec: ProjectSpec,\n preset: Preset\n): string {\n return tokenReplace(content, buildTokenMap(spec, preset) as Record<string, unknown>)\n}\n","import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'\nimport { resolve, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport * as p from '@clack/prompts'\nimport pc from 'picocolors'\nimport { loadSpec, saveSpec } from '../spec.js'\nimport type { ProjectSpec } from 'cinematic-core'\n\n// Assets dir: dist/cli.js → packages/cli/ → assets/ (copied at build time by copy-assets.mjs)\nconst ASSETS_DIR = resolve(fileURLToPath(import.meta.url), '..', '..', 'assets')\nconst PROMPTS_DIR = join(ASSETS_DIR, 'prompts', 'product-development')\n\ninterface ThinkSession {\n brand: string\n purpose: string\n braindump: string\n valueProps?: [string, string, string]\n cta?: string\n}\n\n/**\n * Read a prompt template and fill in the brain dump placeholder.\n */\nfunction fillTemplate(templatePath: string, session: ThinkSession): string {\n if (!existsSync(templatePath)) return ''\n const raw = readFileSync(templatePath, 'utf-8')\n\n const braindumpBlock = `\nBrand: ${session.brand}\nPurpose: ${session.purpose}\n${session.valueProps ? `Value Propositions: ${session.valueProps.join(', ')}` : ''}\n${session.cta ? `CTA: ${session.cta}` : ''}\n\n---\n\n${session.braindump}\n`.trim()\n\n // Replace the placeholder block in the template\n return raw.replace(\n /\\[\\s*\\*\\*<<< PASTE YOUR RAW NOTES.*?>>>\\*\\*\\s*\\]/s,\n braindumpBlock\n )\n}\n\n/**\n * Generate a stub doc header when a template isn't found.\n */\nfunction stubDoc(title: string, session: ThinkSession): string {\n return `# ${title}\n\n**Brand:** ${session.brand}\n**Purpose:** ${session.purpose}\n\n> This document was generated by \\`cinematic-web think\\`.\n> Paste this file's full contents into your LLM of choice to get started.\n\n---\n\n${session.braindump}\n`\n}\n\nexport async function runThink(): Promise<void> {\n p.intro(`${pc.bgCyan(pc.black(' cinematic-web '))} ${pc.dim('think')}`)\n\n const cwd = process.cwd()\n const existingSpec = loadSpec(cwd)\n\n p.note(\n [\n 'The `think` command walks you through the product-development pipeline.',\n 'It fills your brain dump into 5 prompt templates and writes them to docs/.',\n 'Then paste each doc into Claude, GPT, or your LLM of choice to generate the artefacts.',\n ].join('\\n'),\n 'How this works'\n )\n\n // --- Brand ---\n const brandInput = await p.text({\n message: 'Brand name and one-line purpose?',\n placeholder: 'Nura Health — precision longevity medicine',\n initialValue: existingSpec ? `${existingSpec.brand} — ${existingSpec.purpose}` : '',\n validate: (v) => (v.trim().length === 0 ? 'Required' : undefined),\n })\n if (p.isCancel(brandInput)) { p.cancel('Cancelled.'); process.exit(0) }\n\n const brandStr = brandInput as string\n const dashIdx = brandStr.indexOf(' — ')\n const brand = dashIdx > -1 ? brandStr.slice(0, dashIdx).trim() : brandStr.trim()\n let purpose = dashIdx > -1 ? brandStr.slice(dashIdx + 3).trim() : ''\n\n if (!purpose) {\n const purposeInput = await p.text({\n message: 'One-line purpose?',\n placeholder: 'Precision longevity medicine powered by biological data.',\n validate: (v) => (v.trim().length === 0 ? 'Required' : undefined),\n })\n if (p.isCancel(purposeInput)) { p.cancel('Cancelled.'); process.exit(0) }\n purpose = (purposeInput as string).trim()\n }\n\n // --- Brain dump ---\n p.note(\n 'Paste everything you know about this product — goals, features, competitors, users, constraints.\\nNo need to structure it. Stream of consciousness is fine.',\n 'Brain dump'\n )\n\n const braindumpInput = await p.text({\n message: 'Paste your brain dump',\n placeholder: 'We want to build a platform that...',\n validate: (v) => (v.trim().length < 20 ? 'Give me more to work with (20+ chars)' : undefined),\n })\n if (p.isCancel(braindumpInput)) { p.cancel('Cancelled.'); process.exit(0) }\n\n const session: ThinkSession = {\n brand,\n purpose,\n braindump: (braindumpInput as string).trim(),\n }\n\n // --- Optional value props ---\n const wantValueProps = await p.confirm({\n message: 'Do you know your 3 value propositions? (You can skip for now)',\n initialValue: !!existingSpec?.valueProps,\n })\n\n if (!p.isCancel(wantValueProps) && wantValueProps) {\n const vp0 = await p.text({\n message: 'Value prop 1',\n placeholder: 'Telemetry',\n initialValue: existingSpec?.valueProps[0] ?? '',\n })\n const vp1 = await p.text({\n message: 'Value prop 2',\n placeholder: 'Protocol',\n initialValue: existingSpec?.valueProps[1] ?? '',\n })\n const vp2 = await p.text({\n message: 'Value prop 3',\n placeholder: 'Outcomes',\n initialValue: existingSpec?.valueProps[2] ?? '',\n })\n\n if (!p.isCancel(vp0) && !p.isCancel(vp1) && !p.isCancel(vp2)) {\n session.valueProps = [\n (vp0 as string).trim() || 'Value Prop 1',\n (vp1 as string).trim() || 'Value Prop 2',\n (vp2 as string).trim() || 'Value Prop 3',\n ]\n }\n }\n\n // --- Optional CTA ---\n const ctaInput = await p.text({\n message: 'Primary CTA? (optional — press Enter to skip)',\n placeholder: 'Book a consultation',\n initialValue: existingSpec?.cta ?? '',\n })\n if (!p.isCancel(ctaInput) && (ctaInput as string).trim()) {\n session.cta = (ctaInput as string).trim()\n }\n\n // --- Write docs ---\n const spinner = p.spinner()\n spinner.start('Generating prompt templates in docs/…')\n\n const docsDir = join(cwd, 'docs')\n mkdirSync(docsDir, { recursive: true })\n\n const files: Array<{ templateFile: string; outputFile: string; title: string }> = [\n { templateFile: 'Guided-PRD-Creation.md', outputFile: 'PRD.md', title: 'Product Requirements Document' },\n { templateFile: 'Guided-UX-User-Flow.md', outputFile: 'UX.md', title: 'UX & User Flow' },\n { templateFile: 'Guided-MVP-Concept.md', outputFile: 'MVP-Concept.md', title: 'MVP Concept' },\n { templateFile: 'Guided-MVP.md', outputFile: 'MVP.md', title: 'MVP Plan' },\n { templateFile: 'Guided-Test-Plan.md', outputFile: 'Test-Plan.md', title: 'Test Plan' },\n ]\n\n const written: string[] = []\n\n for (const { templateFile, outputFile, title } of files) {\n const templatePath = join(PROMPTS_DIR, templateFile)\n const outputPath = join(docsDir, outputFile)\n\n const content = existsSync(templatePath)\n ? fillTemplate(templatePath, session)\n : stubDoc(title, session)\n\n writeFileSync(outputPath, content, 'utf-8')\n written.push(`docs/${outputFile}`)\n }\n\n // --- Update cinematic.json ---\n const partialSpec: Partial<ProjectSpec> & Pick<ProjectSpec, 'brand' | 'purpose' | 'presetId' | 'valueProps' | 'cta' | 'notes'> = {\n brand,\n purpose,\n presetId: existingSpec?.presetId ?? 'organic-tech',\n valueProps: session.valueProps ?? existingSpec?.valueProps ?? ['Value 1', 'Value 2', 'Value 3'],\n cta: session.cta ?? existingSpec?.cta ?? 'Get started',\n notes: existingSpec?.notes ?? '',\n prd: { path: './docs/PRD.md' },\n ux: { path: './docs/UX.md' },\n mvp: { path: './docs/MVP.md' },\n }\n\n saveSpec(partialSpec as ProjectSpec, cwd)\n\n spinner.stop(`${pc.green('✓')} Generated ${written.length} prompt templates`)\n\n // --- Summary ---\n p.note(\n written.map((f) => `${pc.cyan(f)}`).join('\\n'),\n 'Files written'\n )\n\n p.note(\n [\n `1. Open each doc in ${pc.cyan('docs/')} with your LLM (Claude, GPT, Gemini, etc.)`,\n '2. The brain dump is pre-filled — just send the doc as your first message.',\n '3. Work through PRD → UX → MVP → Test Plan in order.',\n `4. When ready to build: ${pc.cyan('cinematic-web create')} will pre-fill from ${pc.cyan('cinematic.json')}`,\n ].join('\\n'),\n 'Next steps'\n )\n\n p.outro(`${pc.green('Done!')} Your product docs are in ${pc.cyan('docs/')}`)\n}\n","import { existsSync, readFileSync, writeFileSync } from 'node:fs'\nimport { resolve, join } from 'node:path'\nimport { fileURLToPath } from 'node:url'\nimport * as p from '@clack/prompts'\nimport pc from 'picocolors'\n\n// Assets dir: dist/cli.js → packages/cli/ → assets/ (copied at build time by copy-assets.mjs)\nconst ASSETS_DIR = resolve(fileURLToPath(import.meta.url), '..', '..', 'assets')\nconst SOURCE_CLAUDE_MD = join(ASSETS_DIR, 'CLAUDE.md')\n\nexport async function runInit(): Promise<void> {\n p.intro(`${pc.bgCyan(pc.black(' cinematic-web '))} ${pc.dim('init')}`)\n\n const cwd = process.cwd()\n const dest = join(cwd, 'CLAUDE.md')\n\n if (!existsSync(SOURCE_CLAUDE_MD)) {\n p.cancel(`Source CLAUDE.md not found at ${pc.cyan(SOURCE_CLAUDE_MD)}\\nAre you running from inside the cinematic-web repo?`)\n process.exit(1)\n }\n\n if (existsSync(dest)) {\n const overwrite = await p.confirm({\n message: `${pc.cyan('CLAUDE.md')} already exists in this directory. Overwrite?`,\n initialValue: false,\n })\n if (p.isCancel(overwrite) || !overwrite) {\n p.cancel('Aborted — existing CLAUDE.md preserved.')\n process.exit(0)\n }\n }\n\n const content = readFileSync(SOURCE_CLAUDE_MD, 'utf-8')\n writeFileSync(dest, content, 'utf-8')\n\n p.note(\n [\n `${pc.green('✓')} Wrote CLAUDE.md to ${pc.cyan(cwd)}`,\n '',\n 'Load it into Claude Code with:',\n ` ${pc.cyan('claude')} — CLAUDE.md auto-loads when you open this directory`,\n '',\n 'Or reference it explicitly:',\n ` ${pc.cyan('/load CLAUDE.md')} inside a Claude session`,\n ].join('\\n'),\n 'CLAUDE.md installed'\n )\n\n p.outro(`${pc.green('Done!')} Open this directory in Claude Code to start building cinematically.`)\n}\n"],"mappings":"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","names":["existsSync","resolve","join","fileURLToPath","readFileSync","writeFileSync","resolve","resolve","readFileSync","writeFileSync","ASSETS_DIR","resolve","fileURLToPath","join","existsSync","preset","spinner","existsSync","mkdirSync","readFileSync","writeFileSync","resolve","join","fileURLToPath","p","pc","ASSETS_DIR","resolve","fileURLToPath","join","existsSync","readFileSync","pc","spinner","mkdirSync","writeFileSync","existsSync","readFileSync","writeFileSync","resolve","join","fileURLToPath","p","pc","ASSETS_DIR"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cinematic-web",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Scaffold cinematic landing pages from 6 premium design presets — CLI",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"cinematic-web": "./dist/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/cli.js",
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"assets"
|
|
13
|
+
],
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@clack/prompts": "^0.7.0",
|
|
16
|
+
"commander": "^12.1.0",
|
|
17
|
+
"fs-extra": "^11.2.0",
|
|
18
|
+
"picocolors": "^1.0.1",
|
|
19
|
+
"cinematic-core": "0.1.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@types/fs-extra": "^11.0.4",
|
|
23
|
+
"@types/node": "^20.0.0",
|
|
24
|
+
"tsup": "^8.0.0",
|
|
25
|
+
"typescript": "^5.4.0"
|
|
26
|
+
},
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=18.0.0"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"cli",
|
|
32
|
+
"landing-page",
|
|
33
|
+
"cinematic",
|
|
34
|
+
"react",
|
|
35
|
+
"vite",
|
|
36
|
+
"gsap"
|
|
37
|
+
],
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"scripts": {
|
|
40
|
+
"build": "node copy-assets.mjs && tsup",
|
|
41
|
+
"dev": "tsup --watch",
|
|
42
|
+
"type-check": "tsc --noEmit"
|
|
43
|
+
}
|
|
44
|
+
}
|