@nebutra/landing-builder 0.1.3 → 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 +19 -9
- package/.nebutra/debug/content-store.jsonl +0 -40
- package/.nebutra/debug/event-log.jsonl +0 -8
- package/.nebutra/debug/landing-builder.jsonl +0 -8
- package/.turbo/turbo-build.log +0 -22
- package/.turbo/turbo-test.log +0 -16
- package/.turbo/turbo-typecheck.log +0 -4
- package/examples/doctor.ts +0 -11
- package/examples/quickstart.ts +0 -30
- package/examples/theme.ts +0 -20
- package/plays/one_pager/SKILL.md +0 -41
- package/src/cli.ts +0 -55
- package/src/index.test.ts +0 -112
- package/src/index.ts +0 -291
- package/tsconfig.json +0 -9
- package/tsup.config.ts +0 -11
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nebutra/landing-builder",
|
|
3
|
-
"version": "0.1.
|
|
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,18 +23,22 @@
|
|
|
23
23
|
"category": "ai",
|
|
24
24
|
"summary": "Brand-aware landing page Play product over lower content and execution capabilities"
|
|
25
25
|
},
|
|
26
|
-
"main": "./
|
|
27
|
-
"types": "./
|
|
26
|
+
"main": "./dist/index.js",
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
28
|
"exports": {
|
|
29
|
-
".":
|
|
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.
|
|
36
|
+
"@nebutra/capability-kit": "0.2.4",
|
|
33
37
|
"@nebutra/errors": "0.1.2",
|
|
34
|
-
"@nebutra/event-log": "0.1.
|
|
35
|
-
"@nebutra/
|
|
36
|
-
"@nebutra/
|
|
37
|
-
"@nebutra/play-loader": "0.1.
|
|
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
44
|
"@types/node": "^25.9.1",
|
|
@@ -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,40 +0,0 @@
|
|
|
1
|
-
{"at":"2026-05-18T14:42:47.511Z","type":"reindex","tenantId":"tenant_a","files":0}
|
|
2
|
-
{"at":"2026-05-18T14:42:47.514Z","type":"write","tenantId":"tenant_a","path":"company/landing/index.html"}
|
|
3
|
-
{"at":"2026-05-18T14:42:47.515Z","type":"write","tenantId":"tenant_a","path":"company/landing/theme.css"}
|
|
4
|
-
{"at":"2026-05-18T14:42:47.516Z","type":"write","tenantId":"tenant_a","path":"company/landing/deploy.json"}
|
|
5
|
-
{"at":"2026-05-18T14:42:47.526Z","type":"reindex","tenantId":"","files":0}
|
|
6
|
-
{"at":"2026-05-18T14:43:18.013Z","type":"reindex","tenantId":"tenant_a","files":0}
|
|
7
|
-
{"at":"2026-05-18T14:43:18.017Z","type":"write","tenantId":"tenant_a","path":"company/landing/index.html"}
|
|
8
|
-
{"at":"2026-05-18T14:43:18.018Z","type":"write","tenantId":"tenant_a","path":"company/landing/theme.css"}
|
|
9
|
-
{"at":"2026-05-18T14:43:18.020Z","type":"write","tenantId":"tenant_a","path":"company/landing/deploy.json"}
|
|
10
|
-
{"at":"2026-05-18T14:43:18.032Z","type":"reindex","tenantId":"","files":0}
|
|
11
|
-
{"at":"2026-05-18T14:46:58.855Z","type":"reindex","tenantId":"tenant_a","files":0}
|
|
12
|
-
{"at":"2026-05-18T14:46:58.866Z","type":"write","tenantId":"tenant_a","path":"company/landing/index.html"}
|
|
13
|
-
{"at":"2026-05-18T14:46:58.869Z","type":"write","tenantId":"tenant_a","path":"company/landing/theme.css"}
|
|
14
|
-
{"at":"2026-05-18T14:46:58.872Z","type":"write","tenantId":"tenant_a","path":"company/landing/deploy.json"}
|
|
15
|
-
{"at":"2026-05-18T14:46:58.893Z","type":"reindex","tenantId":"","files":0}
|
|
16
|
-
{"at":"2026-05-18T14:49:15.263Z","type":"reindex","tenantId":"tenant_a","files":0}
|
|
17
|
-
{"at":"2026-05-18T14:49:15.269Z","type":"write","tenantId":"tenant_a","path":"company/landing/index.html"}
|
|
18
|
-
{"at":"2026-05-18T14:49:15.271Z","type":"write","tenantId":"tenant_a","path":"company/landing/theme.css"}
|
|
19
|
-
{"at":"2026-05-18T14:49:15.273Z","type":"write","tenantId":"tenant_a","path":"company/landing/deploy.json"}
|
|
20
|
-
{"at":"2026-05-18T14:49:15.293Z","type":"reindex","tenantId":"","files":0}
|
|
21
|
-
{"at":"2026-05-18T14:52:55.141Z","type":"reindex","tenantId":"tenant_a","files":0}
|
|
22
|
-
{"at":"2026-05-18T14:52:55.144Z","type":"write","tenantId":"tenant_a","path":"company/landing/index.html"}
|
|
23
|
-
{"at":"2026-05-18T14:52:55.145Z","type":"write","tenantId":"tenant_a","path":"company/landing/theme.css"}
|
|
24
|
-
{"at":"2026-05-18T14:52:55.146Z","type":"write","tenantId":"tenant_a","path":"company/landing/deploy.json"}
|
|
25
|
-
{"at":"2026-05-18T14:52:55.155Z","type":"reindex","tenantId":"","files":0}
|
|
26
|
-
{"at":"2026-05-19T05:25:17.688Z","type":"reindex","tenantId":"tenant_a","files":0}
|
|
27
|
-
{"at":"2026-05-19T05:25:17.691Z","type":"write","tenantId":"tenant_a","path":"company/landing/index.html"}
|
|
28
|
-
{"at":"2026-05-19T05:25:17.693Z","type":"write","tenantId":"tenant_a","path":"company/landing/theme.css"}
|
|
29
|
-
{"at":"2026-05-19T05:25:17.694Z","type":"write","tenantId":"tenant_a","path":"company/landing/deploy.json"}
|
|
30
|
-
{"at":"2026-05-19T05:25:17.703Z","type":"reindex","tenantId":"","files":0}
|
|
31
|
-
{"at":"2026-05-30T21:07:17.374Z","type":"reindex","tenantId":"tenant_a","files":0}
|
|
32
|
-
{"at":"2026-05-30T21:07:17.382Z","type":"write","tenantId":"tenant_a","path":"company/landing/index.html"}
|
|
33
|
-
{"at":"2026-05-30T21:07:17.386Z","type":"write","tenantId":"tenant_a","path":"company/landing/theme.css"}
|
|
34
|
-
{"at":"2026-05-30T21:07:17.388Z","type":"write","tenantId":"tenant_a","path":"company/landing/deploy.json"}
|
|
35
|
-
{"at":"2026-05-30T21:07:17.448Z","type":"reindex","tenantId":"","files":0}
|
|
36
|
-
{"at":"2026-07-01T01:40:46.262Z","type":"reindex","tenantId":"tenant_a","files":0}
|
|
37
|
-
{"at":"2026-07-01T01:40:46.284Z","type":"write","tenantId":"tenant_a","path":"company/landing/index.html"}
|
|
38
|
-
{"at":"2026-07-01T01:40:46.329Z","type":"write","tenantId":"tenant_a","path":"company/landing/theme.css"}
|
|
39
|
-
{"at":"2026-07-01T01:40:46.378Z","type":"write","tenantId":"tenant_a","path":"company/landing/deploy.json"}
|
|
40
|
-
{"at":"2026-07-01T01:40:46.507Z","type":"reindex","tenantId":"","files":0}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
{"traceId":"landing_builder_loop_mpbbcrbw","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 \"vercel\",\n \"cloudflare-pages\",\n \"netlify\",\n \"github-pages\"\n ],\n \"requiresExplicitDeployConsent\": true\n}\n"},"id":"59f24dbcb4753402b25ac045305e33fdff189a812473d263290497d1a92af42b","tenantId":"tenant_a","at":"2026-05-18T14:42:47.519Z","summary":"content_write touched 3 path(s)","objectHashes":["282b113584ef5dca1d7d5a8ab0acda009a083ae1d338148e3b5f72d7e2326f0d","efd0c917e24ab7adf2d36be8e9800535dcc7e1c3a8c433826a1a13bb996fe967","b0a2d284a6a62fd9612c675940dea8b0500e80f55826df8038be9f1d0dcc5553"]}
|
|
2
|
-
{"traceId":"landing_builder_loop_mpbbdev8","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 \"vercel\",\n \"cloudflare-pages\",\n \"netlify\",\n \"github-pages\"\n ],\n \"requiresExplicitDeployConsent\": true\n}\n"},"id":"2e8eaffaee9ba561f6e390dfc5bbbbbd6ba74bbcefde97e51d6675b5e8b7ac6c","tenantId":"tenant_a","at":"2026-05-18T14:43:18.023Z","summary":"content_write touched 3 path(s)","objectHashes":["282b113584ef5dca1d7d5a8ab0acda009a083ae1d338148e3b5f72d7e2326f0d","efd0c917e24ab7adf2d36be8e9800535dcc7e1c3a8c433826a1a13bb996fe967","b0a2d284a6a62fd9612c675940dea8b0500e80f55826df8038be9f1d0dcc5553"]}
|
|
3
|
-
{"traceId":"landing_builder_loop_mpbbi5a0","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 \"vercel\",\n \"cloudflare-pages\",\n \"netlify\",\n \"github-pages\"\n ],\n \"requiresExplicitDeployConsent\": true\n}\n"},"id":"19c4b0d402fb1f5ef26dfb9823ae1a23d64d9b730927b7e7842cafeffd3a115b","tenantId":"tenant_a","at":"2026-05-18T14:46:58.878Z","summary":"content_write touched 3 path(s)","objectHashes":["282b113584ef5dca1d7d5a8ab0acda009a083ae1d338148e3b5f72d7e2326f0d","efd0c917e24ab7adf2d36be8e9800535dcc7e1c3a8c433826a1a13bb996fe967","b0a2d284a6a62fd9612c675940dea8b0500e80f55826df8038be9f1d0dcc5553"]}
|
|
4
|
-
{"traceId":"landing_builder_loop_mpbbl2iy","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 \"vercel\",\n \"cloudflare-pages\",\n \"netlify\",\n \"github-pages\"\n ],\n \"requiresExplicitDeployConsent\": true\n}\n"},"id":"ff38936be8641f026dbdc5cf0700664cf59887202f1f5906195b63bd6f103f78","tenantId":"tenant_a","at":"2026-05-18T14:49:15.280Z","summary":"content_write touched 3 path(s)","objectHashes":["282b113584ef5dca1d7d5a8ab0acda009a083ae1d338148e3b5f72d7e2326f0d","efd0c917e24ab7adf2d36be8e9800535dcc7e1c3a8c433826a1a13bb996fe967","b0a2d284a6a62fd9612c675940dea8b0500e80f55826df8038be9f1d0dcc5553"]}
|
|
5
|
-
{"traceId":"landing_builder_loop_mpbbps6i","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":"bb6be22b35e48ce70c44ab8ce228ceef6799500c15e0210df7ad2c74ffacb08a","tenantId":"tenant_a","at":"2026-05-18T14:52:55.148Z","summary":"content_write touched 3 path(s)","objectHashes":["282b113584ef5dca1d7d5a8ab0acda009a083ae1d338148e3b5f72d7e2326f0d","efd0c917e24ab7adf2d36be8e9800535dcc7e1c3a8c433826a1a13bb996fe967","2515ff8fb770aeb2584f10e096f9e430af9d9e69022d76f2c96c7feccbfe19eb"]}
|
|
6
|
-
{"traceId":"landing_builder_loop_mpc6vny7","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":"8935725d51add1578c9945e8248c0e141b8b3aeb421dcbb50b33cddbf2bbe4d0","tenantId":"tenant_a","at":"2026-05-19T05:25:17.697Z","summary":"content_write touched 3 path(s)","objectHashes":["282b113584ef5dca1d7d5a8ab0acda009a083ae1d338148e3b5f72d7e2326f0d","efd0c917e24ab7adf2d36be8e9800535dcc7e1c3a8c433826a1a13bb996fe967","2515ff8fb770aeb2584f10e096f9e430af9d9e69022d76f2c96c7feccbfe19eb"]}
|
|
7
|
-
{"at":"2026-05-30T21:07:17.398Z","traceId":"landing_builder_loop_mpsudg5p","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":"9d209c1b0e8a78ca67fe3eafe642690c009c488ddd434800de1b5f322bef7310","tenantId":"tenant_a","summary":"content_write touched 3 path(s)","objectHashes":["282b113584ef5dca1d7d5a8ab0acda009a083ae1d338148e3b5f72d7e2326f0d","efd0c917e24ab7adf2d36be8e9800535dcc7e1c3a8c433826a1a13bb996fe967","2515ff8fb770aeb2584f10e096f9e430af9d9e69022d76f2c96c7feccbfe19eb"]}
|
|
8
|
-
{"at":"2026-07-01T01:40:46.432Z","traceId":"landing_builder_loop_mr1esk36","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":"66470bfcf7d8648e3f6f56c26e1d53d84c3f7d1ffff5338c83beab8518e85505","tenantId":"tenant_a","summary":"content_write touched 3 path(s)","objectHashes":["282b113584ef5dca1d7d5a8ab0acda009a083ae1d338148e3b5f72d7e2326f0d","efd0c917e24ab7adf2d36be8e9800535dcc7e1c3a8c433826a1a13bb996fe967","2515ff8fb770aeb2584f10e096f9e430af9d9e69022d76f2c96c7feccbfe19eb"]}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
{"at":"2026-05-18T14:42:47.520Z","type":"run","tenantId":"tenant_a","brandId":"loop","eventId":"59f24dbcb4753402b25ac045305e33fdff189a812473d263290497d1a92af42b"}
|
|
2
|
-
{"at":"2026-05-18T14:43:18.024Z","type":"run","tenantId":"tenant_a","brandId":"loop","eventId":"2e8eaffaee9ba561f6e390dfc5bbbbbd6ba74bbcefde97e51d6675b5e8b7ac6c"}
|
|
3
|
-
{"at":"2026-05-18T14:46:58.880Z","type":"run","tenantId":"tenant_a","brandId":"loop","eventId":"19c4b0d402fb1f5ef26dfb9823ae1a23d64d9b730927b7e7842cafeffd3a115b"}
|
|
4
|
-
{"at":"2026-05-18T14:49:15.282Z","type":"run","tenantId":"tenant_a","brandId":"loop","eventId":"ff38936be8641f026dbdc5cf0700664cf59887202f1f5906195b63bd6f103f78"}
|
|
5
|
-
{"at":"2026-05-18T14:52:55.149Z","type":"run","tenantId":"tenant_a","brandId":"loop","eventId":"bb6be22b35e48ce70c44ab8ce228ceef6799500c15e0210df7ad2c74ffacb08a"}
|
|
6
|
-
{"at":"2026-05-19T05:25:17.698Z","type":"run","tenantId":"tenant_a","brandId":"loop","eventId":"8935725d51add1578c9945e8248c0e141b8b3aeb421dcbb50b33cddbf2bbe4d0"}
|
|
7
|
-
{"at":"2026-05-30T21:07:17.404Z","type":"run","tenantId":"tenant_a","brandId":"loop","eventId":"9d209c1b0e8a78ca67fe3eafe642690c009c488ddd434800de1b5f322bef7310"}
|
|
8
|
-
{"at":"2026-07-01T01:40:46.445Z","type":"run","tenantId":"tenant_a","brandId":"loop","eventId":"66470bfcf7d8648e3f6f56c26e1d53d84c3f7d1ffff5338c83beab8518e85505"}
|
package/.turbo/turbo-build.log
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> @nebutra/landing-builder@0.1.3 build /Users/tseka_luk/Documents/Nebutra-SaaS-Lab/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: /Users/tseka_luk/Documents/Nebutra-SaaS-Lab/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/index.js 262.00 B
|
|
13
|
-
ESM dist/cli.js 1.78 KB
|
|
14
|
-
ESM dist/chunk-HQNDYIXK.js 8.25 KB
|
|
15
|
-
ESM dist/index.js.map 71.00 B
|
|
16
|
-
ESM dist/cli.js.map 3.24 KB
|
|
17
|
-
ESM dist/chunk-HQNDYIXK.js.map 15.31 KB
|
|
18
|
-
ESM ⚡️ Build success in 14ms
|
|
19
|
-
DTS Build start
|
|
20
|
-
DTS ⚡️ Build success in 1644ms
|
|
21
|
-
DTS dist/cli.d.ts 13.00 B
|
|
22
|
-
DTS dist/index.d.ts 2.64 KB
|
package/.turbo/turbo-test.log
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
> @nebutra/landing-builder@0.1.2 test /Users/tseka_luk/Documents/Nebutra-SaaS-Lab/Nebutra-Sailor/packages/ai/landing-builder
|
|
3
|
-
> vitest run
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
RUN v4.1.4 /Users/tseka_luk/Documents/Nebutra-SaaS-Lab/Nebutra-Sailor/packages/ai/landing-builder
|
|
7
|
-
|
|
8
|
-
(node:57824) 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) 281ms
|
|
11
|
-
|
|
12
|
-
Test Files 1 passed (1)
|
|
13
|
-
Tests 4 passed (4)
|
|
14
|
-
Start at 09:40:45
|
|
15
|
-
Duration 1.35s (transform 350ms, setup 0ms, import 572ms, tests 281ms, environment 0ms)
|
|
16
|
-
|
package/examples/doctor.ts
DELETED
|
@@ -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
|
-
}
|
package/examples/quickstart.ts
DELETED
|
@@ -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
|
-
);
|
package/plays/one_pager/SKILL.md
DELETED
|
@@ -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
|
-
});
|
package/src/index.ts
DELETED
|
@@ -1,291 +0,0 @@
|
|
|
1
|
-
import { mkdir } from "node:fs/promises";
|
|
2
|
-
import { dirname, join } from "node:path";
|
|
3
|
-
import { requireCapabilityTenant } from "@nebutra/capability-kit";
|
|
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, type BrandContext } from "@nebutra/generation-context";
|
|
9
|
-
|
|
10
|
-
export type LandingMode = "one_pager" | "marketing_site" | "webapp_mvp" | "site_edit";
|
|
11
|
-
|
|
12
|
-
export interface OnePagerInput {
|
|
13
|
-
readonly tenantId?: string;
|
|
14
|
-
readonly brand: BrandContext;
|
|
15
|
-
readonly productDesc: string;
|
|
16
|
-
readonly ctaText: string;
|
|
17
|
-
readonly mode?: LandingMode;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface SiteFile {
|
|
21
|
-
readonly path: string;
|
|
22
|
-
readonly mime: string;
|
|
23
|
-
readonly bytes: number;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface SitePreview {
|
|
27
|
-
readonly kind: "static-content-store";
|
|
28
|
-
readonly entrypoint: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface DeployHandoff {
|
|
32
|
-
readonly status: "handoff";
|
|
33
|
-
readonly targets: readonly string[];
|
|
34
|
-
readonly manifestPath: string;
|
|
35
|
-
readonly suggestion: string;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface SitePackage {
|
|
39
|
-
readonly tenantId: string;
|
|
40
|
-
readonly play: "one_pager";
|
|
41
|
-
readonly brandId: string;
|
|
42
|
-
readonly mode: "one_pager";
|
|
43
|
-
readonly files: readonly SiteFile[];
|
|
44
|
-
readonly preview: SitePreview;
|
|
45
|
-
readonly deploy: DeployHandoff;
|
|
46
|
-
readonly eventId: string;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export interface TailwindTheme {
|
|
50
|
-
readonly cssVariables: string;
|
|
51
|
-
readonly primary: string;
|
|
52
|
-
readonly background: string;
|
|
53
|
-
readonly accent: string;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface LandingBuilderDoctorReport {
|
|
57
|
-
readonly capability: "landing-builder";
|
|
58
|
-
readonly ok: boolean;
|
|
59
|
-
readonly checkedAt: string;
|
|
60
|
-
readonly plays: readonly string[];
|
|
61
|
-
readonly modes: readonly LandingMode[];
|
|
62
|
-
readonly deployAdapters: readonly { readonly provider: string; readonly ok: boolean }[];
|
|
63
|
-
readonly suggestion?: string;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export interface LandingBuilderOptions {
|
|
67
|
-
readonly tenantId?: string;
|
|
68
|
-
readonly root?: string;
|
|
69
|
-
readonly debugRoot?: string;
|
|
70
|
-
readonly contentStore?: ContentStore;
|
|
71
|
-
readonly eventLog?: EventLog;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const PLAY_NAME = "one_pager" as const;
|
|
75
|
-
|
|
76
|
-
function colorByRole(brand: BrandContext, role: string, fallback: string): string {
|
|
77
|
-
return brand.palette.find((color) => color.role === role)?.hex ?? fallback;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
function escapeHtml(value: string): string {
|
|
81
|
-
return value
|
|
82
|
-
.replaceAll("&", "&")
|
|
83
|
-
.replaceAll("<", "<")
|
|
84
|
-
.replaceAll(">", ">")
|
|
85
|
-
.replaceAll('"', """);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
export function renderTailwindTheme(brand: BrandContext): TailwindTheme {
|
|
89
|
-
const primary = colorByRole(brand, "primary", "#111827");
|
|
90
|
-
const background = colorByRole(brand, "background", "#FFFFFF");
|
|
91
|
-
const accent = colorByRole(brand, "accent", primary);
|
|
92
|
-
return {
|
|
93
|
-
primary,
|
|
94
|
-
background,
|
|
95
|
-
accent,
|
|
96
|
-
cssVariables: [
|
|
97
|
-
":root {",
|
|
98
|
-
` --brand-primary: ${primary};`,
|
|
99
|
-
` --brand-background: ${background};`,
|
|
100
|
-
` --brand-accent: ${accent};`,
|
|
101
|
-
` --brand-heading: ${brand.typography.heading};`,
|
|
102
|
-
` --brand-body: ${brand.typography.body};`,
|
|
103
|
-
"}",
|
|
104
|
-
"",
|
|
105
|
-
].join("\n"),
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
export function renderOnePagerHtml(input: OnePagerInput): string {
|
|
110
|
-
const { brand } = input;
|
|
111
|
-
const theme = renderTailwindTheme(brand);
|
|
112
|
-
const keywords = brand.visualStyle.keywords.join(" / ");
|
|
113
|
-
return [
|
|
114
|
-
"<!doctype html>",
|
|
115
|
-
'<html lang="en">',
|
|
116
|
-
"<head>",
|
|
117
|
-
' <meta charset="utf-8" />',
|
|
118
|
-
' <meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
119
|
-
` <title>${escapeHtml(brand.name)}</title>`,
|
|
120
|
-
' <link rel="stylesheet" href="./theme.css" />',
|
|
121
|
-
"</head>",
|
|
122
|
-
`<body data-brand-id="${escapeHtml(brand.brandId)}">`,
|
|
123
|
-
' <main class="page">',
|
|
124
|
-
' <section class="hero">',
|
|
125
|
-
` <p class="eyebrow">${escapeHtml(keywords)}</p>`,
|
|
126
|
-
` <h1>${escapeHtml(brand.name)}</h1>`,
|
|
127
|
-
` <p class="lead">${escapeHtml(input.productDesc)}</p>`,
|
|
128
|
-
` <a class="cta" href="#waitlist">${escapeHtml(input.ctaText)}</a>`,
|
|
129
|
-
" </section>",
|
|
130
|
-
' <section class="proof">',
|
|
131
|
-
" <h2>Why it matters</h2>",
|
|
132
|
-
` <p>${escapeHtml(brand.toneKeywords.join(", "))} execution, grounded in one shared brand context.</p>`,
|
|
133
|
-
" </section>",
|
|
134
|
-
' <section id="waitlist" class="waitlist">',
|
|
135
|
-
" <h2>Join the waitlist</h2>",
|
|
136
|
-
" <form>",
|
|
137
|
-
' <input aria-label="Email" placeholder="you@example.com" />',
|
|
138
|
-
' <button type="button">Join</button>',
|
|
139
|
-
" </form>",
|
|
140
|
-
" </section>",
|
|
141
|
-
" </main>",
|
|
142
|
-
"</body>",
|
|
143
|
-
"</html>",
|
|
144
|
-
`<!-- ${theme.primary} ${theme.accent} -->`,
|
|
145
|
-
"",
|
|
146
|
-
].join("\n");
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
function renderThemeCss(brand: BrandContext): string {
|
|
150
|
-
const theme = renderTailwindTheme(brand);
|
|
151
|
-
return [
|
|
152
|
-
theme.cssVariables,
|
|
153
|
-
"* { box-sizing: border-box; }",
|
|
154
|
-
"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; }",
|
|
155
|
-
".page { min-height: 100vh; }",
|
|
156
|
-
".hero { min-height: 82vh; display: grid; align-content: center; gap: 24px; padding: clamp(32px, 8vw, 96px); }",
|
|
157
|
-
".eyebrow { color: var(--brand-accent); font-family: var(--brand-heading), monospace; text-transform: uppercase; letter-spacing: 0; }",
|
|
158
|
-
"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; }",
|
|
159
|
-
".lead { max-width: 760px; font-size: clamp(20px, 3vw, 34px); line-height: 1.2; }",
|
|
160
|
-
".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; }",
|
|
161
|
-
".proof, .waitlist { padding: 48px clamp(32px, 8vw, 96px); border-top: 1px solid color-mix(in oklab, var(--brand-primary) 20%, transparent); }",
|
|
162
|
-
"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; }",
|
|
163
|
-
"form { display: flex; flex-wrap: wrap; gap: 12px; }",
|
|
164
|
-
"",
|
|
165
|
-
].join("\n");
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function deployManifest(input: OnePagerInput): string {
|
|
169
|
-
return `${JSON.stringify(
|
|
170
|
-
{
|
|
171
|
-
mode: "one_pager",
|
|
172
|
-
brandId: input.brand.brandId,
|
|
173
|
-
entrypoint: "company/landing/index.html",
|
|
174
|
-
targets: ["managed-hosting", "edge-pages", "static-hosting", "git-pages"],
|
|
175
|
-
requiresExplicitDeployConsent: true,
|
|
176
|
-
},
|
|
177
|
-
null,
|
|
178
|
-
2,
|
|
179
|
-
)}\n`;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
export class LandingBuilder {
|
|
183
|
-
readonly #tenantId: string | undefined;
|
|
184
|
-
readonly #debugRoot: string;
|
|
185
|
-
readonly #contentStore: ContentStore;
|
|
186
|
-
readonly #eventLog: EventLog;
|
|
187
|
-
|
|
188
|
-
private constructor(
|
|
189
|
-
options: LandingBuilderOptions & { contentStore: ContentStore; eventLog: EventLog },
|
|
190
|
-
) {
|
|
191
|
-
this.#tenantId = options.tenantId;
|
|
192
|
-
this.#debugRoot = options.debugRoot ?? process.cwd();
|
|
193
|
-
this.#contentStore = options.contentStore;
|
|
194
|
-
this.#eventLog = options.eventLog;
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
static async open(
|
|
198
|
-
root = ".nebutra/landing-builder",
|
|
199
|
-
options: Omit<LandingBuilderOptions, "root" | "contentStore" | "eventLog"> = {},
|
|
200
|
-
): Promise<LandingBuilder> {
|
|
201
|
-
const tenantId = options.tenantId ?? "local";
|
|
202
|
-
await mkdir(root, { recursive: true });
|
|
203
|
-
const contentStore = await ContentStore.open(join(root, "content"), { tenantId });
|
|
204
|
-
const eventLog = await EventLog.open(join(root, "event-log"), { tenantId });
|
|
205
|
-
return new LandingBuilder({ ...options, tenantId, root, contentStore, eventLog });
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
async runOnePager(input: OnePagerInput): Promise<SitePackage> {
|
|
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
|
-
});
|
|
219
|
-
const html = renderOnePagerHtml(input);
|
|
220
|
-
const css = renderThemeCss(input.brand);
|
|
221
|
-
const manifest = deployManifest(input);
|
|
222
|
-
const files = [
|
|
223
|
-
{ path: "company/landing/index.html", mime: "text/html", content: html },
|
|
224
|
-
{ path: "company/landing/theme.css", mime: "text/css", content: css },
|
|
225
|
-
{ path: "company/landing/deploy.json", mime: "application/json", content: manifest },
|
|
226
|
-
] as const;
|
|
227
|
-
for (const file of files) await this.#contentStore.write(file.path, file.content);
|
|
228
|
-
const eventId = await this.#eventLog.commit({
|
|
229
|
-
traceId: assetId("landing_builder", input.brand.brandId),
|
|
230
|
-
kind: "content_write",
|
|
231
|
-
affected: files.map((file) => file.path),
|
|
232
|
-
parent: null,
|
|
233
|
-
snapshot: Object.fromEntries(files.map((file) => [file.path, file.content])),
|
|
234
|
-
});
|
|
235
|
-
const result: SitePackage = {
|
|
236
|
-
tenantId,
|
|
237
|
-
play: PLAY_NAME,
|
|
238
|
-
brandId: input.brand.brandId,
|
|
239
|
-
mode: "one_pager",
|
|
240
|
-
files: files.map((file) => ({
|
|
241
|
-
path: file.path,
|
|
242
|
-
mime: file.mime,
|
|
243
|
-
bytes: file.content.length,
|
|
244
|
-
})),
|
|
245
|
-
preview: { kind: "static-content-store", entrypoint: "company/landing/index.html" },
|
|
246
|
-
deploy: {
|
|
247
|
-
status: "handoff",
|
|
248
|
-
targets: ["managed-hosting", "edge-pages", "static-hosting", "git-pages"],
|
|
249
|
-
manifestPath: "company/landing/deploy.json",
|
|
250
|
-
suggestion: "Connect a deploy integration and call the explicit deploy Play after review.",
|
|
251
|
-
},
|
|
252
|
-
eventId,
|
|
253
|
-
};
|
|
254
|
-
await this.#debug({ type: "run", tenantId, brandId: input.brand.brandId, eventId });
|
|
255
|
-
return result;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
async doctor(): Promise<LandingBuilderDoctorReport> {
|
|
259
|
-
return {
|
|
260
|
-
capability: "landing-builder",
|
|
261
|
-
ok: true,
|
|
262
|
-
checkedAt: new Date().toISOString(),
|
|
263
|
-
plays: [PLAY_NAME],
|
|
264
|
-
modes: ["one_pager", "marketing_site", "webapp_mvp", "site_edit"],
|
|
265
|
-
deployAdapters: [
|
|
266
|
-
{ provider: "static-content-store", ok: true },
|
|
267
|
-
{ provider: "integration-vault", ok: false },
|
|
268
|
-
],
|
|
269
|
-
suggestion:
|
|
270
|
-
"Static generation is ready; real deploy requires integration-vault deploy adapters.",
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
async close(): Promise<void> {
|
|
275
|
-
await this.#contentStore.close();
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
async #debug(entry: Record<string, unknown>): Promise<void> {
|
|
279
|
-
await mkdir(dirname(join(this.#debugRoot, ".nebutra", "debug", "landing-builder.jsonl")), {
|
|
280
|
-
recursive: true,
|
|
281
|
-
});
|
|
282
|
-
await appendCapabilityDebug("landing-builder", entry, { root: this.#debugRoot });
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
export async function readLandingBuilderDebug(
|
|
287
|
-
root = process.cwd(),
|
|
288
|
-
limit = 20,
|
|
289
|
-
): Promise<unknown[]> {
|
|
290
|
-
return readCapabilityDebug("landing-builder", { root, limit });
|
|
291
|
-
}
|
package/tsconfig.json
DELETED
package/tsup.config.ts
DELETED