@morya-ui/setup 0.2.8 → 0.3.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/README.md +22 -33
- package/catalog/skills.json +28 -0
- package/package.json +2 -1
- package/src/cli.mjs +58 -12
- package/src/skills.mjs +154 -0
- package/template/.agents/skills/fixing-accessibility/SKILL.md +138 -0
- package/template/.agents/skills/frontend-design/LICENSE.txt +177 -0
- package/template/.agents/skills/frontend-design/SKILL.md +57 -0
- package/template/.agents/skills/morya-ui-pages/SKILL.md +31 -17
- package/template/.agents/skills/morya-ui-pages/evals/evals.json +31 -13
- package/template/.agents/skills/morya-ui-pages/references/component-index.md +29 -10
- package/template/.agents/skills/morya-ui-pages/references/design-system.md +4 -2
- package/template/.agents/skills/morya-ui-pages/references/feedback.md +1 -1
- package/template/.agents/skills/morya-ui-pages/references/optional-companions.md +17 -12
- package/template/.agents/skills/morya-ui-pages/references/page-layouts.md +58 -19
- package/template/.agents/skills/morya-ui-pages/references/review-checklist.md +18 -6
- package/template/.agents/skills/morya-ui-pages/references/surfaces.md +6 -6
- package/template/.agents/skills/morya-ui-pages/references/visual-craft.md +121 -6
- package/template/.cursor/rules/component-usage.mdc +2 -2
- package/template/.cursor/rules/design-system.mdc +3 -3
- package/template/.cursor/rules/page-layout.mdc +5 -0
- package/template/DESIGN.md +34 -106
- package/LICENSE +0 -21
package/README.md
CHANGED
|
@@ -9,7 +9,7 @@ Docs (zh/en):
|
|
|
9
9
|
- [Agent Skill](https://morya-space.github.io/morya-ui/docs/agent-skill) — `morya-ui-pages` behavior
|
|
10
10
|
- [Quick start](https://morya-space.github.io/morya-ui/docs/quick-start) — install and first component
|
|
11
11
|
|
|
12
|
-
Installs the UI library, copies Agent
|
|
12
|
+
Installs the UI library, copies Agent skills / Cursor rules / design docs from `design-kit`, merges Cursor MCP for [`@morya-ui/mcp`](https://www.npmjs.com/package/@morya-ui/mcp), and injects `import 'morya-ui/styles.css'`.
|
|
13
13
|
|
|
14
14
|
## Usage
|
|
15
15
|
|
|
@@ -19,6 +19,8 @@ In your app project root:
|
|
|
19
19
|
npx @morya-ui/setup
|
|
20
20
|
```
|
|
21
21
|
|
|
22
|
+
On a TTY, `full` / `ai` will prompt for optional Agent skills (required `morya-ui-pages` is always included).
|
|
23
|
+
|
|
22
24
|
Other common commands:
|
|
23
25
|
|
|
24
26
|
```bash
|
|
@@ -27,6 +29,11 @@ npx @morya-ui/setup ai
|
|
|
27
29
|
|
|
28
30
|
# Library + styles only
|
|
29
31
|
npx @morya-ui/setup app
|
|
32
|
+
|
|
33
|
+
# Non-interactive skill selection
|
|
34
|
+
npx @morya-ui/setup ai --yes
|
|
35
|
+
npx @morya-ui/setup ai --skills=morya-ui-pages,frontend-design
|
|
36
|
+
npx @morya-ui/setup ai --skills=all
|
|
30
37
|
```
|
|
31
38
|
|
|
32
39
|
### Options
|
|
@@ -35,20 +42,26 @@ npx @morya-ui/setup app
|
|
|
35
42
|
| --- | --- |
|
|
36
43
|
| `--cwd <dir>` | Target project root (default: current directory) |
|
|
37
44
|
| `--pm pnpm\|yarn\|npm` | Package manager (default: detect from lockfile, else `pnpm`) |
|
|
45
|
+
| `--skills <list>` | Comma-separated skill ids, or `all` (skips the prompt) |
|
|
46
|
+
| `--yes` / `-y` | Use default skills without prompting |
|
|
38
47
|
| `--force` | Overwrite existing template files and the `morya-ui` MCP entry |
|
|
39
48
|
| `--dry-run` | Print actions without writing or installing |
|
|
40
49
|
| `--skip-install` | Do not install `morya-ui` |
|
|
41
|
-
| `--skip-template` | Do not copy AI skill / rules / docs
|
|
50
|
+
| `--skip-template` | Do not copy AI skill / rules / docs |
|
|
42
51
|
| `--skip-mcp` | Do not write `.cursor/mcp.json` |
|
|
43
52
|
| `--skip-styles` | Do not inject `styles.css` |
|
|
44
53
|
| `--skip-scripts` | Do not add `check:colors` to `package.json` |
|
|
45
54
|
| `-h`, `--help` | Show help |
|
|
46
55
|
|
|
47
|
-
|
|
56
|
+
### Skills
|
|
48
57
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
58
|
+
| Id | Default | Role |
|
|
59
|
+
| --- | --- | --- |
|
|
60
|
+
| `morya-ui-pages` | required | Page generation with `M*` + golden layouts |
|
|
61
|
+
| `frontend-design` | optional | Express / brand visual taste |
|
|
62
|
+
| `fixing-accessibility` | optional | A11y audit and targeted fixes |
|
|
63
|
+
|
|
64
|
+
Catalog: [`catalog/skills.json`](./catalog/skills.json).
|
|
52
65
|
|
|
53
66
|
### Conflict policy
|
|
54
67
|
|
|
@@ -61,8 +74,8 @@ npx @morya-ui/setup ai --skip-template --skip-scripts
|
|
|
61
74
|
|
|
62
75
|
From the package `template/` (synced from repo `design-kit/`):
|
|
63
76
|
|
|
64
|
-
- `DESIGN.md`
|
|
65
|
-
- `.agents/skills
|
|
77
|
+
- `DESIGN.md` — core design contract (principles, tokens, bans)
|
|
78
|
+
- `.agents/skills/<selected>/` — at least `morya-ui-pages`
|
|
66
79
|
- `.cursor/rules/`
|
|
67
80
|
- `scripts/check-raw-colors.mjs`
|
|
68
81
|
|
|
@@ -81,28 +94,4 @@ Writes / merges:
|
|
|
81
94
|
}
|
|
82
95
|
```
|
|
83
96
|
|
|
84
|
-
Restart Cursor (or reload MCP) after
|
|
85
|
-
|
|
86
|
-
Other clients can use the same stdio command; this CLI only writes `.cursor/mcp.json`.
|
|
87
|
-
|
|
88
|
-
### Styles
|
|
89
|
-
|
|
90
|
-
Looks for `src/main.ts` / `.js` / `.tsx` / `.jsx`, root `main.*`, `src/app.ts` / `.js`, or the module script in `index.html`. If none match, add manually:
|
|
91
|
-
|
|
92
|
-
```ts
|
|
93
|
-
import 'morya-ui/styles.css'
|
|
94
|
-
```
|
|
95
|
-
|
|
96
|
-
The CLI does **not** call `app.use(MoryaUI)` or edit `App.vue`.
|
|
97
|
-
|
|
98
|
-
## Local development (this monorepo)
|
|
99
|
-
|
|
100
|
-
```bash
|
|
101
|
-
pnpm setup:sync-template # refresh packages/setup/template from design-kit
|
|
102
|
-
pnpm setup:build
|
|
103
|
-
node packages/setup/bin/morya-ui-setup.js --cwd /path/to/app --dry-run
|
|
104
|
-
```
|
|
105
|
-
|
|
106
|
-
## License
|
|
107
|
-
|
|
108
|
-
MIT
|
|
97
|
+
Restart Cursor (or reload MCP) after install.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skills": [
|
|
3
|
+
{
|
|
4
|
+
"id": "morya-ui-pages",
|
|
5
|
+
"name": "Morya UI Pages",
|
|
6
|
+
"description": "Build admin and product pages with M* components, golden layouts, and tokens.",
|
|
7
|
+
"required": true,
|
|
8
|
+
"default": true,
|
|
9
|
+
"path": ".agents/skills/morya-ui-pages"
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
"id": "frontend-design",
|
|
13
|
+
"name": "Frontend Design",
|
|
14
|
+
"description": "Distinctive visual direction for Express / brand surfaces (taste only; still remediate with M*).",
|
|
15
|
+
"required": false,
|
|
16
|
+
"default": false,
|
|
17
|
+
"path": ".agents/skills/frontend-design"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
"id": "fixing-accessibility",
|
|
21
|
+
"name": "Fixing Accessibility",
|
|
22
|
+
"description": "Audit and fix names, keyboard, focus, forms, and WCAG basics.",
|
|
23
|
+
"required": false,
|
|
24
|
+
"default": false,
|
|
25
|
+
"path": ".agents/skills/fixing-accessibility"
|
|
26
|
+
}
|
|
27
|
+
]
|
|
28
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@morya-ui/setup",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"description": "One-shot setup for morya-ui: install the library, Cursor MCP, Agent skills, and design config.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://github.com/morya-space/morya-ui/tree/main/packages/setup#readme",
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
"files": [
|
|
23
23
|
"README.md",
|
|
24
24
|
"bin",
|
|
25
|
+
"catalog",
|
|
25
26
|
"src",
|
|
26
27
|
"template"
|
|
27
28
|
],
|
package/src/cli.mjs
CHANGED
|
@@ -6,6 +6,12 @@ import { readJson } from './fs-utils.mjs'
|
|
|
6
6
|
import { installMoryaUi } from './install.mjs'
|
|
7
7
|
import { mergeMcpConfig } from './mcp.mjs'
|
|
8
8
|
import { ensureCheckColorsScript } from './package-json.mjs'
|
|
9
|
+
import {
|
|
10
|
+
buildAiInclude,
|
|
11
|
+
loadSkillsCatalog,
|
|
12
|
+
parseSkillsFlag,
|
|
13
|
+
resolveSkillSelection,
|
|
14
|
+
} from './skills.mjs'
|
|
9
15
|
import { ensureStylesImport } from './styles.mjs'
|
|
10
16
|
|
|
11
17
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
@@ -14,14 +20,6 @@ const TEMPLATE_ROOT = join(PKG_ROOT, 'template')
|
|
|
14
20
|
|
|
15
21
|
const MODES = new Set(['app', 'ai', 'full'])
|
|
16
22
|
|
|
17
|
-
/** AI pack paths under template/. */
|
|
18
|
-
const AI_TEMPLATE_INCLUDE = [
|
|
19
|
-
'DESIGN.md',
|
|
20
|
-
'.agents/skills/morya-ui-pages',
|
|
21
|
-
'.cursor/rules',
|
|
22
|
-
'scripts/check-raw-colors.mjs',
|
|
23
|
-
]
|
|
24
|
-
|
|
25
23
|
/** Mode → default skips (user --skip-* can only add more skips). */
|
|
26
24
|
const MODE_DEFAULTS = {
|
|
27
25
|
full: {},
|
|
@@ -38,17 +36,23 @@ const SKIP_FLAGS = {
|
|
|
38
36
|
}
|
|
39
37
|
|
|
40
38
|
export function printHelp() {
|
|
39
|
+
const { skills } = loadSkillsCatalog()
|
|
40
|
+
const skillList = skills.map((s) => {
|
|
41
|
+
const tag = s.required ? 'required' : s.default ? 'default' : 'optional'
|
|
42
|
+
return ` ${s.id.padEnd(24)} (${tag}) ${s.description}`
|
|
43
|
+
}).join('\n')
|
|
44
|
+
|
|
41
45
|
console.log(`Usage: morya-ui-setup [command] [options]
|
|
42
46
|
|
|
43
47
|
Commands:
|
|
44
48
|
(default) / full Install morya-ui, AI template, MCP, styles, check:colors
|
|
45
49
|
app Install morya-ui and inject styles.css
|
|
46
|
-
ai Copy Agent
|
|
50
|
+
ai Copy Agent skills / rules / DESIGN,
|
|
47
51
|
merge Cursor MCP, add check:colors
|
|
48
52
|
|
|
49
53
|
Default command:
|
|
50
54
|
- install morya-ui
|
|
51
|
-
- copy DESIGN.md, Agent
|
|
55
|
+
- copy DESIGN.md, selected Agent skills, Cursor rules
|
|
52
56
|
- merge .cursor/mcp.json for @morya-ui/mcp
|
|
53
57
|
- inject import 'morya-ui/styles.css' into the app entry when found
|
|
54
58
|
- add check:colors script when missing
|
|
@@ -56,6 +60,8 @@ Default command:
|
|
|
56
60
|
Options:
|
|
57
61
|
--cwd <dir> Target project root (default: process.cwd())
|
|
58
62
|
--pm <name> Package manager: pnpm | yarn | npm (auto-detect by lockfile)
|
|
63
|
+
--skills <list> Comma-separated skill ids, or "all" (skips interactive prompt)
|
|
64
|
+
--yes Use default skills without prompting (CI / non-interactive)
|
|
59
65
|
--force Overwrite existing template files and MCP server entry
|
|
60
66
|
--dry-run Print actions without writing or installing
|
|
61
67
|
--skip-install Skip dependency install
|
|
@@ -64,6 +70,9 @@ Options:
|
|
|
64
70
|
--skip-styles Skip injecting styles.css
|
|
65
71
|
--skip-scripts Skip adding check:colors to package.json
|
|
66
72
|
-h, --help Show this help
|
|
73
|
+
|
|
74
|
+
Skills:
|
|
75
|
+
${skillList}
|
|
67
76
|
`)
|
|
68
77
|
}
|
|
69
78
|
|
|
@@ -75,6 +84,8 @@ export function parseArgs(argv) {
|
|
|
75
84
|
mode: 'full',
|
|
76
85
|
cwd: process.cwd(),
|
|
77
86
|
pm: undefined,
|
|
87
|
+
skills: undefined,
|
|
88
|
+
yes: false,
|
|
78
89
|
force: false,
|
|
79
90
|
dryRun: false,
|
|
80
91
|
skipInstall: false,
|
|
@@ -103,6 +114,10 @@ export function parseArgs(argv) {
|
|
|
103
114
|
options.dryRun = true
|
|
104
115
|
continue
|
|
105
116
|
}
|
|
117
|
+
if (arg === '--yes' || arg === '-y') {
|
|
118
|
+
options.yes = true
|
|
119
|
+
continue
|
|
120
|
+
}
|
|
106
121
|
if (SKIP_FLAGS[arg]) {
|
|
107
122
|
options[SKIP_FLAGS[arg]] = true
|
|
108
123
|
continue
|
|
@@ -127,6 +142,16 @@ export function parseArgs(argv) {
|
|
|
127
142
|
options.pm = arg.slice('--pm='.length)
|
|
128
143
|
continue
|
|
129
144
|
}
|
|
145
|
+
if (arg === '--skills') {
|
|
146
|
+
const value = argv[++i]
|
|
147
|
+
if (!value) throw new Error('--skills requires a comma-separated list or "all"')
|
|
148
|
+
options.skills = value
|
|
149
|
+
continue
|
|
150
|
+
}
|
|
151
|
+
if (arg.startsWith('--skills=')) {
|
|
152
|
+
options.skills = arg.slice('--skills='.length)
|
|
153
|
+
continue
|
|
154
|
+
}
|
|
130
155
|
throw new Error(`Unknown argument: ${arg}`)
|
|
131
156
|
}
|
|
132
157
|
|
|
@@ -176,6 +201,8 @@ export async function runSetup(options) {
|
|
|
176
201
|
skipStyles,
|
|
177
202
|
skipScripts,
|
|
178
203
|
pm,
|
|
204
|
+
skills: skillsFlag,
|
|
205
|
+
yes,
|
|
179
206
|
} = options
|
|
180
207
|
|
|
181
208
|
console.log(`@morya-ui/setup [${mode}] → ${cwd}${dryRun ? ' (dry-run)' : ''}`)
|
|
@@ -188,6 +215,24 @@ export async function runSetup(options) {
|
|
|
188
215
|
console.log('')
|
|
189
216
|
}
|
|
190
217
|
|
|
218
|
+
const catalog = loadSkillsCatalog()
|
|
219
|
+
let selectedSkills = []
|
|
220
|
+
let aiInclude
|
|
221
|
+
|
|
222
|
+
const needsTemplate = !skipTemplate && (mode === 'ai' || mode === 'full')
|
|
223
|
+
if (needsTemplate) {
|
|
224
|
+
if (skillsFlag != null) {
|
|
225
|
+
selectedSkills = parseSkillsFlag(skillsFlag, catalog.skills)
|
|
226
|
+
} else {
|
|
227
|
+
selectedSkills = await resolveSkillSelection(catalog.skills, {
|
|
228
|
+
skipPrompt: yes || dryRun,
|
|
229
|
+
})
|
|
230
|
+
}
|
|
231
|
+
aiInclude = buildAiInclude(selectedSkills, catalog.skills)
|
|
232
|
+
console.log(`Skills: ${selectedSkills.join(', ')}`)
|
|
233
|
+
console.log('')
|
|
234
|
+
}
|
|
235
|
+
|
|
191
236
|
const install = installMoryaUi(cwd, { pm, dryRun, skipInstall })
|
|
192
237
|
|
|
193
238
|
const template = skipTemplate
|
|
@@ -195,7 +240,7 @@ export async function runSetup(options) {
|
|
|
195
240
|
: copyTemplate(TEMPLATE_ROOT, cwd, {
|
|
196
241
|
force,
|
|
197
242
|
dryRun,
|
|
198
|
-
include:
|
|
243
|
+
include: aiInclude,
|
|
199
244
|
})
|
|
200
245
|
|
|
201
246
|
const mcp = skipMcp
|
|
@@ -223,6 +268,7 @@ export async function runSetup(options) {
|
|
|
223
268
|
console.log(
|
|
224
269
|
`Template: ${template.copied.length} copied, ${template.forced.length} overwritten, ${template.skipped.length} skipped`,
|
|
225
270
|
)
|
|
271
|
+
if (selectedSkills.length) console.log(` skills: ${selectedSkills.join(', ')}`)
|
|
226
272
|
if (template.skipped.length && template.skipped.length <= 8) {
|
|
227
273
|
for (const file of template.skipped) console.log(` skip ${file}`)
|
|
228
274
|
} else if (template.skipped.length > 8) {
|
|
@@ -270,5 +316,5 @@ export async function runSetup(options) {
|
|
|
270
316
|
console.log(' 4. Optional: pnpm check:colors')
|
|
271
317
|
}
|
|
272
318
|
|
|
273
|
-
return { mode, install, template, mcp, styles, scripts }
|
|
319
|
+
return { mode, install, template, mcp, styles, scripts, skills: selectedSkills }
|
|
274
320
|
}
|
package/src/skills.mjs
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { createInterface } from 'node:readline'
|
|
2
|
+
import { dirname, join } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { readJson } from './fs-utils.mjs'
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
7
|
+
const CATALOG_PATH = join(__dirname, '..', 'catalog', 'skills.json')
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {{
|
|
11
|
+
* id: string
|
|
12
|
+
* name: string
|
|
13
|
+
* description: string
|
|
14
|
+
* required?: boolean
|
|
15
|
+
* default?: boolean
|
|
16
|
+
* path: string
|
|
17
|
+
* }} SkillEntry
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* @returns {{ skills: SkillEntry[] }}
|
|
22
|
+
*/
|
|
23
|
+
export function loadSkillsCatalog() {
|
|
24
|
+
return readJson(CATALOG_PATH)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* @param {SkillEntry[]} skills
|
|
29
|
+
*/
|
|
30
|
+
export function defaultSkillIds(skills) {
|
|
31
|
+
return skills.filter((s) => s.required || s.default).map((s) => s.id)
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {string | undefined} raw
|
|
36
|
+
* @param {SkillEntry[]} skills
|
|
37
|
+
* @returns {string[]}
|
|
38
|
+
*/
|
|
39
|
+
export function parseSkillsFlag(raw, skills) {
|
|
40
|
+
if (raw == null || raw === '') return defaultSkillIds(skills)
|
|
41
|
+
const known = new Map(skills.map((s) => [s.id, s]))
|
|
42
|
+
if (raw.trim() === 'all') return skills.map((s) => s.id)
|
|
43
|
+
|
|
44
|
+
const ids = raw
|
|
45
|
+
.split(',')
|
|
46
|
+
.map((part) => part.trim())
|
|
47
|
+
.filter(Boolean)
|
|
48
|
+
|
|
49
|
+
if (ids.length === 0) return defaultSkillIds(skills)
|
|
50
|
+
|
|
51
|
+
const missing = ids.filter((id) => !known.has(id))
|
|
52
|
+
if (missing.length) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`Unknown skill id(s): ${missing.join(', ')}. Available: ${skills.map((s) => s.id).join(', ')}, or all`,
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const selected = new Set(ids)
|
|
59
|
+
for (const skill of skills) {
|
|
60
|
+
if (skill.required) selected.add(skill.id)
|
|
61
|
+
}
|
|
62
|
+
return skills.map((s) => s.id).filter((id) => selected.has(id))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* @param {string[]} skillIds
|
|
67
|
+
* @param {SkillEntry[]} skills
|
|
68
|
+
* @returns {string[]}
|
|
69
|
+
*/
|
|
70
|
+
export function skillPathsForIds(skillIds, skills) {
|
|
71
|
+
const byId = new Map(skills.map((s) => [s.id, s]))
|
|
72
|
+
return skillIds.map((id) => {
|
|
73
|
+
const entry = byId.get(id)
|
|
74
|
+
if (!entry) throw new Error(`Unknown skill id: ${id}`)
|
|
75
|
+
return entry.path
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Build template include prefixes for the AI pack.
|
|
81
|
+
* @param {string[]} skillIds
|
|
82
|
+
* @param {SkillEntry[]} skills
|
|
83
|
+
*/
|
|
84
|
+
export function buildAiInclude(skillIds, skills) {
|
|
85
|
+
return [
|
|
86
|
+
'DESIGN.md',
|
|
87
|
+
...skillPathsForIds(skillIds, skills),
|
|
88
|
+
'.cursor/rules',
|
|
89
|
+
'scripts/check-raw-colors.mjs',
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Prompt for optional skills when stdin is a TTY.
|
|
95
|
+
* Required skills are always included.
|
|
96
|
+
* @param {SkillEntry[]} skills
|
|
97
|
+
* @param {{ skipPrompt?: boolean }} [options]
|
|
98
|
+
* @returns {Promise<string[]>}
|
|
99
|
+
*/
|
|
100
|
+
export async function resolveSkillSelection(skills, { skipPrompt = false } = {}) {
|
|
101
|
+
const required = skills.filter((s) => s.required).map((s) => s.id)
|
|
102
|
+
const optional = skills.filter((s) => !s.required)
|
|
103
|
+
|
|
104
|
+
if (skipPrompt || optional.length === 0 || !process.stdin.isTTY) {
|
|
105
|
+
return defaultSkillIds(skills)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
console.log('Select optional Agent skills (in addition to required):')
|
|
109
|
+
optional.forEach((skill, index) => {
|
|
110
|
+
const mark = skill.default ? 'x' : ' '
|
|
111
|
+
console.log(` ${index + 1}. [${mark}] ${skill.id} — ${skill.description}`)
|
|
112
|
+
})
|
|
113
|
+
console.log('')
|
|
114
|
+
console.log('Enter comma-separated numbers or ids (empty = defaults only).')
|
|
115
|
+
console.log(`Required: ${required.join(', ') || '(none)'}`)
|
|
116
|
+
|
|
117
|
+
const answer = await question('> ')
|
|
118
|
+
const trimmed = answer.trim()
|
|
119
|
+
if (!trimmed) return defaultSkillIds(skills)
|
|
120
|
+
|
|
121
|
+
const selected = new Set(required)
|
|
122
|
+
const tokens = trimmed.split(/[\s,]+/).filter(Boolean)
|
|
123
|
+
for (const token of tokens) {
|
|
124
|
+
const asIndex = Number(token)
|
|
125
|
+
if (Number.isInteger(asIndex) && asIndex >= 1 && asIndex <= optional.length) {
|
|
126
|
+
selected.add(optional[asIndex - 1].id)
|
|
127
|
+
continue
|
|
128
|
+
}
|
|
129
|
+
if (token === 'all') {
|
|
130
|
+
for (const skill of optional) selected.add(skill.id)
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
if (!skills.some((s) => s.id === token)) {
|
|
134
|
+
throw new Error(`Unknown skill selection: ${token}`)
|
|
135
|
+
}
|
|
136
|
+
selected.add(token)
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return skills.map((s) => s.id).filter((id) => selected.has(id))
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* @param {string} prompt
|
|
144
|
+
* @returns {Promise<string>}
|
|
145
|
+
*/
|
|
146
|
+
function question(prompt) {
|
|
147
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout })
|
|
148
|
+
return new Promise((resolve) => {
|
|
149
|
+
rl.question(prompt, (answer) => {
|
|
150
|
+
rl.close()
|
|
151
|
+
resolve(answer)
|
|
152
|
+
})
|
|
153
|
+
})
|
|
154
|
+
}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: fixing-accessibility
|
|
3
|
+
description: Audit and fix HTML accessibility issues including ARIA labels, keyboard navigation, focus management, color contrast, and form errors. Use when adding interactive controls, forms, dialogs, or reviewing WCAG compliance.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# fixing-accessibility
|
|
7
|
+
|
|
8
|
+
> **morya-ui consumer note:** Prefer library defaults (`label`, `aria-label`, Esc-dismiss overlays). Do not replace `M*` widgets with custom ARIA hacks. When guidance conflicts with `DESIGN.md` or `morya-ui-pages`, **those win**.
|
|
9
|
+
|
|
10
|
+
Fix accessibility issues.
|
|
11
|
+
|
|
12
|
+
## how to use
|
|
13
|
+
|
|
14
|
+
- `/fixing-accessibility`
|
|
15
|
+
Apply these constraints to any UI work in this conversation.
|
|
16
|
+
|
|
17
|
+
- `/fixing-accessibility <file>`
|
|
18
|
+
Review the file against all rules below and report:
|
|
19
|
+
- violations (quote the exact line or snippet)
|
|
20
|
+
- why it matters (one short sentence)
|
|
21
|
+
- a concrete fix (code-level suggestion)
|
|
22
|
+
|
|
23
|
+
Do not rewrite large parts of the UI. Prefer minimal, targeted fixes.
|
|
24
|
+
|
|
25
|
+
## when to apply
|
|
26
|
+
|
|
27
|
+
Reference these guidelines when:
|
|
28
|
+
- adding or changing buttons, links, inputs, menus, dialogs, tabs, dropdowns
|
|
29
|
+
- building forms, validation, error states, helper text
|
|
30
|
+
- implementing keyboard shortcuts or custom interactions
|
|
31
|
+
- working on focus states, focus trapping, or modal behavior
|
|
32
|
+
- rendering icon-only controls
|
|
33
|
+
- adding hover-only interactions or hidden content
|
|
34
|
+
|
|
35
|
+
## rule categories by priority
|
|
36
|
+
|
|
37
|
+
| priority | category | impact |
|
|
38
|
+
|----------|----------|--------|
|
|
39
|
+
| 1 | accessible names | critical |
|
|
40
|
+
| 2 | keyboard access | critical |
|
|
41
|
+
| 3 | focus and dialogs | critical |
|
|
42
|
+
| 4 | semantics | high |
|
|
43
|
+
| 5 | forms and errors | high |
|
|
44
|
+
| 6 | announcements | medium-high |
|
|
45
|
+
| 7 | contrast and states | medium |
|
|
46
|
+
| 8 | media and motion | low-medium |
|
|
47
|
+
| 9 | tool boundaries | critical |
|
|
48
|
+
|
|
49
|
+
## quick reference
|
|
50
|
+
|
|
51
|
+
### 1. accessible names (critical)
|
|
52
|
+
|
|
53
|
+
- every interactive control must have an accessible name
|
|
54
|
+
- icon-only buttons must have aria-label or aria-labelledby
|
|
55
|
+
- every input, select, and textarea must be labeled
|
|
56
|
+
- links must have meaningful text (no “click here”)
|
|
57
|
+
- decorative icons must be aria-hidden
|
|
58
|
+
|
|
59
|
+
### 2. keyboard access (critical)
|
|
60
|
+
|
|
61
|
+
- do not use div or span as buttons without full keyboard support
|
|
62
|
+
- all interactive elements must be reachable by Tab
|
|
63
|
+
- focus must be visible for keyboard users
|
|
64
|
+
- do not use tabindex greater than 0
|
|
65
|
+
- Escape must close dialogs or overlays when applicable
|
|
66
|
+
|
|
67
|
+
### 3. focus and dialogs (critical)
|
|
68
|
+
|
|
69
|
+
- modals must trap focus while open
|
|
70
|
+
- restore focus to the trigger on close
|
|
71
|
+
- set initial focus inside dialogs
|
|
72
|
+
- opening a dialog should not scroll the page unexpectedly
|
|
73
|
+
|
|
74
|
+
### 4. semantics (high)
|
|
75
|
+
|
|
76
|
+
- prefer native elements (button, a, input) over role-based hacks
|
|
77
|
+
- if a role is used, required aria attributes must be present
|
|
78
|
+
- lists must use ul or ol with li
|
|
79
|
+
- do not skip heading levels
|
|
80
|
+
- tables must use th for headers when applicable
|
|
81
|
+
|
|
82
|
+
### 5. forms and errors (high)
|
|
83
|
+
|
|
84
|
+
- errors must be linked to fields using aria-describedby
|
|
85
|
+
- required fields must be announced
|
|
86
|
+
- invalid fields must set aria-invalid
|
|
87
|
+
- helper text must be associated with inputs
|
|
88
|
+
- disabled submit actions must explain why
|
|
89
|
+
|
|
90
|
+
### 6. announcements (medium-high)
|
|
91
|
+
|
|
92
|
+
- critical form errors should use aria-live
|
|
93
|
+
- loading states should use aria-busy or status text
|
|
94
|
+
- toasts must not be the only way to convey critical information
|
|
95
|
+
- expandable controls must use aria-expanded and aria-controls
|
|
96
|
+
|
|
97
|
+
### 7. contrast and states (medium)
|
|
98
|
+
|
|
99
|
+
- ensure sufficient contrast for text and icons
|
|
100
|
+
- hover-only interactions must have keyboard equivalents
|
|
101
|
+
- disabled states must not rely on color alone
|
|
102
|
+
- do not remove focus outlines without a visible replacement
|
|
103
|
+
|
|
104
|
+
### 8. media and motion (low-medium)
|
|
105
|
+
|
|
106
|
+
- images must have correct alt text (meaningful or empty)
|
|
107
|
+
- videos with speech should provide captions when relevant
|
|
108
|
+
- respect prefers-reduced-motion for non-essential motion
|
|
109
|
+
- avoid autoplaying media with sound
|
|
110
|
+
|
|
111
|
+
### 9. tool boundaries (critical)
|
|
112
|
+
|
|
113
|
+
- prefer minimal changes, do not refactor unrelated code
|
|
114
|
+
- do not add aria when native semantics already solve the problem
|
|
115
|
+
- do not migrate UI libraries unless requested
|
|
116
|
+
|
|
117
|
+
## common fixes
|
|
118
|
+
|
|
119
|
+
```html
|
|
120
|
+
<!-- icon-only button: add aria-label -->
|
|
121
|
+
<!-- before --> <button><svg>...</svg></button>
|
|
122
|
+
<!-- after --> <button aria-label="Close"><svg aria-hidden="true">...</svg></button>
|
|
123
|
+
|
|
124
|
+
<!-- div as button: use native element -->
|
|
125
|
+
<!-- before --> <div onclick="save()">Save</div>
|
|
126
|
+
<!-- after --> <button onclick="save()">Save</button>
|
|
127
|
+
|
|
128
|
+
<!-- form error: link with aria-describedby -->
|
|
129
|
+
<!-- before --> <input id="email" /> <span>Invalid email</span>
|
|
130
|
+
<!-- after --> <input id="email" aria-describedby="email-err" aria-invalid="true" /> <span id="email-err">Invalid email</span>
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
## review guidance
|
|
134
|
+
|
|
135
|
+
- fix critical issues first (names, keyboard, focus, tool boundaries)
|
|
136
|
+
- prefer native HTML before adding aria
|
|
137
|
+
- quote the exact snippet, state the failure, propose a small fix
|
|
138
|
+
- for complex widgets (menu, dialog, combobox), prefer established accessible primitives over custom behavior
|