@nebutra/landing-builder 0.1.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 ADDED
@@ -0,0 +1,14 @@
1
+ # @nebutra/landing-builder
2
+
3
+ Status: **WIP**
4
+
5
+ BrandContext-first landing page Play package.
6
+
7
+ This package owns Play orchestration and static site artifacts only. It does not
8
+ own sandbox execution, deploy credentials, BrandContext, or agent runtime state.
9
+
10
+ ```bash
11
+ pnpm landing-builder:doctor
12
+ pnpm landing-builder:quickstart "AI debugging for indie devs"
13
+ pnpm landing-builder:debug
14
+ ```
@@ -0,0 +1,209 @@
1
+ // src/index.ts
2
+ import { mkdir } from "fs/promises";
3
+ import { dirname, join } from "path";
4
+ import { appendCapabilityDebug, readCapabilityDebug } from "@nebutra/capability-kit/debug";
5
+ import { ContentStore } from "@nebutra/content-store";
6
+ import { CapabilityError } from "@nebutra/errors";
7
+ import { EventLog } from "@nebutra/event-log";
8
+ import { assetId } from "@nebutra/generation-context";
9
+ var PLAY_NAME = "one_pager";
10
+ function requireTenant(explicit, fallback) {
11
+ const tenantId = explicit ?? fallback;
12
+ if (!tenantId?.trim()) {
13
+ throw new CapabilityError("landing-builder", "Landing Builder requires tenant context", {
14
+ suggestion: "Pass tenantId on the input, BrandContext, or construct LandingBuilder with tenantId.",
15
+ statusCode: 400
16
+ });
17
+ }
18
+ return tenantId;
19
+ }
20
+ function colorByRole(brand, role, fallback) {
21
+ return brand.palette.find((color) => color.role === role)?.hex ?? fallback;
22
+ }
23
+ function escapeHtml(value) {
24
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;");
25
+ }
26
+ function renderTailwindTheme(brand) {
27
+ const primary = colorByRole(brand, "primary", "#111827");
28
+ const background = colorByRole(brand, "background", "#FFFFFF");
29
+ const accent = colorByRole(brand, "accent", primary);
30
+ return {
31
+ primary,
32
+ background,
33
+ accent,
34
+ cssVariables: [
35
+ ":root {",
36
+ ` --brand-primary: ${primary};`,
37
+ ` --brand-background: ${background};`,
38
+ ` --brand-accent: ${accent};`,
39
+ ` --brand-heading: ${brand.typography.heading};`,
40
+ ` --brand-body: ${brand.typography.body};`,
41
+ "}",
42
+ ""
43
+ ].join("\n")
44
+ };
45
+ }
46
+ function renderOnePagerHtml(input) {
47
+ const { brand } = input;
48
+ const theme = renderTailwindTheme(brand);
49
+ const keywords = brand.visualStyle.keywords.join(" / ");
50
+ return [
51
+ "<!doctype html>",
52
+ '<html lang="en">',
53
+ "<head>",
54
+ ' <meta charset="utf-8" />',
55
+ ' <meta name="viewport" content="width=device-width, initial-scale=1" />',
56
+ ` <title>${escapeHtml(brand.name)}</title>`,
57
+ ' <link rel="stylesheet" href="./theme.css" />',
58
+ "</head>",
59
+ `<body data-brand-id="${escapeHtml(brand.brandId)}">`,
60
+ ' <main class="page">',
61
+ ' <section class="hero">',
62
+ ` <p class="eyebrow">${escapeHtml(keywords)}</p>`,
63
+ ` <h1>${escapeHtml(brand.name)}</h1>`,
64
+ ` <p class="lead">${escapeHtml(input.productDesc)}</p>`,
65
+ ` <a class="cta" href="#waitlist">${escapeHtml(input.ctaText)}</a>`,
66
+ " </section>",
67
+ ' <section class="proof">',
68
+ " <h2>Why it matters</h2>",
69
+ ` <p>${escapeHtml(brand.toneKeywords.join(", "))} execution, grounded in one shared brand context.</p>`,
70
+ " </section>",
71
+ ' <section id="waitlist" class="waitlist">',
72
+ " <h2>Join the waitlist</h2>",
73
+ " <form>",
74
+ ' <input aria-label="Email" placeholder="you@example.com" />',
75
+ ' <button type="button">Join</button>',
76
+ " </form>",
77
+ " </section>",
78
+ " </main>",
79
+ "</body>",
80
+ "</html>",
81
+ `<!-- ${theme.primary} ${theme.accent} -->`,
82
+ ""
83
+ ].join("\n");
84
+ }
85
+ function renderThemeCss(brand) {
86
+ const theme = renderTailwindTheme(brand);
87
+ return [
88
+ theme.cssVariables,
89
+ "* { box-sizing: border-box; }",
90
+ "body { margin: 0; background: var(--brand-background); color: color-mix(in oklab, var(--brand-primary) 80%, black); font-family: var(--brand-body), system-ui, sans-serif; }",
91
+ ".page { min-height: 100vh; }",
92
+ ".hero { min-height: 82vh; display: grid; align-content: center; gap: 24px; padding: clamp(32px, 8vw, 96px); }",
93
+ ".eyebrow { color: var(--brand-accent); font-family: var(--brand-heading), monospace; text-transform: uppercase; letter-spacing: 0; }",
94
+ "h1 { max-width: 11ch; margin: 0; font-family: var(--brand-heading), system-ui; font-size: clamp(56px, 12vw, 132px); line-height: .92; letter-spacing: 0; }",
95
+ ".lead { max-width: 760px; font-size: clamp(20px, 3vw, 34px); line-height: 1.2; }",
96
+ ".cta, button { width: fit-content; border: 0; border-radius: 8px; background: var(--brand-primary); color: var(--brand-background); padding: 14px 20px; font: inherit; text-decoration: none; }",
97
+ ".proof, .waitlist { padding: 48px clamp(32px, 8vw, 96px); border-top: 1px solid color-mix(in oklab, var(--brand-primary) 20%, transparent); }",
98
+ "input { min-width: min(360px, 100%); border: 1px solid color-mix(in oklab, var(--brand-primary) 35%, transparent); border-radius: 8px; padding: 14px 16px; font: inherit; }",
99
+ "form { display: flex; flex-wrap: wrap; gap: 12px; }",
100
+ ""
101
+ ].join("\n");
102
+ }
103
+ function deployManifest(input) {
104
+ return `${JSON.stringify(
105
+ {
106
+ mode: "one_pager",
107
+ brandId: input.brand.brandId,
108
+ entrypoint: "company/landing/index.html",
109
+ targets: ["managed-hosting", "edge-pages", "static-hosting", "git-pages"],
110
+ requiresExplicitDeployConsent: true
111
+ },
112
+ null,
113
+ 2
114
+ )}
115
+ `;
116
+ }
117
+ var LandingBuilder = class _LandingBuilder {
118
+ #tenantId;
119
+ #debugRoot;
120
+ #contentStore;
121
+ #eventLog;
122
+ constructor(options) {
123
+ this.#tenantId = options.tenantId;
124
+ this.#debugRoot = options.debugRoot ?? process.cwd();
125
+ this.#contentStore = options.contentStore;
126
+ this.#eventLog = options.eventLog;
127
+ }
128
+ static async open(root = ".nebutra/landing-builder", options = {}) {
129
+ const tenantId = options.tenantId ?? "local";
130
+ await mkdir(root, { recursive: true });
131
+ const contentStore = await ContentStore.open(join(root, "content"), { tenantId });
132
+ const eventLog = await EventLog.open(join(root, "event-log"), { tenantId });
133
+ return new _LandingBuilder({ ...options, tenantId, root, contentStore, eventLog });
134
+ }
135
+ async runOnePager(input) {
136
+ const tenantId = requireTenant(input.tenantId ?? input.brand.tenantId, this.#tenantId);
137
+ const html = renderOnePagerHtml(input);
138
+ const css = renderThemeCss(input.brand);
139
+ const manifest = deployManifest(input);
140
+ const files = [
141
+ { path: "company/landing/index.html", mime: "text/html", content: html },
142
+ { path: "company/landing/theme.css", mime: "text/css", content: css },
143
+ { path: "company/landing/deploy.json", mime: "application/json", content: manifest }
144
+ ];
145
+ for (const file of files) await this.#contentStore.write(file.path, file.content);
146
+ const eventId = await this.#eventLog.commit({
147
+ traceId: assetId("landing_builder", input.brand.brandId),
148
+ kind: "content_write",
149
+ affected: files.map((file) => file.path),
150
+ parent: null,
151
+ snapshot: Object.fromEntries(files.map((file) => [file.path, file.content]))
152
+ });
153
+ const result = {
154
+ tenantId,
155
+ play: PLAY_NAME,
156
+ brandId: input.brand.brandId,
157
+ mode: "one_pager",
158
+ files: files.map((file) => ({
159
+ path: file.path,
160
+ mime: file.mime,
161
+ bytes: file.content.length
162
+ })),
163
+ preview: { kind: "static-content-store", entrypoint: "company/landing/index.html" },
164
+ deploy: {
165
+ status: "handoff",
166
+ targets: ["managed-hosting", "edge-pages", "static-hosting", "git-pages"],
167
+ manifestPath: "company/landing/deploy.json",
168
+ suggestion: "Connect a deploy integration and call the explicit deploy Play after review."
169
+ },
170
+ eventId
171
+ };
172
+ await this.#debug({ type: "run", tenantId, brandId: input.brand.brandId, eventId });
173
+ return result;
174
+ }
175
+ async doctor() {
176
+ return {
177
+ capability: "landing-builder",
178
+ ok: true,
179
+ checkedAt: (/* @__PURE__ */ new Date()).toISOString(),
180
+ plays: [PLAY_NAME],
181
+ modes: ["one_pager", "marketing_site", "webapp_mvp", "site_edit"],
182
+ deployAdapters: [
183
+ { provider: "static-content-store", ok: true },
184
+ { provider: "integration-vault", ok: false }
185
+ ],
186
+ suggestion: "Static generation is ready; real deploy requires integration-vault deploy adapters."
187
+ };
188
+ }
189
+ async close() {
190
+ await this.#contentStore.close();
191
+ }
192
+ async #debug(entry) {
193
+ await mkdir(dirname(join(this.#debugRoot, ".nebutra", "debug", "landing-builder.jsonl")), {
194
+ recursive: true
195
+ });
196
+ await appendCapabilityDebug("landing-builder", entry, { root: this.#debugRoot });
197
+ }
198
+ };
199
+ async function readLandingBuilderDebug(root = process.cwd(), limit = 20) {
200
+ return readCapabilityDebug("landing-builder", { root, limit });
201
+ }
202
+
203
+ export {
204
+ renderTailwindTheme,
205
+ renderOnePagerHtml,
206
+ LandingBuilder,
207
+ readLandingBuilderDebug
208
+ };
209
+ //# sourceMappingURL=chunk-E4I5POU4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import { mkdir } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { appendCapabilityDebug, readCapabilityDebug } from \"@nebutra/capability-kit/debug\";\nimport { ContentStore } from \"@nebutra/content-store\";\nimport { CapabilityError } from \"@nebutra/errors\";\nimport { EventLog } from \"@nebutra/event-log\";\nimport { assetId, type BrandContext } from \"@nebutra/generation-context\";\n\nexport type LandingMode = \"one_pager\" | \"marketing_site\" | \"webapp_mvp\" | \"site_edit\";\n\nexport interface OnePagerInput {\n readonly tenantId?: string;\n readonly brand: BrandContext;\n readonly productDesc: string;\n readonly ctaText: string;\n readonly mode?: LandingMode;\n}\n\nexport interface SiteFile {\n readonly path: string;\n readonly mime: string;\n readonly bytes: number;\n}\n\nexport interface SitePreview {\n readonly kind: \"static-content-store\";\n readonly entrypoint: string;\n}\n\nexport interface DeployHandoff {\n readonly status: \"handoff\";\n readonly targets: readonly string[];\n readonly manifestPath: string;\n readonly suggestion: string;\n}\n\nexport interface SitePackage {\n readonly tenantId: string;\n readonly play: \"one_pager\";\n readonly brandId: string;\n readonly mode: \"one_pager\";\n readonly files: readonly SiteFile[];\n readonly preview: SitePreview;\n readonly deploy: DeployHandoff;\n readonly eventId: string;\n}\n\nexport interface TailwindTheme {\n readonly cssVariables: string;\n readonly primary: string;\n readonly background: string;\n readonly accent: string;\n}\n\nexport interface LandingBuilderDoctorReport {\n readonly capability: \"landing-builder\";\n readonly ok: boolean;\n readonly checkedAt: string;\n readonly plays: readonly string[];\n readonly modes: readonly LandingMode[];\n readonly deployAdapters: readonly { readonly provider: string; readonly ok: boolean }[];\n readonly suggestion?: string;\n}\n\nexport interface LandingBuilderOptions {\n readonly tenantId?: string;\n readonly root?: string;\n readonly debugRoot?: string;\n readonly contentStore?: ContentStore;\n readonly eventLog?: EventLog;\n}\n\nconst PLAY_NAME = \"one_pager\" as const;\n\nfunction requireTenant(explicit: string | undefined, fallback: string | undefined): string {\n const tenantId = explicit ?? fallback;\n if (!tenantId?.trim()) {\n throw new CapabilityError(\"landing-builder\", \"Landing Builder requires tenant context\", {\n suggestion:\n \"Pass tenantId on the input, BrandContext, or construct LandingBuilder with tenantId.\",\n statusCode: 400,\n });\n }\n return tenantId;\n}\n\nfunction colorByRole(brand: BrandContext, role: string, fallback: string): string {\n return brand.palette.find((color) => color.role === role)?.hex ?? fallback;\n}\n\nfunction escapeHtml(value: string): string {\n return value\n .replaceAll(\"&\", \"&amp;\")\n .replaceAll(\"<\", \"&lt;\")\n .replaceAll(\">\", \"&gt;\")\n .replaceAll('\"', \"&quot;\");\n}\n\nexport function renderTailwindTheme(brand: BrandContext): TailwindTheme {\n const primary = colorByRole(brand, \"primary\", \"#111827\");\n const background = colorByRole(brand, \"background\", \"#FFFFFF\");\n const accent = colorByRole(brand, \"accent\", primary);\n return {\n primary,\n background,\n accent,\n cssVariables: [\n \":root {\",\n ` --brand-primary: ${primary};`,\n ` --brand-background: ${background};`,\n ` --brand-accent: ${accent};`,\n ` --brand-heading: ${brand.typography.heading};`,\n ` --brand-body: ${brand.typography.body};`,\n \"}\",\n \"\",\n ].join(\"\\n\"),\n };\n}\n\nexport function renderOnePagerHtml(input: OnePagerInput): string {\n const { brand } = input;\n const theme = renderTailwindTheme(brand);\n const keywords = brand.visualStyle.keywords.join(\" / \");\n return [\n \"<!doctype html>\",\n '<html lang=\"en\">',\n \"<head>\",\n ' <meta charset=\"utf-8\" />',\n ' <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />',\n ` <title>${escapeHtml(brand.name)}</title>`,\n ' <link rel=\"stylesheet\" href=\"./theme.css\" />',\n \"</head>\",\n `<body data-brand-id=\"${escapeHtml(brand.brandId)}\">`,\n ' <main class=\"page\">',\n ' <section class=\"hero\">',\n ` <p class=\"eyebrow\">${escapeHtml(keywords)}</p>`,\n ` <h1>${escapeHtml(brand.name)}</h1>`,\n ` <p class=\"lead\">${escapeHtml(input.productDesc)}</p>`,\n ` <a class=\"cta\" href=\"#waitlist\">${escapeHtml(input.ctaText)}</a>`,\n \" </section>\",\n ' <section class=\"proof\">',\n \" <h2>Why it matters</h2>\",\n ` <p>${escapeHtml(brand.toneKeywords.join(\", \"))} execution, grounded in one shared brand context.</p>`,\n \" </section>\",\n ' <section id=\"waitlist\" class=\"waitlist\">',\n \" <h2>Join the waitlist</h2>\",\n \" <form>\",\n ' <input aria-label=\"Email\" placeholder=\"you@example.com\" />',\n ' <button type=\"button\">Join</button>',\n \" </form>\",\n \" </section>\",\n \" </main>\",\n \"</body>\",\n \"</html>\",\n `<!-- ${theme.primary} ${theme.accent} -->`,\n \"\",\n ].join(\"\\n\");\n}\n\nfunction renderThemeCss(brand: BrandContext): string {\n const theme = renderTailwindTheme(brand);\n return [\n theme.cssVariables,\n \"* { box-sizing: border-box; }\",\n \"body { margin: 0; background: var(--brand-background); color: color-mix(in oklab, var(--brand-primary) 80%, black); font-family: var(--brand-body), system-ui, sans-serif; }\",\n \".page { min-height: 100vh; }\",\n \".hero { min-height: 82vh; display: grid; align-content: center; gap: 24px; padding: clamp(32px, 8vw, 96px); }\",\n \".eyebrow { color: var(--brand-accent); font-family: var(--brand-heading), monospace; text-transform: uppercase; letter-spacing: 0; }\",\n \"h1 { max-width: 11ch; margin: 0; font-family: var(--brand-heading), system-ui; font-size: clamp(56px, 12vw, 132px); line-height: .92; letter-spacing: 0; }\",\n \".lead { max-width: 760px; font-size: clamp(20px, 3vw, 34px); line-height: 1.2; }\",\n \".cta, button { width: fit-content; border: 0; border-radius: 8px; background: var(--brand-primary); color: var(--brand-background); padding: 14px 20px; font: inherit; text-decoration: none; }\",\n \".proof, .waitlist { padding: 48px clamp(32px, 8vw, 96px); border-top: 1px solid color-mix(in oklab, var(--brand-primary) 20%, transparent); }\",\n \"input { min-width: min(360px, 100%); border: 1px solid color-mix(in oklab, var(--brand-primary) 35%, transparent); border-radius: 8px; padding: 14px 16px; font: inherit; }\",\n \"form { display: flex; flex-wrap: wrap; gap: 12px; }\",\n \"\",\n ].join(\"\\n\");\n}\n\nfunction deployManifest(input: OnePagerInput): string {\n return `${JSON.stringify(\n {\n mode: \"one_pager\",\n brandId: input.brand.brandId,\n entrypoint: \"company/landing/index.html\",\n targets: [\"managed-hosting\", \"edge-pages\", \"static-hosting\", \"git-pages\"],\n requiresExplicitDeployConsent: true,\n },\n null,\n 2,\n )}\\n`;\n}\n\nexport class LandingBuilder {\n readonly #tenantId: string | undefined;\n readonly #debugRoot: string;\n readonly #contentStore: ContentStore;\n readonly #eventLog: EventLog;\n\n private constructor(\n options: LandingBuilderOptions & { contentStore: ContentStore; eventLog: EventLog },\n ) {\n this.#tenantId = options.tenantId;\n this.#debugRoot = options.debugRoot ?? process.cwd();\n this.#contentStore = options.contentStore;\n this.#eventLog = options.eventLog;\n }\n\n static async open(\n root = \".nebutra/landing-builder\",\n options: Omit<LandingBuilderOptions, \"root\" | \"contentStore\" | \"eventLog\"> = {},\n ): Promise<LandingBuilder> {\n const tenantId = options.tenantId ?? \"local\";\n await mkdir(root, { recursive: true });\n const contentStore = await ContentStore.open(join(root, \"content\"), { tenantId });\n const eventLog = await EventLog.open(join(root, \"event-log\"), { tenantId });\n return new LandingBuilder({ ...options, tenantId, root, contentStore, eventLog });\n }\n\n async runOnePager(input: OnePagerInput): Promise<SitePackage> {\n const tenantId = requireTenant(input.tenantId ?? input.brand.tenantId, this.#tenantId);\n const html = renderOnePagerHtml(input);\n const css = renderThemeCss(input.brand);\n const manifest = deployManifest(input);\n const files = [\n { path: \"company/landing/index.html\", mime: \"text/html\", content: html },\n { path: \"company/landing/theme.css\", mime: \"text/css\", content: css },\n { path: \"company/landing/deploy.json\", mime: \"application/json\", content: manifest },\n ] as const;\n for (const file of files) await this.#contentStore.write(file.path, file.content);\n const eventId = await this.#eventLog.commit({\n traceId: assetId(\"landing_builder\", input.brand.brandId),\n kind: \"content_write\",\n affected: files.map((file) => file.path),\n parent: null,\n snapshot: Object.fromEntries(files.map((file) => [file.path, file.content])),\n });\n const result: SitePackage = {\n tenantId,\n play: PLAY_NAME,\n brandId: input.brand.brandId,\n mode: \"one_pager\",\n files: files.map((file) => ({\n path: file.path,\n mime: file.mime,\n bytes: file.content.length,\n })),\n preview: { kind: \"static-content-store\", entrypoint: \"company/landing/index.html\" },\n deploy: {\n status: \"handoff\",\n targets: [\"managed-hosting\", \"edge-pages\", \"static-hosting\", \"git-pages\"],\n manifestPath: \"company/landing/deploy.json\",\n suggestion: \"Connect a deploy integration and call the explicit deploy Play after review.\",\n },\n eventId,\n };\n await this.#debug({ type: \"run\", tenantId, brandId: input.brand.brandId, eventId });\n return result;\n }\n\n async doctor(): Promise<LandingBuilderDoctorReport> {\n return {\n capability: \"landing-builder\",\n ok: true,\n checkedAt: new Date().toISOString(),\n plays: [PLAY_NAME],\n modes: [\"one_pager\", \"marketing_site\", \"webapp_mvp\", \"site_edit\"],\n deployAdapters: [\n { provider: \"static-content-store\", ok: true },\n { provider: \"integration-vault\", ok: false },\n ],\n suggestion:\n \"Static generation is ready; real deploy requires integration-vault deploy adapters.\",\n };\n }\n\n async close(): Promise<void> {\n await this.#contentStore.close();\n }\n\n async #debug(entry: Record<string, unknown>): Promise<void> {\n await mkdir(dirname(join(this.#debugRoot, \".nebutra\", \"debug\", \"landing-builder.jsonl\")), {\n recursive: true,\n });\n await appendCapabilityDebug(\"landing-builder\", entry, { root: this.#debugRoot });\n }\n}\n\nexport async function readLandingBuilderDebug(\n root = process.cwd(),\n limit = 20,\n): Promise<unknown[]> {\n return readCapabilityDebug(\"landing-builder\", { root, limit });\n}\n"],"mappings":"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","names":[]}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/cli.js ADDED
@@ -0,0 +1,62 @@
1
+ import {
2
+ LandingBuilder,
3
+ readLandingBuilderDebug
4
+ } from "./chunk-E4I5POU4.js";
5
+
6
+ // src/cli.ts
7
+ var command = process.argv[2] ?? "doctor";
8
+ var root = process.env.LANDING_BUILDER_ROOT ?? ".nebutra/landing-builder";
9
+ var tenantId = process.env.NEBUTRA_TENANT_ID ?? "local";
10
+ var brand = {
11
+ tenantId,
12
+ brandId: "loop",
13
+ name: "Loop",
14
+ palette: [
15
+ { name: "electric cyan", hex: "#00D4FF", role: "primary" },
16
+ { name: "carbon black", hex: "#111318", role: "background" },
17
+ { name: "signal amber", hex: "#F3C14B", role: "accent" }
18
+ ],
19
+ typography: { heading: "Geist Mono", body: "Geist Sans", accent: "Geist Mono" },
20
+ visualStyle: { name: "cyberpunk", keywords: ["electric", "terminal-native"], avoid: [] },
21
+ referenceImages: [],
22
+ forbidden: [],
23
+ toneKeywords: ["technical", "direct", "warm"],
24
+ sourcePath: "company/BRAND.md"
25
+ };
26
+ if (command === "doctor") {
27
+ const builder = await LandingBuilder.open(root, { tenantId });
28
+ try {
29
+ process.stdout.write(`${JSON.stringify(await builder.doctor(), null, 2)}
30
+ `);
31
+ } finally {
32
+ await builder.close();
33
+ }
34
+ } else if (command === "quickstart") {
35
+ const builder = await LandingBuilder.open(root, { tenantId });
36
+ try {
37
+ process.stdout.write(
38
+ `${JSON.stringify(
39
+ await builder.runOnePager({
40
+ brand,
41
+ productDesc: process.argv.slice(3).join(" ") || "AI debugging for indie devs",
42
+ ctaText: "Join the waitlist"
43
+ }),
44
+ null,
45
+ 2
46
+ )}
47
+ `
48
+ );
49
+ } finally {
50
+ await builder.close();
51
+ }
52
+ } else if (command === "debug") {
53
+ process.stdout.write(
54
+ `${JSON.stringify({ capability: "landing-builder", entries: await readLandingBuilderDebug() }, null, 2)}
55
+ `
56
+ );
57
+ } else {
58
+ process.stderr.write(`Unknown landing-builder command: ${command}
59
+ `);
60
+ process.exitCode = 1;
61
+ }
62
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["import { LandingBuilder, readLandingBuilderDebug } from \"./index\";\n\nconst command = process.argv[2] ?? \"doctor\";\nconst root = process.env.LANDING_BUILDER_ROOT ?? \".nebutra/landing-builder\";\nconst tenantId = process.env.NEBUTRA_TENANT_ID ?? \"local\";\n\nconst brand = {\n tenantId,\n brandId: \"loop\",\n name: \"Loop\",\n palette: [\n { name: \"electric cyan\", hex: \"#00D4FF\", role: \"primary\" },\n { name: \"carbon black\", hex: \"#111318\", role: \"background\" },\n { name: \"signal amber\", hex: \"#F3C14B\", role: \"accent\" },\n ],\n typography: { heading: \"Geist Mono\", body: \"Geist Sans\", accent: \"Geist Mono\" },\n visualStyle: { name: \"cyberpunk\", keywords: [\"electric\", \"terminal-native\"], avoid: [] },\n referenceImages: [],\n forbidden: [],\n toneKeywords: [\"technical\", \"direct\", \"warm\"],\n sourcePath: \"company/BRAND.md\",\n} as const;\n\nif (command === \"doctor\") {\n const builder = await LandingBuilder.open(root, { tenantId });\n try {\n process.stdout.write(`${JSON.stringify(await builder.doctor(), null, 2)}\\n`);\n } finally {\n await builder.close();\n }\n} else if (command === \"quickstart\") {\n const builder = await LandingBuilder.open(root, { tenantId });\n try {\n process.stdout.write(\n `${JSON.stringify(\n await builder.runOnePager({\n brand,\n productDesc: process.argv.slice(3).join(\" \") || \"AI debugging for indie devs\",\n ctaText: \"Join the waitlist\",\n }),\n null,\n 2,\n )}\\n`,\n );\n } finally {\n await builder.close();\n }\n} else if (command === \"debug\") {\n process.stdout.write(\n `${JSON.stringify({ capability: \"landing-builder\", entries: await readLandingBuilderDebug() }, null, 2)}\\n`,\n );\n} else {\n process.stderr.write(`Unknown landing-builder command: ${command}\\n`);\n process.exitCode = 1;\n}\n"],"mappings":";;;;;;AAEA,IAAM,UAAU,QAAQ,KAAK,CAAC,KAAK;AACnC,IAAM,OAAO,QAAQ,IAAI,wBAAwB;AACjD,IAAM,WAAW,QAAQ,IAAI,qBAAqB;AAElD,IAAM,QAAQ;AAAA,EACZ;AAAA,EACA,SAAS;AAAA,EACT,MAAM;AAAA,EACN,SAAS;AAAA,IACP,EAAE,MAAM,iBAAiB,KAAK,WAAW,MAAM,UAAU;AAAA,IACzD,EAAE,MAAM,gBAAgB,KAAK,WAAW,MAAM,aAAa;AAAA,IAC3D,EAAE,MAAM,gBAAgB,KAAK,WAAW,MAAM,SAAS;AAAA,EACzD;AAAA,EACA,YAAY,EAAE,SAAS,cAAc,MAAM,cAAc,QAAQ,aAAa;AAAA,EAC9E,aAAa,EAAE,MAAM,aAAa,UAAU,CAAC,YAAY,iBAAiB,GAAG,OAAO,CAAC,EAAE;AAAA,EACvF,iBAAiB,CAAC;AAAA,EAClB,WAAW,CAAC;AAAA,EACZ,cAAc,CAAC,aAAa,UAAU,MAAM;AAAA,EAC5C,YAAY;AACd;AAEA,IAAI,YAAY,UAAU;AACxB,QAAM,UAAU,MAAM,eAAe,KAAK,MAAM,EAAE,SAAS,CAAC;AAC5D,MAAI;AACF,YAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,MAAM,QAAQ,OAAO,GAAG,MAAM,CAAC,CAAC;AAAA,CAAI;AAAA,EAC7E,UAAE;AACA,UAAM,QAAQ,MAAM;AAAA,EACtB;AACF,WAAW,YAAY,cAAc;AACnC,QAAM,UAAU,MAAM,eAAe,KAAK,MAAM,EAAE,SAAS,CAAC;AAC5D,MAAI;AACF,YAAQ,OAAO;AAAA,MACb,GAAG,KAAK;AAAA,QACN,MAAM,QAAQ,YAAY;AAAA,UACxB;AAAA,UACA,aAAa,QAAQ,KAAK,MAAM,CAAC,EAAE,KAAK,GAAG,KAAK;AAAA,UAChD,SAAS;AAAA,QACX,CAAC;AAAA,QACD;AAAA,QACA;AAAA,MACF,CAAC;AAAA;AAAA,IACH;AAAA,EACF,UAAE;AACA,UAAM,QAAQ,MAAM;AAAA,EACtB;AACF,WAAW,YAAY,SAAS;AAC9B,UAAQ,OAAO;AAAA,IACb,GAAG,KAAK,UAAU,EAAE,YAAY,mBAAmB,SAAS,MAAM,wBAAwB,EAAE,GAAG,MAAM,CAAC,CAAC;AAAA;AAAA,EACzG;AACF,OAAO;AACL,UAAQ,OAAO,MAAM,oCAAoC,OAAO;AAAA,CAAI;AACpE,UAAQ,WAAW;AACrB;","names":[]}
@@ -0,0 +1,75 @@
1
+ import { ContentStore } from '@nebutra/content-store';
2
+ import { EventLog } from '@nebutra/event-log';
3
+ import { BrandContext } from '@nebutra/generation-context';
4
+
5
+ type LandingMode = "one_pager" | "marketing_site" | "webapp_mvp" | "site_edit";
6
+ interface OnePagerInput {
7
+ readonly tenantId?: string;
8
+ readonly brand: BrandContext;
9
+ readonly productDesc: string;
10
+ readonly ctaText: string;
11
+ readonly mode?: LandingMode;
12
+ }
13
+ interface SiteFile {
14
+ readonly path: string;
15
+ readonly mime: string;
16
+ readonly bytes: number;
17
+ }
18
+ interface SitePreview {
19
+ readonly kind: "static-content-store";
20
+ readonly entrypoint: string;
21
+ }
22
+ interface DeployHandoff {
23
+ readonly status: "handoff";
24
+ readonly targets: readonly string[];
25
+ readonly manifestPath: string;
26
+ readonly suggestion: string;
27
+ }
28
+ interface SitePackage {
29
+ readonly tenantId: string;
30
+ readonly play: "one_pager";
31
+ readonly brandId: string;
32
+ readonly mode: "one_pager";
33
+ readonly files: readonly SiteFile[];
34
+ readonly preview: SitePreview;
35
+ readonly deploy: DeployHandoff;
36
+ readonly eventId: string;
37
+ }
38
+ interface TailwindTheme {
39
+ readonly cssVariables: string;
40
+ readonly primary: string;
41
+ readonly background: string;
42
+ readonly accent: string;
43
+ }
44
+ interface LandingBuilderDoctorReport {
45
+ readonly capability: "landing-builder";
46
+ readonly ok: boolean;
47
+ readonly checkedAt: string;
48
+ readonly plays: readonly string[];
49
+ readonly modes: readonly LandingMode[];
50
+ readonly deployAdapters: readonly {
51
+ readonly provider: string;
52
+ readonly ok: boolean;
53
+ }[];
54
+ readonly suggestion?: string;
55
+ }
56
+ interface LandingBuilderOptions {
57
+ readonly tenantId?: string;
58
+ readonly root?: string;
59
+ readonly debugRoot?: string;
60
+ readonly contentStore?: ContentStore;
61
+ readonly eventLog?: EventLog;
62
+ }
63
+ declare function renderTailwindTheme(brand: BrandContext): TailwindTheme;
64
+ declare function renderOnePagerHtml(input: OnePagerInput): string;
65
+ declare class LandingBuilder {
66
+ #private;
67
+ private constructor();
68
+ static open(root?: string, options?: Omit<LandingBuilderOptions, "root" | "contentStore" | "eventLog">): Promise<LandingBuilder>;
69
+ runOnePager(input: OnePagerInput): Promise<SitePackage>;
70
+ doctor(): Promise<LandingBuilderDoctorReport>;
71
+ close(): Promise<void>;
72
+ }
73
+ declare function readLandingBuilderDebug(root?: string, limit?: number): Promise<unknown[]>;
74
+
75
+ export { type DeployHandoff, LandingBuilder, type LandingBuilderDoctorReport, type LandingBuilderOptions, type LandingMode, type OnePagerInput, type SiteFile, type SitePackage, type SitePreview, type TailwindTheme, readLandingBuilderDebug, renderOnePagerHtml, renderTailwindTheme };
package/dist/index.js ADDED
@@ -0,0 +1,13 @@
1
+ import {
2
+ LandingBuilder,
3
+ readLandingBuilderDebug,
4
+ renderOnePagerHtml,
5
+ renderTailwindTheme
6
+ } from "./chunk-E4I5POU4.js";
7
+ export {
8
+ LandingBuilder,
9
+ readLandingBuilderDebug,
10
+ renderOnePagerHtml,
11
+ renderTailwindTheme
12
+ };
13
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,11 @@
1
+ import { LandingBuilder } from "../src/index";
2
+
3
+ const builder = await LandingBuilder.open(".nebutra/landing-builder-example", {
4
+ tenantId: "local",
5
+ });
6
+
7
+ try {
8
+ process.stdout.write(`${JSON.stringify(await builder.doctor(), null, 2)}\n`);
9
+ } finally {
10
+ await builder.close();
11
+ }
@@ -0,0 +1,30 @@
1
+ import { LandingBuilder } from "../src/index";
2
+
3
+ const builder = await LandingBuilder.open(".nebutra/landing-builder-example", {
4
+ tenantId: "local",
5
+ });
6
+
7
+ try {
8
+ const result = await builder.runOnePager({
9
+ brand: {
10
+ tenantId: "local",
11
+ brandId: "loop",
12
+ name: "Loop",
13
+ palette: [
14
+ { name: "electric cyan", hex: "#00D4FF", role: "primary" },
15
+ { name: "carbon black", hex: "#111318", role: "background" },
16
+ ],
17
+ typography: { heading: "Geist Mono", body: "Geist Sans", accent: "Geist Mono" },
18
+ visualStyle: { name: "cyberpunk", keywords: ["electric"], avoid: [] },
19
+ referenceImages: [],
20
+ forbidden: [],
21
+ toneKeywords: ["technical"],
22
+ sourcePath: "company/BRAND.md",
23
+ },
24
+ productDesc: "AI debugging for indie devs",
25
+ ctaText: "Join the waitlist",
26
+ });
27
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
28
+ } finally {
29
+ await builder.close();
30
+ }
@@ -0,0 +1,20 @@
1
+ import { renderTailwindTheme } from "../src/index";
2
+
3
+ process.stdout.write(
4
+ `${JSON.stringify(
5
+ renderTailwindTheme({
6
+ tenantId: "local",
7
+ brandId: "loop",
8
+ name: "Loop",
9
+ palette: [{ name: "electric cyan", hex: "#00D4FF", role: "primary" }],
10
+ typography: { heading: "Geist Mono", body: "Geist Sans", accent: "Geist Mono" },
11
+ visualStyle: { name: "minimal", keywords: ["clear"], avoid: [] },
12
+ referenceImages: [],
13
+ forbidden: [],
14
+ toneKeywords: ["direct"],
15
+ sourcePath: "company/BRAND.md",
16
+ }),
17
+ null,
18
+ 2,
19
+ )}\n`,
20
+ );
package/package.json ADDED
@@ -0,0 +1,63 @@
1
+ {
2
+ "name": "@nebutra/landing-builder",
3
+ "version": "0.1.1",
4
+ "description": "BrandContext-first landing page Play package for generating deployable launch sites",
5
+ "private": false,
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "nebutra": {
9
+ "status": "wip",
10
+ "productionReady": false,
11
+ "surface": "play-product",
12
+ "requires": [
13
+ "@nebutra/play-loader for SKILL.md play parsing",
14
+ "@nebutra/generation-context for BrandContext",
15
+ "@nebutra/content-store for generated site file truth",
16
+ "@nebutra/event-log for Time Machine checkpoints"
17
+ ],
18
+ "gaps": [
19
+ "Preview is a deterministic static artifact until sandbox-backed Vite preview is wired",
20
+ "Deployment is represented as a handoff manifest until integration-vault deploy adapters are available"
21
+ ],
22
+ "featureId": "landing-builder",
23
+ "category": "ai",
24
+ "summary": "Brand-aware landing page Play product over lower content and execution capabilities"
25
+ },
26
+ "main": "./src/index.ts",
27
+ "types": "./src/index.ts",
28
+ "exports": {
29
+ ".": "./src/index.ts"
30
+ },
31
+ "dependencies": {
32
+ "@nebutra/capability-kit": "0.2.0",
33
+ "@nebutra/content-store": "0.1.0",
34
+ "@nebutra/errors": "0.1.0",
35
+ "@nebutra/event-log": "0.1.0",
36
+ "@nebutra/generation-context": "0.1.0",
37
+ "@nebutra/play-loader": "0.1.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^22.19.15",
41
+ "tsup": "^8.5.1",
42
+ "tsx": "^4.21.0",
43
+ "typescript": "^5.9.3",
44
+ "vitest": "^4.1.4"
45
+ },
46
+ "homepage": "https://github.com/Nebutra/Nebutra-Sailor/tree/main/packages/ai/landing-builder#readme",
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "git+https://github.com/Nebutra/Nebutra-Sailor.git",
50
+ "directory": "packages/ai/landing-builder"
51
+ },
52
+ "bugs": {
53
+ "url": "https://github.com/Nebutra/Nebutra-Sailor/issues"
54
+ },
55
+ "publishConfig": {
56
+ "access": "public"
57
+ },
58
+ "scripts": {
59
+ "build": "tsup",
60
+ "test": "vitest run",
61
+ "typecheck": "tsc --noEmit"
62
+ }
63
+ }
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: one_pager
3
+ kind: play
4
+ version: 1.0.0
5
+ description: Build a brand-aware static landing page from BrandContext and a product description
6
+ inputs:
7
+ brand_md: { type: file, path: company/BRAND.md, mime: text/markdown }
8
+ product_desc: { type: string, max_length: 500 }
9
+ cta_text: { type: string, max_length: 80 }
10
+ outputs:
11
+ html: { type: file, path: company/landing/index.html, mime: text/html }
12
+ theme: { type: file, path: company/landing/theme.css, mime: text/css }
13
+ deploy_manifest: { type: file, path: company/landing/deploy.json, mime: application/json }
14
+ budget:
15
+ duration_s: 60
16
+ cost_usd: 0.5
17
+ required_skills:
18
+ - content_store.write
19
+ - content_store.read
20
+ - code_execution.preview
21
+ sub_agents:
22
+ - role: web_builder
23
+ allowed_skills: [content_store.write, code_execution.preview]
24
+ depends_on_plays: []
25
+ ---
26
+
27
+ ## What this play does
28
+
29
+ Builds a single-page launch site from the current BrandContext.
30
+
31
+ The Play owns the site composition, theme assembly, preview handoff, and deploy
32
+ manifest. It does not own sandbox execution, deploy credentials, or the
33
+ BrandContext schema.
34
+
35
+ ## Rules
36
+
37
+ - Read BrandContext first.
38
+ - Use brand colors and typography as the source of truth.
39
+ - Write generated files through content-store.
40
+ - Produce a deploy manifest, but require explicit deploy consent before any
41
+ external provider call.
package/src/cli.ts ADDED
@@ -0,0 +1,55 @@
1
+ import { LandingBuilder, readLandingBuilderDebug } from "./index";
2
+
3
+ const command = process.argv[2] ?? "doctor";
4
+ const root = process.env.LANDING_BUILDER_ROOT ?? ".nebutra/landing-builder";
5
+ const tenantId = process.env.NEBUTRA_TENANT_ID ?? "local";
6
+
7
+ const brand = {
8
+ tenantId,
9
+ brandId: "loop",
10
+ name: "Loop",
11
+ palette: [
12
+ { name: "electric cyan", hex: "#00D4FF", role: "primary" },
13
+ { name: "carbon black", hex: "#111318", role: "background" },
14
+ { name: "signal amber", hex: "#F3C14B", role: "accent" },
15
+ ],
16
+ typography: { heading: "Geist Mono", body: "Geist Sans", accent: "Geist Mono" },
17
+ visualStyle: { name: "cyberpunk", keywords: ["electric", "terminal-native"], avoid: [] },
18
+ referenceImages: [],
19
+ forbidden: [],
20
+ toneKeywords: ["technical", "direct", "warm"],
21
+ sourcePath: "company/BRAND.md",
22
+ } as const;
23
+
24
+ if (command === "doctor") {
25
+ const builder = await LandingBuilder.open(root, { tenantId });
26
+ try {
27
+ process.stdout.write(`${JSON.stringify(await builder.doctor(), null, 2)}\n`);
28
+ } finally {
29
+ await builder.close();
30
+ }
31
+ } else if (command === "quickstart") {
32
+ const builder = await LandingBuilder.open(root, { tenantId });
33
+ try {
34
+ process.stdout.write(
35
+ `${JSON.stringify(
36
+ await builder.runOnePager({
37
+ brand,
38
+ productDesc: process.argv.slice(3).join(" ") || "AI debugging for indie devs",
39
+ ctaText: "Join the waitlist",
40
+ }),
41
+ null,
42
+ 2,
43
+ )}\n`,
44
+ );
45
+ } finally {
46
+ await builder.close();
47
+ }
48
+ } else if (command === "debug") {
49
+ process.stdout.write(
50
+ `${JSON.stringify({ capability: "landing-builder", entries: await readLandingBuilderDebug() }, null, 2)}\n`,
51
+ );
52
+ } else {
53
+ process.stderr.write(`Unknown landing-builder command: ${command}\n`);
54
+ process.exitCode = 1;
55
+ }