@nebutra/landing-builder 0.1.1 → 0.1.2

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.
@@ -1,5 +1,5 @@
1
- {"at":"2026-05-18T16:46:11.319Z","type":"reindex","tenantId":"tenant_a","files":0}
2
- {"at":"2026-05-18T16:46:11.356Z","type":"write","tenantId":"tenant_a","path":"company/landing/index.html"}
3
- {"at":"2026-05-18T16:46:11.370Z","type":"write","tenantId":"tenant_a","path":"company/landing/theme.css"}
4
- {"at":"2026-05-18T16:46:11.378Z","type":"write","tenantId":"tenant_a","path":"company/landing/deploy.json"}
5
- {"at":"2026-05-18T16:46:11.438Z","type":"reindex","tenantId":"","files":0}
1
+ {"at":"2026-05-19T08:15:26.801Z","type":"reindex","tenantId":"tenant_a","files":0}
2
+ {"at":"2026-05-19T08:15:26.871Z","type":"write","tenantId":"tenant_a","path":"company/landing/index.html"}
3
+ {"at":"2026-05-19T08:15:26.902Z","type":"write","tenantId":"tenant_a","path":"company/landing/theme.css"}
4
+ {"at":"2026-05-19T08:15:26.909Z","type":"write","tenantId":"tenant_a","path":"company/landing/deploy.json"}
5
+ {"at":"2026-05-19T08:15:27.113Z","type":"reindex","tenantId":"","files":0}
@@ -1 +1 @@
1
- {"traceId":"landing_builder_loop_mpbfrg6q","kind":"content_write","affected":["company/landing/index.html","company/landing/theme.css","company/landing/deploy.json"],"parent":null,"snapshot":{"company/landing/index.html":"<!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>Loop</title>\n <link rel=\"stylesheet\" href=\"./theme.css\" />\n</head>\n<body data-brand-id=\"loop\">\n <main class=\"page\">\n <section class=\"hero\">\n <p class=\"eyebrow\">electric / terminal-native</p>\n <h1>Loop</h1>\n <p class=\"lead\">AI debugging for indie devs</p>\n <a class=\"cta\" href=\"#waitlist\">Join the waitlist</a>\n </section>\n <section class=\"proof\">\n <h2>Why it matters</h2>\n <p>technical, direct, warm 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<!-- #00D4FF #F3C14B -->\n","company/landing/theme.css":":root {\n --brand-primary: #00D4FF;\n --brand-background: #111318;\n --brand-accent: #F3C14B;\n --brand-heading: Geist Mono;\n --brand-body: Geist Sans;\n}\n\n* { box-sizing: border-box; }\nbody { 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; }\nh1 { 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); }\ninput { 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; }\nform { display: flex; flex-wrap: wrap; gap: 12px; }\n","company/landing/deploy.json":"{\n \"mode\": \"one_pager\",\n \"brandId\": \"loop\",\n \"entrypoint\": \"company/landing/index.html\",\n \"targets\": [\n \"managed-hosting\",\n \"edge-pages\",\n \"static-hosting\",\n \"git-pages\"\n ],\n \"requiresExplicitDeployConsent\": true\n}\n"},"id":"9be5ddcf65b5c7b65abbf3a7f8f60aa47b88f423dfd2d862a2a30ada41e00073","tenantId":"tenant_a","at":"2026-05-18T16:46:11.391Z","summary":"content_write touched 3 path(s)","objectHashes":["282b113584ef5dca1d7d5a8ab0acda009a083ae1d338148e3b5f72d7e2326f0d","efd0c917e24ab7adf2d36be8e9800535dcc7e1c3a8c433826a1a13bb996fe967","2515ff8fb770aeb2584f10e096f9e430af9d9e69022d76f2c96c7feccbfe19eb"]}
1
+ {"at":"2026-05-19T08:15:26.951Z","traceId":"landing_builder_loop_mpccyhfq","kind":"content_write","affected":["company/landing/index.html","company/landing/theme.css","company/landing/deploy.json"],"parent":null,"snapshot":{"company/landing/index.html":"<!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>Loop</title>\n <link rel=\"stylesheet\" href=\"./theme.css\" />\n</head>\n<body data-brand-id=\"loop\">\n <main class=\"page\">\n <section class=\"hero\">\n <p class=\"eyebrow\">electric / terminal-native</p>\n <h1>Loop</h1>\n <p class=\"lead\">AI debugging for indie devs</p>\n <a class=\"cta\" href=\"#waitlist\">Join the waitlist</a>\n </section>\n <section class=\"proof\">\n <h2>Why it matters</h2>\n <p>technical, direct, warm 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<!-- #00D4FF #F3C14B -->\n","company/landing/theme.css":":root {\n --brand-primary: #00D4FF;\n --brand-background: #111318;\n --brand-accent: #F3C14B;\n --brand-heading: Geist Mono;\n --brand-body: Geist Sans;\n}\n\n* { box-sizing: border-box; }\nbody { 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; }\nh1 { 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); }\ninput { 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; }\nform { display: flex; flex-wrap: wrap; gap: 12px; }\n","company/landing/deploy.json":"{\n \"mode\": \"one_pager\",\n \"brandId\": \"loop\",\n \"entrypoint\": \"company/landing/index.html\",\n \"targets\": [\n \"managed-hosting\",\n \"edge-pages\",\n \"static-hosting\",\n \"git-pages\"\n ],\n \"requiresExplicitDeployConsent\": true\n}\n"},"id":"5b8c9ee05fdcc8ad9c0602d11b1f24bc253b4ef8e9cc986882f070f057ca19c5","tenantId":"tenant_a","summary":"content_write touched 3 path(s)","objectHashes":["282b113584ef5dca1d7d5a8ab0acda009a083ae1d338148e3b5f72d7e2326f0d","efd0c917e24ab7adf2d36be8e9800535dcc7e1c3a8c433826a1a13bb996fe967","2515ff8fb770aeb2584f10e096f9e430af9d9e69022d76f2c96c7feccbfe19eb"]}
@@ -1 +1 @@
1
- {"at":"2026-05-18T16:46:11.397Z","type":"run","tenantId":"tenant_a","brandId":"loop","eventId":"9be5ddcf65b5c7b65abbf3a7f8f60aa47b88f423dfd2d862a2a30ada41e00073"}
1
+ {"at":"2026-05-19T08:15:26.970Z","type":"run","tenantId":"tenant_a","brandId":"loop","eventId":"5b8c9ee05fdcc8ad9c0602d11b1f24bc253b4ef8e9cc986882f070f057ca19c5"}
@@ -1,5 +1,5 @@
1
1
 
2
- > @nebutra/landing-builder@0.1.1 build /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/landing-builder
2
+ > @nebutra/landing-builder@0.1.2 build /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/landing-builder
3
3
  > tsup
4
4
 
5
5
  CLI Building entry: src/cli.ts, src/index.ts
@@ -11,12 +11,12 @@
11
11
  ESM Build start
12
12
  ESM dist/cli.js 1.78 KB
13
13
  ESM dist/index.js 262.00 B
14
- ESM dist/chunk-E4I5POU4.js 8.25 KB
14
+ ESM dist/chunk-HQNDYIXK.js 8.25 KB
15
15
  ESM dist/cli.js.map 3.24 KB
16
16
  ESM dist/index.js.map 71.00 B
17
- ESM dist/chunk-E4I5POU4.js.map 15.38 KB
18
- ESM ⚡️ Build success in 36ms
17
+ ESM dist/chunk-HQNDYIXK.js.map 15.31 KB
18
+ ESM ⚡️ Build success in 60ms
19
19
  DTS Build start
20
- DTS ⚡️ Build success in 7646ms
20
+ DTS ⚡️ Build success in 10057ms
21
21
  DTS dist/cli.d.ts 13.00 B
22
22
  DTS dist/index.d.ts 2.64 KB
@@ -1,16 +1,15 @@
1
1
 
2
- > @nebutra/landing-builder@0.1.1 test /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/landing-builder
2
+ > @nebutra/landing-builder@0.1.2 test /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/landing-builder
3
3
  > vitest run
4
4
 
5
5
 
6
6
   RUN  v4.1.4 /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/landing-builder
7
7
 
8
- (node:11922) ExperimentalWarning: SQLite is an experimental feature and might change at any time
9
- (Use `node --trace-warnings ...` to show where the warning was created)
10
- ✓ src/index.test.ts (4 tests) 287ms
8
+ ✓ src/index.test.ts (4 tests) 718ms
9
+ ✓ runs one_pager and writes site files plus a deploy handoff  469ms
11
10
 
12
11
   Test Files  1 passed (1)
13
12
   Tests  4 passed (4)
14
-  Start at  16:46:09
15
-  Duration  2.43s (transform 978ms, setup 0ms, import 1.26s, tests 287ms, environment 0ms)
13
+  Start at  08:15:23
14
+  Duration  3.51s (transform 1.30s, setup 0ms, import 1.73s, tests 718ms, environment 0ms)
16
15
 
@@ -1,4 +1,4 @@
1
1
 
2
- > @nebutra/landing-builder@0.1.1 typecheck /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/landing-builder
2
+ > @nebutra/landing-builder@0.1.2 typecheck /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/landing-builder
3
3
  > tsc --noEmit
4
4
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,19 @@
1
1
  # @nebutra/landing-builder
2
2
 
3
+ ## 0.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - Publish registry package metadata under the MIT license.
8
+
9
+ - Updated dependencies []:
10
+ - @nebutra/capability-kit@0.2.1
11
+ - @nebutra/content-store@0.1.1
12
+ - @nebutra/errors@0.1.1
13
+ - @nebutra/event-log@0.1.1
14
+ - @nebutra/generation-context@0.1.1
15
+ - @nebutra/play-loader@0.1.1
16
+
3
17
  ## 0.1.1
4
18
 
5
19
  ### Patch Changes
@@ -1,22 +1,13 @@
1
1
  // src/index.ts
2
2
  import { mkdir } from "fs/promises";
3
3
  import { dirname, join } from "path";
4
+ import { requireCapabilityTenant } from "@nebutra/capability-kit";
4
5
  import { appendCapabilityDebug, readCapabilityDebug } from "@nebutra/capability-kit/debug";
5
6
  import { ContentStore } from "@nebutra/content-store";
6
7
  import { CapabilityError } from "@nebutra/errors";
7
8
  import { EventLog } from "@nebutra/event-log";
8
9
  import { assetId } from "@nebutra/generation-context";
9
10
  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
11
  function colorByRole(brand, role, fallback) {
21
12
  return brand.palette.find((color) => color.role === role)?.hex ?? fallback;
22
13
  }
@@ -133,7 +124,14 @@ var LandingBuilder = class _LandingBuilder {
133
124
  return new _LandingBuilder({ ...options, tenantId, root, contentStore, eventLog });
134
125
  }
135
126
  async runOnePager(input) {
136
- const tenantId = requireTenant(input.tenantId ?? input.brand.tenantId, this.#tenantId);
127
+ const tenantId = requireCapabilityTenant({
128
+ explicit: input.tenantId ?? input.brand.tenantId,
129
+ fallback: this.#tenantId,
130
+ onMissing: () => new CapabilityError("landing-builder", "Landing Builder requires tenant context", {
131
+ suggestion: "Pass tenantId on the input, BrandContext, or construct LandingBuilder with tenantId.",
132
+ statusCode: 400
133
+ })
134
+ });
137
135
  const html = renderOnePagerHtml(input);
138
136
  const css = renderThemeCss(input.brand);
139
137
  const manifest = deployManifest(input);
@@ -206,4 +204,4 @@ export {
206
204
  LandingBuilder,
207
205
  readLandingBuilderDebug
208
206
  };
209
- //# sourceMappingURL=chunk-E4I5POU4.js.map
207
+ //# sourceMappingURL=chunk-HQNDYIXK.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 { requireCapabilityTenant } from \"@nebutra/capability-kit\";\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 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 = requireCapabilityTenant({\n explicit: input.tenantId ?? input.brand.tenantId,\n fallback: this.#tenantId,\n onMissing: () =>\n 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 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.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  LandingBuilder,
3
3
  readLandingBuilderDebug
4
- } from "./chunk-E4I5POU4.js";
4
+ } from "./chunk-HQNDYIXK.js";
5
5
 
6
6
  // src/cli.ts
7
7
  var command = process.argv[2] ?? "doctor";
package/dist/index.js CHANGED
@@ -3,7 +3,7 @@ import {
3
3
  readLandingBuilderDebug,
4
4
  renderOnePagerHtml,
5
5
  renderTailwindTheme
6
- } from "./chunk-E4I5POU4.js";
6
+ } from "./chunk-HQNDYIXK.js";
7
7
  export {
8
8
  LandingBuilder,
9
9
  readLandingBuilderDebug,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nebutra/landing-builder",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "BrandContext-first landing page Play package for generating deployable launch sites",
5
5
  "private": false,
6
6
  "license": "MIT",
@@ -29,12 +29,12 @@
29
29
  ".": "./src/index.ts"
30
30
  },
31
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"
32
+ "@nebutra/capability-kit": "0.2.1",
33
+ "@nebutra/content-store": "0.1.1",
34
+ "@nebutra/errors": "0.1.1",
35
+ "@nebutra/event-log": "0.1.1",
36
+ "@nebutra/generation-context": "0.1.1",
37
+ "@nebutra/play-loader": "0.1.1"
38
38
  },
39
39
  "devDependencies": {
40
40
  "@types/node": "^22.19.15",
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { mkdir } from "node:fs/promises";
2
2
  import { dirname, join } from "node:path";
3
+ import { requireCapabilityTenant } from "@nebutra/capability-kit";
3
4
  import { appendCapabilityDebug, readCapabilityDebug } from "@nebutra/capability-kit/debug";
4
5
  import { ContentStore } from "@nebutra/content-store";
5
6
  import { CapabilityError } from "@nebutra/errors";
@@ -72,18 +73,6 @@ export interface LandingBuilderOptions {
72
73
 
73
74
  const PLAY_NAME = "one_pager" as const;
74
75
 
75
- function requireTenant(explicit: string | undefined, fallback: string | undefined): string {
76
- const tenantId = explicit ?? fallback;
77
- if (!tenantId?.trim()) {
78
- throw new CapabilityError("landing-builder", "Landing Builder requires tenant context", {
79
- suggestion:
80
- "Pass tenantId on the input, BrandContext, or construct LandingBuilder with tenantId.",
81
- statusCode: 400,
82
- });
83
- }
84
- return tenantId;
85
- }
86
-
87
76
  function colorByRole(brand: BrandContext, role: string, fallback: string): string {
88
77
  return brand.palette.find((color) => color.role === role)?.hex ?? fallback;
89
78
  }
@@ -217,7 +206,16 @@ export class LandingBuilder {
217
206
  }
218
207
 
219
208
  async runOnePager(input: OnePagerInput): Promise<SitePackage> {
220
- const tenantId = requireTenant(input.tenantId ?? input.brand.tenantId, this.#tenantId);
209
+ const tenantId = requireCapabilityTenant({
210
+ explicit: input.tenantId ?? input.brand.tenantId,
211
+ fallback: this.#tenantId,
212
+ onMissing: () =>
213
+ new CapabilityError("landing-builder", "Landing Builder requires tenant context", {
214
+ suggestion:
215
+ "Pass tenantId on the input, BrandContext, or construct LandingBuilder with tenantId.",
216
+ statusCode: 400,
217
+ }),
218
+ });
221
219
  const html = renderOnePagerHtml(input);
222
220
  const css = renderThemeCss(input.brand);
223
221
  const manifest = deployManifest(input);
@@ -1 +0,0 @@
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":[]}