get-shit-pretty 0.7.4 → 0.8.3
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 +21 -14
- package/bin/install.js +10 -10
- package/bin/theme-css.js +331 -0
- package/gsp/agents/gsp-brand-coherence.md +7 -0
- package/gsp/skills/get-shit-pretty/SKILL.md +3 -1
- package/gsp/skills/gsp-brand-brief/SKILL.md +50 -5
- package/gsp/skills/gsp-brand-guidelines/SKILL.md +51 -31
- package/gsp/skills/gsp-brand-guidelines/design-tokens.md +2 -0
- package/gsp/skills/gsp-brand-guidelines/guidelines-structure.md +167 -0
- package/gsp/skills/gsp-brand-guidelines/methodology/gsp-brand-coherence.md +86 -0
- package/gsp/skills/gsp-brand-guidelines/methodology/gsp-brand-engineer.md +13 -5
- package/gsp/skills/gsp-brand-guidelines/token-mapping.md +16 -319
- package/gsp/skills/gsp-brand-identity/SKILL.md +1 -1
- package/gsp/skills/gsp-brand-refine/SKILL.md +5 -6
- package/gsp/skills/gsp-brand-research/SKILL.md +1 -1
- package/gsp/skills/gsp-brand-strategy/SKILL.md +1 -1
- package/gsp/skills/gsp-design-system/SKILL.md +1 -1
- package/gsp/skills/gsp-doctor/SKILL.md +54 -6
- package/gsp/skills/gsp-progress/SKILL.md +1 -1
- package/gsp/skills/gsp-project-brief/SKILL.md +40 -6
- package/gsp/skills/gsp-project-build/SKILL.md +22 -29
- package/gsp/skills/gsp-project-build/flows/figma.md +50 -0
- package/gsp/skills/gsp-project-build/flows/revision.md +64 -0
- package/gsp/skills/gsp-project-build/methodology/gsp-project-builder.md +57 -4
- package/gsp/skills/gsp-project-build/shadcn-composition.md +217 -0
- package/gsp/skills/gsp-project-critique/SKILL.md +3 -1
- package/gsp/skills/gsp-project-design/SKILL.md +3 -1
- package/gsp/skills/gsp-project-research/SKILL.md +3 -1
- package/gsp/skills/gsp-project-review/SKILL.md +10 -1
- package/gsp/skills/gsp-scaffold/SKILL.md +49 -12
- package/gsp/skills/gsp-scaffold/shadcn-rules.md +433 -0
- package/gsp/skills/gsp-scaffold/shadcn-theming.md +310 -0
- package/gsp/skills/gsp-start/SKILL.md +18 -2
- package/gsp/skills/gsp-style/SKILL.md +1 -1
- package/gsp/skills/gsp-style/style-preset-schema.md +59 -14
- package/gsp/skills/gsp-style/styles/academia.yml +58 -8
- package/gsp/skills/gsp-style/styles/art-deco.yml +39 -7
- package/gsp/skills/gsp-style/styles/bauhaus.yml +39 -8
- package/gsp/skills/gsp-style/styles/bold-typography.yml +39 -8
- package/gsp/skills/gsp-style/styles/botanical.yml +39 -9
- package/gsp/skills/gsp-style/styles/claymorphism.yml +39 -9
- package/gsp/skills/gsp-style/styles/cyberpunk.yml +39 -7
- package/gsp/skills/gsp-style/styles/enterprise.yml +39 -10
- package/gsp/skills/gsp-style/styles/flat-design.yml +39 -9
- package/gsp/skills/gsp-style/styles/fluent.yml +39 -10
- package/gsp/skills/gsp-style/styles/glassmorphism.yml +39 -8
- package/gsp/skills/gsp-style/styles/humanist-literary.yml +39 -9
- package/gsp/skills/gsp-style/styles/industrial.yml +59 -9
- package/gsp/skills/gsp-style/styles/kinetic.yml +32 -7
- package/gsp/skills/gsp-style/styles/liquid-glass.yml +59 -9
- package/gsp/skills/gsp-style/styles/luxury.yml +59 -9
- package/gsp/skills/gsp-style/styles/material.yml +59 -9
- package/gsp/skills/gsp-style/styles/maximalism.yml +32 -6
- package/gsp/skills/gsp-style/styles/minimal-dark.yml +32 -7
- package/gsp/skills/gsp-style/styles/modern-dark.yml +32 -7
- package/gsp/skills/gsp-style/styles/monochrome.yml +59 -10
- package/gsp/skills/gsp-style/styles/neubrutalism.yml +59 -9
- package/gsp/skills/gsp-style/styles/neumorphism.yml +32 -7
- package/gsp/skills/gsp-style/styles/newsprint.yml +32 -7
- package/gsp/skills/gsp-style/styles/nothing.yml +44 -13
- package/gsp/skills/gsp-style/styles/organic.yml +42 -9
- package/gsp/skills/gsp-style/styles/playful-geometric.yml +43 -9
- package/gsp/skills/gsp-style/styles/professional.yml +41 -10
- package/gsp/skills/gsp-style/styles/retro.yml +42 -8
- package/gsp/skills/gsp-style/styles/saas.yml +42 -9
- package/gsp/skills/gsp-style/styles/sketch.yml +42 -9
- package/gsp/skills/gsp-style/styles/swiss-minimalist.yml +41 -10
- package/gsp/skills/gsp-style/styles/terminal.yml +42 -8
- package/gsp/skills/gsp-style/styles/vaporwave.yml +42 -7
- package/gsp/skills/gsp-style/styles/web3.yml +42 -9
- package/gsp/skills/gsp-update/SKILL.md +9 -6
- package/gsp/templates/branding/brief.md +9 -0
- package/gsp/templates/branding/config.json +1 -1
- package/gsp/templates/design-claude.md +6 -0
- package/gsp/templates/phases/patterns.md +2 -2
- package/gsp/templates/projects/config.json +6 -3
- package/gsp/templates/projects/state.md +1 -1
- package/gsp/templates/system/STACK.md +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -12,7 +12,9 @@
|
|
|
12
12
|
<br>
|
|
13
13
|
|
|
14
14
|
```bash
|
|
15
|
-
|
|
15
|
+
pnpm dlx get-shit-pretty
|
|
16
|
+
# or with bun
|
|
17
|
+
bunx get-shit-pretty
|
|
16
18
|
```
|
|
17
19
|
|
|
18
20
|
**Works on Mac, Windows, and Linux.**
|
|
@@ -50,8 +52,9 @@ Both disciplines. Same pipeline. Same environment. The missing half of the bridg
|
|
|
50
52
|
## Quick Start
|
|
51
53
|
|
|
52
54
|
```bash
|
|
53
|
-
# 1. Install
|
|
54
|
-
|
|
55
|
+
# 1. Install (pnpm or bun)
|
|
56
|
+
pnpm dlx get-shit-pretty
|
|
57
|
+
# bunx get-shit-pretty
|
|
55
58
|
|
|
56
59
|
# 2. Define your brand — or skip with a style preset
|
|
57
60
|
/gsp-brand-brief # guided brand definition
|
|
@@ -418,7 +421,9 @@ GSP works across all major AI coding tools. The installer converts Claude Code's
|
|
|
418
421
|
## Install
|
|
419
422
|
|
|
420
423
|
```bash
|
|
421
|
-
|
|
424
|
+
pnpm dlx get-shit-pretty
|
|
425
|
+
# or with bun
|
|
426
|
+
bunx get-shit-pretty
|
|
422
427
|
```
|
|
423
428
|
|
|
424
429
|
The installer prompts you to choose:
|
|
@@ -430,32 +435,34 @@ The installer prompts you to choose:
|
|
|
430
435
|
|
|
431
436
|
```bash
|
|
432
437
|
# Claude Code
|
|
433
|
-
|
|
434
|
-
|
|
438
|
+
pnpm dlx get-shit-pretty --claude --global
|
|
439
|
+
pnpm dlx get-shit-pretty --claude --local
|
|
435
440
|
|
|
436
441
|
# OpenCode
|
|
437
|
-
|
|
442
|
+
pnpm dlx get-shit-pretty --opencode --global
|
|
438
443
|
|
|
439
444
|
# Gemini CLI
|
|
440
|
-
|
|
445
|
+
pnpm dlx get-shit-pretty --gemini --global
|
|
441
446
|
|
|
442
447
|
# Codex CLI
|
|
443
|
-
|
|
448
|
+
pnpm dlx get-shit-pretty --codex --global
|
|
444
449
|
|
|
445
450
|
# All runtimes
|
|
446
|
-
|
|
451
|
+
pnpm dlx get-shit-pretty --all --global
|
|
447
452
|
```
|
|
448
453
|
|
|
454
|
+
> Substitute `bunx` for `pnpm dlx` if you prefer bun.
|
|
455
|
+
|
|
449
456
|
</details>
|
|
450
457
|
|
|
451
458
|
<details>
|
|
452
459
|
<summary><strong>Uninstall</strong></summary>
|
|
453
460
|
|
|
454
461
|
```bash
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
462
|
+
pnpm dlx get-shit-pretty --claude --global --uninstall
|
|
463
|
+
pnpm dlx get-shit-pretty --opencode --global --uninstall
|
|
464
|
+
pnpm dlx get-shit-pretty --gemini --global --uninstall
|
|
465
|
+
pnpm dlx get-shit-pretty --codex --global --uninstall
|
|
459
466
|
```
|
|
460
467
|
|
|
461
468
|
</details>
|
package/bin/install.js
CHANGED
|
@@ -204,7 +204,7 @@ console.log(banner);
|
|
|
204
204
|
|
|
205
205
|
// Help
|
|
206
206
|
if (hasHelp) {
|
|
207
|
-
console.log(` ${yellow}Usage:${reset}
|
|
207
|
+
console.log(` ${yellow}Usage:${reset} pnpm dlx get-shit-pretty [options] ${dim}# or: bunx get-shit-pretty [options]${reset}\n
|
|
208
208
|
${yellow}Options:${reset}
|
|
209
209
|
${cyan}-g, --global${reset} Install globally (to config directory)
|
|
210
210
|
${cyan}-l, --local${reset} Install locally (to current directory)
|
|
@@ -221,19 +221,19 @@ if (hasHelp) {
|
|
|
221
221
|
|
|
222
222
|
${yellow}Examples:${reset}
|
|
223
223
|
${dim}# Interactive install (prompts for runtime and location)${reset}
|
|
224
|
-
|
|
224
|
+
pnpm dlx get-shit-pretty
|
|
225
225
|
|
|
226
226
|
${dim}# Install for Claude Code globally${reset}
|
|
227
|
-
|
|
227
|
+
pnpm dlx get-shit-pretty --claude --global
|
|
228
228
|
|
|
229
229
|
${dim}# Install for all runtimes globally${reset}
|
|
230
|
-
|
|
230
|
+
pnpm dlx get-shit-pretty --all --global
|
|
231
231
|
|
|
232
232
|
${dim}# Install to current project only${reset}
|
|
233
|
-
|
|
233
|
+
pnpm dlx get-shit-pretty --claude --local
|
|
234
234
|
|
|
235
235
|
${dim}# Uninstall GSP from Claude Code globally${reset}
|
|
236
|
-
|
|
236
|
+
pnpm dlx get-shit-pretty --claude --global --uninstall
|
|
237
237
|
`);
|
|
238
238
|
process.exit(0);
|
|
239
239
|
}
|
|
@@ -1817,8 +1817,8 @@ function promptRuntime(callback) {
|
|
|
1817
1817
|
|
|
1818
1818
|
function promptLocation(runtimes) {
|
|
1819
1819
|
if (!process.stdin.isTTY) {
|
|
1820
|
-
console.log(` ${yellow}Non-interactive terminal detected, defaulting to
|
|
1821
|
-
installAllRuntimes(runtimes,
|
|
1820
|
+
console.log(` ${yellow}Non-interactive terminal detected, defaulting to local (project) install${reset}\n`);
|
|
1821
|
+
installAllRuntimes(runtimes, false, false);
|
|
1822
1822
|
return;
|
|
1823
1823
|
}
|
|
1824
1824
|
|
|
@@ -1898,8 +1898,8 @@ if (require.main === module) {
|
|
|
1898
1898
|
installAllRuntimes(['claude'], hasGlobal, false);
|
|
1899
1899
|
} else {
|
|
1900
1900
|
if (!process.stdin.isTTY) {
|
|
1901
|
-
console.log(` ${yellow}Non-interactive terminal detected, defaulting to Claude Code
|
|
1902
|
-
installAllRuntimes(['claude'],
|
|
1901
|
+
console.log(` ${yellow}Non-interactive terminal detected, defaulting to Claude Code local (project) install${reset}\n`);
|
|
1902
|
+
installAllRuntimes(['claude'], false, false);
|
|
1903
1903
|
} else {
|
|
1904
1904
|
promptRuntime((runtimes) => {
|
|
1905
1905
|
promptLocation(runtimes);
|
package/bin/theme-css.js
ADDED
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* theme-css.js — GSP deterministic token-to-CSS generator
|
|
4
|
+
*
|
|
5
|
+
* Reads a GSP style preset `.yml` file and outputs a shadcn/ui-compatible
|
|
6
|
+
* CSS variables block for `:root` and `.dark`.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node bin/theme-css.js <path-to-preset.yml>
|
|
10
|
+
* node bin/theme-css.js <path-to-preset.yml> --output globals.css
|
|
11
|
+
* node bin/theme-css.js <path-to-preset.yml> --stdout
|
|
12
|
+
*
|
|
13
|
+
* Token → CSS var mapping is 1:1. No derivation, no LLM guesswork.
|
|
14
|
+
* Hex values are converted to OKLCH. Alpha values (oklch with /) pass through.
|
|
15
|
+
* Sidebar vars are output verbatim. Extras (success/warning/info) become custom props.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
'use strict';
|
|
19
|
+
|
|
20
|
+
const fs = require('fs');
|
|
21
|
+
const path = require('path');
|
|
22
|
+
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
// YAML parser (zero-dependency, handles the subset GSP uses)
|
|
25
|
+
// ---------------------------------------------------------------------------
|
|
26
|
+
|
|
27
|
+
function parseYaml(text) {
|
|
28
|
+
const lines = text.split('\n');
|
|
29
|
+
const root = {};
|
|
30
|
+
const stack = [{ indent: -1, obj: root }];
|
|
31
|
+
|
|
32
|
+
for (let i = 0; i < lines.length; i++) {
|
|
33
|
+
const raw = lines[i];
|
|
34
|
+
const trimmed = raw.trimEnd();
|
|
35
|
+
if (!trimmed || trimmed.trimStart().startsWith('#')) continue;
|
|
36
|
+
|
|
37
|
+
const indent = raw.length - raw.trimStart().length;
|
|
38
|
+
const content = trimmed.trimStart();
|
|
39
|
+
|
|
40
|
+
// Handle inline arrays: key: [a, b, c]
|
|
41
|
+
const colonIdx = content.indexOf(':');
|
|
42
|
+
if (colonIdx === -1) continue;
|
|
43
|
+
|
|
44
|
+
const key = content.slice(0, colonIdx).trim();
|
|
45
|
+
const rest = content.slice(colonIdx + 1).trim();
|
|
46
|
+
|
|
47
|
+
// Pop stack to correct parent
|
|
48
|
+
while (stack.length > 1 && stack[stack.length - 1].indent >= indent) {
|
|
49
|
+
stack.pop();
|
|
50
|
+
}
|
|
51
|
+
const parent = stack[stack.length - 1].obj;
|
|
52
|
+
|
|
53
|
+
if (rest === '' || rest.startsWith('#')) {
|
|
54
|
+
// Nested object
|
|
55
|
+
parent[key] = {};
|
|
56
|
+
stack.push({ indent, obj: parent[key] });
|
|
57
|
+
} else if (rest.startsWith('[')) {
|
|
58
|
+
// Inline array — parse as string, not needed for color extraction
|
|
59
|
+
parent[key] = rest;
|
|
60
|
+
} else {
|
|
61
|
+
// Scalar — strip inline comments and quotes
|
|
62
|
+
let val = rest.split(' #')[0].trim();
|
|
63
|
+
if ((val.startsWith('"') && val.endsWith('"')) ||
|
|
64
|
+
(val.startsWith("'") && val.endsWith("'"))) {
|
|
65
|
+
val = val.slice(1, -1);
|
|
66
|
+
}
|
|
67
|
+
// Numbers
|
|
68
|
+
if (/^-?\d+(\.\d+)?$/.test(val)) {
|
|
69
|
+
parent[key] = parseFloat(val);
|
|
70
|
+
} else if (val === 'true') {
|
|
71
|
+
parent[key] = true;
|
|
72
|
+
} else if (val === 'false') {
|
|
73
|
+
parent[key] = false;
|
|
74
|
+
} else {
|
|
75
|
+
parent[key] = val;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return root;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Color conversion: hex → OKLCH
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
|
|
87
|
+
function hexToRgb(hex) {
|
|
88
|
+
const h = hex.replace('#', '');
|
|
89
|
+
const len = h.length;
|
|
90
|
+
if (len === 3) {
|
|
91
|
+
return [
|
|
92
|
+
parseInt(h[0] + h[0], 16),
|
|
93
|
+
parseInt(h[1] + h[1], 16),
|
|
94
|
+
parseInt(h[2] + h[2], 16),
|
|
95
|
+
];
|
|
96
|
+
}
|
|
97
|
+
return [
|
|
98
|
+
parseInt(h.slice(0, 2), 16),
|
|
99
|
+
parseInt(h.slice(2, 4), 16),
|
|
100
|
+
parseInt(h.slice(4, 6), 16),
|
|
101
|
+
];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// sRGB → linear
|
|
105
|
+
function toLinear(c) {
|
|
106
|
+
c = c / 255;
|
|
107
|
+
return c <= 0.04045 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Linear RGB → XYZ (D65)
|
|
111
|
+
function linearToXyz(r, g, b) {
|
|
112
|
+
return [
|
|
113
|
+
r * 0.4124564 + g * 0.3575761 + b * 0.1804375,
|
|
114
|
+
r * 0.2126729 + g * 0.7151522 + b * 0.0721750,
|
|
115
|
+
r * 0.0193339 + g * 0.1191920 + b * 0.9503041,
|
|
116
|
+
];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// XYZ → OKLab
|
|
120
|
+
function xyzToOklab(x, y, z) {
|
|
121
|
+
const l_ = Math.cbrt(0.8189330101 * x + 0.3618667424 * y - 0.1288597137 * z);
|
|
122
|
+
const m_ = Math.cbrt(0.0329845436 * x + 0.9293118715 * y + 0.0361456387 * z);
|
|
123
|
+
const s_ = Math.cbrt(0.0482003018 * x + 0.2643662691 * y + 0.6338517070 * z);
|
|
124
|
+
return [
|
|
125
|
+
0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_,
|
|
126
|
+
1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_,
|
|
127
|
+
0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_,
|
|
128
|
+
];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// OKLab → OKLCH
|
|
132
|
+
function oklabToOklch(L, a, b) {
|
|
133
|
+
const C = Math.sqrt(a * a + b * b);
|
|
134
|
+
let H = Math.atan2(b, a) * (180 / Math.PI);
|
|
135
|
+
if (H < 0) H += 360;
|
|
136
|
+
return [L, C, H];
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function hexToOklch(hex) {
|
|
140
|
+
const [r, g, b] = hexToRgb(hex);
|
|
141
|
+
const lr = toLinear(r);
|
|
142
|
+
const lg = toLinear(g);
|
|
143
|
+
const lb = toLinear(b);
|
|
144
|
+
const [x, y, z] = linearToXyz(lr, lg, lb);
|
|
145
|
+
const [L, a, bk] = xyzToOklab(x, y, z);
|
|
146
|
+
const [Lch, C, H] = oklabToOklch(L, a, bk);
|
|
147
|
+
// Format: oklch(L% C H)
|
|
148
|
+
const Lpct = (Lch * 100).toFixed(2);
|
|
149
|
+
const Cfmt = C.toFixed(4);
|
|
150
|
+
const Hfmt = H.toFixed(2);
|
|
151
|
+
return `oklch(${Lpct}% ${Cfmt} ${Hfmt})`;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// ---------------------------------------------------------------------------
|
|
155
|
+
// Value formatter: hex → oklch, alpha values pass through
|
|
156
|
+
// ---------------------------------------------------------------------------
|
|
157
|
+
|
|
158
|
+
function formatValue(val) {
|
|
159
|
+
if (typeof val !== 'string') return String(val);
|
|
160
|
+
const v = val.trim();
|
|
161
|
+
|
|
162
|
+
// Already oklch (alpha or not) — pass through
|
|
163
|
+
if (v.startsWith('oklch(')) return v;
|
|
164
|
+
|
|
165
|
+
// Hex color
|
|
166
|
+
if (/^#[0-9a-fA-F]{3,6}$/.test(v)) {
|
|
167
|
+
return hexToOklch(v);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Everything else (rgba, named colors, etc.) — pass through
|
|
171
|
+
return v;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
// CSS var name mapping (1:1 to shadcn/ui)
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
// Core color tokens → CSS var names
|
|
179
|
+
const CORE_VARS = [
|
|
180
|
+
'background', 'foreground',
|
|
181
|
+
'card', 'card-foreground',
|
|
182
|
+
'popover', 'popover-foreground',
|
|
183
|
+
'primary', 'primary-foreground',
|
|
184
|
+
'secondary', 'secondary-foreground',
|
|
185
|
+
'accent', 'accent-foreground',
|
|
186
|
+
'muted', 'muted-foreground',
|
|
187
|
+
'destructive',
|
|
188
|
+
'border', 'input', 'ring',
|
|
189
|
+
];
|
|
190
|
+
|
|
191
|
+
// NOTE: 'sidebar' (not 'sidebar-background') matches shadcn's CSS var --sidebar
|
|
192
|
+
const SIDEBAR_VARS = [
|
|
193
|
+
'sidebar', 'sidebar-foreground',
|
|
194
|
+
'sidebar-primary', 'sidebar-primary-foreground',
|
|
195
|
+
'sidebar-accent', 'sidebar-accent-foreground',
|
|
196
|
+
'sidebar-border', 'sidebar-ring',
|
|
197
|
+
];
|
|
198
|
+
|
|
199
|
+
const EXTRA_VARS = ['success', 'warning', 'info'];
|
|
200
|
+
|
|
201
|
+
// Shape tokens → CSS vars
|
|
202
|
+
const SHAPE_VARS = {
|
|
203
|
+
'border-radius-lg': '--radius',
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
// ---------------------------------------------------------------------------
|
|
207
|
+
// CSS block generator
|
|
208
|
+
// ---------------------------------------------------------------------------
|
|
209
|
+
|
|
210
|
+
function generateBlock(colorObj, shapeObj, typographyObj, selector) {
|
|
211
|
+
if (!colorObj) return '';
|
|
212
|
+
const lines = [];
|
|
213
|
+
|
|
214
|
+
// Core vars
|
|
215
|
+
for (const key of CORE_VARS) {
|
|
216
|
+
if (colorObj[key] !== undefined) {
|
|
217
|
+
lines.push(` --${key}: ${formatValue(colorObj[key])};`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Sidebar vars
|
|
222
|
+
for (const key of SIDEBAR_VARS) {
|
|
223
|
+
if (colorObj[key] !== undefined) {
|
|
224
|
+
lines.push(` --${key}: ${formatValue(colorObj[key])};`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Chart vars — emit in both :root and .dark (dark falls back to light values if not set)
|
|
229
|
+
{
|
|
230
|
+
// For :root use light palette; for .dark use dark overrides falling back to light
|
|
231
|
+
const chartColors = [
|
|
232
|
+
colorObj['chart-1'] || colorObj.primary,
|
|
233
|
+
colorObj['chart-2'] || colorObj.secondary,
|
|
234
|
+
colorObj['chart-3'] || colorObj.accent,
|
|
235
|
+
colorObj['chart-4'] || colorObj.info || colorObj.primary,
|
|
236
|
+
colorObj['chart-5'] || colorObj.success || colorObj.secondary || colorObj.primary,
|
|
237
|
+
];
|
|
238
|
+
chartColors.forEach((c, i) => {
|
|
239
|
+
if (c) lines.push(` --chart-${i + 1}: ${formatValue(c)};`);
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Shape → radius (only in :root)
|
|
244
|
+
if (selector === ':root' && shapeObj) {
|
|
245
|
+
const lg = shapeObj['border-radius-lg'];
|
|
246
|
+
if (lg !== undefined) {
|
|
247
|
+
lines.push(` --radius: ${lg};`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Typography → font CSS vars (only in :root)
|
|
252
|
+
if (selector === ':root' && typographyObj) {
|
|
253
|
+
const fontMappings = [
|
|
254
|
+
['font-family-primary', '--font-sans'],
|
|
255
|
+
['font-family-mono', '--font-mono'],
|
|
256
|
+
['font-family-display', '--font-display'],
|
|
257
|
+
['font-family-secondary', '--font-secondary'],
|
|
258
|
+
];
|
|
259
|
+
for (const [ymlKey, cssVar] of fontMappings) {
|
|
260
|
+
if (typographyObj[ymlKey] !== undefined) {
|
|
261
|
+
lines.push(` ${cssVar}: ${typographyObj[ymlKey]};`);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Extras as custom properties
|
|
267
|
+
for (const key of EXTRA_VARS) {
|
|
268
|
+
if (colorObj[key] !== undefined) {
|
|
269
|
+
lines.push(` --${key}: ${formatValue(colorObj[key])};`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (!lines.length) return '';
|
|
274
|
+
return `${selector} {\n${lines.join('\n')}\n}`;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// ---------------------------------------------------------------------------
|
|
278
|
+
// Main
|
|
279
|
+
// ---------------------------------------------------------------------------
|
|
280
|
+
|
|
281
|
+
function main() {
|
|
282
|
+
const args = process.argv.slice(2);
|
|
283
|
+
if (!args.length || args.includes('--help') || args.includes('-h')) {
|
|
284
|
+
console.log(`Usage: node bin/theme-css.js <preset.yml> [--output <file>] [--stdout]`);
|
|
285
|
+
console.log(` node bin/theme-css.js gsp/skills/gsp-style/styles/saas.yml`);
|
|
286
|
+
process.exit(0);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const inputPath = path.resolve(args[0]);
|
|
290
|
+
if (!fs.existsSync(inputPath)) {
|
|
291
|
+
console.error(`Error: File not found: ${inputPath}`);
|
|
292
|
+
process.exit(1);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const outputIdx = args.indexOf('--output');
|
|
296
|
+
const outputPath = outputIdx !== -1 ? path.resolve(args[outputIdx + 1]) : null;
|
|
297
|
+
const toStdout = args.includes('--stdout') || !outputPath;
|
|
298
|
+
|
|
299
|
+
const raw = fs.readFileSync(inputPath, 'utf8');
|
|
300
|
+
const preset = parseYaml(raw);
|
|
301
|
+
|
|
302
|
+
const colorLight = (preset.tokens && preset.tokens.color) || {};
|
|
303
|
+
const colorDark = (preset.dark_mode && preset.dark_mode.color) || {};
|
|
304
|
+
const shape = (preset.tokens && preset.tokens.shape) || {};
|
|
305
|
+
const typography = (preset.tokens && preset.tokens.typography) || null;
|
|
306
|
+
|
|
307
|
+
const rootBlock = generateBlock(colorLight, shape, typography, ':root');
|
|
308
|
+
const darkBlock = generateBlock(colorDark, null, null, '.dark');
|
|
309
|
+
|
|
310
|
+
const presetName = preset.name || path.basename(inputPath, '.yml');
|
|
311
|
+
const presetDesc = preset.description || '';
|
|
312
|
+
|
|
313
|
+
const header = [
|
|
314
|
+
`/* GSP theme: ${presetName} */`,
|
|
315
|
+
presetDesc ? `/* ${presetDesc} */` : null,
|
|
316
|
+
`/* Generated by bin/theme-css.js from ${path.basename(inputPath)} */`,
|
|
317
|
+
`/* Edit the .yml file, not this output */`,
|
|
318
|
+
'',
|
|
319
|
+
].filter(Boolean).join('\n');
|
|
320
|
+
|
|
321
|
+
const output = [header, rootBlock, darkBlock ? '' : null, darkBlock].filter(s => s !== null).join('\n');
|
|
322
|
+
|
|
323
|
+
if (toStdout) {
|
|
324
|
+
process.stdout.write(output + '\n');
|
|
325
|
+
} else {
|
|
326
|
+
fs.writeFileSync(outputPath, output + '\n', 'utf8');
|
|
327
|
+
console.log(`Written to ${outputPath}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
main();
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gsp-brand-coherence
|
|
3
|
+
description: Brand coherence auditor — scores intensity dials against archetype and surfaces tensions between declared values and expressed output
|
|
4
|
+
tools:
|
|
5
|
+
- Read
|
|
6
|
+
---
|
|
7
|
+
Coherence auditor for gsp-brand-guidelines. Spawned by the skill after Pass 1 to evaluate the generated artifacts before user review.
|
|
@@ -11,7 +11,9 @@ Design engineering system for AI coding tools. Brand identity + design projects,
|
|
|
11
11
|
## Install
|
|
12
12
|
|
|
13
13
|
```bash
|
|
14
|
-
|
|
14
|
+
pnpm dlx get-shit-pretty
|
|
15
|
+
# or with bun
|
|
16
|
+
bunx get-shit-pretty
|
|
15
17
|
```
|
|
16
18
|
|
|
17
19
|
Pick your runtime (Claude Code, OpenCode, Gemini CLI, or Codex CLI), choose global or local install, and you're set.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: gsp-brand-brief
|
|
3
|
-
description: Define your brand — who, why, and what it should feel like
|
|
3
|
+
description: Define your brand — who, why, and what it should feel like — use when: create a brand, define our brand identity, who are we, what's our brand
|
|
4
4
|
user-invocable: true
|
|
5
5
|
allowed-tools:
|
|
6
6
|
- Read
|
|
@@ -86,23 +86,35 @@ Before presenting personality options, **internally synthesize** promise (what s
|
|
|
86
86
|
- Note: this is a high-level direction only. Brand strategy phase will deepen this into archetype + voice — don't over-refine here.
|
|
87
87
|
8. What should the brand NEVER feel like? (use `AskUserQuestion` with 2-3 anti-directions inferred from their personality pick, plus open-ended option)
|
|
88
88
|
9. Brands admired or styles to avoid? (open-ended `AskUserQuestion`)
|
|
89
|
+
10. Visual direction — raw aesthetic feeling. Use `AskUserQuestion` (open-ended):
|
|
90
|
+
> "What should it look and feel like visually? You can share image or website links, describe a mood ('editorial and dark', 'warm brutalist', 'cinematographic with beautiful stills'), drop adjective clusters ('rounded, clean, airy'), or even describe a scene or texture. The weirder and more specific the better — this is what prevents a bland brand."
|
|
91
|
+
- Synthesize the answer into a `visual_direction` block in the brief: mood words, reference aesthetics, texture/atmosphere descriptors, any specific anti-patterns (e.g., "never stock-photo corporate"). This block directly informs color, typography, and imagery choices downstream.
|
|
89
92
|
|
|
90
93
|
## Step 4: Constraints & confirmation
|
|
91
94
|
|
|
92
|
-
|
|
93
|
-
|
|
95
|
+
11. Any non-negotiables or constraints? (timeline, budget, must-haves) — open-ended `AskUserQuestion`
|
|
96
|
+
12. State your understanding back — but lead with *feeling*, not facts. Format:
|
|
97
|
+
|
|
98
|
+
> "Here's what I'm hearing: [2-sentence factual summary].
|
|
99
|
+
> The feeling this brand should leave: **[emotional compass — one evocative sentence capturing the brand's energy, not its category]**."
|
|
100
|
+
|
|
101
|
+
The emotional compass is the hardest line to write and the most important. It should make the user feel something when they read it. Not "a fintech tool that simplifies investing" but "the brand that makes financial confidence feel earned, not given." Synthesize it from the personality direction, the persona aspiration, the brand POV, and the visual direction. It should be specific enough to be wrong — vague sentences aren't compasses.
|
|
102
|
+
|
|
103
|
+
Use `AskUserQuestion`:
|
|
94
104
|
- **Looks good** — "That's accurate, let's go"
|
|
95
|
-
- **Adjust
|
|
105
|
+
- **Adjust the feeling** — "The compass is off — let me reframe it"
|
|
106
|
+
- **Adjust something else** — "Facts are right but I want to change something"
|
|
96
107
|
|
|
97
108
|
If "Adjust" — ask what to change, update your understanding, re-confirm. Don't re-ask everything.
|
|
98
109
|
|
|
99
|
-
## Step 5: Write artifacts
|
|
110
|
+
## Step 5: Write artifacts and register brand
|
|
100
111
|
|
|
101
112
|
Read templates at write time from `${CLAUDE_SKILL_DIR}/../../templates/branding/` and write:
|
|
102
113
|
|
|
103
114
|
1. `.design/branding/{name}/BRIEF.md` from `brief.md` template
|
|
104
115
|
- Populate all sections from conversation answers
|
|
105
116
|
- Synthesize brand promise, POV, and personality (these are inferred, not asked directly)
|
|
117
|
+
- Write the confirmed emotional compass as `brand_heartbeat` in the Emotional Compass section
|
|
106
118
|
- Set `brand_mode` to `new`
|
|
107
119
|
- Set evolve-only sections (Existing Brand State, Evolution Scope) to "N/A — new brand"
|
|
108
120
|
|
|
@@ -117,6 +129,14 @@ Read templates at write time from `${CLAUDE_SKILL_DIR}/../../templates/branding/
|
|
|
117
129
|
|
|
118
130
|
4. `.design/branding/{name}/ROADMAP.md` from `roadmap.md` template
|
|
119
131
|
|
|
132
|
+
5. Write/update `.design/CLAUDE.md` — register the brand as started. If the file doesn't exist, read `${CLAUDE_SKILL_DIR}/../../templates/design-claude.md` first. Append under `## Brands`:
|
|
133
|
+
|
|
134
|
+
```markdown
|
|
135
|
+
### {brand-name} · in progress · {DATE}
|
|
136
|
+
"{brand_heartbeat}"
|
|
137
|
+
next: gsp-brand-research · .design/branding/{brand-name}/
|
|
138
|
+
```
|
|
139
|
+
|
|
120
140
|
## Step 6: Route
|
|
121
141
|
|
|
122
142
|
Use `AskUserQuestion` — always offer Continue / Stop here / What happens next:
|
|
@@ -126,4 +146,29 @@ Use `AskUserQuestion` — always offer Continue / Stop here / What happens next:
|
|
|
126
146
|
- **What happens next?** — "Explain the research phase" → explain what brand-research does (market landscape, competitive audit, trend analysis, mood board direction) and how it uses the brief
|
|
127
147
|
|
|
128
148
|
If `e2e: true`, mention that after the full branding diamond completes, it will auto-transition to project setup.
|
|
149
|
+
|
|
150
|
+
## Step 7: e2e transition (only when `e2e: true` and branding diamond is complete)
|
|
151
|
+
|
|
152
|
+
After all four brand phases complete (brand-research → brand-strategy → brand-identity → brand-guidelines), scaffold the project directory before invoking `/gsp-project-brief`:
|
|
153
|
+
|
|
154
|
+
1. Derive `{project-slug}` from the brand name: lowercase, spaces and underscores replaced with hyphens.
|
|
155
|
+
|
|
156
|
+
2. Create the project directory:
|
|
157
|
+
```bash
|
|
158
|
+
mkdir -p .design/projects/{project-slug}/
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
3. Read templates at write time from `${CLAUDE_SKILL_DIR}/../../templates/projects/` and write:
|
|
162
|
+
- `.design/projects/{project-slug}/config.json` from `config.json` template — set `project.name` (title-cased from project-slug) and `project.created` (ISO date)
|
|
163
|
+
- `.design/projects/{project-slug}/STATE.md` from `state.md` template — fill in project name and brand name
|
|
164
|
+
|
|
165
|
+
4. Write `.design/projects/{project-slug}/brand.ref` containing the brand directory name (e.g. `{brand-name}`), so the project knows which brand it belongs to.
|
|
166
|
+
|
|
167
|
+
5. Display:
|
|
168
|
+
```
|
|
169
|
+
brand complete — {brand-name}
|
|
170
|
+
now let's scope your project.
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
6. Invoke `/gsp-project-brief` via Skill tool, passing `{project-slug}` so Step 0 resolves the existing directory rather than prompting for one.
|
|
129
174
|
</process>
|