@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.
Files changed (41) hide show
  1. package/README.md +23 -40
  2. package/catalog/skills.json +28 -0
  3. package/package.json +2 -1
  4. package/src/cli.mjs +63 -36
  5. package/src/skills.mjs +154 -0
  6. package/src/styles.mjs +2 -87
  7. package/template/.agents/skills/fixing-accessibility/SKILL.md +138 -0
  8. package/template/.agents/skills/frontend-design/LICENSE.txt +177 -0
  9. package/template/.agents/skills/frontend-design/SKILL.md +57 -0
  10. package/template/.agents/skills/morya-ui-pages/SKILL.md +17 -16
  11. package/template/.agents/skills/morya-ui-pages/evals/evals.json +18 -12
  12. package/template/.agents/skills/morya-ui-pages/references/component-index.md +31 -9
  13. package/template/.agents/skills/morya-ui-pages/references/design-system.md +5 -3
  14. package/template/.agents/skills/morya-ui-pages/references/feedback.md +2 -2
  15. package/template/.agents/skills/morya-ui-pages/references/optional-companions.md +17 -12
  16. package/template/.agents/skills/morya-ui-pages/references/page-layouts.md +26 -16
  17. package/template/.agents/skills/morya-ui-pages/references/review-checklist.md +14 -2
  18. package/template/.agents/skills/morya-ui-pages/references/surfaces.md +4 -4
  19. package/template/.agents/skills/morya-ui-pages/references/visual-craft.md +121 -6
  20. package/template/.cursor/rules/component-usage.mdc +4 -4
  21. package/template/.cursor/rules/design-system.mdc +5 -5
  22. package/template/.cursor/rules/page-layout.mdc +7 -9
  23. package/template/DESIGN.md +29 -106
  24. package/LICENSE +0 -21
  25. package/template/design-tokens/tokens.css +0 -55
  26. package/template/design-tokens/tokens.json +0 -77
  27. package/template/docs/components.md +0 -145
  28. package/template/docs/feedback-message-vs-toast.md +0 -103
  29. package/template/docs/golden-pages/dashboard-page.vue +0 -103
  30. package/template/docs/golden-pages/empty-state.vue +0 -66
  31. package/template/docs/golden-pages/form-page.vue +0 -107
  32. package/template/docs/golden-pages/landing-page.vue +0 -328
  33. package/template/docs/golden-pages/list-page.vue +0 -138
  34. package/template/docs/golden-pages/login-page.vue +0 -191
  35. package/template/src/examples/DashboardPageExample.vue +0 -103
  36. package/template/src/examples/EmptyStateExample.vue +0 -66
  37. package/template/src/examples/FormPageExample.vue +0 -107
  38. package/template/src/examples/LandingPageExample.vue +0 -328
  39. package/template/src/examples/ListPageExample.vue +0 -138
  40. package/template/src/examples/LoginPageExample.vue +0 -191
  41. 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 skill / Cursor rules / design docs from `design-kit`, merges Cursor MCP for [`@morya-ui/mcp`](https://www.npmjs.com/package/@morya-ui/mcp), injects `import 'morya-ui/styles.css'`, and writes `src/styles/morya-app-shell.css` with its entry import.
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 / tokens |
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` / write app-shell 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
- Example: MCP only:
56
+ ### Skills
48
57
 
49
- ```bash
50
- npx @morya-ui/setup ai --skip-template --skip-scripts
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/morya-ui-pages/`
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 setup.
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.7",
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 { ensureShellStyles, ensureStylesImport } from './styles.mjs'
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 / app-shell CSS
49
- ai Copy Agent skill / rules / DESIGN / docs / tokens / examples,
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 skill, Cursor rules, docs, tokens, examples
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 / app-shell 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: mode === 'ai' || mode === 'full' ? AI_TEMPLATE_INCLUDE : undefined,
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 + morya-app-shell.css imports are in your app entry.')
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 + morya-app-shell.css imports are in your app entry.')
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, shell, scripts }
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, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
2
- import { dirname, join, relative, resolve } from 'node:path'
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
- }