brand-intent 0.1.3 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -19
- package/brands/krume/krume.brand +54 -34
- package/cli/index.mjs +89 -116
- package/package.json +4 -1
- package/skills/brand-intent.md +103 -16
package/README.md
CHANGED
|
@@ -17,9 +17,9 @@ Brand Intent is a set of five composable file types that encode brand knowledge
|
|
|
17
17
|
| Layer | File | Question |
|
|
18
18
|
|-------|------|----------|
|
|
19
19
|
| Identity | `.identity` | Who is this brand? |
|
|
20
|
-
| Brand | `.brand` |
|
|
21
|
-
| Format | `.format` | Where does
|
|
22
|
-
| Purpose | `.purpose` | What is this content
|
|
20
|
+
| Brand | `.brand` | How is it expressed? |
|
|
21
|
+
| Format | `.format` | Where does it appear? |
|
|
22
|
+
| Purpose | `.purpose` | What is this content for? |
|
|
23
23
|
| Composition | `.composition` | How are elements composed? |
|
|
24
24
|
|
|
25
25
|
## Get started
|
|
@@ -29,23 +29,15 @@ Brand Intent is a set of five composable file types that encode brand knowledge
|
|
|
29
29
|
Syntax highlighting, autocomplete, and validation for all five file types.
|
|
30
30
|
|
|
31
31
|
```
|
|
32
|
-
ext install
|
|
32
|
+
ext install studiostoll.brand-intent-lang
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
### 2.
|
|
35
|
+
### 2. Run init
|
|
36
36
|
|
|
37
|
-
|
|
38
|
-
npx brand-intent install-skills
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
Works with Claude Code, Cursor, Gemini CLI, VS Code Copilot, Codex CLI, and any MCP-compatible agent.
|
|
42
|
-
|
|
43
|
-
### 3. Author your brand
|
|
44
|
-
|
|
45
|
-
Start with `.identity` (strategy, voice, audiences), then derive `.brand` (expression intent). Add formats, purposes, and compositions as needed. The Krume bakery brand in `brands/krume/` is a fully commented reference implementation.
|
|
37
|
+
Installs the Brand Intent skill for your AI agent. The skill handles everything else: onboard your brand through a guided interview, explore the Krume bakery reference, generate and validate on-brand content.
|
|
46
38
|
|
|
47
39
|
```bash
|
|
48
|
-
npx brand-intent init
|
|
40
|
+
npx brand-intent init
|
|
49
41
|
```
|
|
50
42
|
|
|
51
43
|
## Documentation
|
|
@@ -54,10 +46,6 @@ npx brand-intent init --from krume
|
|
|
54
46
|
- [Design Decisions](DESIGN.md): reasoning behind the framework
|
|
55
47
|
- [intent.stoll.studio](https://intent.stoll.studio): project landing page
|
|
56
48
|
|
|
57
|
-
## Relationship to Impeccable
|
|
58
|
-
|
|
59
|
-
Brand Intent is complementary to [Impeccable](https://impeccable.style). Where Impeccable teaches agents *how to design well* (general craft), Brand Intent teaches them *what this brand's expression intent is* (specific identity). Together: execution quality and brand specificity.
|
|
60
|
-
|
|
61
49
|
## License
|
|
62
50
|
|
|
63
51
|
MIT
|
package/brands/krume/krume.brand
CHANGED
|
@@ -27,10 +27,15 @@ theme Laden
|
|
|
27
27
|
text-primary: $primary
|
|
28
28
|
text-secondary: $secondary
|
|
29
29
|
text-tertiary: $accent1
|
|
30
|
+
text-subtle: #A89282
|
|
31
|
+
text-muted: #C4B5A7
|
|
30
32
|
logo: $primary
|
|
33
|
+
sticker-bg: $accent1
|
|
34
|
+
sticker-fg: $primary
|
|
31
35
|
cta: $accent2
|
|
32
36
|
label-bg: $accent1
|
|
33
37
|
label-fg: $primary
|
|
38
|
+
icon: $secondary
|
|
34
39
|
divider: $accent1
|
|
35
40
|
|
|
36
41
|
theme Tafel
|
|
@@ -38,33 +43,61 @@ theme Tafel
|
|
|
38
43
|
text-primary: $white
|
|
39
44
|
text-secondary: #D4C4B0
|
|
40
45
|
text-tertiary: #8A7A68
|
|
46
|
+
text-subtle: #6A5A48
|
|
47
|
+
text-muted: #4A3A28
|
|
41
48
|
logo: $accent1
|
|
49
|
+
sticker-bg: $accent2
|
|
50
|
+
sticker-fg: $white
|
|
42
51
|
cta: $accent1
|
|
43
52
|
label-bg: $accent2
|
|
44
53
|
label-fg: $white
|
|
54
|
+
icon: #D4C4B0
|
|
45
55
|
divider: $accent1
|
|
46
56
|
|
|
47
57
|
# ── Typography ──
|
|
48
|
-
#
|
|
49
|
-
#
|
|
58
|
+
# Hanken Grotesk: clean, geometric, contemporary.
|
|
59
|
+
# ABC Stefan Simple: display face for hero moments.
|
|
50
60
|
|
|
51
61
|
font primary
|
|
52
|
-
name:
|
|
62
|
+
name: Hanken Grotesk
|
|
53
63
|
fallback: sans-serif
|
|
54
64
|
source: google
|
|
55
65
|
|
|
66
|
+
font secondary
|
|
67
|
+
name: ABC Stefan Simple
|
|
68
|
+
fallback: sans-serif
|
|
69
|
+
source: local
|
|
70
|
+
|
|
71
|
+
typography display
|
|
72
|
+
font: $secondary
|
|
73
|
+
weight: 400
|
|
74
|
+
size: 8
|
|
75
|
+
lineHeight: 1.1
|
|
76
|
+
autofit-min: 5
|
|
77
|
+
autofit-max: 12
|
|
78
|
+
|
|
56
79
|
typography headline
|
|
57
80
|
weight: 700
|
|
58
81
|
strong: 800
|
|
59
|
-
size: 6
|
|
82
|
+
size: 6
|
|
60
83
|
lineHeight: 1.15
|
|
61
84
|
autofit-min: 4
|
|
62
85
|
autofit-max: 10
|
|
63
86
|
|
|
64
87
|
typography kicker
|
|
65
|
-
weight:
|
|
66
|
-
size:
|
|
88
|
+
weight: 400
|
|
89
|
+
size: 2.6
|
|
67
90
|
lineHeight: 1.3
|
|
91
|
+
autofit-min: 2.5
|
|
92
|
+
autofit-max: 3.5
|
|
93
|
+
|
|
94
|
+
typography quote
|
|
95
|
+
weight: 700
|
|
96
|
+
strong: 800
|
|
97
|
+
size: 5.5
|
|
98
|
+
lineHeight: 1.2
|
|
99
|
+
autofit-min: 4
|
|
100
|
+
autofit-max: 8
|
|
68
101
|
|
|
69
102
|
typography body
|
|
70
103
|
weight: 400
|
|
@@ -76,6 +109,12 @@ typography caption
|
|
|
76
109
|
size: 2.2
|
|
77
110
|
lineHeight: 1.4
|
|
78
111
|
|
|
112
|
+
typography attribution
|
|
113
|
+
weight: 300
|
|
114
|
+
size: 2.2
|
|
115
|
+
lineHeight: 1.4
|
|
116
|
+
letterSpacing: 0.08
|
|
117
|
+
|
|
79
118
|
typography label
|
|
80
119
|
weight: 600
|
|
81
120
|
size: 2
|
|
@@ -83,10 +122,10 @@ typography label
|
|
|
83
122
|
uppercase
|
|
84
123
|
|
|
85
124
|
typography number
|
|
86
|
-
weight:
|
|
87
|
-
size:
|
|
88
|
-
lineHeight:
|
|
89
|
-
letterSpacing:
|
|
125
|
+
weight: 300
|
|
126
|
+
size: 14
|
|
127
|
+
lineHeight: 0.9
|
|
128
|
+
letterSpacing: -0.03
|
|
90
129
|
|
|
91
130
|
# ── Spacing ──
|
|
92
131
|
|
|
@@ -105,31 +144,12 @@ divider line
|
|
|
105
144
|
width: 100%
|
|
106
145
|
color: divider
|
|
107
146
|
align: left
|
|
108
|
-
spacing:
|
|
147
|
+
spacing: s
|
|
109
148
|
|
|
110
|
-
divider
|
|
111
|
-
thickness: 0.
|
|
112
|
-
width:
|
|
149
|
+
divider ruler
|
|
150
|
+
thickness: 0.8
|
|
151
|
+
width: 20%
|
|
113
152
|
color: divider
|
|
114
153
|
align: left
|
|
115
|
-
spacing:
|
|
154
|
+
spacing: m
|
|
116
155
|
|
|
117
|
-
# ── Voice Constraints ──
|
|
118
|
-
# Operationalized from identity voice reasoning.
|
|
119
|
-
# These are enforceable limits, not restated strategy.
|
|
120
|
-
# Derived from: persona is direct, rhythm is short sentences.
|
|
121
|
-
|
|
122
|
-
voice-constraints
|
|
123
|
-
register: informal
|
|
124
|
-
sentence-max: 8 # words - short, direct, like the baker talks
|
|
125
|
-
headline-pattern: noun-first
|
|
126
|
-
number-format: cardinal # "drei", not "3"
|
|
127
|
-
|
|
128
|
-
# ── Content Defaults ──
|
|
129
|
-
# Derived from identity: this is a small bakery, not a media brand.
|
|
130
|
-
# Low density. Natural images. Simple dividers.
|
|
131
|
-
|
|
132
|
-
content-defaults
|
|
133
|
-
density: low
|
|
134
|
-
image-treatment: natural
|
|
135
|
-
divider: line
|
package/cli/index.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
import { existsSync, mkdirSync,
|
|
4
|
-
import { join, dirname, resolve
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
4
|
+
import { join, dirname, resolve } from 'path';
|
|
5
5
|
import { fileURLToPath } from 'url';
|
|
6
|
+
import { createInterface } from 'readline';
|
|
6
7
|
|
|
7
8
|
const __filename = fileURLToPath(import.meta.url);
|
|
8
9
|
const __dirname = dirname(__filename);
|
|
@@ -10,108 +11,77 @@ const PKG_ROOT = resolve(__dirname, '..');
|
|
|
10
11
|
|
|
11
12
|
const SKILL_FILE = join(PKG_ROOT, 'skills', 'brand-intent.md');
|
|
12
13
|
|
|
13
|
-
// ── Harness detection
|
|
14
|
+
// ── Harness detection ──
|
|
14
15
|
|
|
15
|
-
const
|
|
16
|
-
claude:
|
|
17
|
-
cursor:
|
|
18
|
-
gemini:
|
|
19
|
-
copilot: '.github/copilot-instructions',
|
|
20
|
-
|
|
16
|
+
const HARNESSES = [
|
|
17
|
+
{ id: 'claude', label: 'Claude Code', dir: '.claude/commands' },
|
|
18
|
+
{ id: 'cursor', label: 'Cursor', dir: '.cursor/rules' },
|
|
19
|
+
{ id: 'gemini', label: 'Gemini CLI', dir: '.gemini' },
|
|
20
|
+
{ id: 'copilot', label: 'VS Code Copilot', dir: '.github/copilot-instructions' },
|
|
21
|
+
];
|
|
21
22
|
|
|
22
23
|
function detectHarnesses(cwd) {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
return HARNESSES.filter(h => {
|
|
25
|
+
const parent = h.dir.split('/')[0];
|
|
26
|
+
return existsSync(join(cwd, parent));
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// ── Readline ──
|
|
31
|
+
|
|
32
|
+
let _rl;
|
|
33
|
+
function rl() {
|
|
34
|
+
if (!_rl) {
|
|
35
|
+
_rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
30
36
|
}
|
|
31
|
-
return
|
|
37
|
+
return _rl;
|
|
38
|
+
}
|
|
39
|
+
function closeRl() {
|
|
40
|
+
if (_rl) { _rl.close(); _rl = null; }
|
|
32
41
|
}
|
|
33
42
|
|
|
34
|
-
|
|
43
|
+
function ask(q) {
|
|
44
|
+
return new Promise((res) => rl().question(q, res));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ── Install skill ──
|
|
35
48
|
|
|
36
|
-
function installSkills(cwd) {
|
|
49
|
+
async function installSkills(cwd) {
|
|
37
50
|
if (!existsSync(SKILL_FILE)) {
|
|
38
51
|
console.error('Error: skill file not found at', SKILL_FILE);
|
|
39
52
|
process.exit(1);
|
|
40
53
|
}
|
|
41
54
|
|
|
42
55
|
const skill = readFileSync(SKILL_FILE, 'utf-8');
|
|
43
|
-
|
|
56
|
+
let harnesses = detectHarnesses(cwd);
|
|
44
57
|
|
|
45
58
|
if (harnesses.length === 0) {
|
|
46
|
-
|
|
47
|
-
|
|
59
|
+
console.log(' Which AI tools do you use?\n');
|
|
60
|
+
HARNESSES.forEach((h, i) => {
|
|
61
|
+
console.log(` ${i + 1}) ${h.label}`);
|
|
62
|
+
});
|
|
63
|
+
console.log(` ${HARNESSES.length + 1}) all of the above`);
|
|
64
|
+
console.log('');
|
|
65
|
+
const answer = await ask(` Choose [1-${HARNESSES.length + 1}]: `);
|
|
66
|
+
const idx = parseInt(answer, 10) - 1;
|
|
67
|
+
if (idx === HARNESSES.length) {
|
|
68
|
+
harnesses = HARNESSES;
|
|
69
|
+
} else if (idx >= 0 && idx < HARNESSES.length) {
|
|
70
|
+
harnesses = [HARNESSES[idx]];
|
|
71
|
+
} else {
|
|
72
|
+
harnesses = [HARNESSES[0]];
|
|
73
|
+
}
|
|
48
74
|
}
|
|
49
75
|
|
|
50
|
-
|
|
51
|
-
for (const { name, dir } of harnesses) {
|
|
76
|
+
for (const { label, dir } of harnesses) {
|
|
52
77
|
const target = join(cwd, dir);
|
|
53
78
|
mkdirSync(target, { recursive: true });
|
|
54
79
|
const dest = join(target, 'brand-intent.md');
|
|
55
80
|
writeFileSync(dest, skill, 'utf-8');
|
|
56
|
-
console.log(`
|
|
57
|
-
installed++;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
console.log(`\nInstalled Brand Intent skill to ${installed} harness${installed > 1 ? 'es' : ''}.`);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function copyDirRecursive(src, dest) {
|
|
64
|
-
mkdirSync(dest, { recursive: true });
|
|
65
|
-
for (const entry of readdirSync(src)) {
|
|
66
|
-
const srcPath = join(src, entry);
|
|
67
|
-
const destPath = join(dest, entry);
|
|
68
|
-
if (statSync(srcPath).isDirectory()) {
|
|
69
|
-
copyDirRecursive(srcPath, destPath);
|
|
70
|
-
} else {
|
|
71
|
-
copyFileSync(srcPath, destPath);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function initFromBrand(brandName, cwd) {
|
|
77
|
-
const brandSrc = join(PKG_ROOT, 'brands', brandName);
|
|
78
|
-
if (!existsSync(brandSrc)) {
|
|
79
|
-
console.error(`Error: brand "${brandName}" not found.`);
|
|
80
|
-
console.error(`Available brands: ${readdirSync(join(PKG_ROOT, 'brands')).join(', ')}`);
|
|
81
|
-
process.exit(1);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Copy brand files
|
|
85
|
-
const brandDest = join(cwd, `brands/${brandName}`);
|
|
86
|
-
copyDirRecursive(brandSrc, brandDest);
|
|
87
|
-
console.log(` ✓ brand files → brands/${brandName}/`);
|
|
88
|
-
|
|
89
|
-
// Copy shared layers (formats, purposes, compositions)
|
|
90
|
-
for (const layer of ['formats', 'purposes', 'compositions']) {
|
|
91
|
-
const layerSrc = join(PKG_ROOT, layer);
|
|
92
|
-
if (existsSync(layerSrc) && readdirSync(layerSrc).length > 0) {
|
|
93
|
-
const layerDest = join(cwd, layer);
|
|
94
|
-
copyDirRecursive(layerSrc, layerDest);
|
|
95
|
-
console.log(` ✓ ${layer}/ → ${layer}/`);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
console.log(`\nScaffolded from "${brandName}". Edit the .identity and .brand files to make it yours.`);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
function addBrand(brandRef, cwd) {
|
|
103
|
-
// brandRef is "brandName/brand" — extract the brand name
|
|
104
|
-
const brandName = brandRef.split('/')[0];
|
|
105
|
-
const brandSrc = join(PKG_ROOT, 'brands', brandName);
|
|
106
|
-
if (!existsSync(brandSrc)) {
|
|
107
|
-
console.error(`Error: brand "${brandName}" not found.`);
|
|
108
|
-
process.exit(1);
|
|
81
|
+
console.log(` \u2713 ${label} \u2014 ${dir}/brand-intent.md`);
|
|
109
82
|
}
|
|
110
83
|
|
|
111
|
-
|
|
112
|
-
copyDirRecursive(brandSrc, brandDest);
|
|
113
|
-
console.log(` ✓ brand files → brands/${brandName}/`);
|
|
114
|
-
console.log(`\nAdded "${brandName}" brand files to your project.`);
|
|
84
|
+
return harnesses;
|
|
115
85
|
}
|
|
116
86
|
|
|
117
87
|
// ── CLI entry point ──
|
|
@@ -123,41 +93,44 @@ const cwd = process.cwd();
|
|
|
123
93
|
const pkg = JSON.parse(readFileSync(join(PKG_ROOT, 'package.json'), 'utf-8'));
|
|
124
94
|
console.log(`Brand Intent v${pkg.version}\n`);
|
|
125
95
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
break;
|
|
130
|
-
|
|
131
|
-
case 'init': {
|
|
132
|
-
const fromIdx = args.indexOf('--from');
|
|
133
|
-
if (fromIdx === -1 || !args[fromIdx + 1]) {
|
|
134
|
-
console.error('Usage: brand-intent init --from <brand-name>');
|
|
135
|
-
console.error('Example: brand-intent init --from krume');
|
|
136
|
-
process.exit(1);
|
|
137
|
-
}
|
|
138
|
-
initFromBrand(args[fromIdx + 1], cwd);
|
|
139
|
-
break;
|
|
140
|
-
}
|
|
96
|
+
if (command && command !== 'init' && command !== 'install-skills' && command !== '--help' && command !== '-h') {
|
|
97
|
+
console.log(` Unknown command: ${command}\n`);
|
|
98
|
+
}
|
|
141
99
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
100
|
+
if (!command || command === '--help' || command === '-h') {
|
|
101
|
+
console.log('Usage:');
|
|
102
|
+
console.log(' brand-intent init Install the Brand Intent skill for your AI agent');
|
|
103
|
+
console.log(' brand-intent install-skills Same as init (alias)');
|
|
104
|
+
console.log('');
|
|
105
|
+
console.log('Example:');
|
|
106
|
+
console.log(' npx brand-intent init');
|
|
107
|
+
process.exit(0);
|
|
108
|
+
}
|
|
151
109
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
110
|
+
// Both "init" and "install-skills" do the same thing now
|
|
111
|
+
const installed = await installSkills(cwd);
|
|
112
|
+
closeRl();
|
|
113
|
+
|
|
114
|
+
// Show next steps with skill-specific commands
|
|
115
|
+
const hasClaudeCode = installed.some(h => h.id === 'claude');
|
|
116
|
+
const hasCursor = installed.some(h => h.id === 'cursor');
|
|
117
|
+
|
|
118
|
+
console.log('\n Done. Now open your AI agent and try:\n');
|
|
119
|
+
console.log(' "Onboard my brand"');
|
|
120
|
+
console.log(' "Show me the Krume bakery example"');
|
|
121
|
+
|
|
122
|
+
if (hasClaudeCode || hasCursor) {
|
|
123
|
+
console.log('\n Or use the skill directly:\n');
|
|
124
|
+
if (hasClaudeCode) {
|
|
125
|
+
console.log(' /brand-intent onboard Create your brand through a guided interview');
|
|
126
|
+
console.log(' /brand-intent example Scaffold the Krume reference implementation');
|
|
127
|
+
console.log(' /brand-intent validate Validate content against your brand rules');
|
|
128
|
+
console.log(' /brand-intent Discover brand files and available purposes');
|
|
129
|
+
}
|
|
130
|
+
if (hasCursor) {
|
|
131
|
+
console.log(' @brand-intent onboard Create your brand through a guided interview');
|
|
132
|
+
console.log(' @brand-intent example Scaffold the Krume reference implementation');
|
|
133
|
+
}
|
|
163
134
|
}
|
|
135
|
+
|
|
136
|
+
console.log('');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brand-intent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "An open file format specification for making brand intent machine-readable.",
|
|
5
5
|
"author": "Christophe Stoll <christophe@stoll.studio>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
"brand-identity",
|
|
21
21
|
"design-system"
|
|
22
22
|
],
|
|
23
|
+
"scripts": {
|
|
24
|
+
"release": "./scripts/release.sh"
|
|
25
|
+
},
|
|
23
26
|
"files": [
|
|
24
27
|
"cli/",
|
|
25
28
|
"skills/",
|
package/skills/brand-intent.md
CHANGED
|
@@ -6,6 +6,8 @@ description: Generate and validate on-brand content using Brand Intent files
|
|
|
6
6
|
|
|
7
7
|
**Scope:** `$ARGUMENTS`
|
|
8
8
|
|
|
9
|
+
> If `$ARGUMENTS` is "onboard": run the guided brand onboarding interview (see Onboarding section below).
|
|
10
|
+
> If `$ARGUMENTS` is "example": scaffold the Krume bakery reference implementation into `examples/krume/` and explain what each file does.
|
|
9
11
|
> If `$ARGUMENTS` names a purpose (e.g. "daily-bread", "event", "quote"): compose context from the five layers and generate content for that purpose.
|
|
10
12
|
> If `$ARGUMENTS` is "validate": read the most recent generated or provided content and run the validation checklist against the brand files.
|
|
11
13
|
> If `$ARGUMENTS` is empty: discover all `.identity`, `.brand`, `.purpose` files in the project, summarize the brand voice and list available purposes.
|
|
@@ -98,9 +100,6 @@ When generating content for a specific purpose, read all layers silently (do not
|
|
|
98
100
|
|
|
99
101
|
### Output format:
|
|
100
102
|
|
|
101
|
-
For each brand that has the requested purpose:
|
|
102
|
-
|
|
103
|
-
- If multiple brands exist, show the **brand name** as a heading
|
|
104
103
|
- Show each **slot** with its generated content — nothing else
|
|
105
104
|
- After the content, show a **compact validation summary** (pass/fail per rule, only call out failures in detail)
|
|
106
105
|
|
|
@@ -132,27 +131,115 @@ After generating or reviewing content, validate against these rules in order:
|
|
|
132
131
|
Brand Intent files follow this structure:
|
|
133
132
|
|
|
134
133
|
```
|
|
135
|
-
# Single brand
|
|
136
134
|
brandname.identity
|
|
137
135
|
brandname.brand
|
|
138
136
|
formats/*.format
|
|
139
137
|
purposes/*.purpose
|
|
140
138
|
compositions/*.composition
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
To find the active brand, look for the `.identity` and `.brand` files at the project root. There is exactly one of each per project.
|
|
142
|
+
|
|
143
|
+
## Onboarding: Create a New Brand
|
|
144
|
+
|
|
145
|
+
When `$ARGUMENTS` is "onboard", guide the user through a structured interview to create their brand files. Do NOT jump to file creation — the interview comes first.
|
|
146
|
+
|
|
147
|
+
### Interview Flow
|
|
148
|
+
|
|
149
|
+
Have a natural conversation. Don't dump all questions at once — adapt based on answers. Group questions into rounds of 2-4.
|
|
150
|
+
|
|
151
|
+
**Round 1: Identity**
|
|
152
|
+
- Brand name? (and brand ID — lowercase, no spaces, used in filenames)
|
|
153
|
+
- What does this brand do? One sentence.
|
|
154
|
+
- Language? (e.g., de, en)
|
|
155
|
+
- Essence — the irreducible truth in 3-8 words?
|
|
156
|
+
|
|
157
|
+
Listen for tone, confidence, specificity. A founder who says "we make artisan coffee" vs "we roast single-origin in a garage in Kreuzberg" gives you very different brands.
|
|
158
|
+
|
|
159
|
+
**Round 2: Voice & Personality**
|
|
160
|
+
- If this brand were a person talking to a customer, who would they be?
|
|
161
|
+
- Formal or informal?
|
|
162
|
+
- What does this brand ALWAYS do in communication? (2-3 concrete rules)
|
|
163
|
+
- What does this brand NEVER do? (2-3 concrete anti-patterns)
|
|
164
|
+
|
|
165
|
+
The user will often describe what the brand is NOT before what it IS. Capture both.
|
|
166
|
+
|
|
167
|
+
**Round 3: Visual Direction**
|
|
168
|
+
- Describe the color palette. Hex values, or a mood? (e.g., "warm earth tones", "cold Nordic blue")
|
|
169
|
+
- What fonts define this brand? (primary + body. If unknown: serif or sans-serif? Heavy or light?)
|
|
170
|
+
|
|
171
|
+
If the user describes a mood: propose a palette of 4-5 colors (primary, secondary, accent1, accent2, white/background) and confirm before proceeding.
|
|
172
|
+
|
|
173
|
+
**Round 4: Content & Audience**
|
|
174
|
+
- Who are the 1-2 main audiences? For each: who are they, what do they want, how should you talk to them?
|
|
175
|
+
- Anti-audience — someone this brand is explicitly NOT for?
|
|
176
|
+
- What content topics are on-brand? (primary pillars)
|
|
177
|
+
- What topics should the brand avoid?
|
|
178
|
+
|
|
179
|
+
### Synthesis
|
|
141
180
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
181
|
+
After the interview, present a Brand Brief for confirmation:
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
## Brand Brief: [Brand Name]
|
|
185
|
+
ID: [brandid] | Language: [language]
|
|
186
|
+
Essence: [essence]
|
|
187
|
+
|
|
188
|
+
Voice: [persona], [register]
|
|
189
|
+
Always: [rules]
|
|
190
|
+
Never: [anti-patterns]
|
|
191
|
+
|
|
192
|
+
Colors: primary [hex], secondary [hex], accent [hex], background [hex]
|
|
193
|
+
Font: [primary font name]
|
|
194
|
+
|
|
195
|
+
Audiences: [summaries]
|
|
196
|
+
Pillars: [topics] | Avoid: [topics]
|
|
148
197
|
```
|
|
149
198
|
|
|
150
|
-
|
|
199
|
+
Ask the user to confirm or adjust. Iterate until they're happy.
|
|
200
|
+
|
|
201
|
+
### File Generation
|
|
202
|
+
|
|
203
|
+
Once confirmed, generate these files at the project root:
|
|
204
|
+
|
|
205
|
+
1. **`[brandid].identity`** — essence, promise, tagline, archetype, voice (register, persona, rhythm, always/never), pillars, audience blocks, anti-audience blocks. Follow the structure of the Krume reference.
|
|
151
206
|
|
|
152
|
-
|
|
207
|
+
2. **`[brandid].brand`** — id, name, language, locale, brand-colors, at least one theme (mapping semantic slots to color references), font blocks, typography blocks (headline, body, caption, label at minimum), spacing, dividers. Use `$name` references for theme colors.
|
|
153
208
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
209
|
+
3. **`purposes/starter.purpose`** — a starter purpose with slots that have on-brand sample content derived from the identity. Samples must be in the brand's language and respect voice rules.
|
|
210
|
+
|
|
211
|
+
### Rules
|
|
212
|
+
|
|
213
|
+
- **Every voice rule, value, and narrative must come from the user's answers.** Don't invent brand personality.
|
|
214
|
+
- **Sample content in purposes must match the brand's language.**
|
|
215
|
+
- **Don't skip the interview.** Even if the user says "just make a brand called X" — ask at least the essentials (essence, voice, colors, one audience).
|
|
216
|
+
- **Typography sizes use `cqmin` units.** Use these sensible defaults unless the user specified otherwise: headline 6, body 2.8, caption 2.2, label 2.
|
|
217
|
+
- **Spacing defaults:** unit cqmin, xs 1.5, s 2, m 3, l 5, xl 6.5.
|
|
218
|
+
|
|
219
|
+
## Example: Scaffold the Krume Reference
|
|
220
|
+
|
|
221
|
+
When `$ARGUMENTS` is "example", copy the Krume bakery reference files into `examples/krume/` in the current project. The Krume files are distributed with the npm package.
|
|
222
|
+
|
|
223
|
+
Run this shell command to copy them:
|
|
224
|
+
|
|
225
|
+
```bash
|
|
226
|
+
node -e "
|
|
227
|
+
const pkg = require.resolve('brand-intent/package.json');
|
|
228
|
+
const root = require('path').dirname(pkg);
|
|
229
|
+
const { cpSync, mkdirSync } = require('fs');
|
|
230
|
+
const dest = 'examples/krume';
|
|
231
|
+
mkdirSync(dest, { recursive: true });
|
|
232
|
+
for (const d of ['brands/krume', 'formats', 'purposes', 'compositions']) {
|
|
233
|
+
const src = require('path').join(root, d);
|
|
234
|
+
try { cpSync(src, require('path').join(dest, d.replace('brands/krume', '')), { recursive: true }); } catch {}
|
|
235
|
+
}
|
|
236
|
+
console.log('Krume example scaffolded into examples/krume/');
|
|
237
|
+
"
|
|
238
|
+
```
|
|
157
239
|
|
|
158
|
-
|
|
240
|
+
Then explain to the user what each file does:
|
|
241
|
+
- **`.identity`** — the strategic layer: essence, voice, values, audiences. Read this first.
|
|
242
|
+
- **`.brand`** — derived expression: colors, themes, typography, spacing. Every decision traces back to identity.
|
|
243
|
+
- **`.format`** — canvas dimensions, safe zones, grid. Where content appears.
|
|
244
|
+
- **`.purpose`** — content type with semantic slots, samples, constraints. What content does.
|
|
245
|
+
- **`.composition`** — spatial arrangement of slots. How elements are placed.
|