@morya-ui/setup 0.3.3 → 0.3.4
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 +103 -103
- package/bin/morya-ui-setup.js +14 -14
- package/catalog/skills.json +46 -46
- package/package.json +5 -3
- package/src/cli.mjs +335 -335
- package/src/copy-template.mjs +78 -78
- package/src/fs-utils.mjs +24 -24
- package/src/install.mjs +63 -63
- package/src/mcp.mjs +50 -50
- package/src/package-json.mjs +30 -30
- package/src/skills.mjs +223 -223
- package/src/styles.mjs +119 -119
- package/template/.agents/skills/morya-ui-pages/SKILL.md +184 -164
- package/template/.agents/skills/morya-ui-pages/evals/evals.json +89 -89
- package/template/.agents/skills/morya-ui-pages/references/component-index.md +99 -99
- package/template/.agents/skills/morya-ui-pages/references/decision-recipes.md +32 -0
- package/template/.agents/skills/morya-ui-pages/references/design-system.md +101 -100
- package/template/.agents/skills/morya-ui-pages/references/feedback.md +68 -68
- package/template/.agents/skills/morya-ui-pages/references/optional-companions.md +107 -34
- package/template/.agents/skills/morya-ui-pages/references/page-layouts.md +141 -119
- package/template/.agents/skills/morya-ui-pages/references/review-checklist.md +64 -61
- package/template/.agents/skills/morya-ui-pages/references/style-presets.md +73 -0
- package/template/.agents/skills/morya-ui-pages/references/surfaces.md +91 -89
- package/template/.agents/skills/morya-ui-pages/references/visual-craft.md +221 -196
- package/template/.cursor/rules/coding-style.mdc +41 -41
- package/template/.cursor/rules/component-usage.mdc +41 -41
- package/template/.cursor/rules/design-system.mdc +17 -17
- package/template/.cursor/rules/page-layout.mdc +97 -78
- package/template/DESIGN.md +59 -59
- package/template/scripts/check-raw-colors.mjs +74 -74
- package/LICENSE +0 -21
package/src/cli.mjs
CHANGED
|
@@ -1,335 +1,335 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs'
|
|
2
|
-
import { dirname, join, resolve } from 'node:path'
|
|
3
|
-
import { fileURLToPath } from 'node:url'
|
|
4
|
-
import { copyTemplate } from './copy-template.mjs'
|
|
5
|
-
import { readJson } from './fs-utils.mjs'
|
|
6
|
-
import { installMoryaUi } from './install.mjs'
|
|
7
|
-
import { mergeMcpConfig } from './mcp.mjs'
|
|
8
|
-
import { ensureCheckColorsScript } from './package-json.mjs'
|
|
9
|
-
import {
|
|
10
|
-
buildAiInclude,
|
|
11
|
-
installSkillsCli,
|
|
12
|
-
loadSkillsCatalog,
|
|
13
|
-
parseSkillsFlag,
|
|
14
|
-
resolveSkillSelection,
|
|
15
|
-
} from './skills.mjs'
|
|
16
|
-
import { ensureStylesImport } from './styles.mjs'
|
|
17
|
-
|
|
18
|
-
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
19
|
-
const PKG_ROOT = resolve(__dirname, '..')
|
|
20
|
-
const TEMPLATE_ROOT = join(PKG_ROOT, 'template')
|
|
21
|
-
|
|
22
|
-
const MODES = new Set(['app', 'ai', 'full'])
|
|
23
|
-
|
|
24
|
-
/** Mode → default skips (user --skip-* can only add more skips). */
|
|
25
|
-
const MODE_DEFAULTS = {
|
|
26
|
-
full: {},
|
|
27
|
-
app: { skipTemplate: true, skipMcp: true, skipScripts: true },
|
|
28
|
-
ai: { skipInstall: true, skipStyles: true },
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const SKIP_FLAGS = {
|
|
32
|
-
'--skip-install': 'skipInstall',
|
|
33
|
-
'--skip-template': 'skipTemplate',
|
|
34
|
-
'--skip-mcp': 'skipMcp',
|
|
35
|
-
'--skip-styles': 'skipStyles',
|
|
36
|
-
'--skip-scripts': 'skipScripts',
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export function printHelp() {
|
|
40
|
-
const { skills } = loadSkillsCatalog()
|
|
41
|
-
const skillList = skills.map((s) => {
|
|
42
|
-
const tag = s.required ? 'required' : s.default ? 'default' : 'optional'
|
|
43
|
-
const via = s.install === 'skills-cli' ? ' [skills-cli latest]' : ''
|
|
44
|
-
return ` ${s.id.padEnd(24)} (${tag})${via} ${s.description}`
|
|
45
|
-
}).join('\n')
|
|
46
|
-
|
|
47
|
-
console.log(`Usage: morya-ui-setup [command] [options]
|
|
48
|
-
|
|
49
|
-
Commands:
|
|
50
|
-
(default) / full Install morya-ui, AI template, MCP, styles, check:colors
|
|
51
|
-
app Install morya-ui and inject styles.css
|
|
52
|
-
ai Copy Agent skills / rules / DESIGN,
|
|
53
|
-
merge Cursor MCP, add check:colors
|
|
54
|
-
|
|
55
|
-
Default command:
|
|
56
|
-
- install morya-ui
|
|
57
|
-
- copy DESIGN.md, selected Agent skills, Cursor rules
|
|
58
|
-
- merge .cursor/mcp.json for @morya-ui/mcp
|
|
59
|
-
- inject import 'morya-ui/styles.css' into the app entry when found
|
|
60
|
-
- add check:colors script when missing
|
|
61
|
-
|
|
62
|
-
Options:
|
|
63
|
-
--cwd <dir> Target project root (default: process.cwd())
|
|
64
|
-
--pm <name> Package manager: pnpm | yarn | npm (auto-detect by lockfile)
|
|
65
|
-
--skills <list> Comma-separated skill ids, or "all" (skips interactive prompt)
|
|
66
|
-
--yes Use default skills without prompting (CI / non-interactive)
|
|
67
|
-
--force Overwrite existing template files and MCP server entry
|
|
68
|
-
--dry-run Print actions without writing or installing
|
|
69
|
-
--skip-install Skip dependency install
|
|
70
|
-
--skip-template Skip copying AI template files
|
|
71
|
-
--skip-mcp Skip writing .cursor/mcp.json
|
|
72
|
-
--skip-styles Skip injecting styles.css
|
|
73
|
-
--skip-scripts Skip adding check:colors to package.json
|
|
74
|
-
-h, --help Show this help
|
|
75
|
-
|
|
76
|
-
Skills:
|
|
77
|
-
${skillList}
|
|
78
|
-
`)
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* @param {string[]} argv
|
|
83
|
-
*/
|
|
84
|
-
export function parseArgs(argv) {
|
|
85
|
-
const options = {
|
|
86
|
-
mode: 'full',
|
|
87
|
-
cwd: process.cwd(),
|
|
88
|
-
pm: undefined,
|
|
89
|
-
skills: undefined,
|
|
90
|
-
yes: false,
|
|
91
|
-
force: false,
|
|
92
|
-
dryRun: false,
|
|
93
|
-
skipInstall: false,
|
|
94
|
-
skipTemplate: false,
|
|
95
|
-
skipMcp: false,
|
|
96
|
-
skipStyles: false,
|
|
97
|
-
skipScripts: false,
|
|
98
|
-
help: false,
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
for (let i = 0; i < argv.length; i++) {
|
|
102
|
-
const arg = argv[i]
|
|
103
|
-
if (arg === '-h' || arg === '--help') {
|
|
104
|
-
options.help = true
|
|
105
|
-
continue
|
|
106
|
-
}
|
|
107
|
-
if (MODES.has(arg)) {
|
|
108
|
-
options.mode = arg
|
|
109
|
-
continue
|
|
110
|
-
}
|
|
111
|
-
if (arg === '--force') {
|
|
112
|
-
options.force = true
|
|
113
|
-
continue
|
|
114
|
-
}
|
|
115
|
-
if (arg === '--dry-run') {
|
|
116
|
-
options.dryRun = true
|
|
117
|
-
continue
|
|
118
|
-
}
|
|
119
|
-
if (arg === '--yes' || arg === '-y') {
|
|
120
|
-
options.yes = true
|
|
121
|
-
continue
|
|
122
|
-
}
|
|
123
|
-
if (SKIP_FLAGS[arg]) {
|
|
124
|
-
options[SKIP_FLAGS[arg]] = true
|
|
125
|
-
continue
|
|
126
|
-
}
|
|
127
|
-
if (arg === '--cwd') {
|
|
128
|
-
const value = argv[++i]
|
|
129
|
-
if (!value) throw new Error('--cwd requires a path')
|
|
130
|
-
options.cwd = resolve(value)
|
|
131
|
-
continue
|
|
132
|
-
}
|
|
133
|
-
if (arg.startsWith('--cwd=')) {
|
|
134
|
-
options.cwd = resolve(arg.slice('--cwd='.length))
|
|
135
|
-
continue
|
|
136
|
-
}
|
|
137
|
-
if (arg === '--pm') {
|
|
138
|
-
const value = argv[++i]
|
|
139
|
-
if (!value) throw new Error('--pm requires pnpm, yarn, or npm')
|
|
140
|
-
options.pm = value
|
|
141
|
-
continue
|
|
142
|
-
}
|
|
143
|
-
if (arg.startsWith('--pm=')) {
|
|
144
|
-
options.pm = arg.slice('--pm='.length)
|
|
145
|
-
continue
|
|
146
|
-
}
|
|
147
|
-
if (arg === '--skills') {
|
|
148
|
-
const value = argv[++i]
|
|
149
|
-
if (!value) throw new Error('--skills requires a comma-separated list or "all"')
|
|
150
|
-
options.skills = value
|
|
151
|
-
continue
|
|
152
|
-
}
|
|
153
|
-
if (arg.startsWith('--skills=')) {
|
|
154
|
-
options.skills = arg.slice('--skills='.length)
|
|
155
|
-
continue
|
|
156
|
-
}
|
|
157
|
-
throw new Error(`Unknown argument: ${arg}`)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const defaults = MODE_DEFAULTS[options.mode] || {}
|
|
161
|
-
for (const [key, value] of Object.entries(defaults)) {
|
|
162
|
-
if (value) options[key] = true
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
return options
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
function rel(cwd, path) {
|
|
169
|
-
if (!path) return ''
|
|
170
|
-
return path.startsWith(cwd) ? path.slice(cwd.length).replace(/^[\\/]/, '') || path : path
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
/**
|
|
174
|
-
* @param {string} cwd
|
|
175
|
-
*/
|
|
176
|
-
function hasMoryaUiDependency(cwd) {
|
|
177
|
-
const path = join(cwd, 'package.json')
|
|
178
|
-
if (!existsSync(path)) return false
|
|
179
|
-
try {
|
|
180
|
-
const pkg = readJson(path)
|
|
181
|
-
return Boolean(
|
|
182
|
-
pkg.dependencies?.['morya-ui']
|
|
183
|
-
|| pkg.devDependencies?.['morya-ui']
|
|
184
|
-
|| pkg.peerDependencies?.['morya-ui'],
|
|
185
|
-
)
|
|
186
|
-
} catch {
|
|
187
|
-
return false
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* @param {ReturnType<typeof parseArgs>} options
|
|
193
|
-
*/
|
|
194
|
-
export async function runSetup(options) {
|
|
195
|
-
const {
|
|
196
|
-
mode,
|
|
197
|
-
cwd,
|
|
198
|
-
force,
|
|
199
|
-
dryRun,
|
|
200
|
-
skipInstall,
|
|
201
|
-
skipTemplate,
|
|
202
|
-
skipMcp,
|
|
203
|
-
skipStyles,
|
|
204
|
-
skipScripts,
|
|
205
|
-
pm,
|
|
206
|
-
skills: skillsFlag,
|
|
207
|
-
yes,
|
|
208
|
-
} = options
|
|
209
|
-
|
|
210
|
-
console.log(`@morya-ui/setup [${mode}] → ${cwd}${dryRun ? ' (dry-run)' : ''}`)
|
|
211
|
-
console.log('')
|
|
212
|
-
|
|
213
|
-
if (mode === 'ai' && !hasMoryaUiDependency(cwd)) {
|
|
214
|
-
console.log(
|
|
215
|
-
'Warning: morya-ui is not listed in package.json. Run `npx @morya-ui/setup app` first (or pnpm add morya-ui).',
|
|
216
|
-
)
|
|
217
|
-
console.log('')
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const catalog = loadSkillsCatalog()
|
|
221
|
-
let selectedSkills = []
|
|
222
|
-
let aiInclude
|
|
223
|
-
|
|
224
|
-
const needsTemplate = !skipTemplate && (mode === 'ai' || mode === 'full')
|
|
225
|
-
if (needsTemplate) {
|
|
226
|
-
if (skillsFlag != null) {
|
|
227
|
-
selectedSkills = parseSkillsFlag(skillsFlag, catalog.skills)
|
|
228
|
-
} else {
|
|
229
|
-
selectedSkills = await resolveSkillSelection(catalog.skills, {
|
|
230
|
-
skipPrompt: yes || dryRun,
|
|
231
|
-
})
|
|
232
|
-
}
|
|
233
|
-
aiInclude = buildAiInclude(selectedSkills, catalog.skills)
|
|
234
|
-
console.log(`Skills: ${selectedSkills.join(', ')}`)
|
|
235
|
-
console.log('')
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
const install = installMoryaUi(cwd, { pm, dryRun, skipInstall })
|
|
239
|
-
|
|
240
|
-
const template = skipTemplate
|
|
241
|
-
? { copied: [], skipped: [], forced: [], skippedStep: true }
|
|
242
|
-
: copyTemplate(TEMPLATE_ROOT, cwd, {
|
|
243
|
-
force,
|
|
244
|
-
dryRun,
|
|
245
|
-
include: aiInclude,
|
|
246
|
-
})
|
|
247
|
-
|
|
248
|
-
const remoteSkills = skipTemplate || !selectedSkills.length
|
|
249
|
-
? { installed: [], commands: [], skipped: true }
|
|
250
|
-
: installSkillsCli(cwd, selectedSkills, catalog.skills, { dryRun })
|
|
251
|
-
|
|
252
|
-
const mcp = skipMcp
|
|
253
|
-
? { path: join(cwd, '.cursor', 'mcp.json'), action: 'skipped-flag' }
|
|
254
|
-
: mergeMcpConfig(cwd, { force, dryRun })
|
|
255
|
-
|
|
256
|
-
const styles = skipStyles
|
|
257
|
-
? { action: 'skipped', reason: 'skip-styles' }
|
|
258
|
-
: ensureStylesImport(cwd, { dryRun })
|
|
259
|
-
|
|
260
|
-
const scripts = skipScripts
|
|
261
|
-
? { action: 'skipped-flag' }
|
|
262
|
-
: ensureCheckColorsScript(cwd, { force, dryRun })
|
|
263
|
-
|
|
264
|
-
console.log('--- Summary ---')
|
|
265
|
-
if (install.skipped) {
|
|
266
|
-
console.log(`Install: skipped (${install.reason}) — would run: ${install.command}`)
|
|
267
|
-
} else {
|
|
268
|
-
console.log(`Install: ok (${install.pm}) — ${install.command}`)
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
if (template.skippedStep) {
|
|
272
|
-
console.log('Template: skipped (--skip-template or app mode)')
|
|
273
|
-
} else {
|
|
274
|
-
console.log(
|
|
275
|
-
`Template: ${template.copied.length} copied, ${template.forced.length} overwritten, ${template.skipped.length} skipped`,
|
|
276
|
-
)
|
|
277
|
-
if (selectedSkills.length) console.log(` skills: ${selectedSkills.join(', ')}`)
|
|
278
|
-
if (template.skipped.length && template.skipped.length <= 8) {
|
|
279
|
-
for (const file of template.skipped) console.log(` skip ${file}`)
|
|
280
|
-
} else if (template.skipped.length > 8) {
|
|
281
|
-
for (const file of template.skipped.slice(0, 5)) console.log(` skip ${file}`)
|
|
282
|
-
console.log(` … and ${template.skipped.length - 5} more (use --force to overwrite)`)
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
if (remoteSkills.skipped) {
|
|
287
|
-
// no companions selected or template step skipped
|
|
288
|
-
} else if (remoteSkills.dryRun) {
|
|
289
|
-
console.log(`Skills CLI: dry-run — would install ${remoteSkills.installed.join(', ')}`)
|
|
290
|
-
for (const command of remoteSkills.commands) console.log(` $ ${command}`)
|
|
291
|
-
} else {
|
|
292
|
-
console.log(`Skills CLI: installed ${remoteSkills.installed.join(', ')} (latest)`)
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
if (mcp.action === 'skipped-flag') {
|
|
296
|
-
console.log('MCP: skipped (--skip-mcp or app mode)')
|
|
297
|
-
} else {
|
|
298
|
-
console.log(`MCP: ${mcp.action} (${rel(cwd, mcp.path) || '.cursor/mcp.json'})`)
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
if (styles.reason === 'skip-styles') {
|
|
302
|
-
console.log('Styles: skipped (--skip-styles or ai mode)')
|
|
303
|
-
} else if (styles.action === 'injected') {
|
|
304
|
-
console.log(`Styles: injected into ${rel(cwd, styles.path)}`)
|
|
305
|
-
} else if (styles.action === 'skipped') {
|
|
306
|
-
console.log(`Styles: skipped (${styles.reason}) in ${rel(cwd, styles.path)}`)
|
|
307
|
-
} else {
|
|
308
|
-
console.log('Styles: no entry file found — add manually:')
|
|
309
|
-
console.log(" import 'morya-ui/styles.css'")
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
if (scripts.action === 'skipped-flag') {
|
|
313
|
-
console.log('Scripts: skipped (--skip-scripts or app mode)')
|
|
314
|
-
} else {
|
|
315
|
-
console.log(`Scripts: check:colors ${scripts.action}`)
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
console.log('')
|
|
319
|
-
console.log('Next:')
|
|
320
|
-
if (mode === 'app') {
|
|
321
|
-
console.log(' 1. Ensure the styles.css import is in your app entry.')
|
|
322
|
-
console.log(' 2. Optional AI pack: npx @morya-ui/setup ai')
|
|
323
|
-
} else if (mode === 'ai') {
|
|
324
|
-
console.log(' 1. Restart Cursor (or reload MCP) so morya-ui MCP tools appear.')
|
|
325
|
-
console.log(' 2. Have the agent read DESIGN.md before generating pages.')
|
|
326
|
-
console.log(' 3. Optional: pnpm check:colors')
|
|
327
|
-
} else {
|
|
328
|
-
console.log(' 1. Ensure the styles.css import is in your app entry.')
|
|
329
|
-
console.log(' 2. Restart Cursor (or reload MCP) so morya-ui MCP tools appear.')
|
|
330
|
-
console.log(' 3. Have the agent read DESIGN.md before generating pages.')
|
|
331
|
-
console.log(' 4. Optional: pnpm check:colors')
|
|
332
|
-
}
|
|
333
|
-
|
|
334
|
-
return { mode, install, template, remoteSkills, mcp, styles, scripts, skills: selectedSkills }
|
|
335
|
-
}
|
|
1
|
+
import { existsSync } from 'node:fs'
|
|
2
|
+
import { dirname, join, resolve } from 'node:path'
|
|
3
|
+
import { fileURLToPath } from 'node:url'
|
|
4
|
+
import { copyTemplate } from './copy-template.mjs'
|
|
5
|
+
import { readJson } from './fs-utils.mjs'
|
|
6
|
+
import { installMoryaUi } from './install.mjs'
|
|
7
|
+
import { mergeMcpConfig } from './mcp.mjs'
|
|
8
|
+
import { ensureCheckColorsScript } from './package-json.mjs'
|
|
9
|
+
import {
|
|
10
|
+
buildAiInclude,
|
|
11
|
+
installSkillsCli,
|
|
12
|
+
loadSkillsCatalog,
|
|
13
|
+
parseSkillsFlag,
|
|
14
|
+
resolveSkillSelection,
|
|
15
|
+
} from './skills.mjs'
|
|
16
|
+
import { ensureStylesImport } from './styles.mjs'
|
|
17
|
+
|
|
18
|
+
const __dirname = dirname(fileURLToPath(import.meta.url))
|
|
19
|
+
const PKG_ROOT = resolve(__dirname, '..')
|
|
20
|
+
const TEMPLATE_ROOT = join(PKG_ROOT, 'template')
|
|
21
|
+
|
|
22
|
+
const MODES = new Set(['app', 'ai', 'full'])
|
|
23
|
+
|
|
24
|
+
/** Mode → default skips (user --skip-* can only add more skips). */
|
|
25
|
+
const MODE_DEFAULTS = {
|
|
26
|
+
full: {},
|
|
27
|
+
app: { skipTemplate: true, skipMcp: true, skipScripts: true },
|
|
28
|
+
ai: { skipInstall: true, skipStyles: true },
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const SKIP_FLAGS = {
|
|
32
|
+
'--skip-install': 'skipInstall',
|
|
33
|
+
'--skip-template': 'skipTemplate',
|
|
34
|
+
'--skip-mcp': 'skipMcp',
|
|
35
|
+
'--skip-styles': 'skipStyles',
|
|
36
|
+
'--skip-scripts': 'skipScripts',
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function printHelp() {
|
|
40
|
+
const { skills } = loadSkillsCatalog()
|
|
41
|
+
const skillList = skills.map((s) => {
|
|
42
|
+
const tag = s.required ? 'required' : s.default ? 'default' : 'optional'
|
|
43
|
+
const via = s.install === 'skills-cli' ? ' [skills-cli latest]' : ''
|
|
44
|
+
return ` ${s.id.padEnd(24)} (${tag})${via} ${s.description}`
|
|
45
|
+
}).join('\n')
|
|
46
|
+
|
|
47
|
+
console.log(`Usage: morya-ui-setup [command] [options]
|
|
48
|
+
|
|
49
|
+
Commands:
|
|
50
|
+
(default) / full Install morya-ui, AI template, MCP, styles, check:colors
|
|
51
|
+
app Install morya-ui and inject styles.css
|
|
52
|
+
ai Copy Agent skills / rules / DESIGN,
|
|
53
|
+
merge Cursor MCP, add check:colors
|
|
54
|
+
|
|
55
|
+
Default command:
|
|
56
|
+
- install morya-ui
|
|
57
|
+
- copy DESIGN.md, selected Agent skills, Cursor rules
|
|
58
|
+
- merge .cursor/mcp.json for @morya-ui/mcp
|
|
59
|
+
- inject import 'morya-ui/styles.css' into the app entry when found
|
|
60
|
+
- add check:colors script when missing
|
|
61
|
+
|
|
62
|
+
Options:
|
|
63
|
+
--cwd <dir> Target project root (default: process.cwd())
|
|
64
|
+
--pm <name> Package manager: pnpm | yarn | npm (auto-detect by lockfile)
|
|
65
|
+
--skills <list> Comma-separated skill ids, or "all" (skips interactive prompt)
|
|
66
|
+
--yes Use default skills without prompting (CI / non-interactive)
|
|
67
|
+
--force Overwrite existing template files and MCP server entry
|
|
68
|
+
--dry-run Print actions without writing or installing
|
|
69
|
+
--skip-install Skip dependency install
|
|
70
|
+
--skip-template Skip copying AI template files
|
|
71
|
+
--skip-mcp Skip writing .cursor/mcp.json
|
|
72
|
+
--skip-styles Skip injecting styles.css
|
|
73
|
+
--skip-scripts Skip adding check:colors to package.json
|
|
74
|
+
-h, --help Show this help
|
|
75
|
+
|
|
76
|
+
Skills:
|
|
77
|
+
${skillList}
|
|
78
|
+
`)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {string[]} argv
|
|
83
|
+
*/
|
|
84
|
+
export function parseArgs(argv) {
|
|
85
|
+
const options = {
|
|
86
|
+
mode: 'full',
|
|
87
|
+
cwd: process.cwd(),
|
|
88
|
+
pm: undefined,
|
|
89
|
+
skills: undefined,
|
|
90
|
+
yes: false,
|
|
91
|
+
force: false,
|
|
92
|
+
dryRun: false,
|
|
93
|
+
skipInstall: false,
|
|
94
|
+
skipTemplate: false,
|
|
95
|
+
skipMcp: false,
|
|
96
|
+
skipStyles: false,
|
|
97
|
+
skipScripts: false,
|
|
98
|
+
help: false,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
for (let i = 0; i < argv.length; i++) {
|
|
102
|
+
const arg = argv[i]
|
|
103
|
+
if (arg === '-h' || arg === '--help') {
|
|
104
|
+
options.help = true
|
|
105
|
+
continue
|
|
106
|
+
}
|
|
107
|
+
if (MODES.has(arg)) {
|
|
108
|
+
options.mode = arg
|
|
109
|
+
continue
|
|
110
|
+
}
|
|
111
|
+
if (arg === '--force') {
|
|
112
|
+
options.force = true
|
|
113
|
+
continue
|
|
114
|
+
}
|
|
115
|
+
if (arg === '--dry-run') {
|
|
116
|
+
options.dryRun = true
|
|
117
|
+
continue
|
|
118
|
+
}
|
|
119
|
+
if (arg === '--yes' || arg === '-y') {
|
|
120
|
+
options.yes = true
|
|
121
|
+
continue
|
|
122
|
+
}
|
|
123
|
+
if (SKIP_FLAGS[arg]) {
|
|
124
|
+
options[SKIP_FLAGS[arg]] = true
|
|
125
|
+
continue
|
|
126
|
+
}
|
|
127
|
+
if (arg === '--cwd') {
|
|
128
|
+
const value = argv[++i]
|
|
129
|
+
if (!value) throw new Error('--cwd requires a path')
|
|
130
|
+
options.cwd = resolve(value)
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
if (arg.startsWith('--cwd=')) {
|
|
134
|
+
options.cwd = resolve(arg.slice('--cwd='.length))
|
|
135
|
+
continue
|
|
136
|
+
}
|
|
137
|
+
if (arg === '--pm') {
|
|
138
|
+
const value = argv[++i]
|
|
139
|
+
if (!value) throw new Error('--pm requires pnpm, yarn, or npm')
|
|
140
|
+
options.pm = value
|
|
141
|
+
continue
|
|
142
|
+
}
|
|
143
|
+
if (arg.startsWith('--pm=')) {
|
|
144
|
+
options.pm = arg.slice('--pm='.length)
|
|
145
|
+
continue
|
|
146
|
+
}
|
|
147
|
+
if (arg === '--skills') {
|
|
148
|
+
const value = argv[++i]
|
|
149
|
+
if (!value) throw new Error('--skills requires a comma-separated list or "all"')
|
|
150
|
+
options.skills = value
|
|
151
|
+
continue
|
|
152
|
+
}
|
|
153
|
+
if (arg.startsWith('--skills=')) {
|
|
154
|
+
options.skills = arg.slice('--skills='.length)
|
|
155
|
+
continue
|
|
156
|
+
}
|
|
157
|
+
throw new Error(`Unknown argument: ${arg}`)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const defaults = MODE_DEFAULTS[options.mode] || {}
|
|
161
|
+
for (const [key, value] of Object.entries(defaults)) {
|
|
162
|
+
if (value) options[key] = true
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
return options
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function rel(cwd, path) {
|
|
169
|
+
if (!path) return ''
|
|
170
|
+
return path.startsWith(cwd) ? path.slice(cwd.length).replace(/^[\\/]/, '') || path : path
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* @param {string} cwd
|
|
175
|
+
*/
|
|
176
|
+
function hasMoryaUiDependency(cwd) {
|
|
177
|
+
const path = join(cwd, 'package.json')
|
|
178
|
+
if (!existsSync(path)) return false
|
|
179
|
+
try {
|
|
180
|
+
const pkg = readJson(path)
|
|
181
|
+
return Boolean(
|
|
182
|
+
pkg.dependencies?.['morya-ui']
|
|
183
|
+
|| pkg.devDependencies?.['morya-ui']
|
|
184
|
+
|| pkg.peerDependencies?.['morya-ui'],
|
|
185
|
+
)
|
|
186
|
+
} catch {
|
|
187
|
+
return false
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* @param {ReturnType<typeof parseArgs>} options
|
|
193
|
+
*/
|
|
194
|
+
export async function runSetup(options) {
|
|
195
|
+
const {
|
|
196
|
+
mode,
|
|
197
|
+
cwd,
|
|
198
|
+
force,
|
|
199
|
+
dryRun,
|
|
200
|
+
skipInstall,
|
|
201
|
+
skipTemplate,
|
|
202
|
+
skipMcp,
|
|
203
|
+
skipStyles,
|
|
204
|
+
skipScripts,
|
|
205
|
+
pm,
|
|
206
|
+
skills: skillsFlag,
|
|
207
|
+
yes,
|
|
208
|
+
} = options
|
|
209
|
+
|
|
210
|
+
console.log(`@morya-ui/setup [${mode}] → ${cwd}${dryRun ? ' (dry-run)' : ''}`)
|
|
211
|
+
console.log('')
|
|
212
|
+
|
|
213
|
+
if (mode === 'ai' && !hasMoryaUiDependency(cwd)) {
|
|
214
|
+
console.log(
|
|
215
|
+
'Warning: morya-ui is not listed in package.json. Run `npx @morya-ui/setup app` first (or pnpm add morya-ui).',
|
|
216
|
+
)
|
|
217
|
+
console.log('')
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const catalog = loadSkillsCatalog()
|
|
221
|
+
let selectedSkills = []
|
|
222
|
+
let aiInclude
|
|
223
|
+
|
|
224
|
+
const needsTemplate = !skipTemplate && (mode === 'ai' || mode === 'full')
|
|
225
|
+
if (needsTemplate) {
|
|
226
|
+
if (skillsFlag != null) {
|
|
227
|
+
selectedSkills = parseSkillsFlag(skillsFlag, catalog.skills)
|
|
228
|
+
} else {
|
|
229
|
+
selectedSkills = await resolveSkillSelection(catalog.skills, {
|
|
230
|
+
skipPrompt: yes || dryRun,
|
|
231
|
+
})
|
|
232
|
+
}
|
|
233
|
+
aiInclude = buildAiInclude(selectedSkills, catalog.skills)
|
|
234
|
+
console.log(`Skills: ${selectedSkills.join(', ')}`)
|
|
235
|
+
console.log('')
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const install = installMoryaUi(cwd, { pm, dryRun, skipInstall })
|
|
239
|
+
|
|
240
|
+
const template = skipTemplate
|
|
241
|
+
? { copied: [], skipped: [], forced: [], skippedStep: true }
|
|
242
|
+
: copyTemplate(TEMPLATE_ROOT, cwd, {
|
|
243
|
+
force,
|
|
244
|
+
dryRun,
|
|
245
|
+
include: aiInclude,
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
const remoteSkills = skipTemplate || !selectedSkills.length
|
|
249
|
+
? { installed: [], commands: [], skipped: true }
|
|
250
|
+
: installSkillsCli(cwd, selectedSkills, catalog.skills, { dryRun })
|
|
251
|
+
|
|
252
|
+
const mcp = skipMcp
|
|
253
|
+
? { path: join(cwd, '.cursor', 'mcp.json'), action: 'skipped-flag' }
|
|
254
|
+
: mergeMcpConfig(cwd, { force, dryRun })
|
|
255
|
+
|
|
256
|
+
const styles = skipStyles
|
|
257
|
+
? { action: 'skipped', reason: 'skip-styles' }
|
|
258
|
+
: ensureStylesImport(cwd, { dryRun })
|
|
259
|
+
|
|
260
|
+
const scripts = skipScripts
|
|
261
|
+
? { action: 'skipped-flag' }
|
|
262
|
+
: ensureCheckColorsScript(cwd, { force, dryRun })
|
|
263
|
+
|
|
264
|
+
console.log('--- Summary ---')
|
|
265
|
+
if (install.skipped) {
|
|
266
|
+
console.log(`Install: skipped (${install.reason}) — would run: ${install.command}`)
|
|
267
|
+
} else {
|
|
268
|
+
console.log(`Install: ok (${install.pm}) — ${install.command}`)
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (template.skippedStep) {
|
|
272
|
+
console.log('Template: skipped (--skip-template or app mode)')
|
|
273
|
+
} else {
|
|
274
|
+
console.log(
|
|
275
|
+
`Template: ${template.copied.length} copied, ${template.forced.length} overwritten, ${template.skipped.length} skipped`,
|
|
276
|
+
)
|
|
277
|
+
if (selectedSkills.length) console.log(` skills: ${selectedSkills.join(', ')}`)
|
|
278
|
+
if (template.skipped.length && template.skipped.length <= 8) {
|
|
279
|
+
for (const file of template.skipped) console.log(` skip ${file}`)
|
|
280
|
+
} else if (template.skipped.length > 8) {
|
|
281
|
+
for (const file of template.skipped.slice(0, 5)) console.log(` skip ${file}`)
|
|
282
|
+
console.log(` … and ${template.skipped.length - 5} more (use --force to overwrite)`)
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (remoteSkills.skipped) {
|
|
287
|
+
// no companions selected or template step skipped
|
|
288
|
+
} else if (remoteSkills.dryRun) {
|
|
289
|
+
console.log(`Skills CLI: dry-run — would install ${remoteSkills.installed.join(', ')}`)
|
|
290
|
+
for (const command of remoteSkills.commands) console.log(` $ ${command}`)
|
|
291
|
+
} else {
|
|
292
|
+
console.log(`Skills CLI: installed ${remoteSkills.installed.join(', ')} (latest)`)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (mcp.action === 'skipped-flag') {
|
|
296
|
+
console.log('MCP: skipped (--skip-mcp or app mode)')
|
|
297
|
+
} else {
|
|
298
|
+
console.log(`MCP: ${mcp.action} (${rel(cwd, mcp.path) || '.cursor/mcp.json'})`)
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
if (styles.reason === 'skip-styles') {
|
|
302
|
+
console.log('Styles: skipped (--skip-styles or ai mode)')
|
|
303
|
+
} else if (styles.action === 'injected') {
|
|
304
|
+
console.log(`Styles: injected into ${rel(cwd, styles.path)}`)
|
|
305
|
+
} else if (styles.action === 'skipped') {
|
|
306
|
+
console.log(`Styles: skipped (${styles.reason}) in ${rel(cwd, styles.path)}`)
|
|
307
|
+
} else {
|
|
308
|
+
console.log('Styles: no entry file found — add manually:')
|
|
309
|
+
console.log(" import 'morya-ui/styles.css'")
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (scripts.action === 'skipped-flag') {
|
|
313
|
+
console.log('Scripts: skipped (--skip-scripts or app mode)')
|
|
314
|
+
} else {
|
|
315
|
+
console.log(`Scripts: check:colors ${scripts.action}`)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
console.log('')
|
|
319
|
+
console.log('Next:')
|
|
320
|
+
if (mode === 'app') {
|
|
321
|
+
console.log(' 1. Ensure the styles.css import is in your app entry.')
|
|
322
|
+
console.log(' 2. Optional AI pack: npx @morya-ui/setup ai')
|
|
323
|
+
} else if (mode === 'ai') {
|
|
324
|
+
console.log(' 1. Restart Cursor (or reload MCP) so morya-ui MCP tools appear.')
|
|
325
|
+
console.log(' 2. Have the agent read DESIGN.md before generating pages.')
|
|
326
|
+
console.log(' 3. Optional: pnpm check:colors')
|
|
327
|
+
} else {
|
|
328
|
+
console.log(' 1. Ensure the styles.css import is in your app entry.')
|
|
329
|
+
console.log(' 2. Restart Cursor (or reload MCP) so morya-ui MCP tools appear.')
|
|
330
|
+
console.log(' 3. Have the agent read DESIGN.md before generating pages.')
|
|
331
|
+
console.log(' 4. Optional: pnpm check:colors')
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
return { mode, install, template, remoteSkills, mcp, styles, scripts, skills: selectedSkills }
|
|
335
|
+
}
|