get-shit-pretty 0.8.2 → 0.9.1
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/gsp/skills/get-shit-pretty/SKILL.md +3 -1
- package/gsp/skills/gsp-brand-apply/SKILL.md +213 -0
- package/gsp/skills/gsp-brand-apply/bin/serve-preset.js +71 -0
- package/gsp/skills/gsp-brand-guidelines/SKILL.md +53 -2
- package/{bin → gsp/skills/gsp-brand-guidelines/bin}/theme-css.js +124 -10
- package/gsp/skills/gsp-brand-guidelines/design-tokens.md +1 -1
- package/gsp/skills/gsp-brand-guidelines/methodology/gsp-brand-engineer.md +1 -1
- package/gsp/skills/gsp-brand-guidelines/token-mapping.md +2 -2
- package/gsp/skills/gsp-brand-refine/SKILL.md +33 -2
- package/gsp/skills/gsp-doctor/SKILL.md +3 -3
- package/gsp/skills/gsp-project-build/SKILL.md +26 -2
- package/gsp/skills/gsp-project-build/methodology/gsp-project-builder.md +2 -2
- package/gsp/skills/gsp-scaffold/shadcn-rules.md +8 -12
- package/gsp/skills/gsp-style/SKILL.md +1 -1
- package/gsp/skills/gsp-style/style-preset-schema.md +1 -1
- package/gsp/skills/gsp-update/SKILL.md +9 -6
- package/gsp/templates/branding/config.json +1 -1
- package/gsp/templates/phases/patterns.md +2 -2
- package/gsp/templates/projects/config.json +1 -1
- 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);
|
|
@@ -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.
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gsp-brand-apply
|
|
3
|
+
description: Install a brand theme into a shadcn codebase — use when: apply the brand to the code, install the theme, switch to brand X, refresh the theme
|
|
4
|
+
user-invocable: true
|
|
5
|
+
allowed-tools:
|
|
6
|
+
- Read
|
|
7
|
+
- Write
|
|
8
|
+
- Bash
|
|
9
|
+
- Glob
|
|
10
|
+
- Grep
|
|
11
|
+
- AskUserQuestion
|
|
12
|
+
---
|
|
13
|
+
<context>
|
|
14
|
+
The universal theme-install primitive. Reads a `{brand}.theme.json` (registry:theme) artifact and installs it into a shadcn codebase via `shadcn apply --only theme`. Surgical — only cssVars (and optionally fonts) change; components are untouched.
|
|
15
|
+
|
|
16
|
+
Spawns an ephemeral localhost HTTP server (`serve-preset.js`, colocated in this skill's `bin/`) because shadcn CLI's `--preset` flag accepts HTTP URLs only (`file://` and bare paths are rejected).
|
|
17
|
+
|
|
18
|
+
Called explicitly by the user, or invoked by `/gsp-brand-guidelines` (after generation, with consent) and `/gsp-brand-refine` (after token regen).
|
|
19
|
+
</context>
|
|
20
|
+
|
|
21
|
+
<objective>
|
|
22
|
+
Install a brand's `{brand}.theme.json` into a target shadcn project's `globals.css`.
|
|
23
|
+
|
|
24
|
+
**Input:** brand name (positional arg) + optional `--target <path>`
|
|
25
|
+
**Output:** Updated CSS file (path declared in `{target}/components.json` → `tailwind.css`) — cssVars only — plus log entry in `{BRAND_PATH}/STATE.md`
|
|
26
|
+
**Agent:** None — inline Bash
|
|
27
|
+
</objective>
|
|
28
|
+
|
|
29
|
+
<rules>
|
|
30
|
+
- Always use `AskUserQuestion` for user-facing questions — never raw text prompts
|
|
31
|
+
- One decision per question — never batch multiple questions in a single message
|
|
32
|
+
</rules>
|
|
33
|
+
|
|
34
|
+
<process>
|
|
35
|
+
## Step 0: Resolve brand
|
|
36
|
+
|
|
37
|
+
Parse the user's argument:
|
|
38
|
+
- If a brand name was passed (e.g. `/gsp-brand-apply lyra`) → `BRAND={arg}`.
|
|
39
|
+
- If no name was given → scan `.design/branding/` for brand directories.
|
|
40
|
+
- If exactly one → use it.
|
|
41
|
+
- If multiple → use `AskUserQuestion`: "Which brand should I apply?" — list names as options.
|
|
42
|
+
- If zero → error: "No brand found. Run `/gsp-brand-guidelines` first." Stop.
|
|
43
|
+
|
|
44
|
+
Set `BRAND_PATH=.design/branding/{BRAND}`.
|
|
45
|
+
Set `THEME_JSON={BRAND_PATH}/patterns/{BRAND}.theme.json`.
|
|
46
|
+
|
|
47
|
+
If `THEME_JSON` does not exist → error: "Brand '{BRAND}' has no theme.json. Run `/gsp-brand-guidelines {BRAND}` to generate it." Stop.
|
|
48
|
+
|
|
49
|
+
## Step 0.5: Pre-flight — preserve user work
|
|
50
|
+
|
|
51
|
+
`shadcn apply --only theme` is not strictly cssVars-only. Its preflight may also bump dependency versions in `package.json` (notably `shadcn` itself and `@base-ui/react`), regenerate `package-lock.json`, and rewrite `components.json`. We don't suppress this — there's no upstream flag (see issue tracker) — but we refuse to run when those files have uncommitted work, so the user can review the dep changes via `git diff` afterward.
|
|
52
|
+
|
|
53
|
+
If `{TARGET}` is inside a git repository, run:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
git -C {TARGET} diff --quiet -- package.json package-lock.json components.json 2>/dev/null
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
If the exit code is non-zero (uncommitted changes exist in any of those three files), use `AskUserQuestion`:
|
|
60
|
+
|
|
61
|
+
- Question: "`{TARGET}` has uncommitted changes in `package.json`, `package-lock.json`, or `components.json`. `shadcn apply` may modify these (preflight bumps versions). Continue anyway?"
|
|
62
|
+
- Options:
|
|
63
|
+
- A: "Cancel — I'll commit/stash first"
|
|
64
|
+
- B: "Proceed anyway — I accept potential conflicts with my pending work"
|
|
65
|
+
|
|
66
|
+
If A → exit cleanly. If B → continue.
|
|
67
|
+
|
|
68
|
+
If `{TARGET}` is not a git repo, skip this check silently — there's nothing to compare against.
|
|
69
|
+
|
|
70
|
+
## Step 1: Resolve target
|
|
71
|
+
|
|
72
|
+
Parse `--target` flag if present.
|
|
73
|
+
|
|
74
|
+
If no flag:
|
|
75
|
+
- Read project config at `.design/projects/*/config.json` and find `app_path`.
|
|
76
|
+
- If exactly one project config → use that `app_path`.
|
|
77
|
+
- If multiple → use `AskUserQuestion`: "Which project's codebase?" — list project names + paths.
|
|
78
|
+
- If zero project configs → error: "No project found. Pass `--target <path>` explicitly." Stop.
|
|
79
|
+
|
|
80
|
+
Set `TARGET={app_path}` (default `.` if empty).
|
|
81
|
+
|
|
82
|
+
Verify `{TARGET}/components.json` exists. If not → error: "{TARGET} is not a shadcn project (no components.json). Run `/gsp-scaffold` first." Stop.
|
|
83
|
+
|
|
84
|
+
## Step 2: Detect currently-installed brand (informational)
|
|
85
|
+
|
|
86
|
+
Resolve the CSS path: read `{TARGET}/components.json` and extract the value at `.tailwind.css` (a relative path from `{TARGET}`). Open `{TARGET}/{cssPath}`. Look for OKLCH cssVars in `:root`. Compare the `--background` light value against `.design/branding/*/patterns/*.theme.json` files in the workspace.
|
|
87
|
+
|
|
88
|
+
- If a match is found → `CURRENT={matched-brand-name}`.
|
|
89
|
+
- If file exists but no match → `CURRENT="custom or shadcn defaults"`.
|
|
90
|
+
- If file missing → `CURRENT="(none — fresh project)"`.
|
|
91
|
+
|
|
92
|
+
This is informational only — surfaced in the confirmation message in Step 3.
|
|
93
|
+
|
|
94
|
+
## Step 2.5: Detect custom token indirection
|
|
95
|
+
|
|
96
|
+
Scan the cssVars sections of `{TARGET}/{cssPath}` (`:root` and `.dark` blocks) for declarations of the form `--<name>: var(--<other>);` — these are custom indirection layers (e.g. `--background: var(--brand-bg);`).
|
|
97
|
+
|
|
98
|
+
`shadcn apply --only theme` will REPLACE those `var()` references with literal OKLCH values from the theme.json. The custom indirection is lost — the upstream tokens (e.g. `--brand-bg`) remain defined elsewhere in the file, but the shadcn cssVars no longer reference them.
|
|
99
|
+
|
|
100
|
+
If any `var(--*)` declarations are found in the cssVars blocks, use `AskUserQuestion`:
|
|
101
|
+
- Question: "`{cssPath}` has **{N} cssVar(s) using `var(--*)` indirection** (e.g. `{first-example}`). `shadcn apply` will replace these with literal OKLCH values, breaking the indirection layer. Continue?"
|
|
102
|
+
- Options:
|
|
103
|
+
- A: "Yes, replace with literal values"
|
|
104
|
+
- B: "No, cancel — preserve the indirection"
|
|
105
|
+
|
|
106
|
+
If B → append `- {ISO-8601 timestamp}: apply cancelled — would have broken indirection in {cssPath}` to `{BRAND_PATH}/STATE.md` under `## Apply log`. Output "Apply cancelled — preserve your indirection layer manually if needed." Exit cleanly.
|
|
107
|
+
If A → continue.
|
|
108
|
+
|
|
109
|
+
If no `var(--*)` indirection is found, skip this confirmation silently.
|
|
110
|
+
|
|
111
|
+
## Step 3: Confirm (when overwriting a different installed brand)
|
|
112
|
+
|
|
113
|
+
If `CURRENT` is a recognized brand name AND it differs from `BRAND`:
|
|
114
|
+
|
|
115
|
+
Use `AskUserQuestion`:
|
|
116
|
+
- Question: "Currently installed: **{CURRENT}**. Replace with **{BRAND}**?"
|
|
117
|
+
- Options:
|
|
118
|
+
- A: "Yes, replace"
|
|
119
|
+
- B: "No, cancel"
|
|
120
|
+
|
|
121
|
+
If B → append `- {ISO-8601 timestamp}: apply cancelled by user (target: {TARGET})` to `{BRAND_PATH}/STATE.md` under `## Apply log`. Output "Apply cancelled" and exit cleanly.
|
|
122
|
+
If A → continue.
|
|
123
|
+
|
|
124
|
+
If `CURRENT` is unrecognized or fresh, skip this confirmation and proceed silently.
|
|
125
|
+
|
|
126
|
+
## Steps 4–6: Spawn preset server, run apply, kill server
|
|
127
|
+
|
|
128
|
+
**Run Steps 4 through 6 as a single Bash call.** `SERVER_PID`, `PRESET_URL`, and `APPLY_EXIT` are shell variables — they do not survive across separate Bash tool invocations.
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# Step 4: spawn preset server, capture URL
|
|
132
|
+
node ${CLAUDE_SKILL_DIR}/bin/serve-preset.js {THEME_JSON} > /tmp/preset-server-url-$$.txt 2>/dev/null &
|
|
133
|
+
SERVER_PID=$!
|
|
134
|
+
# Wait up to 2s for the URL to be written
|
|
135
|
+
for i in 1 2 3 4 5 6 7 8 9 10; do sleep 0.2; [ -s /tmp/preset-server-url-$$.txt ] && break; done
|
|
136
|
+
PRESET_URL=$(head -1 /tmp/preset-server-url-$$.txt 2>/dev/null)
|
|
137
|
+
rm -f /tmp/preset-server-url-$$.txt
|
|
138
|
+
|
|
139
|
+
# Bail if the server didn't print a URL
|
|
140
|
+
if [[ -z "$PRESET_URL" || "$PRESET_URL" != http* ]]; then
|
|
141
|
+
kill "$SERVER_PID" 2>/dev/null
|
|
142
|
+
wait "$SERVER_PID" 2>/dev/null
|
|
143
|
+
echo "ERROR: preset server failed to start — serve-preset.js executable? Node available?"
|
|
144
|
+
exit 1
|
|
145
|
+
fi
|
|
146
|
+
|
|
147
|
+
# Step 5: run apply (quote {TARGET} — paths may contain spaces)
|
|
148
|
+
cd "{TARGET}" && npx shadcn@latest apply --only theme --preset "$PRESET_URL" --yes 2>&1
|
|
149
|
+
APPLY_EXIT=$?
|
|
150
|
+
|
|
151
|
+
# Step 6: kill preset server unconditionally
|
|
152
|
+
kill "$SERVER_PID" 2>/dev/null
|
|
153
|
+
wait "$SERVER_PID" 2>/dev/null
|
|
154
|
+
|
|
155
|
+
# Surface APPLY_EXIT for the verification step
|
|
156
|
+
echo "APPLY_EXIT=$APPLY_EXIT"
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Capture the bash output (especially the `APPLY_EXIT=N` line and any shadcn stderr) to use in Step 7.
|
|
160
|
+
|
|
161
|
+
## Step 7: Verify or report failure
|
|
162
|
+
|
|
163
|
+
If `APPLY_EXIT != 0`:
|
|
164
|
+
- Surface the captured shadcn output to the user (concise — full stderr, plus a single-line summary).
|
|
165
|
+
- Append to `{BRAND_PATH}/STATE.md` under `## Apply log`: `- {ISO-timestamp}: apply FAILED — exit {code} on {TARGET}`.
|
|
166
|
+
- Stop. Do NOT auto-retry. Do NOT manually paste tokens as a fallback.
|
|
167
|
+
|
|
168
|
+
If `APPLY_EXIT == 0`:
|
|
169
|
+
- Resolve the CSS path: read `{TARGET}/components.json` and extract the value at `.tailwind.css` (a relative path from `{TARGET}`).
|
|
170
|
+
- Read `{BRAND_PATH}/patterns/{BRAND}.theme.json` and extract `cssVars.light.background` (the brand's signature OKLCH value).
|
|
171
|
+
- Open `{TARGET}/{cssPath}`. Verify:
|
|
172
|
+
- Contains `oklch(`
|
|
173
|
+
- Has both `:root {` and `.dark {` blocks
|
|
174
|
+
- Declares `--background`, `--foreground`, `--primary`, `--radius`
|
|
175
|
+
- **Contains the exact `cssVars.light.background` value from the theme.json** (this distinguishes a successful brand apply from shadcn's own nova defaults, which also satisfy the structural checks above)
|
|
176
|
+
- If any check fails → warn (do not error): "Apply reported success but expected tokens not found in `{cssPath}` — the apply may have fallen back to defaults. Inspect manually." Continue to Step 8.
|
|
177
|
+
- If all checks pass → continue to Step 8.
|
|
178
|
+
|
|
179
|
+
## Step 8: Log success
|
|
180
|
+
|
|
181
|
+
Append to `{BRAND_PATH}/STATE.md` under a `## Apply log` section. If the file or section doesn't exist, create it.
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
## Apply log
|
|
185
|
+
|
|
186
|
+
- {ISO-8601 timestamp}: applied to `{TARGET}` (replaced: {CURRENT})
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## Step 9: Output
|
|
190
|
+
|
|
191
|
+
After apply, check whether `package.json`, `package-lock.json`, or `components.json` were modified by shadcn's preflight:
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
cd {TARGET} && git diff --name-only -- package.json package-lock.json components.json 2>/dev/null
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
If the output is non-empty, list those files in the success block under a "Review" line so the user knows to inspect them. If empty (or the target is not a git repo), omit the Review line.
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
◆ brand applied — {BRAND} → {TARGET}
|
|
201
|
+
|
|
202
|
+
{cssPath} updated
|
|
203
|
+
Re-apply: /gsp-brand-apply {BRAND}
|
|
204
|
+
Refine: /gsp-brand-refine
|
|
205
|
+
|
|
206
|
+
[if dep files changed]
|
|
207
|
+
Review: git diff {file1} {file2} ...
|
|
208
|
+
(shadcn preflight may have bumped dep versions —
|
|
209
|
+
commit or revert as you prefer)
|
|
210
|
+
|
|
211
|
+
──────────────────────────────
|
|
212
|
+
```
|
|
213
|
+
</process>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* serve-preset.js — ephemeral HTTP server for shadcn --preset URL fetch.
|
|
4
|
+
*
|
|
5
|
+
* Usage (from repo root):
|
|
6
|
+
* node gsp/skills/gsp-brand-apply/bin/serve-preset.js <path-to-registry-item.json>
|
|
7
|
+
*
|
|
8
|
+
* Behavior:
|
|
9
|
+
* - Listens on a random free port on 127.0.0.1.
|
|
10
|
+
* - Prints the URL ("http://127.0.0.1:<port>/<basename>") to stdout.
|
|
11
|
+
* - Serves the file as application/json on any request path.
|
|
12
|
+
* - Exits cleanly on SIGTERM/SIGINT.
|
|
13
|
+
* - Self-exits after 60s as a safety net.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
'use strict';
|
|
17
|
+
|
|
18
|
+
const http = require('http');
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
|
|
22
|
+
const TIMEOUT_MS = 60_000;
|
|
23
|
+
|
|
24
|
+
function main() {
|
|
25
|
+
const filePath = process.argv[2];
|
|
26
|
+
if (!filePath) {
|
|
27
|
+
console.error('Usage: node gsp/skills/gsp-brand-apply/bin/serve-preset.js <path-to-json>');
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
const abs = path.resolve(filePath);
|
|
31
|
+
if (!fs.existsSync(abs)) {
|
|
32
|
+
console.error(`File not found: ${abs}`);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
const body = fs.readFileSync(abs, 'utf8');
|
|
36
|
+
const basename = path.basename(abs);
|
|
37
|
+
|
|
38
|
+
const server = http.createServer((req, res) => {
|
|
39
|
+
res.setHeader('Content-Type', 'application/json');
|
|
40
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
41
|
+
res.end(body);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
server.on('error', (err) => {
|
|
45
|
+
console.error(`Server error: ${err.message}`);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
server.listen(0, '127.0.0.1', () => {
|
|
50
|
+
const { port } = server.address();
|
|
51
|
+
const url = `http://127.0.0.1:${port}/${basename}`;
|
|
52
|
+
process.stdout.write(url + '\n');
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
let shuttingDown = false;
|
|
56
|
+
const shutdown = () => {
|
|
57
|
+
if (shuttingDown) return;
|
|
58
|
+
shuttingDown = true;
|
|
59
|
+
server.close(() => process.exit(0));
|
|
60
|
+
setTimeout(() => process.exit(0), 1000).unref();
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
process.on('SIGTERM', shutdown);
|
|
64
|
+
process.on('SIGINT', shutdown);
|
|
65
|
+
setTimeout(() => {
|
|
66
|
+
console.error('serve-preset: 60s safety timeout reached, exiting');
|
|
67
|
+
shutdown();
|
|
68
|
+
}, TIMEOUT_MS).unref();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
main();
|
|
@@ -20,7 +20,7 @@ Identity made the creative decisions. This phase makes them work in code.
|
|
|
20
20
|
Operationalize brand identity into project-ready artifacts and complete the branding diamond.
|
|
21
21
|
|
|
22
22
|
**Input:** Brand identity (enriched by domain skills) + strategy + BRIEF.md
|
|
23
|
-
**Output:** `{brand}/patterns/` ({brand-name}.yml, STYLE.md, guidelines.html, components/, INDEX.md)
|
|
23
|
+
**Output:** `{brand}/patterns/` ({brand-name}.yml, {brand-name}.theme.json, STYLE.md, guidelines.html, components/, INDEX.md)
|
|
24
24
|
**Agent:** `gsp-brand-engineer`
|
|
25
25
|
</objective>
|
|
26
26
|
|
|
@@ -216,6 +216,57 @@ Spawn the `gsp-brand-engineer` agent with (reuse **Agent methodology** loaded in
|
|
|
216
216
|
>
|
|
217
217
|
> The `.yml` and `STYLE.md` are confirmed — do not modify them. Focus on mapping tokens to the detected component library and specifying overrides.
|
|
218
218
|
|
|
219
|
+
## Step 4.75: Emit shadcn theme registry artifact
|
|
220
|
+
|
|
221
|
+
Generate `{brand-name}.theme.json` (registry:theme) alongside the existing patterns. This is the artifact `/gsp-brand-apply` installs into shadcn codebases.
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
node ${CLAUDE_SKILL_DIR}/bin/theme-css.js \
|
|
225
|
+
{BRAND_PATH}/patterns/{brand-name}.yml \
|
|
226
|
+
--registry \
|
|
227
|
+
--output {BRAND_PATH}/patterns/{brand-name}.theme.json
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
Verify the file was written and contains valid JSON:
|
|
231
|
+
|
|
232
|
+
```bash
|
|
233
|
+
node -e "JSON.parse(require('fs').readFileSync('{BRAND_PATH}/patterns/{brand-name}.theme.json', 'utf8'))" \
|
|
234
|
+
&& echo "✓ theme.json emitted"
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
If either command fails, surface the error and stop — the brand pipeline is incomplete without this artifact.
|
|
238
|
+
|
|
239
|
+
## Step 4.8: Offer to apply theme to codebase
|
|
240
|
+
|
|
241
|
+
Detect installable target. Read project config (`.design/projects/*/config.json`) and look for `preferences.app_path`:
|
|
242
|
+
|
|
243
|
+
- If no project config exists, or `app_path` is empty/missing → skip this step. Output a one-line note: `Apply later with /gsp-brand-apply {brand-name}`. Continue to Step 4.5.
|
|
244
|
+
- If `app_path` exists, check `{app_path}/components.json`:
|
|
245
|
+
- If missing → skip (no shadcn project to install into). Same one-line note.
|
|
246
|
+
- If present → continue.
|
|
247
|
+
|
|
248
|
+
Detect currently-installed brand (informational):
|
|
249
|
+
- Resolve the CSS path from `{app_path}/components.json` → `.tailwind.css` (a relative path).
|
|
250
|
+
- Read `{app_path}/{cssPath}` if it exists.
|
|
251
|
+
- Look for OKLCH `:root` declarations.
|
|
252
|
+
- Compare `--background` light value against other `.design/branding/*/patterns/*.theme.json` files in the workspace.
|
|
253
|
+
- Set `CURRENT={matched-brand-name}` or `CURRENT="shadcn defaults"` or `CURRENT="(none)"`.
|
|
254
|
+
|
|
255
|
+
Use `AskUserQuestion`:
|
|
256
|
+
- Question: "Apply **{brand-name}** to `{app_path}`? Currently installed: **{CURRENT}**. This replaces cssVars in the CSS file; components stay as-is."
|
|
257
|
+
- Options:
|
|
258
|
+
- A: "Apply now"
|
|
259
|
+
- B: "Skip — I'll apply later"
|
|
260
|
+
- C: "Apply to a different project"
|
|
261
|
+
|
|
262
|
+
On A: output `Run /gsp-brand-apply {brand-name}` as the next step the user should take.
|
|
263
|
+
|
|
264
|
+
On B: output `Skipped. Apply later with /gsp-brand-apply {brand-name}.`
|
|
265
|
+
|
|
266
|
+
On C: use `AskUserQuestion` to ask for the target path. Then output `Run /gsp-brand-apply {brand-name} --target {chosen-path}` as the next step.
|
|
267
|
+
|
|
268
|
+
Continue to Step 4.5 regardless of choice.
|
|
269
|
+
|
|
219
270
|
## Step 4.5: Update state
|
|
220
271
|
|
|
221
272
|
Update `{BRAND_PATH}/STATE.md`:
|
|
@@ -228,7 +279,7 @@ Update `.design/CLAUDE.md` — replace the existing `### {brand-name}` entry (wr
|
|
|
228
279
|
```markdown
|
|
229
280
|
### {brand-name} · complete · {DATE}
|
|
230
281
|
"{brand_heartbeat}"
|
|
231
|
-
.design/branding/{brand-name}/patterns/ — guidelines.html · STYLE.md · {brand-name}.yml
|
|
282
|
+
.design/branding/{brand-name}/patterns/ — guidelines.html · STYLE.md · {brand-name}.yml · {brand-name}.theme.json
|
|
232
283
|
```
|
|
233
284
|
|
|
234
285
|
## Step 5: Phase transition output
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* theme-css.js — GSP deterministic token
|
|
3
|
+
* theme-css.js — GSP deterministic token generator
|
|
4
4
|
*
|
|
5
|
-
* Reads a GSP style preset `.yml` file and
|
|
6
|
-
* CSS variables block
|
|
5
|
+
* Reads a GSP style preset `.yml` file and emits either a shadcn-compatible
|
|
6
|
+
* CSS variables block (`:root` and `.dark`) or a `registry:theme`
|
|
7
|
+
* registry-item.json artifact for use with `shadcn apply --only theme`.
|
|
7
8
|
*
|
|
8
|
-
* Usage:
|
|
9
|
-
* node bin/theme-css.js <
|
|
10
|
-
* node bin/theme-css.js <
|
|
11
|
-
* node bin/theme-css.js <
|
|
9
|
+
* Usage (from repo root):
|
|
10
|
+
* node gsp/skills/gsp-brand-guidelines/bin/theme-css.js <preset.yml> # CSS to stdout
|
|
11
|
+
* node gsp/skills/gsp-brand-guidelines/bin/theme-css.js <preset.yml> --output globals.css # CSS to file
|
|
12
|
+
* node gsp/skills/gsp-brand-guidelines/bin/theme-css.js <preset.yml> --stdout # CSS to stdout (explicit)
|
|
13
|
+
* node gsp/skills/gsp-brand-guidelines/bin/theme-css.js <preset.yml> --registry --output theme.json # registry-item.json
|
|
12
14
|
*
|
|
13
15
|
* Token → CSS var mapping is 1:1. No derivation, no LLM guesswork.
|
|
14
16
|
* Hex values are converted to OKLCH. Alpha values (oklch with /) pass through.
|
|
@@ -274,6 +276,103 @@ function generateBlock(colorObj, shapeObj, typographyObj, selector) {
|
|
|
274
276
|
return `${selector} {\n${lines.join('\n')}\n}`;
|
|
275
277
|
}
|
|
276
278
|
|
|
279
|
+
// ---------------------------------------------------------------------------
|
|
280
|
+
// Registry-item.json builder
|
|
281
|
+
// ---------------------------------------------------------------------------
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Walk a flat color object (e.g. preset.tokens.color) and convert every value
|
|
285
|
+
* through formatValue, returning a flat key→value map for cssVars.
|
|
286
|
+
* Keys are the same as in the YAML (background, primary, etc.) — NOT prefixed
|
|
287
|
+
* with "--" because shadcn's registry-item.json schema uses bare names.
|
|
288
|
+
*/
|
|
289
|
+
// Note: unlike generateBlock (CSS path), this does NOT synthesize chart-1..5
|
|
290
|
+
// from primary/secondary/etc. when not explicitly defined in the YAML. All
|
|
291
|
+
// current GSP presets define all five chart vars explicitly, so the paths
|
|
292
|
+
// stay symmetric in practice; if a future preset omits chart vars, add the
|
|
293
|
+
// fallback logic here too.
|
|
294
|
+
function colorObjToCssVars(colorObj) {
|
|
295
|
+
if (!colorObj) return {};
|
|
296
|
+
const out = {};
|
|
297
|
+
// Walk all known CSS var groups: core, sidebar, chart, extras
|
|
298
|
+
const allKeys = [...CORE_VARS, ...SIDEBAR_VARS, ...EXTRA_VARS];
|
|
299
|
+
for (const key of allKeys) {
|
|
300
|
+
if (colorObj[key] !== undefined) {
|
|
301
|
+
out[key] = formatValue(colorObj[key]);
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
// Chart vars
|
|
305
|
+
const chartSources = [
|
|
306
|
+
colorObj['chart-1'],
|
|
307
|
+
colorObj['chart-2'],
|
|
308
|
+
colorObj['chart-3'],
|
|
309
|
+
colorObj['chart-4'],
|
|
310
|
+
colorObj['chart-5'],
|
|
311
|
+
];
|
|
312
|
+
chartSources.forEach((c, i) => {
|
|
313
|
+
if (c !== undefined) {
|
|
314
|
+
out[`chart-${i + 1}`] = formatValue(c);
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
return out;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Build a shadcn registry-item.json object from a parsed preset.
|
|
322
|
+
*/
|
|
323
|
+
function buildRegistryItem(preset, inputPath) {
|
|
324
|
+
const name = preset.name || path.basename(inputPath, '.yml');
|
|
325
|
+
const title = preset.title || name.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
|
|
326
|
+
const description = preset.description || '';
|
|
327
|
+
|
|
328
|
+
const colorLight = (preset.tokens && preset.tokens.color) || {};
|
|
329
|
+
const colorDark = (preset.dark_mode && preset.dark_mode.color) || {};
|
|
330
|
+
const shape = (preset.tokens && preset.tokens.shape) || {};
|
|
331
|
+
const typography = (preset.tokens && preset.tokens.typography) || {};
|
|
332
|
+
|
|
333
|
+
// Build cssVars.theme from typography font mappings
|
|
334
|
+
const theme = {};
|
|
335
|
+
const fontMappings = [
|
|
336
|
+
['font-family-primary', 'font-sans'],
|
|
337
|
+
['font-family-mono', 'font-mono'],
|
|
338
|
+
['font-family-display', 'font-display'],
|
|
339
|
+
['font-family-secondary', 'font-secondary'],
|
|
340
|
+
];
|
|
341
|
+
for (const [ymlKey, cssKey] of fontMappings) {
|
|
342
|
+
if (typography[ymlKey] !== undefined) {
|
|
343
|
+
theme[cssKey] = typography[ymlKey];
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Build cssVars.light — colors + radius
|
|
348
|
+
const light = colorObjToCssVars(colorLight);
|
|
349
|
+
const lg = shape['border-radius-lg'];
|
|
350
|
+
if (lg !== undefined) {
|
|
351
|
+
light['radius'] = String(lg);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Build cssVars.dark — dark_mode color overrides
|
|
355
|
+
const dark = colorObjToCssVars(colorDark);
|
|
356
|
+
|
|
357
|
+
const registryItem = {
|
|
358
|
+
$schema: 'https://ui.shadcn.com/schema/registry-item.json',
|
|
359
|
+
name,
|
|
360
|
+
type: 'registry:theme',
|
|
361
|
+
title,
|
|
362
|
+
cssVars: {
|
|
363
|
+
...(Object.keys(theme).length ? { theme } : {}),
|
|
364
|
+
light,
|
|
365
|
+
dark,
|
|
366
|
+
},
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
if (description) {
|
|
370
|
+
registryItem.description = description;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
return registryItem;
|
|
374
|
+
}
|
|
375
|
+
|
|
277
376
|
// ---------------------------------------------------------------------------
|
|
278
377
|
// Main
|
|
279
378
|
// ---------------------------------------------------------------------------
|
|
@@ -281,8 +380,10 @@ function generateBlock(colorObj, shapeObj, typographyObj, selector) {
|
|
|
281
380
|
function main() {
|
|
282
381
|
const args = process.argv.slice(2);
|
|
283
382
|
if (!args.length || args.includes('--help') || args.includes('-h')) {
|
|
284
|
-
|
|
285
|
-
console.log(`
|
|
383
|
+
const cmd = 'node gsp/skills/gsp-brand-guidelines/bin/theme-css.js';
|
|
384
|
+
console.log(`Usage: ${cmd} <preset.yml> [--output <file>] [--stdout] [--registry]`);
|
|
385
|
+
console.log(` ${cmd} gsp/skills/gsp-style/styles/saas.yml`);
|
|
386
|
+
console.log(` ${cmd} gsp/skills/gsp-style/styles/saas.yml --registry --output saas.theme.json`);
|
|
286
387
|
process.exit(0);
|
|
287
388
|
}
|
|
288
389
|
|
|
@@ -295,10 +396,23 @@ function main() {
|
|
|
295
396
|
const outputIdx = args.indexOf('--output');
|
|
296
397
|
const outputPath = outputIdx !== -1 ? path.resolve(args[outputIdx + 1]) : null;
|
|
297
398
|
const toStdout = args.includes('--stdout') || !outputPath;
|
|
399
|
+
const asRegistry = args.includes('--registry');
|
|
298
400
|
|
|
299
401
|
const raw = fs.readFileSync(inputPath, 'utf8');
|
|
300
402
|
const preset = parseYaml(raw);
|
|
301
403
|
|
|
404
|
+
if (asRegistry) {
|
|
405
|
+
const registryItem = buildRegistryItem(preset, inputPath);
|
|
406
|
+
const output = JSON.stringify(registryItem, null, 2);
|
|
407
|
+
if (toStdout) {
|
|
408
|
+
process.stdout.write(output + '\n');
|
|
409
|
+
} else {
|
|
410
|
+
fs.writeFileSync(outputPath, output + '\n', 'utf8');
|
|
411
|
+
console.log(`Written to ${outputPath}`);
|
|
412
|
+
}
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
|
|
302
416
|
const colorLight = (preset.tokens && preset.tokens.color) || {};
|
|
303
417
|
const colorDark = (preset.dark_mode && preset.dark_mode.color) || {};
|
|
304
418
|
const shape = (preset.tokens && preset.tokens.shape) || {};
|
|
@@ -313,7 +427,7 @@ function main() {
|
|
|
313
427
|
const header = [
|
|
314
428
|
`/* GSP theme: ${presetName} */`,
|
|
315
429
|
presetDesc ? `/* ${presetDesc} */` : null,
|
|
316
|
-
`/* Generated by
|
|
430
|
+
`/* Generated by theme-css.js from ${path.basename(inputPath)} */`,
|
|
317
431
|
`/* Edit the .yml file, not this output */`,
|
|
318
432
|
'',
|
|
319
433
|
].filter(Boolean).join('\n');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Design Token Standards
|
|
2
2
|
|
|
3
|
-
> **GSP approach (v0.8.0+):** GSP presets use **shadcn/ui-native token names** directly — keys in `.yml` files map 1:1 to shadcn CSS variables (`background`, `foreground`, `primary`, `accent`, etc.). The W3C format below is background context on token standards in general; it does not reflect GSP's flat, shadcn-native schema. See `
|
|
3
|
+
> **GSP approach (v0.8.0+):** GSP presets use **shadcn/ui-native token names** directly — keys in `.yml` files map 1:1 to shadcn CSS variables (`background`, `foreground`, `primary`, `accent`, etc.). The W3C format below is background context on token standards in general; it does not reflect GSP's flat, shadcn-native schema. See `theme-css.js` (colocated with this skill) for how GSP converts `.yml` tokens to CSS.
|
|
4
4
|
|
|
5
5
|
**Format:** W3C Design Tokens Community Group specification
|
|
6
6
|
|
|
@@ -74,7 +74,7 @@ Read `system_strategy` from brand config:
|
|
|
74
74
|
|
|
75
75
|
Leverage existing UI libraries — don't rewrite from scratch.
|
|
76
76
|
|
|
77
|
-
**Tier 1: Token mapping** (always) — `components/token-mapping.md`. Maps brand tokens to library's theming API.
|
|
77
|
+
**Tier 1: Token mapping** (always) — `components/token-mapping.md`. Maps brand tokens to library's theming API. Documents the OKLCH values generated by `theme-css.js` — these are installed into `globals.css` via `/gsp-brand-apply` (not manually pasted). Token names in `.yml` are 1:1 with shadcn/ui CSS var names — no translation needed.
|
|
78
78
|
|
|
79
79
|
**Tier 2: Override specs** (selective) — one file per component needing treatment beyond tokens. Why it's overridden, code hints.
|
|
80
80
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Token Mapping →
|
|
1
|
+
# Token Mapping → theme-css.js
|
|
2
2
|
|
|
3
3
|
> **Superseded in v0.8.0.** The static mapping table has been replaced by a deterministic script.
|
|
4
4
|
|
|
@@ -7,7 +7,7 @@ GSP presets use shadcn/ui-native token names directly. The `.yml` token keys mat
|
|
|
7
7
|
## Generating CSS from a preset
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
node bin/theme-css.js gsp/skills/gsp-style/styles/professional.yml --stdout
|
|
10
|
+
node gsp/skills/gsp-brand-guidelines/bin/theme-css.js gsp/skills/gsp-style/styles/professional.yml --stdout
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
Output is `:root { }` + `.dark { }` blocks ready to paste into `globals.css`.
|
|
@@ -21,7 +21,7 @@ This skill modifies **`{brand-name}.yml`** — the single source of truth for br
|
|
|
21
21
|
Accept natural language feedback about brand visuals, identify which `.yml` values are affected, apply targeted changes, and regenerate `STYLE.md` if it exists.
|
|
22
22
|
|
|
23
23
|
**Input:** Natural language feedback (e.g., "accent is too muted", "make buttons rounder", "more motion")
|
|
24
|
-
**Output:** Updated `{brand-name}.yml` + regenerated `STYLE.md` (if exists) + `REFINE-LOG.md`
|
|
24
|
+
**Output:** Updated `{brand-name}.yml` + regenerated `STYLE.md` (if exists) + `{brand-name}.theme.json` (regenerated) + `REFINE-LOG.md`
|
|
25
25
|
**Agent:** None — inline skill, surgical edits
|
|
26
26
|
</objective>
|
|
27
27
|
|
|
@@ -123,7 +123,38 @@ Apply confirmed changes:
|
|
|
123
123
|
1. **`{brand-name}.yml`** — edit values in place with `Edit`. Preserve structure.
|
|
124
124
|
2. **`STYLE.md`** — if it exists, regenerate the affected sections (Patterns tables, Constraints lists, Effects tables, or Intensity dials) to reflect the `.yml` changes. Read the template from `${CLAUDE_SKILL_DIR}/../../templates/phases/style.md` for format reference.
|
|
125
125
|
|
|
126
|
-
## Step 4:
|
|
126
|
+
## Step 4: Regenerate theme.json and offer to apply
|
|
127
|
+
|
|
128
|
+
After updating `{brand-name}.yml` and (if applicable) regenerating `STYLE.md`, regenerate the shadcn registry artifact:
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
node ${CLAUDE_SKILL_DIR}/../gsp-brand-guidelines/bin/theme-css.js \
|
|
132
|
+
{BRAND_PATH}/patterns/{brand-name}.yml \
|
|
133
|
+
--registry \
|
|
134
|
+
--output {BRAND_PATH}/patterns/{brand-name}.theme.json
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Verify the file is valid JSON:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
node -e "JSON.parse(require('fs').readFileSync('{BRAND_PATH}/patterns/{brand-name}.theme.json', 'utf8'))" \
|
|
141
|
+
&& echo "✓ theme.json refreshed"
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
If a project config exists (`.design/projects/*/config.json` with a non-empty `app_path`) AND `{app_path}/components.json` exists (a shadcn target), use `AskUserQuestion`:
|
|
145
|
+
|
|
146
|
+
- Question: "Theme refreshed. Apply changes to `{app_path}` now?"
|
|
147
|
+
- Options:
|
|
148
|
+
- A: "Yes — apply now"
|
|
149
|
+
- B: "Skip — apply later"
|
|
150
|
+
|
|
151
|
+
On A: output `Run /gsp-brand-apply {brand-name}` as the next user step.
|
|
152
|
+
|
|
153
|
+
On B: output `Refreshed. Apply later with /gsp-brand-apply {brand-name}.`
|
|
154
|
+
|
|
155
|
+
If no shadcn target is detected, skip the prompt and output a passive note: `Theme refreshed. No shadcn target detected — apply later with /gsp-brand-apply {brand-name} once a shadcn project is set up.`
|
|
156
|
+
|
|
157
|
+
## Step 5: Log and finish
|
|
127
158
|
|
|
128
159
|
Append to `{BRAND_PATH}/REFINE-LOG.md`:
|
|
129
160
|
|
|
@@ -245,12 +245,12 @@ Glob for all SKILL.md files in the skills directory (`{runtime-dir}/skills/*/SKI
|
|
|
245
245
|
**Check I2: Skill directories are complete (not just SKILL.md)**
|
|
246
246
|
For each gsp-* skill directory, check if `SKILL.md` references sibling files via `${CLAUDE_SKILL_DIR}/` paths (e.g. `styles/INDEX.yml`). If it does, verify those files/dirs exist in the installed skill directory.
|
|
247
247
|
- All referenced siblings present → PASS
|
|
248
|
-
- Missing siblings → FAIL: "Skill {name} references {path} but it's missing. Re-run the installer: `
|
|
248
|
+
- Missing siblings → FAIL: "Skill {name} references {path} but it's missing. Re-run the installer: `pnpm dlx get-shit-pretty` (or `bunx get-shit-pretty`)"
|
|
249
249
|
|
|
250
250
|
**Check I3: Bundle directories accessible**
|
|
251
251
|
Check that the runtime bundle directories exist (`{runtime-dir}/templates/`, `{runtime-dir}/references/`). Skills reference these via `${CLAUDE_SKILL_DIR}/../../`.
|
|
252
252
|
- All present → PASS
|
|
253
|
-
- Missing → FAIL: "Bundle directory {dir} missing. Re-run the installer: `
|
|
253
|
+
- Missing → FAIL: "Bundle directory {dir} missing. Re-run the installer: `pnpm dlx get-shit-pretty` (or `bunx get-shit-pretty`)"
|
|
254
254
|
|
|
255
255
|
**Check I4: VERSION file present**
|
|
256
256
|
Check `{runtime-dir}/VERSION` exists and contains a valid semver string.
|
|
@@ -262,7 +262,7 @@ Check `{runtime-dir}/VERSION` exists and contains a valid semver string.
|
|
|
262
262
|
Check if `~/.claude/skills/` contains `gsp-*` directories when running from a local install. These cause duplicates between global and local.
|
|
263
263
|
- Run: `ls ~/.claude/skills/ | grep '^gsp-'`
|
|
264
264
|
- No matches → PASS
|
|
265
|
-
- Matches found → FAIL: "Found {N} stale GSP skills in ~/.claude/skills/. Fix: run `
|
|
265
|
+
- Matches found → FAIL: "Found {N} stale GSP skills in ~/.claude/skills/. Fix: run `pnpm dlx get-shit-pretty --claude --local` (or `bunx get-shit-pretty --claude --local`) to reinstall (the installer cleans stale globals automatically), or manually remove: `rm -rf ~/.claude/skills/gsp-*`"
|
|
266
266
|
|
|
267
267
|
### Stack Compliance Checks (shadcn targets)
|
|
268
268
|
|
|
@@ -142,6 +142,30 @@ Hold their content for inlining into agent prompts in Steps 3, 4.5, 5, 7, and 8.
|
|
|
142
142
|
|
|
143
143
|
> **Note:** Anti-patterns are distilled into the `gsp-project-builder` agent prompt. Full ref remains on disk for edge-case agent lookup.
|
|
144
144
|
|
|
145
|
+
## Step 2.7: Theme apply gate
|
|
146
|
+
|
|
147
|
+
Verify brand tokens are installed in the codebase before spawning foundations. Foundations no longer pastes tokens — `/gsp-brand-apply` is the install primitive.
|
|
148
|
+
|
|
149
|
+
1. If `{APP_PATH}/components.json` does not exist, skip this gate silently — the target is non-shadcn (scaffold's failure gate at end of Step 2 already covers a broken shadcn scaffold).
|
|
150
|
+
2. Resolve the CSS path: read `{APP_PATH}/components.json` and extract the value at `.tailwind.css` (a relative path from `APP_PATH`).
|
|
151
|
+
3. Open `{APP_PATH}/{cssPath}`. Verify:
|
|
152
|
+
- Contains `oklch(`
|
|
153
|
+
- Has both `:root {` and `.dark {` blocks
|
|
154
|
+
- Declares `--background`, `--foreground`, `--primary`, `--radius`
|
|
155
|
+
- **If** `{BRAND_PATH}/patterns/{brand-name}.theme.json` exists: contains the brand's signature `cssVars.light.background` value from that file (this distinguishes the applied brand from shadcn's nova defaults). If `{brand-name}.theme.json` does NOT exist (older brand from before Task 4 landed), skip the brand-signature check and rely on the structural checks only — log `⚠ Brand theme.json not found — skipping brand-signature check.`
|
|
156
|
+
|
|
157
|
+
If any required check fails, brand tokens are not applied (or the wrong brand is applied). Use `AskUserQuestion`:
|
|
158
|
+
- Question: "Brand tokens for **{brand-name}** not detected in `{APP_PATH}/{cssPath}`. Run `/gsp-brand-apply {brand-name}` now?"
|
|
159
|
+
- Options:
|
|
160
|
+
- A: "Yes — I'll run /gsp-brand-apply {brand-name}, then re-run /gsp-project-build"
|
|
161
|
+
- B: "No, abort the build"
|
|
162
|
+
|
|
163
|
+
On A: output `Next: run /gsp-brand-apply {brand-name}, then re-invoke /gsp-project-build` and exit this skill. The build does not auto-continue — the apply runs out-of-band and you re-invoke when ready.
|
|
164
|
+
|
|
165
|
+
On B: stop the build phase. Output: `Build aborted — apply brand tokens with /gsp-brand-apply {brand-name} and re-run /gsp-project-build.`
|
|
166
|
+
|
|
167
|
+
If all checks pass, log `✓ Brand tokens verified` and continue to Step 3.
|
|
168
|
+
|
|
145
169
|
## Step 3: Phase 2 — FOUNDATIONS
|
|
146
170
|
|
|
147
171
|
Spawn `gsp-project-builder` agent with **execution_mode: foundations**.
|
|
@@ -169,8 +193,8 @@ Spawn `gsp-project-builder` agent with **execution_mode: foundations**.
|
|
|
169
193
|
>
|
|
170
194
|
> Build token integration, global styles, and layout primitives ONLY.
|
|
171
195
|
>
|
|
172
|
-
> 1.
|
|
173
|
-
> 2.
|
|
196
|
+
> 1. **Verify** brand tokens are already installed in `globals.css`. The orchestrator gates this — by the time you run, tokens MUST be present (installed via `/gsp-brand-apply` either directly or through the brand-guidelines prompt). If you find tokens missing, abort with a clear error pointing at `/gsp-brand-apply {brand-name}`. Do NOT manually paste tokens.
|
|
197
|
+
> 2. Add base styles, dark mode setup, and any font imports that `apply` did not handle. (Note: `cssVars.theme.font-sans` may set the CSS variable but not generate the `next/font/google` import in `layout.tsx`. If the import is missing, add it. If present, leave it alone.)
|
|
174
198
|
> 3. Create root layout with nav shell and footer shell (structure only — no page content)
|
|
175
199
|
> 4. Create shared utilities (cn helper, theme provider if needed)
|
|
176
200
|
> 5. Apply the STYLE.md bold bets and effects vocabulary — create CSS utilities or Tailwind extensions for the brand's signature effects. Validate against constraints (never/always rules are non-negotiable).
|
|
@@ -17,8 +17,8 @@ You are spawned with an `execution_mode` parameter. Follow the mode strictly:
|
|
|
17
17
|
|
|
18
18
|
### `foundations`
|
|
19
19
|
Build token integration, global styles, and layout primitives ONLY. Stop after foundations.
|
|
20
|
-
-
|
|
21
|
-
-
|
|
20
|
+
- **Verify** brand tokens are already installed in the CSS file (path declared in `components.json` → `.tailwind.css`). The orchestrator has already gated this — by the time you run, tokens MUST be present (installed via `/gsp-brand-apply` either directly or through the brand-guidelines prompt). If you find tokens missing, abort with a clear error pointing at `/gsp-brand-apply {brand-name}`. **Do NOT manually paste tokens or run `theme-css.js`.**
|
|
21
|
+
- Base styles, dark mode setup, and any font imports that `apply` did not handle (`cssVars.theme.font-sans` may set the CSS variable but not generate the `next/font/google` import in `layout.tsx` — add it if missing, leave it alone if present)
|
|
22
22
|
- Layout components (root layout, nav shell, footer shell)
|
|
23
23
|
- Shared utilities (cn helper, theme provider)
|
|
24
24
|
- **Do NOT build individual screens or page content**
|
|
@@ -94,11 +94,11 @@ Key fields from the JSON:
|
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
:root {
|
|
97
|
-
/*
|
|
97
|
+
/* Brand tokens installed here by /gsp-brand-apply (runs `shadcn apply --only theme`) */
|
|
98
98
|
}
|
|
99
99
|
|
|
100
100
|
.dark {
|
|
101
|
-
/*
|
|
101
|
+
/* Dark-mode overrides installed here by /gsp-brand-apply */
|
|
102
102
|
}
|
|
103
103
|
|
|
104
104
|
@layer base {
|
|
@@ -136,7 +136,7 @@ Then set font vars with literal values in `@theme inline` (not `var()` self-refe
|
|
|
136
136
|
}
|
|
137
137
|
```
|
|
138
138
|
|
|
139
|
-
`
|
|
139
|
+
`theme-css.js` emits `--font-sans` / `--font-mono` / `--font-display` from the preset's `typography` block. These values are wired into `@theme inline` when the theme is installed via `/gsp-brand-apply`.
|
|
140
140
|
|
|
141
141
|
### Tailwind v3
|
|
142
142
|
|
|
@@ -159,7 +159,7 @@ Then set font vars with literal values in `@theme inline` (not `var()` self-refe
|
|
|
159
159
|
|
|
160
160
|
## Token injection
|
|
161
161
|
|
|
162
|
-
**Order matters** — run component installs first, then
|
|
162
|
+
**Order matters** — run component installs first, then apply the brand theme. This prevents `shadcn add` from appending its own `cssVars` after your custom OKLCH values.
|
|
163
163
|
|
|
164
164
|
**Step 1 — Install all components first:**
|
|
165
165
|
|
|
@@ -167,13 +167,9 @@ Then set font vars with literal values in `@theme inline` (not `var()` self-refe
|
|
|
167
167
|
npx shadcn@latest add button card dialog popover select tooltip ...
|
|
168
168
|
```
|
|
169
169
|
|
|
170
|
-
**Step 2 —
|
|
170
|
+
**Step 2 — Apply the brand theme:**
|
|
171
171
|
|
|
172
|
-
|
|
173
|
-
node bin/theme-css.js .design/branding/{brand}/patterns/{brand}.yml --stdout
|
|
174
|
-
```
|
|
175
|
-
|
|
176
|
-
Paste the `:root { }` and `.dark { }` output into `globals.css`, replacing any `:root`/`.dark` blocks the shadcn CLI wrote. The `@theme inline` block stays untouched — it only contains `var()` aliases and radius/font values, not actual color values.
|
|
172
|
+
Brand tokens are installed via `/gsp-brand-apply`, which runs `shadcn apply --only theme` against the `{brand}.theme.json` registry artifact produced by `gsp-brand-guidelines`. This replaces any `:root`/`.dark` blocks the shadcn CLI wrote with OKLCH brand values. The `@theme inline` block stays untouched — it only contains `var()` aliases and radius/font values, not actual color values.
|
|
177
173
|
|
|
178
174
|
**Format:** OKLCH (`oklch(L C H)`). shadcn/ui v2+ accepts OKLCH natively. No `hsl()` wrapper needed.
|
|
179
175
|
|
|
@@ -402,7 +398,7 @@ Check `base` from `npx shadcn@latest info` before writing any component code. Th
|
|
|
402
398
|
<DialogTrigger render={<Button />}>Open</DialogTrigger>
|
|
403
399
|
```
|
|
404
400
|
|
|
405
|
-
For full API differences with code examples, read `${CLAUDE_SKILL_DIR}
|
|
401
|
+
For full API differences with code examples, read `${CLAUDE_SKILL_DIR}/shadcn-theming.md` or run `npx shadcn@latest docs <component>`.
|
|
406
402
|
|
|
407
403
|
---
|
|
408
404
|
|
|
@@ -413,7 +409,7 @@ For full API differences with code examples, read `${CLAUDE_SKILL_DIR}/../../gsp
|
|
|
413
409
|
3. **Tailwind v4 source scoping** — if the repo has non-source files with CSS class names (`.design/`, `gsp/`), add `source("../")` to the `@import "tailwindcss"` line to limit scanning
|
|
414
410
|
4. **Import alias consistency** — all generated code must use the alias from `shadcn info` (`@/` or `~/`), never relative paths to component files
|
|
415
411
|
5. **Never hardcode colors** — use `bg-primary`, `text-muted-foreground`, `border-border`, etc. — never `bg-blue-500`
|
|
416
|
-
6. **Install components before
|
|
412
|
+
6. **Install components before applying the brand theme** — run `shadcn add` first; then install the brand theme via `/gsp-brand-apply`. Reversing this order risks shadcn appending its own vars after yours
|
|
417
413
|
|
|
418
414
|
---
|
|
419
415
|
|
|
@@ -127,7 +127,7 @@ Copy the preset `.yml` to the output path as the brand's style source:
|
|
|
127
127
|
|
|
128
128
|
If a `.yml` already exists at the output path, use `AskUserQuestion`: "A style preset already exists — overwrite?" with options **Overwrite** and **Cancel**. If cancelled, skip and proceed.
|
|
129
129
|
|
|
130
|
-
The `.yml` IS the token source of truth — no separate `tokens.json` needed. Token names in `.yml` map 1:1 to shadcn/ui CSS variable names. Run `node bin/theme-css.js {preset-name}.yml` to generate a ready-to-paste `:root`/`.dark` CSS block.
|
|
130
|
+
The `.yml` IS the token source of truth — no separate `tokens.json` needed. Token names in `.yml` map 1:1 to shadcn/ui CSS variable names. Run `node gsp/skills/gsp-brand-guidelines/bin/theme-css.js {preset-name}.yml` to generate a ready-to-paste `:root`/`.dark` CSS block.
|
|
131
131
|
|
|
132
132
|
## Step 7: Write STYLE.md
|
|
133
133
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Template for brand-derived style preset files (`{brand-name}.yml`). All token values must trace to foundation chunks. See any preset in `styles/` for a complete example.
|
|
4
4
|
|
|
5
|
-
Token names map 1:1 to shadcn/ui CSS variables — no translation layer. `
|
|
5
|
+
Token names map 1:1 to shadcn/ui CSS variables — no translation layer. `theme-css.js` (in `gsp-brand-guidelines/bin/`) reads this file and outputs a ready-to-paste `:root`/`.dark` block.
|
|
6
6
|
|
|
7
7
|
**Value formats:**
|
|
8
8
|
- Solid colors: `"#RRGGBB"` hex — theme-css.js converts to OKLCH
|
|
@@ -39,7 +39,9 @@ Record which runtime(s) and install type (local/global) were found.
|
|
|
39
39
|
|
|
40
40
|
If no VERSION file exists anywhere, tell the user GSP doesn't appear to be installed and suggest:
|
|
41
41
|
```
|
|
42
|
-
|
|
42
|
+
pnpm dlx get-shit-pretty
|
|
43
|
+
# or with bun
|
|
44
|
+
bunx get-shit-pretty
|
|
43
45
|
```
|
|
44
46
|
Then stop.
|
|
45
47
|
|
|
@@ -103,14 +105,15 @@ Build the installer command based on what was detected in Step 1:
|
|
|
103
105
|
**Scope flag:** `--local` if local install was detected, `--global` if global.
|
|
104
106
|
|
|
105
107
|
```bash
|
|
106
|
-
|
|
108
|
+
pnpm dlx get-shit-pretty@latest {runtime-flag} {scope-flag}
|
|
109
|
+
# or: bunx get-shit-pretty@latest {runtime-flag} {scope-flag}
|
|
107
110
|
```
|
|
108
111
|
|
|
109
112
|
Examples:
|
|
110
|
-
- Local Claude: `
|
|
111
|
-
- Global Claude: `
|
|
112
|
-
- Global OpenCode: `
|
|
113
|
-
- Multiple runtimes: `
|
|
113
|
+
- Local Claude: `pnpm dlx get-shit-pretty@latest --claude --local`
|
|
114
|
+
- Global Claude: `pnpm dlx get-shit-pretty@latest --claude --global`
|
|
115
|
+
- Global OpenCode: `pnpm dlx get-shit-pretty@latest --opencode --global`
|
|
116
|
+
- Multiple runtimes: `pnpm dlx get-shit-pretty@latest --all --global`
|
|
114
117
|
|
|
115
118
|
Show the output to the user.
|
|
116
119
|
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
|
|
27
27
|
| File | Content |
|
|
28
28
|
|------|---------|
|
|
29
|
-
| `{brand-name}.yml` | **Single source of truth.** The brand's aesthetic in GSP preset format: tokens + intensity + patterns + constraints + effects + dark_mode. Inherits from `style_base` preset, overrides brand-specific values. Token names map 1:1 to shadcn/ui CSS vars.
|
|
29
|
+
| `{brand-name}.yml` | **Single source of truth.** The brand's aesthetic in GSP preset format: tokens + intensity + patterns + constraints + effects + dark_mode. Inherits from `style_base` preset, overrides brand-specific values. Token names map 1:1 to shadcn/ui CSS vars. Generation runs through the brand pipeline — see `/gsp-brand-guidelines` and `/gsp-brand-apply`. |
|
|
30
30
|
| `STYLE.md` | **Agent contract.** The single document designer and builder agents consume. Rendered from the `.yml` + brand philosophy (from strategy) + bold bets (from identity's most distinctive choices) + implementation patterns (from preset `.md` companion). Follows `templates/phases/style.md` format. |
|
|
31
31
|
| `guidelines.html` | **User-visible brand guide.** Self-contained HTML with embedded CSS. Shows the brand using its own tokens: color swatches, type scale in actual fonts, component previews, spacing/elevation vis, constraints. Open in browser. |
|
|
32
32
|
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
|
|
35
35
|
Component output is library-aware:
|
|
36
36
|
|
|
37
|
-
1. **`token-mapping.md`** (always) — brand tokens → component library theming API. Complete, copy-paste-ready config.
|
|
37
|
+
1. **`token-mapping.md`** (always) — brand tokens → component library theming API. Complete, copy-paste-ready config. The CSS block is generated by the `theme-css.js` script the brand pipeline invokes — outputs OKLCH `:root`/`.dark` with all shadcn vars.
|
|
38
38
|
2. **Override specs** (selective) — one file per library component needing treatment beyond tokens. Singular kebab-case naming.
|
|
39
39
|
3. **Custom component specs** (selective) — one file per brand-distinctive component with no library equivalent. Includes: states, anatomy, usage rules, accessibility spec, code hints.
|
|
40
40
|
|
package/package.json
CHANGED