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.
- package/LICENSE +21 -0
- package/README.md +163 -0
- package/bin/cli.js +475 -0
- package/package.json +61 -0
- package/src/AGENTS.md +24 -0
- package/src/SKILL.md +206 -0
- package/src/agents/openai.yaml +4 -0
- package/src/assets/motion-snippets.css +73 -0
- package/src/assets/tokens.template.css +134 -0
- package/src/assets/tokens.template.json +64 -0
- package/src/commands/ux-3d.md +27 -0
- package/src/commands/ux-a11y.md +18 -0
- package/src/commands/ux-brief.md +14 -0
- package/src/commands/ux-build.md +31 -0
- package/src/commands/ux-color.md +28 -0
- package/src/commands/ux-components.md +26 -0
- package/src/commands/ux-critique.md +20 -0
- package/src/commands/ux-discover.md +20 -0
- package/src/commands/ux-framer.md +27 -0
- package/src/commands/ux-gsap.md +27 -0
- package/src/commands/ux-handoff.md +27 -0
- package/src/commands/ux-ia.md +24 -0
- package/src/commands/ux-motion.md +29 -0
- package/src/commands/ux-pages.md +24 -0
- package/src/commands/ux-perf.md +16 -0
- package/src/commands/ux-responsive.md +14 -0
- package/src/commands/ux-storybook.md +30 -0
- package/src/commands/ux-style.md +28 -0
- package/src/commands/ux-theme.md +26 -0
- package/src/commands/ux-tokens.md +29 -0
- package/src/commands/ux-type.md +29 -0
- package/src/docs/00-commands-reference.md +69 -0
- package/src/docs/01-claude-code.md +62 -0
- package/src/docs/02-codex.md +51 -0
- package/src/docs/03-antigravity.md +57 -0
- package/src/docs/04-cursor.md +50 -0
- package/src/docs/05-windsurf.md +36 -0
- package/src/docs/06-vscode-copilot.md +45 -0
- package/src/docs/07-claude-apps.md +42 -0
- package/src/docs/08-customizing.md +85 -0
- package/src/docs/09-workflows.md +82 -0
- package/src/docs/10-npm-cli.md +54 -0
- package/src/docs/11-vscode-guide.md +139 -0
- package/src/references/01-discovery-interview.md +75 -0
- package/src/references/02-foundations.md +80 -0
- package/src/references/03-typography-color-theming.md +168 -0
- package/src/references/04-visual-styles.md +90 -0
- package/src/references/05-website-type-patterns.md +71 -0
- package/src/references/06-age-inclusive-design.md +58 -0
- package/src/references/07-craft-and-accessibility.md +90 -0
- package/src/references/08-motion-system.md +122 -0
- package/src/references/09-gsap-recipes.md +209 -0
- package/src/references/10-motion-react-recipes.md +166 -0
- package/src/references/11-threejs-webgl.md +129 -0
- package/src/references/12-storybook-template.md +265 -0
- package/src/references/13-implementation-handoff.md +93 -0
- 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
|
+
[](https://github.com/nomiwsd/ui-ux-design-skill/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/uiux-storybook-architect)
|
|
5
|
+
[](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
|
+
}
|