@nebutra/brand-genesis 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,15 @@
1
+ import {
2
+ BrandGenesis,
3
+ brandContextFromDraft,
4
+ distillBrandIdea,
5
+ readBrandGenesisDebug,
6
+ renderBrandMarkdown
7
+ } from "./chunk-SB4N3ZZW.js";
8
+ export {
9
+ BrandGenesis,
10
+ brandContextFromDraft,
11
+ distillBrandIdea,
12
+ readBrandGenesisDebug,
13
+ renderBrandMarkdown
14
+ };
15
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,9 @@
1
+ import { brandContextFromDraft, distillBrandIdea, renderBrandMarkdown } from "../src";
2
+
3
+ const draft = distillBrandIdea({
4
+ idea: "Customer support copilot for solo founders called Harbor",
5
+ visualDirectionHint: "minimal",
6
+ });
7
+ const brand = brandContextFromDraft(draft, "tenant_demo");
8
+
9
+ process.stdout.write(renderBrandMarkdown(draft, brand));
@@ -0,0 +1,11 @@
1
+ import { BrandGenesis } from "../src";
2
+
3
+ const genesis = await BrandGenesis.open(".nebutra/brand-genesis-doctor", {
4
+ tenantId: "tenant_demo",
5
+ });
6
+
7
+ try {
8
+ process.stdout.write(`${JSON.stringify(await genesis.doctor(), null, 2)}\n`);
9
+ } finally {
10
+ await genesis.close();
11
+ }
@@ -0,0 +1,27 @@
1
+ import { BrandGenesis } from "../src";
2
+
3
+ const genesis = await BrandGenesis.open(".nebutra/brand-genesis-example", {
4
+ tenantId: "tenant_demo",
5
+ });
6
+
7
+ try {
8
+ const result = await genesis.run({
9
+ idea: "AI debugging for indie devs called Loop",
10
+ visualDirectionHint: "cyberpunk",
11
+ });
12
+
13
+ process.stdout.write(
14
+ `${JSON.stringify(
15
+ {
16
+ brand: result.brand.name,
17
+ brandMd: result.brandMdPath,
18
+ film: result.film.path,
19
+ eventId: result.eventId,
20
+ },
21
+ null,
22
+ 2,
23
+ )}\n`,
24
+ );
25
+ } finally {
26
+ await genesis.close();
27
+ }
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "@nebutra/brand-genesis",
3
+ "version": "0.2.0",
4
+ "description": "BrandContext-first flagship play for creating a complete brand package from one idea",
5
+ "private": false,
6
+ "license": "MIT",
7
+ "type": "module",
8
+ "nebutra": {
9
+ "status": "wip",
10
+ "productionReady": false,
11
+ "surface": "play-product",
12
+ "requires": [
13
+ "@nebutra/play-loader for SKILL.md play parsing",
14
+ "@nebutra/generation-context for BrandContext",
15
+ "@nebutra/content-store for BrandContext materialization",
16
+ "@nebutra/image-pipeline, @nebutra/video-pipeline, @nebutra/audio-pipeline, @nebutra/voice-realtime, and @nebutra/3d-pipeline for generated assets",
17
+ "@nebutra/event-log for Time Machine checkpoints"
18
+ ],
19
+ "gaps": [
20
+ "Generated media uses deterministic local fallbacks until model-backed providers are configured",
21
+ "Storyboard editing is represented as an orchestration checkpoint, not yet a GUI step",
22
+ "Landing page handoff is a manifest placeholder until landing-builder is implemented"
23
+ ],
24
+ "featureId": "brand-genesis",
25
+ "category": "ai",
26
+ "summary": "Flagship play that distills an idea into BrandContext and orchestrates lower generation capabilities"
27
+ },
28
+ "main": "./src/index.ts",
29
+ "types": "./src/index.ts",
30
+ "exports": {
31
+ ".": "./src/index.ts"
32
+ },
33
+ "dependencies": {
34
+ "@nebutra/3d-pipeline": "0.1.0",
35
+ "@nebutra/content-store": "0.1.0",
36
+ "@nebutra/capability-kit": "0.2.0",
37
+ "@nebutra/audio-pipeline": "0.1.0",
38
+ "@nebutra/errors": "0.1.0",
39
+ "@nebutra/event-log": "0.1.0",
40
+ "@nebutra/generation-context": "0.1.0",
41
+ "@nebutra/image-pipeline": "0.1.0",
42
+ "@nebutra/play-loader": "0.1.0",
43
+ "@nebutra/video-pipeline": "0.1.0",
44
+ "@nebutra/voice-realtime": "0.1.0"
45
+ },
46
+ "devDependencies": {
47
+ "@types/node": "^22.19.15",
48
+ "tsup": "^8.5.1",
49
+ "tsx": "^4.21.0",
50
+ "typescript": "^5.9.3",
51
+ "vitest": "^4.1.4"
52
+ },
53
+ "homepage": "https://github.com/Nebutra/Nebutra-Sailor/tree/main/packages/ai/brand-genesis#readme",
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "git+https://github.com/Nebutra/Nebutra-Sailor.git",
57
+ "directory": "packages/ai/brand-genesis"
58
+ },
59
+ "bugs": {
60
+ "url": "https://github.com/Nebutra/Nebutra-Sailor/issues"
61
+ },
62
+ "publishConfig": {
63
+ "access": "public"
64
+ },
65
+ "scripts": {
66
+ "build": "tsup",
67
+ "test": "vitest run",
68
+ "typecheck": "tsc --noEmit"
69
+ }
70
+ }
@@ -0,0 +1,84 @@
1
+ ---
2
+ name: brand_film_60s
3
+ kind: play
4
+ version: 1.0.0
5
+ description: From one idea to a brand system, generated assets, narration, and a 60s film manifest
6
+ inputs:
7
+ idea:
8
+ type: string
9
+ max_length: 500
10
+ description: One-sentence company idea
11
+ founder_voice_id:
12
+ type: string
13
+ optional: true
14
+ description: Enrolled voice profile for narration
15
+ visual_direction_hint:
16
+ type: enum
17
+ options: [cyberpunk, minimal, organic, playful, corporate, retro, futurist]
18
+ optional: true
19
+ outputs:
20
+ brand_md: { type: file, path: company/BRAND.md, mime: text/markdown }
21
+ logo: { type: file, path: company/assets/logo.svg, mime: image/svg+xml }
22
+ palette: { type: file, path: company/assets/palette.json, mime: application/json }
23
+ film: { type: file, path: company/film_60s.mp4, mime: video/mp4 }
24
+ landing: { type: file, path: company/landing/index.html, mime: text/html }
25
+ bgm: { type: file, path: company/audio/bgm.wav, mime: audio/wav }
26
+ budget:
27
+ duration_s: 300
28
+ cost_usd: 5
29
+ alert_at_pct: 80
30
+ required_skills:
31
+ - image_pipeline.generate
32
+ - video_pipeline.plan
33
+ - video_pipeline.render
34
+ - video_pipeline.compose
35
+ - audio_pipeline.generate
36
+ - voice_realtime.synthesize_narration
37
+ - mesh_pipeline.from_image
38
+ - content_store.write
39
+ required_plays:
40
+ - landing_page_v1
41
+ sub_agents:
42
+ - role: brand_strategist
43
+ allowed_skills: [llm_gateway.complete, content_store.write]
44
+ - role: visual_designer
45
+ allowed_skills: [image_pipeline.generate, mesh_pipeline.from_image, content_store.write]
46
+ - role: video_director
47
+ allowed_skills: [video_pipeline.plan, video_pipeline.render, video_pipeline.compose]
48
+ - role: music_composer
49
+ allowed_skills: [audio_pipeline.generate]
50
+ - role: narrator
51
+ allowed_skills: [voice_realtime.synthesize_narration]
52
+ - role: web_builder
53
+ allowed_skills: [play_loader.run]
54
+ depends_on_plays: []
55
+ checkpoints:
56
+ - after_step: brand_distillation
57
+ - after_step: visual_direction
58
+ - after_step: assets_generated
59
+ - after_step: video_rendered
60
+ - after_step: final_compose
61
+ ---
62
+
63
+ ## What this play does
64
+
65
+ Creates a first brand package from one short company idea. The Play must first
66
+ materialize `company/BRAND.md`; every downstream media call reads the resulting
67
+ BrandContext instead of inventing its own style.
68
+
69
+ ## Steps
70
+
71
+ 1. Distill the idea into a compact brand identity.
72
+ 2. Choose one visual direction and write `company/BRAND.md`.
73
+ 3. Generate independent visual assets in parallel.
74
+ 4. Plan the storyboard before any video rendering.
75
+ 5. Render video, then generate music and narration with the final timing.
76
+ 6. Compose the film manifest and write a landing handoff manifest.
77
+ 7. Commit the resulting paths to the event log.
78
+
79
+ ## Anti-patterns
80
+
81
+ - Do not generate assets without BrandContext.
82
+ - Do not define image, video, audio, voice, or mesh generation logic here.
83
+ - Do not skip storyboard planning.
84
+ - Do not hardcode one visual style across all companies.
@@ -0,0 +1,5 @@
1
+ {
2
+ "tenantId": "tenant_demo",
3
+ "idea": "AI debugging for indie devs called Loop",
4
+ "visualDirectionHint": "cyberpunk"
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "tenantId": "tenant_demo",
3
+ "idea": "Customer support copilot for solo founders called Harbor",
4
+ "visualDirectionHint": "minimal"
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "tenantId": "tenant_demo",
3
+ "idea": "Analytics workspace for creator businesses called Signal Garden",
4
+ "visualDirectionHint": "organic"
5
+ }
@@ -0,0 +1,29 @@
1
+ ---
2
+ type: brand_profile
3
+ brand_id: {{brand_id}}
4
+ name: {{name}}
5
+ ---
6
+
7
+ # {{name}}
8
+
9
+ {{one_liner}}
10
+
11
+ ## Tagline
12
+
13
+ {{tagline}}
14
+
15
+ ## Audience
16
+
17
+ {{target_audience}}
18
+
19
+ ## Tone
20
+
21
+ {{tone_keywords}}
22
+
23
+ ## Visual Direction
24
+
25
+ {{visual_style}}
26
+
27
+ ## Differentiation
28
+
29
+ {{differentiation}}
@@ -0,0 +1,4 @@
1
+ {
2
+ "brandId": "{{brand_id}}",
3
+ "colors": []
4
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,64 @@
1
+ import { mkdir, rm, stat } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { BrandGenesis, readBrandGenesisDebug } from "./index";
4
+
5
+ const command = process.argv[2] ?? "doctor";
6
+ const root = process.env.BRAND_GENESIS_ROOT ?? ".nebutra/brand-genesis";
7
+ const tenantId = process.env.NEBUTRA_TENANT_ID ?? "local";
8
+
9
+ async function sleep(ms: number): Promise<void> {
10
+ await new Promise((resolve) => setTimeout(resolve, ms));
11
+ }
12
+
13
+ async function acquireCliLock(rootPath: string): Promise<() => Promise<void>> {
14
+ const lockPath = join(rootPath, ".cli.lock");
15
+ await mkdir(rootPath, { recursive: true });
16
+ for (let attempt = 0; attempt < 40; attempt += 1) {
17
+ try {
18
+ await mkdir(lockPath);
19
+ return async () => {
20
+ await rm(lockPath, { recursive: true, force: true });
21
+ };
22
+ } catch (error) {
23
+ const code = (error as NodeJS.ErrnoException).code;
24
+ if (code !== "EEXIST") throw error;
25
+ const lockStat = await stat(lockPath).catch(() => ({ mtimeMs: Date.now() }));
26
+ if (Date.now() - lockStat.mtimeMs > 30_000) {
27
+ await rm(lockPath, { recursive: true, force: true });
28
+ continue;
29
+ }
30
+ await sleep(50);
31
+ }
32
+ }
33
+ throw new Error("brand-genesis CLI is busy. Suggestion: wait a moment and retry the command.");
34
+ }
35
+
36
+ if (command === "doctor") {
37
+ const releaseLock = await acquireCliLock(root);
38
+ const genesis = await BrandGenesis.open(root, { tenantId });
39
+ try {
40
+ process.stdout.write(`${JSON.stringify(await genesis.doctor(), null, 2)}\n`);
41
+ } finally {
42
+ await genesis.close();
43
+ await releaseLock();
44
+ }
45
+ } else if (command === "quickstart") {
46
+ const idea = process.argv.slice(3).join(" ") || "AI debugging for indie devs called Loop";
47
+ const releaseLock = await acquireCliLock(root);
48
+ const genesis = await BrandGenesis.open(root, { tenantId });
49
+ try {
50
+ process.stdout.write(
51
+ `${JSON.stringify({ capability: "brand-genesis", result: await genesis.run({ idea }) }, null, 2)}\n`,
52
+ );
53
+ } finally {
54
+ await genesis.close();
55
+ await releaseLock();
56
+ }
57
+ } else if (command === "debug") {
58
+ process.stdout.write(
59
+ `${JSON.stringify({ capability: "brand-genesis", entries: await readBrandGenesisDebug() }, null, 2)}\n`,
60
+ );
61
+ } else {
62
+ process.stderr.write(`Unknown brand-genesis command: ${command}\n`);
63
+ process.exitCode = 1;
64
+ }
@@ -0,0 +1,123 @@
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
+ BrandGenesis,
8
+ brandContextFromDraft,
9
+ distillBrandIdea,
10
+ readBrandGenesisDebug,
11
+ renderBrandMarkdown,
12
+ } from "./index";
13
+
14
+ let root: string | undefined;
15
+ let genesis: BrandGenesis | undefined;
16
+
17
+ afterEach(async () => {
18
+ if (genesis) await genesis.close();
19
+ if (root) await rm(root, { recursive: true, force: true });
20
+ root = undefined;
21
+ genesis = undefined;
22
+ });
23
+
24
+ async function open(tenantId = "tenant_demo"): Promise<BrandGenesis> {
25
+ root = await mkdtemp(join(tmpdir(), "brand-genesis-"));
26
+ genesis = await BrandGenesis.open(root, { tenantId });
27
+ return genesis;
28
+ }
29
+
30
+ describe("brand-genesis", () => {
31
+ it("distills an idea into a BrandContext without owning generation primitives", () => {
32
+ const draft = distillBrandIdea({
33
+ idea: "AI debugging for indie devs called Loop",
34
+ visualDirectionHint: "cyberpunk",
35
+ });
36
+ const brand = brandContextFromDraft(draft, "tenant_a");
37
+ const markdown = renderBrandMarkdown(draft, brand);
38
+
39
+ expect(draft.name).toBe("Loop");
40
+ expect(brand).toMatchObject({
41
+ tenantId: "tenant_a",
42
+ brandId: "loop",
43
+ sourcePath: "company/BRAND.md",
44
+ });
45
+ expect(markdown).toContain("type: brand_profile");
46
+ expect(markdown).toContain("Debug like it is a conversation");
47
+ });
48
+
49
+ it("runs the brand_film_60s play through lower capabilities and writes checkpoints", async () => {
50
+ const runtime = await open("tenant_a");
51
+
52
+ const result = await runtime.run({
53
+ idea: "AI debugging for indie devs called Loop",
54
+ visualDirectionHint: "cyberpunk",
55
+ founderVoiceId: "founder_voice",
56
+ });
57
+
58
+ expect(result.brand.name).toBe("Loop");
59
+ expect(result.logo.brandId).toBe(result.brand.brandId);
60
+ expect(result.mesh.brandId).toBe(result.brand.brandId);
61
+ expect(result.film.kind).toBe("video");
62
+ expect(result.bgm.license.status).toBe("commercial-ok");
63
+ expect(result.narration.voiceProfileId).toBe("founder_voice");
64
+ expect(result.checkpoints).toEqual([
65
+ "brand_distillation",
66
+ "visual_direction",
67
+ "assets_generated",
68
+ "video_rendered",
69
+ "final_compose",
70
+ ]);
71
+ await expect(readBrandGenesisDebug(root)).resolves.toEqual(expect.any(Array));
72
+ });
73
+
74
+ it("requires tenant context before persistent operations", async () => {
75
+ root = await mkdtemp(join(tmpdir(), "brand-genesis-"));
76
+ const runtime = await BrandGenesis.open(root);
77
+ genesis = runtime;
78
+
79
+ await expect(runtime.run({ tenantId: "", idea: "AI tool called Loop" })).rejects.toMatchObject({
80
+ capability: "brand-genesis",
81
+ suggestion: expect.stringContaining("tenantId"),
82
+ });
83
+ });
84
+
85
+ it("keeps the flagship Play as SKILL.md instead of a new workflow format", async () => {
86
+ const skill = await readFile(join(process.cwd(), "plays", "brand_film_60s", "SKILL.md"), {
87
+ encoding: "utf8",
88
+ }).catch(async () =>
89
+ readFile(
90
+ join(
91
+ process.cwd(),
92
+ "packages",
93
+ "ai",
94
+ "brand-genesis",
95
+ "plays",
96
+ "brand_film_60s",
97
+ "SKILL.md",
98
+ ),
99
+ "utf8",
100
+ ),
101
+ );
102
+ const play = parsePlayMarkdown(skill);
103
+
104
+ expect(play.meta).toMatchObject({
105
+ name: "brand_film_60s",
106
+ kind: "play",
107
+ version: "1.0.0",
108
+ });
109
+ expect(play.requiredSkills).toContain("image_pipeline.generate");
110
+ expect(play.requiredSkills).toContain("content_store.write");
111
+ expect(play.subAgents.map((agent) => agent.role)).toContain("visual_designer");
112
+ });
113
+
114
+ it("reports doctor health from delegated media capabilities", async () => {
115
+ const runtime = await open("tenant_a");
116
+
117
+ await expect(runtime.doctor()).resolves.toMatchObject({
118
+ capability: "brand-genesis",
119
+ ok: true,
120
+ play: { ok: true, name: "brand_film_60s" },
121
+ });
122
+ });
123
+ });