uiux-storybook-architect 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +163 -0
  3. package/bin/cli.js +475 -0
  4. package/package.json +61 -0
  5. package/src/AGENTS.md +24 -0
  6. package/src/SKILL.md +206 -0
  7. package/src/agents/openai.yaml +4 -0
  8. package/src/assets/motion-snippets.css +73 -0
  9. package/src/assets/tokens.template.css +134 -0
  10. package/src/assets/tokens.template.json +64 -0
  11. package/src/commands/ux-3d.md +27 -0
  12. package/src/commands/ux-a11y.md +18 -0
  13. package/src/commands/ux-brief.md +14 -0
  14. package/src/commands/ux-build.md +31 -0
  15. package/src/commands/ux-color.md +28 -0
  16. package/src/commands/ux-components.md +26 -0
  17. package/src/commands/ux-critique.md +20 -0
  18. package/src/commands/ux-discover.md +20 -0
  19. package/src/commands/ux-framer.md +27 -0
  20. package/src/commands/ux-gsap.md +27 -0
  21. package/src/commands/ux-handoff.md +27 -0
  22. package/src/commands/ux-ia.md +24 -0
  23. package/src/commands/ux-motion.md +29 -0
  24. package/src/commands/ux-pages.md +24 -0
  25. package/src/commands/ux-perf.md +16 -0
  26. package/src/commands/ux-responsive.md +14 -0
  27. package/src/commands/ux-storybook.md +30 -0
  28. package/src/commands/ux-style.md +28 -0
  29. package/src/commands/ux-theme.md +26 -0
  30. package/src/commands/ux-tokens.md +29 -0
  31. package/src/commands/ux-type.md +29 -0
  32. package/src/docs/00-commands-reference.md +69 -0
  33. package/src/docs/01-claude-code.md +62 -0
  34. package/src/docs/02-codex.md +51 -0
  35. package/src/docs/03-antigravity.md +57 -0
  36. package/src/docs/04-cursor.md +50 -0
  37. package/src/docs/05-windsurf.md +36 -0
  38. package/src/docs/06-vscode-copilot.md +45 -0
  39. package/src/docs/07-claude-apps.md +42 -0
  40. package/src/docs/08-customizing.md +85 -0
  41. package/src/docs/09-workflows.md +82 -0
  42. package/src/docs/10-npm-cli.md +54 -0
  43. package/src/docs/11-vscode-guide.md +139 -0
  44. package/src/references/01-discovery-interview.md +75 -0
  45. package/src/references/02-foundations.md +80 -0
  46. package/src/references/03-typography-color-theming.md +168 -0
  47. package/src/references/04-visual-styles.md +90 -0
  48. package/src/references/05-website-type-patterns.md +71 -0
  49. package/src/references/06-age-inclusive-design.md +58 -0
  50. package/src/references/07-craft-and-accessibility.md +90 -0
  51. package/src/references/08-motion-system.md +122 -0
  52. package/src/references/09-gsap-recipes.md +209 -0
  53. package/src/references/10-motion-react-recipes.md +166 -0
  54. package/src/references/11-threejs-webgl.md +129 -0
  55. package/src/references/12-storybook-template.md +265 -0
  56. package/src/references/13-implementation-handoff.md +93 -0
  57. package/src/references/14-ai-workflow.md +35 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Muhammad Nouman
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,163 @@
1
+ # UI/UX Storybook Architect
2
+
3
+ [![CI](https://github.com/nomiwsd/ui-ux-design-skill/actions/workflows/ci.yml/badge.svg)](https://github.com/nomiwsd/ui-ux-design-skill/actions/workflows/ci.yml)
4
+ [![npm version](https://img.shields.io/npm/v/uiux-storybook-architect.svg)](https://www.npmjs.com/package/uiux-storybook-architect)
5
+ [![license](https://img.shields.io/npm/l/uiux-storybook-architect.svg)](LICENSE)
6
+
7
+ An interview-first UI/UX design skill and 21 slash commands for Claude Code, OpenAI Codex, Google Antigravity, Cursor, Windsurf, and VS Code Copilot.
8
+
9
+ It turns a vague product idea into a decision-complete **Design Storybook**: audience and task research, information architecture, design tokens, typography, color, light and dark themes, component states, page blueprints, motion, accessibility, and an implementation plan.
10
+
11
+ ## Quick start
12
+
13
+ Install the skill into the current project:
14
+
15
+ ```bash
16
+ npx uiux-storybook-architect init
17
+ ```
18
+
19
+ The interactive installer detects supported agentic IDEs. For CI, scripts, or a known setup, use a non-interactive command:
20
+
21
+ ```bash
22
+ # Install for every supported IDE in the current project
23
+ npx uiux-storybook-architect init --all --yes
24
+
25
+ # Install only for selected IDEs
26
+ npx uiux-storybook-architect init --ide claude,codex --yes
27
+
28
+ # Install globally for every IDE that supports global skills
29
+ npx uiux-storybook-architect init --all --global --yes
30
+ ```
31
+
32
+ You can also run it with pnpm, Yarn, or Bun:
33
+
34
+ ```bash
35
+ pnpm dlx uiux-storybook-architect init --all
36
+ yarn dlx uiux-storybook-architect init --all
37
+ bunx uiux-storybook-architect init --all
38
+ ```
39
+
40
+ Until the npm release is available, install directly from GitHub:
41
+
42
+ ```bash
43
+ npx github:nomiwsd/ui-ux-design-skill init --all --yes
44
+ ```
45
+
46
+ Restart the IDE or begin a new agent session after installation.
47
+
48
+ ## Use the skill
49
+
50
+ Start a new design with a plain prompt or the discovery command:
51
+
52
+ ```text
53
+ Design a booking site for a physiotherapy clinic serving mostly 50+ patients.
54
+
55
+ /ux-discover a booking site for a physiotherapy clinic serving mostly 50+ patients
56
+ ```
57
+
58
+ For a new product, the skill asks two short batches of questions with recommended defaults. It then writes the reusable design specification to `design/`.
59
+
60
+ ```text
61
+ /ux-storybook Create the full written design system and build plan
62
+ /ux-build all Implement from the approved Design Storybook
63
+ /ux-a11y src Run the accessibility gate against the implementation
64
+ /ux-handoff Prepare the final implementation handoff
65
+ ```
66
+
67
+ The interview runs once per project. When `design/00-brief.md` exists, the skill reads it instead of asking again. Small additions, single design decisions, critiques, accessibility audits, and performance audits use focused fast paths.
68
+
69
+ ## Commands
70
+
71
+ | Group | Commands |
72
+ |---|---|
73
+ | Research and structure | `/ux-discover` `/ux-brief` `/ux-ia` |
74
+ | Full deliverable | `/ux-storybook` |
75
+ | Foundations | `/ux-tokens` `/ux-type` `/ux-color` `/ux-theme` `/ux-style` |
76
+ | Specifications | `/ux-components` `/ux-pages` |
77
+ | Motion and 3D | `/ux-motion` `/ux-gsap` `/ux-framer` `/ux-3d` |
78
+ | Audits | `/ux-critique` `/ux-a11y` `/ux-perf` `/ux-responsive` |
79
+ | Implementation | `/ux-build` `/ux-handoff` |
80
+
81
+ Run `npx uiux-storybook-architect list` to print every command and its description.
82
+
83
+ ## Supported IDEs
84
+
85
+ | IDE | Skill location | Command location | Project rules |
86
+ |---|---|---|---|
87
+ | Claude Code | `.claude/skills/` or `~/.claude/skills/` | `.claude/commands/` | `CLAUDE.md` |
88
+ | OpenAI Codex | `.agents/skills/` or `~/.codex/skills/` | `~/.codex/prompts/` | `AGENTS.md` |
89
+ | Google Antigravity | `.agents/skills/` or `~/.gemini/antigravity/skills/` | `.agents/workflows/` | `AGENTS.md` |
90
+ | Cursor | `.agents/skills/` | `.cursor/commands/` | `.cursor/rules/uiux.mdc` |
91
+ | Windsurf | `.agents/skills/` | `.windsurf/workflows/` | `.windsurf/rules/uiux.md` |
92
+ | VS Code Copilot | `.agents/skills/` | `.github/prompts/*.prompt.md` | `.github/copilot-instructions.md` |
93
+
94
+ Existing rule files are appended to, not overwritten. Re-running the installer refreshes the bundled skill and commands without touching the project's `design/` output.
95
+
96
+ ## CLI reference
97
+
98
+ | Command | Purpose |
99
+ |---|---|
100
+ | `init` | Detect and install the skill and slash commands |
101
+ | `list` | List all bundled commands |
102
+ | `doctor` | Check installed locations and interview logic |
103
+ | `eject` | Copy the source into a directory for customization |
104
+ | `uninstall` | Remove installed skill and `ux-*` commands |
105
+ | `version` | Print the package version |
106
+
107
+ Common options:
108
+
109
+ ```text
110
+ --all target every supported IDE
111
+ --ide claude,codex target selected IDEs
112
+ --global use global installation locations
113
+ --dest <directory> set the project root (default: current directory)
114
+ --yes run non-interactively
115
+ --src <directory> install an ejected/customized skill source
116
+ ```
117
+
118
+ Check an installation with:
119
+
120
+ ```bash
121
+ npx uiux-storybook-architect doctor --dest .
122
+ ```
123
+
124
+ ## Customize it
125
+
126
+ Eject a copy, edit it, and install from that copy:
127
+
128
+ ```bash
129
+ npx uiux-storybook-architect eject --dest ./my-uiux-skill
130
+ npx uiux-storybook-architect init --all --src ./my-uiux-skill --yes
131
+ ```
132
+
133
+ The installed skill includes detailed guides under `docs/`, including per-IDE setup, the npm CLI, customization, and end-to-end workflows.
134
+
135
+ ## Repository structure
136
+
137
+ ```text
138
+ bin/ zero-dependency installer CLI
139
+ src/SKILL.md skill entry point and operating guidelines
140
+ src/agents/ skill UI metadata
141
+ src/commands/ 21 slash-command definitions
142
+ src/references/ 14 on-demand design references
143
+ src/assets/ reusable token and motion templates
144
+ src/docs/ setup and usage guides
145
+ test/ installer and package tests
146
+ .github/workflows/ CI and npm release automation
147
+ ```
148
+
149
+ ## Development
150
+
151
+ Requirements: Node.js 18 or newer and npm.
152
+
153
+ ```bash
154
+ npm install
155
+ npm test
156
+ npm run check
157
+ ```
158
+
159
+ `npm run check` runs the test suite and previews the exact npm tarball contents. See [PUBLISHING.md](PUBLISHING.md) for the release procedure.
160
+
161
+ ## License
162
+
163
+ [MIT](LICENSE) © 2026 Muhammad Nouman
package/bin/cli.js ADDED
@@ -0,0 +1,475 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * uiux-storybook-architect
4
+ * Installs the UI/UX design skill + slash commands into any agentic IDE.
5
+ * Zero dependencies. Node >= 18.
6
+ */
7
+ import fs from "node:fs";
8
+ import path from "node:path";
9
+ import os from "node:os";
10
+ import readline from "node:readline";
11
+ import { fileURLToPath } from "node:url";
12
+
13
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
+ const PKG_ROOT = path.join(__dirname, "..");
15
+ let SRC = path.join(PKG_ROOT, "src");
16
+ const NAME = "uiux-storybook-architect";
17
+ const VERSION = JSON.parse(
18
+ fs.readFileSync(path.join(PKG_ROOT, "package.json"), "utf8")
19
+ ).version;
20
+
21
+ /* ---------------------------------------------------------------- ui ---- */
22
+ const tty = process.stdout.isTTY && !process.env.NO_COLOR;
23
+ const c = {
24
+ b: (s) => (tty ? `\x1b[1m${s}\x1b[0m` : s),
25
+ dim: (s) => (tty ? `\x1b[2m${s}\x1b[0m` : s),
26
+ g: (s) => (tty ? `\x1b[32m${s}\x1b[0m` : s),
27
+ y: (s) => (tty ? `\x1b[33m${s}\x1b[0m` : s),
28
+ r: (s) => (tty ? `\x1b[31m${s}\x1b[0m` : s),
29
+ cy: (s) => (tty ? `\x1b[36m${s}\x1b[0m` : s),
30
+ };
31
+ const log = (s = "") => console.log(s);
32
+ const ok = (s) => log(` ${c.g("✓")} ${s}`);
33
+ const warn = (s) => log(` ${c.y("!")} ${s}`);
34
+
35
+ /* ------------------------------------------------------------ targets ---- */
36
+ const HOME = os.homedir();
37
+
38
+ /**
39
+ * Each target describes where the skill and the commands go, for both scopes.
40
+ * `detect` returns true when the IDE looks present on this machine / repo.
41
+ */
42
+ const TARGETS = {
43
+ claude: {
44
+ label: "Claude Code",
45
+ detect: (d) => exists(path.join(HOME, ".claude")) || exists(path.join(d, ".claude")),
46
+ project: (d) => ({
47
+ skill: path.join(d, ".claude/skills"),
48
+ cmds: path.join(d, ".claude/commands"),
49
+ ref: `.claude/skills/${NAME}`,
50
+ memory: path.join(d, "CLAUDE.md"),
51
+ }),
52
+ global: () => ({
53
+ skill: path.join(HOME, ".claude/skills"),
54
+ cmds: path.join(HOME, ".claude/commands"),
55
+ ref: path.join(HOME, ".claude/skills", NAME),
56
+ }),
57
+ verify: "/help → the ux-* commands should be listed",
58
+ },
59
+ codex: {
60
+ label: "OpenAI Codex CLI",
61
+ detect: (d) => exists(path.join(HOME, ".codex")) || exists(path.join(d, ".agents/skills")),
62
+ project: (d) => ({
63
+ skill: path.join(d, ".agents/skills"),
64
+ cmds: path.join(HOME, ".codex/prompts"),
65
+ ref: path.join(d, ".agents/skills", NAME),
66
+ memory: path.join(d, "AGENTS.md"),
67
+ note: "Codex reads custom prompts from ~/.codex/prompts (global only)",
68
+ }),
69
+ global: () => ({
70
+ skill: path.join(HOME, ".codex/skills"),
71
+ cmds: path.join(HOME, ".codex/prompts"),
72
+ ref: path.join(HOME, ".codex/skills", NAME),
73
+ }),
74
+ verify: "/skills → uiux-storybook-architect should be listed",
75
+ },
76
+ antigravity: {
77
+ label: "Google Antigravity",
78
+ detect: (d) =>
79
+ exists(path.join(HOME, ".gemini/antigravity")) ||
80
+ exists(path.join(d, ".agents/workflows")) ||
81
+ exists(path.join(d, ".agent")),
82
+ project: (d) => ({
83
+ skill: path.join(d, ".agents/skills"),
84
+ cmds: path.join(d, ".agents/workflows"),
85
+ ref: `.agents/skills/${NAME}`,
86
+ memory: path.join(d, "AGENTS.md"),
87
+ }),
88
+ global: () => ({
89
+ skill: path.join(HOME, ".gemini/antigravity/skills"),
90
+ cmds: path.join(HOME, ".gemini/antigravity/global_workflows"),
91
+ ref: path.join(HOME, ".gemini/antigravity/skills", NAME),
92
+ }),
93
+ verify: "type / in the agent panel → ux-* workflows appear",
94
+ },
95
+ cursor: {
96
+ label: "Cursor",
97
+ detect: (d) => exists(path.join(d, ".cursor")) || exists(path.join(HOME, ".cursor")),
98
+ project: (d) => ({
99
+ skill: path.join(d, ".agents/skills"),
100
+ cmds: path.join(d, ".cursor/commands"),
101
+ ref: `.agents/skills/${NAME}`,
102
+ rule: path.join(d, ".cursor/rules/uiux.mdc"),
103
+ ruleFrontmatter:
104
+ "---\ndescription: UI/UX design workflow — interview first, storybook before code\nglobs: [\"**/*.tsx\",\"**/*.jsx\",\"**/*.vue\",\"**/*.css\",\"design/**\"]\nalwaysApply: false\n---\n",
105
+ }),
106
+ global: null,
107
+ verify: "type / in chat → ux-* commands appear",
108
+ },
109
+ windsurf: {
110
+ label: "Windsurf",
111
+ detect: (d) => exists(path.join(d, ".windsurf")) || exists(path.join(HOME, ".codeium")),
112
+ project: (d) => ({
113
+ skill: path.join(d, ".agents/skills"),
114
+ cmds: path.join(d, ".windsurf/workflows"),
115
+ ref: `.agents/skills/${NAME}`,
116
+ rule: path.join(d, ".windsurf/rules/uiux.md"),
117
+ }),
118
+ global: null,
119
+ verify: "type / in Cascade → ux-* workflows appear",
120
+ },
121
+ copilot: {
122
+ label: "VS Code / GitHub Copilot",
123
+ detect: (d) => exists(path.join(d, ".github")) || exists(path.join(d, ".vscode")),
124
+ project: (d) => ({
125
+ skill: path.join(d, ".agents/skills"),
126
+ cmds: path.join(d, ".github/prompts"),
127
+ suffix: ".prompt.md",
128
+ ref: `.agents/skills/${NAME}`,
129
+ rule: path.join(d, ".github/copilot-instructions.md"),
130
+ }),
131
+ global: null,
132
+ verify: "Agent mode → type / → ux-* prompts appear",
133
+ },
134
+ };
135
+
136
+ /* ------------------------------------------------------------- helpers ---- */
137
+ function exists(p) {
138
+ try { fs.accessSync(p); return true; } catch { return false; }
139
+ }
140
+ function copyDir(from, to) {
141
+ fs.mkdirSync(to, { recursive: true });
142
+ for (const e of fs.readdirSync(from, { withFileTypes: true })) {
143
+ const s = path.join(from, e.name), d = path.join(to, e.name);
144
+ e.isDirectory() ? copyDir(s, d) : fs.copyFileSync(s, d);
145
+ }
146
+ }
147
+ function installSkill(parent) {
148
+ const dest = path.join(parent, NAME);
149
+ fs.rmSync(dest, { recursive: true, force: true });
150
+ fs.mkdirSync(dest, { recursive: true });
151
+ fs.copyFileSync(path.join(SRC, "SKILL.md"), path.join(dest, "SKILL.md"));
152
+ copyDir(path.join(SRC, "agents"), path.join(dest, "agents"));
153
+ copyDir(path.join(SRC, "references"), path.join(dest, "references"));
154
+ copyDir(path.join(SRC, "assets"), path.join(dest, "assets"));
155
+ copyDir(path.join(SRC, "docs"), path.join(dest, "docs"));
156
+ return dest;
157
+ }
158
+ function installCommands(dir, skillRef, suffix = ".md") {
159
+ fs.mkdirSync(dir, { recursive: true });
160
+ const files = fs.readdirSync(path.join(SRC, "commands")).filter((f) => f.endsWith(".md"));
161
+ for (const f of files) {
162
+ const body = fs
163
+ .readFileSync(path.join(SRC, "commands", f), "utf8")
164
+ .replaceAll("{{SKILL_PATH}}", skillRef);
165
+ fs.writeFileSync(path.join(dir, path.basename(f, ".md") + suffix), body);
166
+ }
167
+ return files.length;
168
+ }
169
+ function appendPointer(file, skillRef, frontmatter) {
170
+ fs.mkdirSync(path.dirname(file), { recursive: true });
171
+ const current = exists(file) ? fs.readFileSync(file, "utf8") : "";
172
+ if (current.includes(NAME)) return "already present";
173
+ const block = fs
174
+ .readFileSync(path.join(SRC, "AGENTS.md"), "utf8")
175
+ .replaceAll(`.agents/skills/${NAME}`, skillRef)
176
+ .trim();
177
+ const head = !current && frontmatter ? frontmatter : "";
178
+ fs.writeFileSync(file, current + (current ? "\n\n" : head) + block + "\n");
179
+ return "written";
180
+ }
181
+ function removeIfOurs(p) {
182
+ if (exists(p)) { fs.rmSync(p, { recursive: true, force: true }); return true; }
183
+ return false;
184
+ }
185
+
186
+ /* ---------------------------------------------------------------- args ---- */
187
+ function parseArgs(argv) {
188
+ const out = { _: [], flags: {} };
189
+ for (let i = 0; i < argv.length; i++) {
190
+ const a = argv[i];
191
+ if (a.startsWith("--")) {
192
+ const [k, v] = a.slice(2).split("=");
193
+ if (v !== undefined) out.flags[k] = v;
194
+ else if (argv[i + 1] && !argv[i + 1].startsWith("-")) out.flags[k] = argv[++i];
195
+ else out.flags[k] = true;
196
+ } else if (a.startsWith("-")) {
197
+ out.flags[a.slice(1)] = true;
198
+ } else out._.push(a);
199
+ }
200
+ return out;
201
+ }
202
+ function ask(q) {
203
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
204
+ return new Promise((res) => rl.question(q, (a) => { rl.close(); res(a.trim()); }));
205
+ }
206
+
207
+ /* -------------------------------------------------------------- banner ---- */
208
+ function banner() {
209
+ log();
210
+ log(` ${c.b("UI/UX Storybook Architect")} ${c.dim("v" + VERSION)}`);
211
+ log(` ${c.dim("Interview-first design skill + 21 slash commands for every agentic IDE")}`);
212
+ log();
213
+ }
214
+
215
+ /* ---------------------------------------------------------------- init ---- */
216
+ async function cmdInit(args) {
217
+ const dest = path.resolve(args.flags.dest || process.cwd());
218
+ const scope = args.flags.global ? "global" : "project";
219
+ const yes = !!(args.flags.yes || args.flags.y || args.flags.all);
220
+ const keys = Object.keys(TARGETS);
221
+
222
+ banner();
223
+
224
+ let chosen;
225
+ if (args.flags.ide) {
226
+ chosen = String(args.flags.ide).split(",").map((s) => s.trim()).filter(Boolean);
227
+ } else if (args.flags.all) {
228
+ chosen = keys;
229
+ } else {
230
+ const detected = keys.filter((k) => TARGETS[k].detect(dest));
231
+ log(` ${c.b("Detected on this machine:")}`);
232
+ keys.forEach((k, i) => {
233
+ const hit = detected.includes(k);
234
+ log(
235
+ ` ${c.dim(String(i + 1) + ")")} ${hit ? c.g("●") : c.dim("○")} ${TARGETS[k].label}` +
236
+ (hit ? c.dim(" found") : "")
237
+ );
238
+ });
239
+ log();
240
+ if (!process.stdin.isTTY) {
241
+ chosen = detected.length ? detected : keys;
242
+ warn("non-interactive shell — installing for detected IDEs");
243
+ } else {
244
+ const a = await ask(
245
+ ` Install for which? ${c.dim("[enter = detected, a = all, or numbers e.g. 1,3]")} `
246
+ );
247
+ if (!a) chosen = detected.length ? detected : keys;
248
+ else if (a.toLowerCase() === "a") chosen = keys;
249
+ else
250
+ chosen = a
251
+ .split(/[,\s]+/)
252
+ .map((n) => keys[parseInt(n, 10) - 1])
253
+ .filter(Boolean);
254
+ }
255
+ }
256
+
257
+ chosen = chosen.filter((k) => {
258
+ if (!TARGETS[k]) { warn(`unknown IDE "${k}" — skipped`); return false; }
259
+ return true;
260
+ });
261
+ if (!chosen.length) { warn("nothing selected — exiting"); return; }
262
+
263
+ log();
264
+ log(` ${c.b("Scope:")} ${scope}${scope === "project" ? c.dim(" " + dest) : ""}`);
265
+ log();
266
+
267
+ const summary = [];
268
+ for (const key of chosen) {
269
+ const t = TARGETS[key];
270
+ let plan = scope === "global" ? t.global?.() : t.project(dest);
271
+ if (!plan) {
272
+ plan = t.project(dest);
273
+ warn(`${t.label} has no global scope — installing into the project instead`);
274
+ }
275
+ log(` ${c.cy(t.label)}`);
276
+ const skillDir = installSkill(plan.skill);
277
+ ok(`skill ${c.dim(path.relative(dest, skillDir) || skillDir)}`);
278
+ const n = installCommands(plan.cmds, plan.ref, plan.suffix || ".md");
279
+ ok(`commands ${c.dim(path.relative(dest, plan.cmds) || plan.cmds)} ${c.dim(`(${n})`)}`);
280
+ if (plan.note) log(` ${c.dim(plan.note)}`);
281
+ const rulesFile = plan.rule || plan.memory;
282
+ if (rulesFile && scope === "project") {
283
+ const r = appendPointer(rulesFile, plan.ref, plan.ruleFrontmatter);
284
+ ok(`rules ${c.dim(path.relative(dest, rulesFile))} ${c.dim("(" + r + ")")}`);
285
+ }
286
+ summary.push([t.label, t.verify]);
287
+ log();
288
+ }
289
+
290
+ log(` ${c.b("Done.")} Restart your IDE / start a new session, then:`);
291
+ log();
292
+ log(` ${c.cy("/ux-discover")} a landing page for a fintech app aimed at 55+ users`);
293
+ log();
294
+ log(` ${c.dim("It must ask you 8 numbered questions before designing anything.")}`);
295
+ log(` ${c.dim("If it starts designing immediately, the skill did not load — run:")} ${c.cy("npx " + NAME + " doctor")}`);
296
+ log();
297
+ log(` ${c.b("Verify per IDE")}`);
298
+ for (const [label, v] of summary) log(` ${c.dim("·")} ${label}: ${c.dim(v)}`);
299
+ log();
300
+ log(` ${c.dim("Commands:")} npx ${NAME} list ${c.dim("Guides:")} ${path.join("<skill>", "docs")}`);
301
+ log();
302
+ }
303
+
304
+ /* ---------------------------------------------------------------- list ---- */
305
+ function cmdList() {
306
+ banner();
307
+ const dir = path.join(SRC, "commands");
308
+ const groups = {
309
+ "Research & structure": ["ux-discover", "ux-brief", "ux-ia"],
310
+ Deliverable: ["ux-storybook"],
311
+ Foundations: ["ux-tokens", "ux-type", "ux-color", "ux-theme", "ux-style"],
312
+ Specifications: ["ux-components", "ux-pages"],
313
+ "Motion & 3D": ["ux-motion", "ux-gsap", "ux-framer", "ux-3d"],
314
+ Audits: ["ux-critique", "ux-a11y", "ux-perf", "ux-responsive"],
315
+ Implementation: ["ux-build", "ux-handoff"],
316
+ };
317
+ const desc = (n) => {
318
+ const f = path.join(dir, n + ".md");
319
+ if (!exists(f)) return "";
320
+ const m = fs.readFileSync(f, "utf8").match(/^description:\s*(.+)$/m);
321
+ return m ? m[1] : "";
322
+ };
323
+ for (const [g, cmds] of Object.entries(groups)) {
324
+ log(` ${c.b(g)}`);
325
+ for (const n of cmds) log(` ${c.cy("/" + n).padEnd(tty ? 26 : 16)} ${c.dim(desc(n))}`);
326
+ log();
327
+ }
328
+ log(` ${c.dim("Normal path:")} /ux-discover → /ux-storybook → /ux-build → /ux-a11y → /ux-handoff`);
329
+ log();
330
+ }
331
+
332
+ /* -------------------------------------------------------------- doctor ---- */
333
+ function cmdDoctor(args) {
334
+ const dest = path.resolve(args.flags.dest || process.cwd());
335
+ banner();
336
+ log(` ${c.b("Checking")} ${c.dim(dest)}`);
337
+ log();
338
+ let found = 0;
339
+ for (const [key, t] of Object.entries(TARGETS)) {
340
+ for (const scope of ["project", "global"]) {
341
+ const plan = scope === "global" ? t.global?.() : t.project(dest);
342
+ if (!plan) continue;
343
+ const skillPath = path.join(plan.skill, NAME, "SKILL.md");
344
+ if (!exists(skillPath)) continue;
345
+ const cmdCount = exists(plan.cmds)
346
+ ? fs.readdirSync(plan.cmds).filter((f) => f.startsWith("ux-")).length
347
+ : 0;
348
+ // several IDEs share .agents/skills — without commands this target isn't really installed
349
+ if (cmdCount === 0 && plan.skill.includes(path.join(".agents", "skills"))) continue;
350
+ found++;
351
+ const gated = fs.readFileSync(skillPath, "utf8").includes("When to interview");
352
+ log(` ${c.cy(t.label)} ${c.dim("(" + scope + ")")}`);
353
+ ok(`skill ${c.dim(path.join(plan.skill, NAME))}`);
354
+ cmdCount
355
+ ? ok(`commands ${cmdCount} installed ${c.dim(plan.cmds)}`)
356
+ : warn(`no commands found in ${plan.cmds}`);
357
+ gated ? ok("interview logic present (new projects only)") : warn("interview logic MISSING — reinstall");
358
+ log();
359
+ }
360
+ }
361
+ if (!found) {
362
+ warn("no installation found");
363
+ log(` ${c.dim("run:")} npx ${NAME} init`);
364
+ log();
365
+ process.exitCode = 1;
366
+ } else {
367
+ log(` ${c.dim("Tip: the real test is behavioural — run /ux-discover and confirm it asks questions.")}`);
368
+ log();
369
+ }
370
+ }
371
+
372
+ /* ----------------------------------------------------------- uninstall ---- */
373
+ function cmdUninstall(args) {
374
+ const dest = path.resolve(args.flags.dest || process.cwd());
375
+ banner();
376
+ let n = 0;
377
+ for (const [key, t] of Object.entries(TARGETS)) {
378
+ for (const scope of ["project", "global"]) {
379
+ const plan = scope === "global" ? t.global?.() : t.project(dest);
380
+ if (!plan) continue;
381
+ if (removeIfOurs(path.join(plan.skill, NAME))) { ok(`removed skill ${c.dim(path.join(plan.skill, NAME))}`); n++; }
382
+ if (exists(plan.cmds)) {
383
+ for (const f of fs.readdirSync(plan.cmds)) {
384
+ if (/^ux-.*\.md$/.test(f)) { fs.rmSync(path.join(plan.cmds, f)); n++; }
385
+ }
386
+ }
387
+ }
388
+ }
389
+ log();
390
+ log(` ${n ? c.g("Removed " + n + " items.") : c.y("Nothing to remove.")}`);
391
+ log(` ${c.dim("Rule files (CLAUDE.md, AGENTS.md, .cursor/rules) were left alone — edit them by hand.")}`);
392
+ log();
393
+ }
394
+
395
+ /* ---------------------------------------------------------------- eject --- */
396
+ function cmdEject(args) {
397
+ const dest = path.resolve(args.flags.dest || path.join(process.cwd(), NAME));
398
+ banner();
399
+ copyDir(SRC, dest);
400
+ ok(`source copied to ${c.dim(dest)}`);
401
+ log();
402
+ log(` Edit it, then install your version from that folder:`);
403
+ log(` ${c.cy(`npx ${NAME} init --src ${path.relative(process.cwd(), dest) || "."}`)}`);
404
+ log();
405
+ log(` ${c.dim("Best first edits: SKILL.md defaults table, references/04-visual-styles.md (your house style), assets/tokens.template.css")}`);
406
+ log();
407
+ }
408
+
409
+ /* ----------------------------------------------------------------- help --- */
410
+ function help() {
411
+ banner();
412
+ log(` ${c.b("Usage")}`);
413
+ log(` npx ${NAME} <command> [options]`);
414
+ log();
415
+ log(` ${c.b("Commands")}`);
416
+ log(` init install the skill + slash commands (interactive)`);
417
+ log(` list show all 21 slash commands`);
418
+ log(` doctor check what is installed and whether the gate is intact`);
419
+ log(` eject copy the source out so you can customize it`);
420
+ log(` uninstall remove skill and ux-* commands`);
421
+ log();
422
+ log(` ${c.b("Options")}`);
423
+ log(` --all every supported IDE`);
424
+ log(` --ide claude,codex pick specific IDEs`);
425
+ log(` --global install into ~ instead of the project`);
426
+ log(` --dest <dir> project root (default: cwd)`);
427
+ log(` --yes no prompts`);
428
+ log();
429
+ log(` ${c.b("IDEs")} ${Object.values(TARGETS).map((t) => t.label).join(" · ")}`);
430
+ log();
431
+ log(` ${c.b("Examples")}`);
432
+ log(` npx ${NAME} init ${c.dim("detect and install")}`);
433
+ log(` npx ${NAME} init --all --global ${c.dim("everything, everywhere")}`);
434
+ log(` npx ${NAME} init --ide claude --global`);
435
+ log(` pnpm dlx ${NAME} init --all`);
436
+ log();
437
+ }
438
+
439
+ /* ----------------------------------------------------------------- main --- */
440
+ const args = parseArgs(process.argv.slice(2));
441
+ const cmd = args._[0] || (args.flags.help || args.flags.h ? "help" : "init");
442
+
443
+ // allow `--src` to install from an ejected/edited copy
444
+ if (args.flags.src) {
445
+ const custom = path.resolve(String(args.flags.src));
446
+ if (exists(path.join(custom, "SKILL.md"))) SRC = custom;
447
+ else warn(`--src ${custom} has no SKILL.md — using the bundled source instead`);
448
+ }
449
+
450
+ switch (cmd) {
451
+ case "init":
452
+ case "install":
453
+ await cmdInit(args);
454
+ break;
455
+ case "list":
456
+ case "commands":
457
+ cmdList();
458
+ break;
459
+ case "doctor":
460
+ case "check":
461
+ cmdDoctor(args);
462
+ break;
463
+ case "eject":
464
+ cmdEject(args);
465
+ break;
466
+ case "uninstall":
467
+ case "remove":
468
+ cmdUninstall(args);
469
+ break;
470
+ case "version":
471
+ log(VERSION);
472
+ break;
473
+ default:
474
+ help();
475
+ }