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 CHANGED
@@ -12,7 +12,9 @@
12
12
  <br>
13
13
 
14
14
  ```bash
15
- npx get-shit-pretty
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
- npx get-shit-pretty
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
- npx get-shit-pretty
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
- npx get-shit-pretty --claude --global
434
- npx get-shit-pretty --claude --local
438
+ pnpm dlx get-shit-pretty --claude --global
439
+ pnpm dlx get-shit-pretty --claude --local
435
440
 
436
441
  # OpenCode
437
- npx get-shit-pretty --opencode --global
442
+ pnpm dlx get-shit-pretty --opencode --global
438
443
 
439
444
  # Gemini CLI
440
- npx get-shit-pretty --gemini --global
445
+ pnpm dlx get-shit-pretty --gemini --global
441
446
 
442
447
  # Codex CLI
443
- npx get-shit-pretty --codex --global
448
+ pnpm dlx get-shit-pretty --codex --global
444
449
 
445
450
  # All runtimes
446
- npx get-shit-pretty --all --global
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
- npx get-shit-pretty --claude --global --uninstall
456
- npx get-shit-pretty --opencode --global --uninstall
457
- npx get-shit-pretty --gemini --global --uninstall
458
- npx get-shit-pretty --codex --global --uninstall
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} npx get-shit-pretty [options]\n
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
- npx get-shit-pretty
224
+ pnpm dlx get-shit-pretty
225
225
 
226
226
  ${dim}# Install for Claude Code globally${reset}
227
- npx get-shit-pretty --claude --global
227
+ pnpm dlx get-shit-pretty --claude --global
228
228
 
229
229
  ${dim}# Install for all runtimes globally${reset}
230
- npx get-shit-pretty --all --global
230
+ pnpm dlx get-shit-pretty --all --global
231
231
 
232
232
  ${dim}# Install to current project only${reset}
233
- npx get-shit-pretty --claude --local
233
+ pnpm dlx get-shit-pretty --claude --local
234
234
 
235
235
  ${dim}# Uninstall GSP from Claude Code globally${reset}
236
- npx get-shit-pretty --claude --global --uninstall
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 global install${reset}\n`);
1821
- installAllRuntimes(runtimes, true, false);
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 global install${reset}\n`);
1902
- installAllRuntimes(['claude'], true, false);
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
- npx get-shit-pretty
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-to-CSS generator
3
+ * theme-css.js — GSP deterministic token generator
4
4
  *
5
- * Reads a GSP style preset `.yml` file and outputs a shadcn/ui-compatible
6
- * CSS variables block for `:root` and `.dark`.
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 <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
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
- 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`);
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 bin/theme-css.js from ${path.basename(inputPath)} */`,
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 `bin/theme-css.js` for how GSP converts `.yml` tokens to CSS.
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. Copy-paste-ready. Generate the CSS variables block by running `node bin/theme-css.js {brand-name}.yml` — it outputs a ready-to-paste `:root`/`.dark` block with OKLCH values. Token names in `.yml` are 1:1 with shadcn/ui CSS var names — no translation needed.
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 → bin/theme-css.js
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: Log and finish
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: `npx get-shit-pretty`"
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: `npx get-shit-pretty`"
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 `npx get-shit-pretty --claude --local` to reinstall (the installer cleans stale globals automatically), or manually remove: `rm -rf ~/.claude/skills/gsp-*`"
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. Integrate design tokens into the codebase: run `node bin/theme-css.js {brand-name}.yml --stdout` and paste the OKLCH `:root`/`.dark` output into `globals.css`. For shadcn targets, follow the `@theme inline` pattern from `shadcn-rules.md`. Map ALL variables background, foreground, card, popover, primary, secondary, muted, accent, destructive, border, input, ring, sidebar-*, chart-1 through chart-5, and --radius.
173
- > 2. Create global CSS (resets, base styles, font imports, dark mode setup)
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
- - Design tokens CSS variables / Tailwind config. Run `node bin/theme-css.js <preset.yml> --stdout` to generate a ready-to-paste `:root` and `.dark` block in OKLCH format. If `bin/theme-css.js` is unavailable, convert hex to OKLCH manually. Write ALL variables in `:root` and `.dark` scopes (background, foreground, card, popover, primary, secondary, muted, accent, destructive, border, input, ring, sidebar-*, chart-1 through chart-5, --radius). Write only **global tokens**: brand colors, font families, spacing scale, base radius, base shadows. Do NOT write screen-specific tokens yet.
21
- - Global CSS (resets, base styles, dark mode)
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
- /* Insert OKLCH custom properties from bin/theme-css.js output here */
97
+ /* Brand tokens installed here by /gsp-brand-apply (runs `shadcn apply --only theme`) */
98
98
  }
99
99
 
100
100
  .dark {
101
- /* Insert .dark overrides from bin/theme-css.js output here */
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
- `bin/theme-css.js` emits `--font-sans` / `--font-mono` / `--font-display` from the preset's `typography` block paste those values (not the var declarations) into `@theme inline`.
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 overwrite tokens. This prevents `shadcn add` from appending its own `cssVars` after your custom OKLCH values.
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 — Generate and inject brand tokens:**
170
+ **Step 2 — Apply the brand theme:**
171
171
 
172
- ```bash
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}/../../gsp-scaffold/shadcn-theming.md` or run `npx shadcn@latest docs <component>`.
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 writing tokens** — run `shadcn add` first; then overwrite `:root`/`.dark` with `bin/theme-css.js` output. Reversing this order risks shadcn appending its own vars after yours
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. `bin/theme-css.js` reads this file and outputs a ready-to-paste `:root`/`.dark` block.
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
- npx get-shit-pretty
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
- npx get-shit-pretty@latest {runtime-flag} {scope-flag}
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: `npx get-shit-pretty@latest --claude --local`
111
- - Global Claude: `npx get-shit-pretty@latest --claude --global`
112
- - Global OpenCode: `npx get-shit-pretty@latest --opencode --global`
113
- - Multiple runtimes: `npx get-shit-pretty@latest --all --global`
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
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.8.2",
2
+ "version": "0.9.1",
3
3
  "project_type": "brand",
4
4
  "brand": {
5
5
  "name": "",
@@ -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. Run `node bin/theme-css.js {brand-name}.yml` to generate the `:root`/`.dark` CSS block. |
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. Generate the CSS block with `node bin/theme-css.js {brand-name}.yml` — outputs OKLCH `:root`/`.dark` with all shadcn vars.
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
 
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.8.2",
2
+ "version": "0.9.1",
3
3
  "project_type": "design",
4
4
  "project": {
5
5
  "name": "",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "get-shit-pretty",
3
- "version": "0.8.2",
3
+ "version": "0.9.1",
4
4
  "description": "Design engineering system for AI coding agents. Brand identity + design projects, from strategy to code.",
5
5
  "bin": {
6
6
  "get-shit-pretty": "bin/install.js"