@morya-ui/setup 0.2.7 → 0.2.9
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 +23 -40
- package/catalog/skills.json +28 -0
- package/package.json +2 -1
- package/src/cli.mjs +63 -36
- package/src/skills.mjs +154 -0
- package/src/styles.mjs +2 -87
- 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 +17 -16
- package/template/.agents/skills/morya-ui-pages/evals/evals.json +18 -12
- package/template/.agents/skills/morya-ui-pages/references/component-index.md +31 -9
- package/template/.agents/skills/morya-ui-pages/references/design-system.md +5 -3
- package/template/.agents/skills/morya-ui-pages/references/feedback.md +2 -2
- package/template/.agents/skills/morya-ui-pages/references/optional-companions.md +17 -12
- package/template/.agents/skills/morya-ui-pages/references/page-layouts.md +26 -16
- package/template/.agents/skills/morya-ui-pages/references/review-checklist.md +14 -2
- package/template/.agents/skills/morya-ui-pages/references/surfaces.md +4 -4
- package/template/.agents/skills/morya-ui-pages/references/visual-craft.md +121 -6
- package/template/.cursor/rules/component-usage.mdc +4 -4
- package/template/.cursor/rules/design-system.mdc +5 -5
- package/template/.cursor/rules/page-layout.mdc +7 -9
- package/template/DESIGN.md +29 -106
- package/LICENSE +0 -21
- package/template/design-tokens/tokens.css +0 -55
- package/template/design-tokens/tokens.json +0 -77
- package/template/docs/components.md +0 -145
- package/template/docs/feedback-message-vs-toast.md +0 -103
- package/template/docs/golden-pages/dashboard-page.vue +0 -103
- package/template/docs/golden-pages/empty-state.vue +0 -66
- package/template/docs/golden-pages/form-page.vue +0 -107
- package/template/docs/golden-pages/landing-page.vue +0 -328
- package/template/docs/golden-pages/list-page.vue +0 -138
- package/template/docs/golden-pages/login-page.vue +0 -191
- package/template/src/examples/DashboardPageExample.vue +0 -103
- package/template/src/examples/EmptyStateExample.vue +0 -66
- package/template/src/examples/FormPageExample.vue +0 -107
- package/template/src/examples/LandingPageExample.vue +0 -328
- package/template/src/examples/ListPageExample.vue +0 -138
- package/template/src/examples/LoginPageExample.vue +0 -191
- package/template/src/styles/morya-app-shell.css +0 -6
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
|
-
| `--skip-styles` | Do not inject `styles.css`
|
|
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,15 +74,10 @@ 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
|
-
- `docs/` (component index, golden pages, feedback guide)
|
|
68
|
-
- `design-tokens/`
|
|
69
80
|
- `scripts/check-raw-colors.mjs`
|
|
70
|
-
- `src/examples/`
|
|
71
|
-
|
|
72
|
-
`morya-app-shell.css` is written by the CLI (not copied from the template tree).
|
|
73
81
|
|
|
74
82
|
### MCP (Cursor)
|
|
75
83
|
|
|
@@ -86,29 +94,4 @@ Writes / merges:
|
|
|
86
94
|
}
|
|
87
95
|
```
|
|
88
96
|
|
|
89
|
-
Restart Cursor (or reload MCP) after
|
|
90
|
-
|
|
91
|
-
Other clients can use the same stdio command; this CLI only writes `.cursor/mcp.json`.
|
|
92
|
-
|
|
93
|
-
### Styles
|
|
94
|
-
|
|
95
|
-
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:
|
|
96
|
-
|
|
97
|
-
```ts
|
|
98
|
-
import 'morya-ui/styles.css'
|
|
99
|
-
import './styles/morya-app-shell.css'
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
The CLI does **not** call `app.use(MoryaUI)` or edit `App.vue`.
|
|
103
|
-
|
|
104
|
-
## Local development (this monorepo)
|
|
105
|
-
|
|
106
|
-
```bash
|
|
107
|
-
pnpm setup:sync-template # refresh packages/setup/template from design-kit
|
|
108
|
-
pnpm setup:build
|
|
109
|
-
node packages/setup/bin/morya-ui-setup.js --cwd /path/to/app --dry-run
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
## License
|
|
113
|
-
|
|
114
|
-
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.2.
|
|
4
|
+
"version": "0.2.9",
|
|
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,7 +6,13 @@ 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 {
|
|
9
|
+
import {
|
|
10
|
+
buildAiInclude,
|
|
11
|
+
loadSkillsCatalog,
|
|
12
|
+
parseSkillsFlag,
|
|
13
|
+
resolveSkillSelection,
|
|
14
|
+
} from './skills.mjs'
|
|
15
|
+
import { ensureStylesImport } from './styles.mjs'
|
|
10
16
|
|
|
11
17
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
12
18
|
const PKG_ROOT = resolve(__dirname, '..')
|
|
@@ -14,17 +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/ (excludes runtime src/styles — owned by app mode). */
|
|
18
|
-
const AI_TEMPLATE_INCLUDE = [
|
|
19
|
-
'DESIGN.md',
|
|
20
|
-
'.agents/skills/morya-ui-pages',
|
|
21
|
-
'.cursor/rules',
|
|
22
|
-
'docs',
|
|
23
|
-
'design-tokens',
|
|
24
|
-
'scripts/check-raw-colors.mjs',
|
|
25
|
-
'src/examples',
|
|
26
|
-
]
|
|
27
|
-
|
|
28
23
|
/** Mode → default skips (user --skip-* can only add more skips). */
|
|
29
24
|
const MODE_DEFAULTS = {
|
|
30
25
|
full: {},
|
|
@@ -41,33 +36,43 @@ const SKIP_FLAGS = {
|
|
|
41
36
|
}
|
|
42
37
|
|
|
43
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
|
+
|
|
44
45
|
console.log(`Usage: morya-ui-setup [command] [options]
|
|
45
46
|
|
|
46
47
|
Commands:
|
|
47
48
|
(default) / full Install morya-ui, AI template, MCP, styles, check:colors
|
|
48
|
-
app Install morya-ui and inject styles
|
|
49
|
-
ai Copy Agent
|
|
49
|
+
app Install morya-ui and inject styles.css
|
|
50
|
+
ai Copy Agent skills / rules / DESIGN,
|
|
50
51
|
merge Cursor MCP, add check:colors
|
|
51
52
|
|
|
52
53
|
Default command:
|
|
53
54
|
- install morya-ui
|
|
54
|
-
- copy DESIGN.md, Agent
|
|
55
|
+
- copy DESIGN.md, selected Agent skills, Cursor rules
|
|
55
56
|
- merge .cursor/mcp.json for @morya-ui/mcp
|
|
56
57
|
- inject import 'morya-ui/styles.css' into the app entry when found
|
|
57
|
-
- write src/styles/morya-app-shell.css and inject its import
|
|
58
58
|
- add check:colors script when missing
|
|
59
59
|
|
|
60
60
|
Options:
|
|
61
61
|
--cwd <dir> Target project root (default: process.cwd())
|
|
62
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)
|
|
63
65
|
--force Overwrite existing template files and MCP server entry
|
|
64
66
|
--dry-run Print actions without writing or installing
|
|
65
67
|
--skip-install Skip dependency install
|
|
66
68
|
--skip-template Skip copying AI template files
|
|
67
69
|
--skip-mcp Skip writing .cursor/mcp.json
|
|
68
|
-
--skip-styles Skip injecting styles.css
|
|
70
|
+
--skip-styles Skip injecting styles.css
|
|
69
71
|
--skip-scripts Skip adding check:colors to package.json
|
|
70
72
|
-h, --help Show this help
|
|
73
|
+
|
|
74
|
+
Skills:
|
|
75
|
+
${skillList}
|
|
71
76
|
`)
|
|
72
77
|
}
|
|
73
78
|
|
|
@@ -79,6 +84,8 @@ export function parseArgs(argv) {
|
|
|
79
84
|
mode: 'full',
|
|
80
85
|
cwd: process.cwd(),
|
|
81
86
|
pm: undefined,
|
|
87
|
+
skills: undefined,
|
|
88
|
+
yes: false,
|
|
82
89
|
force: false,
|
|
83
90
|
dryRun: false,
|
|
84
91
|
skipInstall: false,
|
|
@@ -107,6 +114,10 @@ export function parseArgs(argv) {
|
|
|
107
114
|
options.dryRun = true
|
|
108
115
|
continue
|
|
109
116
|
}
|
|
117
|
+
if (arg === '--yes' || arg === '-y') {
|
|
118
|
+
options.yes = true
|
|
119
|
+
continue
|
|
120
|
+
}
|
|
110
121
|
if (SKIP_FLAGS[arg]) {
|
|
111
122
|
options[SKIP_FLAGS[arg]] = true
|
|
112
123
|
continue
|
|
@@ -131,6 +142,16 @@ export function parseArgs(argv) {
|
|
|
131
142
|
options.pm = arg.slice('--pm='.length)
|
|
132
143
|
continue
|
|
133
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
|
+
}
|
|
134
155
|
throw new Error(`Unknown argument: ${arg}`)
|
|
135
156
|
}
|
|
136
157
|
|
|
@@ -180,6 +201,8 @@ export async function runSetup(options) {
|
|
|
180
201
|
skipStyles,
|
|
181
202
|
skipScripts,
|
|
182
203
|
pm,
|
|
204
|
+
skills: skillsFlag,
|
|
205
|
+
yes,
|
|
183
206
|
} = options
|
|
184
207
|
|
|
185
208
|
console.log(`@morya-ui/setup [${mode}] → ${cwd}${dryRun ? ' (dry-run)' : ''}`)
|
|
@@ -192,6 +215,24 @@ export async function runSetup(options) {
|
|
|
192
215
|
console.log('')
|
|
193
216
|
}
|
|
194
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
|
+
|
|
195
236
|
const install = installMoryaUi(cwd, { pm, dryRun, skipInstall })
|
|
196
237
|
|
|
197
238
|
const template = skipTemplate
|
|
@@ -199,7 +240,7 @@ export async function runSetup(options) {
|
|
|
199
240
|
: copyTemplate(TEMPLATE_ROOT, cwd, {
|
|
200
241
|
force,
|
|
201
242
|
dryRun,
|
|
202
|
-
include:
|
|
243
|
+
include: aiInclude,
|
|
203
244
|
})
|
|
204
245
|
|
|
205
246
|
const mcp = skipMcp
|
|
@@ -210,10 +251,6 @@ export async function runSetup(options) {
|
|
|
210
251
|
? { action: 'skipped', reason: 'skip-styles' }
|
|
211
252
|
: ensureStylesImport(cwd, { dryRun })
|
|
212
253
|
|
|
213
|
-
const shell = skipStyles
|
|
214
|
-
? { fileAction: 'skipped', importAction: 'skipped', reason: 'skip-styles' }
|
|
215
|
-
: ensureShellStyles(cwd, { dryRun, force })
|
|
216
|
-
|
|
217
254
|
const scripts = skipScripts
|
|
218
255
|
? { action: 'skipped-flag' }
|
|
219
256
|
: ensureCheckColorsScript(cwd, { force, dryRun })
|
|
@@ -231,6 +268,7 @@ export async function runSetup(options) {
|
|
|
231
268
|
console.log(
|
|
232
269
|
`Template: ${template.copied.length} copied, ${template.forced.length} overwritten, ${template.skipped.length} skipped`,
|
|
233
270
|
)
|
|
271
|
+
if (selectedSkills.length) console.log(` skills: ${selectedSkills.join(', ')}`)
|
|
234
272
|
if (template.skipped.length && template.skipped.length <= 8) {
|
|
235
273
|
for (const file of template.skipped) console.log(` skip ${file}`)
|
|
236
274
|
} else if (template.skipped.length > 8) {
|
|
@@ -256,17 +294,6 @@ export async function runSetup(options) {
|
|
|
256
294
|
console.log(" import 'morya-ui/styles.css'")
|
|
257
295
|
}
|
|
258
296
|
|
|
259
|
-
if (shell.reason === 'skip-styles') {
|
|
260
|
-
console.log('Shell CSS: skipped (--skip-styles or ai mode)')
|
|
261
|
-
} else {
|
|
262
|
-
console.log(
|
|
263
|
-
`Shell CSS: file ${shell.fileAction}`
|
|
264
|
-
+ (shell.path ? ` (${rel(cwd, shell.path)})` : '')
|
|
265
|
-
+ `, import ${shell.importAction}`
|
|
266
|
-
+ (shell.entry ? ` (${rel(cwd, shell.entry)})` : ''),
|
|
267
|
-
)
|
|
268
|
-
}
|
|
269
|
-
|
|
270
297
|
if (scripts.action === 'skipped-flag') {
|
|
271
298
|
console.log('Scripts: skipped (--skip-scripts or app mode)')
|
|
272
299
|
} else {
|
|
@@ -276,18 +303,18 @@ export async function runSetup(options) {
|
|
|
276
303
|
console.log('')
|
|
277
304
|
console.log('Next:')
|
|
278
305
|
if (mode === 'app') {
|
|
279
|
-
console.log(' 1. Ensure styles.css
|
|
306
|
+
console.log(' 1. Ensure the styles.css import is in your app entry.')
|
|
280
307
|
console.log(' 2. Optional AI pack: npx @morya-ui/setup ai')
|
|
281
308
|
} else if (mode === 'ai') {
|
|
282
309
|
console.log(' 1. Restart Cursor (or reload MCP) so morya-ui MCP tools appear.')
|
|
283
310
|
console.log(' 2. Have the agent read DESIGN.md before generating pages.')
|
|
284
311
|
console.log(' 3. Optional: pnpm check:colors')
|
|
285
312
|
} else {
|
|
286
|
-
console.log(' 1. Ensure styles.css
|
|
313
|
+
console.log(' 1. Ensure the styles.css import is in your app entry.')
|
|
287
314
|
console.log(' 2. Restart Cursor (or reload MCP) so morya-ui MCP tools appear.')
|
|
288
315
|
console.log(' 3. Have the agent read DESIGN.md before generating pages.')
|
|
289
316
|
console.log(' 4. Optional: pnpm check:colors')
|
|
290
317
|
}
|
|
291
318
|
|
|
292
|
-
return { mode, install, template, mcp, styles,
|
|
319
|
+
return { mode, install, template, mcp, styles, scripts, skills: selectedSkills }
|
|
293
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
|
+
}
|
package/src/styles.mjs
CHANGED
|
@@ -1,10 +1,8 @@
|
|
|
1
|
-
import { existsSync,
|
|
2
|
-
import {
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
+
import { join, resolve } from 'node:path'
|
|
3
3
|
|
|
4
4
|
const STYLE_IMPORT = "import 'morya-ui/styles.css'"
|
|
5
5
|
const STYLE_MARKER = 'morya-ui/styles.css'
|
|
6
|
-
const SHELL_MARKER = 'morya-app-shell.css'
|
|
7
|
-
const SHELL_REL = 'src/styles/morya-app-shell.css'
|
|
8
6
|
|
|
9
7
|
const CANDIDATES = [
|
|
10
8
|
'src/main.ts',
|
|
@@ -17,14 +15,6 @@ const CANDIDATES = [
|
|
|
17
15
|
'src/app.js',
|
|
18
16
|
]
|
|
19
17
|
|
|
20
|
-
const SHELL_CSS = `html,
|
|
21
|
-
body,
|
|
22
|
-
#app {
|
|
23
|
-
height: 100%;
|
|
24
|
-
margin: 0;
|
|
25
|
-
}
|
|
26
|
-
`
|
|
27
|
-
|
|
28
18
|
/**
|
|
29
19
|
* @param {string} cwd
|
|
30
20
|
* @returns {string | null} absolute path
|
|
@@ -100,16 +90,6 @@ export function injectStyleImport(source) {
|
|
|
100
90
|
return injectImportLine(source, STYLE_IMPORT, STYLE_MARKER)
|
|
101
91
|
}
|
|
102
92
|
|
|
103
|
-
/**
|
|
104
|
-
* @param {string} entryFile absolute
|
|
105
|
-
* @param {string} shellFile absolute
|
|
106
|
-
*/
|
|
107
|
-
export function shellImportLine(entryFile, shellFile) {
|
|
108
|
-
let rel = relative(dirname(entryFile), shellFile).replace(/\\/g, '/')
|
|
109
|
-
if (!rel.startsWith('.')) rel = `./${rel}`
|
|
110
|
-
return `import '${rel}'`
|
|
111
|
-
}
|
|
112
|
-
|
|
113
93
|
/**
|
|
114
94
|
* @returns {{
|
|
115
95
|
* action: 'injected' | 'skipped' | 'missing-entry',
|
|
@@ -137,68 +117,3 @@ export function ensureStylesImport(cwd, { dryRun = false } = {}) {
|
|
|
137
117
|
|
|
138
118
|
return { action: 'injected', path: entry, dryRun }
|
|
139
119
|
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Write morya-app-shell.css (height chain) and inject its import into the entry.
|
|
143
|
-
* @returns {{
|
|
144
|
-
* fileAction: 'written' | 'skipped' | 'dry-run',
|
|
145
|
-
* importAction: 'injected' | 'skipped' | 'missing-entry',
|
|
146
|
-
* path?: string,
|
|
147
|
-
* entry?: string,
|
|
148
|
-
* reason?: string,
|
|
149
|
-
* dryRun?: boolean
|
|
150
|
-
* }}
|
|
151
|
-
*/
|
|
152
|
-
export function ensureShellStyles(cwd, { dryRun = false, force = false } = {}) {
|
|
153
|
-
const shellFile = join(cwd, SHELL_REL)
|
|
154
|
-
const entry = findEntryFile(cwd)
|
|
155
|
-
|
|
156
|
-
let fileAction = 'skipped'
|
|
157
|
-
if (!existsSync(shellFile) || force) {
|
|
158
|
-
if (dryRun) {
|
|
159
|
-
fileAction = 'dry-run'
|
|
160
|
-
}
|
|
161
|
-
else {
|
|
162
|
-
mkdirSync(dirname(shellFile), { recursive: true })
|
|
163
|
-
writeFileSync(shellFile, SHELL_CSS, 'utf8')
|
|
164
|
-
fileAction = 'written'
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (!entry) {
|
|
169
|
-
return {
|
|
170
|
-
fileAction,
|
|
171
|
-
importAction: 'missing-entry',
|
|
172
|
-
path: shellFile,
|
|
173
|
-
reason: 'no-entry-found',
|
|
174
|
-
dryRun,
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const importLine = shellImportLine(entry, shellFile)
|
|
179
|
-
const original = readFileSync(entry, 'utf8')
|
|
180
|
-
const { source, changed, reason } = injectImportLine(original, importLine, SHELL_MARKER)
|
|
181
|
-
|
|
182
|
-
if (!changed) {
|
|
183
|
-
return {
|
|
184
|
-
fileAction,
|
|
185
|
-
importAction: 'skipped',
|
|
186
|
-
path: shellFile,
|
|
187
|
-
entry,
|
|
188
|
-
reason: reason || 'already-present',
|
|
189
|
-
dryRun,
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
if (!dryRun) {
|
|
194
|
-
writeFileSync(entry, source, 'utf8')
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
return {
|
|
198
|
-
fileAction,
|
|
199
|
-
importAction: 'injected',
|
|
200
|
-
path: shellFile,
|
|
201
|
-
entry,
|
|
202
|
-
dryRun,
|
|
203
|
-
}
|
|
204
|
-
}
|