@nebutra/landing-builder 0.1.2 → 0.1.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nebutra/landing-builder",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "BrandContext-first landing page Play package for generating deployable launch sites",
5
5
  "private": false,
6
6
  "license": "MIT",
@@ -23,21 +23,25 @@
23
23
  "category": "ai",
24
24
  "summary": "Brand-aware landing page Play product over lower content and execution capabilities"
25
25
  },
26
- "main": "./src/index.ts",
27
- "types": "./src/index.ts",
26
+ "main": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
28
28
  "exports": {
29
- ".": "./src/index.ts"
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.js",
32
+ "default": "./dist/index.js"
33
+ }
30
34
  },
31
35
  "dependencies": {
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"
36
+ "@nebutra/capability-kit": "0.2.4",
37
+ "@nebutra/errors": "0.1.2",
38
+ "@nebutra/event-log": "0.1.4",
39
+ "@nebutra/generation-context": "0.1.3",
40
+ "@nebutra/content-store": "0.1.4",
41
+ "@nebutra/play-loader": "0.1.3"
38
42
  },
39
43
  "devDependencies": {
40
- "@types/node": "^22.19.15",
44
+ "@types/node": "^25.9.1",
41
45
  "tsup": "^8.5.1",
42
46
  "tsx": "^4.21.0",
43
47
  "typescript": "^5.9.3",
@@ -55,6 +59,12 @@
55
59
  "publishConfig": {
56
60
  "access": "public"
57
61
  },
62
+ "files": [
63
+ "dist",
64
+ "README.md",
65
+ "LICENSE",
66
+ "CHANGELOG.md"
67
+ ],
58
68
  "scripts": {
59
69
  "build": "tsup",
60
70
  "test": "vitest run",
@@ -1,5 +0,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 +0,0 @@
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 +0,0 @@
1
- {"at":"2026-05-19T08:15:26.970Z","type":"run","tenantId":"tenant_a","brandId":"loop","eventId":"5b8c9ee05fdcc8ad9c0602d11b1f24bc253b4ef8e9cc986882f070f057ca19c5"}
@@ -1,22 +0,0 @@
1
-
2
- > @nebutra/landing-builder@0.1.2 build /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/landing-builder
3
- > tsup
4
-
5
- CLI Building entry: src/cli.ts, src/index.ts
6
- CLI Using tsconfig: tsconfig.json
7
- CLI tsup v8.5.1
8
- CLI Using tsup config: /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/landing-builder/tsup.config.ts
9
- CLI Target: es2022
10
- CLI Cleaning output folder
11
- ESM Build start
12
- ESM dist/cli.js 1.78 KB
13
- ESM dist/index.js 262.00 B
14
- ESM dist/chunk-HQNDYIXK.js 8.25 KB
15
- ESM dist/cli.js.map 3.24 KB
16
- ESM dist/index.js.map 71.00 B
17
- ESM dist/chunk-HQNDYIXK.js.map 15.31 KB
18
- ESM ⚡️ Build success in 60ms
19
- DTS Build start
20
- DTS ⚡️ Build success in 10057ms
21
- DTS dist/cli.d.ts 13.00 B
22
- DTS dist/index.d.ts 2.64 KB
@@ -1,15 +0,0 @@
1
-
2
- > @nebutra/landing-builder@0.1.2 test /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/landing-builder
3
- > vitest run
4
-
5
-
6
-  RUN  v4.1.4 /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/landing-builder
7
-
8
- ✓ src/index.test.ts (4 tests) 718ms
9
- ✓ runs one_pager and writes site files plus a deploy handoff  469ms
10
-
11
-  Test Files  1 passed (1)
12
-  Tests  4 passed (4)
13
-  Start at  08:15:23
14
-  Duration  3.51s (transform 1.30s, setup 0ms, import 1.73s, tests 718ms, environment 0ms)
15
-
@@ -1,4 +0,0 @@
1
-
2
- > @nebutra/landing-builder@0.1.2 typecheck /home/runner/work/Nebutra-Sailor/Nebutra-Sailor/packages/ai/landing-builder
3
- > tsc --noEmit
4
-
@@ -1,11 +0,0 @@
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
- }
@@ -1,30 +0,0 @@
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
- }
package/examples/theme.ts DELETED
@@ -1,20 +0,0 @@
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
- );
@@ -1,41 +0,0 @@
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 DELETED
@@ -1,55 +0,0 @@
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
- }
package/src/index.test.ts DELETED
@@ -1,112 +0,0 @@
1
- import { mkdtemp, readFile, rm } from "node:fs/promises";
2
- import { tmpdir } from "node:os";
3
- import { join } from "node:path";
4
- import { parsePlayMarkdown } from "@nebutra/play-loader";
5
- import { afterEach, describe, expect, it } from "vitest";
6
- import {
7
- LandingBuilder,
8
- readLandingBuilderDebug,
9
- renderOnePagerHtml,
10
- renderTailwindTheme,
11
- } from "./index";
12
-
13
- let root: string | undefined;
14
- let builder: LandingBuilder | undefined;
15
-
16
- const brand = {
17
- tenantId: "tenant_a",
18
- brandId: "loop",
19
- name: "Loop",
20
- palette: [
21
- { name: "electric cyan", hex: "#00D4FF", role: "primary" },
22
- { name: "carbon black", hex: "#111318", role: "background" },
23
- { name: "signal amber", hex: "#F3C14B", role: "accent" },
24
- ],
25
- typography: { heading: "Geist Mono", body: "Geist Sans", accent: "Geist Mono" },
26
- visualStyle: {
27
- name: "cyberpunk",
28
- keywords: ["electric", "terminal-native"],
29
- avoid: ["generic stock"],
30
- },
31
- referenceImages: [],
32
- forbidden: ["generic stock"],
33
- toneKeywords: ["technical", "direct", "warm"],
34
- sourcePath: "company/BRAND.md",
35
- } as const;
36
-
37
- afterEach(async () => {
38
- if (builder) await builder.close();
39
- if (root) await rm(root, { recursive: true, force: true });
40
- root = undefined;
41
- builder = undefined;
42
- });
43
-
44
- async function open(): Promise<LandingBuilder> {
45
- root = await mkdtemp(join(tmpdir(), "landing-builder-"));
46
- builder = await LandingBuilder.open(root, { tenantId: "tenant_a" });
47
- return builder;
48
- }
49
-
50
- describe("landing-builder", () => {
51
- it("renders a brand-aware one pager without owning BrandContext", () => {
52
- const theme = renderTailwindTheme(brand);
53
- const html = renderOnePagerHtml({
54
- brand,
55
- productDesc: "AI debugging for indie devs",
56
- ctaText: "Join the waitlist",
57
- });
58
-
59
- expect(theme.cssVariables).toContain("--brand-primary: #00D4FF");
60
- expect(html).toContain("Loop");
61
- expect(html).toContain("AI debugging for indie devs");
62
- expect(html).toContain("Join the waitlist");
63
- expect(html).toContain('data-brand-id="loop"');
64
- });
65
-
66
- it("runs one_pager and writes site files plus a deploy handoff", async () => {
67
- const runtime = await open();
68
-
69
- const result = await runtime.runOnePager({
70
- brand,
71
- productDesc: "AI debugging for indie devs",
72
- ctaText: "Join the waitlist",
73
- });
74
-
75
- expect(result.play).toBe("one_pager");
76
- expect(result.files.map((file) => file.path)).toEqual([
77
- "company/landing/index.html",
78
- "company/landing/theme.css",
79
- "company/landing/deploy.json",
80
- ]);
81
- expect(result.preview.kind).toBe("static-content-store");
82
- expect(result.deploy.status).toBe("handoff");
83
- expect(result.eventId).toEqual(expect.any(String));
84
- await expect(readLandingBuilderDebug(root)).resolves.toEqual(expect.any(Array));
85
- });
86
-
87
- it("requires tenant context before persistent operations", async () => {
88
- root = await mkdtemp(join(tmpdir(), "landing-builder-"));
89
- const runtime = await LandingBuilder.open(root, { tenantId: "" });
90
- builder = runtime;
91
-
92
- await expect(
93
- runtime.runOnePager({
94
- brand: { ...brand, tenantId: "" },
95
- productDesc: "AI debugging",
96
- ctaText: "Join",
97
- }),
98
- ).rejects.toMatchObject({
99
- capability: "landing-builder",
100
- suggestion: expect.stringContaining("tenantId"),
101
- });
102
- });
103
-
104
- it("keeps the landing Play as SKILL.md instead of a new workflow format", async () => {
105
- const skill = await readFile(join(process.cwd(), "plays", "one_pager", "SKILL.md"), "utf8");
106
- const play = parsePlayMarkdown(skill);
107
-
108
- expect(play.meta).toMatchObject({ name: "one_pager", kind: "play", version: "1.0.0" });
109
- expect(play.requiredSkills).toContain("content_store.write");
110
- expect(play.subAgents.map((agent) => agent.role)).toContain("web_builder");
111
- });
112
- });