soureeui 1.0.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/AGENTS.md +84 -0
- package/LICENSE +6 -0
- package/README.md +210 -0
- package/SKILL.md +148 -0
- package/bin/soureeui.js +491 -0
- package/package.json +47 -0
- package/references/accessibility.md +77 -0
- package/references/anti-ai-patterns.md +74 -0
- package/references/audit-and-refactor.md +64 -0
- package/references/content-and-copy.md +75 -0
- package/references/design-directions.md +81 -0
- package/references/design-system.md +124 -0
- package/references/imagery-and-icons.md +87 -0
- package/references/motion.md +186 -0
- package/references/packages.md +127 -0
- package/references/product-brief.md +68 -0
- package/references/responsive.md +145 -0
- package/references/review-and-done.md +98 -0
- package/references/workflow.md +107 -0
- package/templates/design-plan.md +124 -0
- package/templates/image-prompt.md +71 -0
- package/templates/ui-audit.md +77 -0
package/bin/soureeui.js
ADDED
|
@@ -0,0 +1,491 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict'
|
|
3
|
+
|
|
4
|
+
const fs = require('fs')
|
|
5
|
+
const os = require('os')
|
|
6
|
+
const path = require('path')
|
|
7
|
+
const readline = require('readline')
|
|
8
|
+
|
|
9
|
+
const PKG = require('../package.json')
|
|
10
|
+
const ROOT = path.resolve(__dirname, '..')
|
|
11
|
+
const PAYLOAD = ['AGENTS.md', 'SKILL.md', 'references', 'templates']
|
|
12
|
+
const BODY_DIR = '.soureeui'
|
|
13
|
+
const MARK_BEGIN = '<!-- soureeui:begin -->'
|
|
14
|
+
const MARK_END = '<!-- soureeui:end -->'
|
|
15
|
+
|
|
16
|
+
const POINTER = (bodyPath) => `${MARK_BEGIN}
|
|
17
|
+
## UI / UX work
|
|
18
|
+
|
|
19
|
+
Before any task that touches UI, UX, layout, visual design, styling, components,
|
|
20
|
+
responsive behavior, or a redesign, read \`${bodyPath}\` and follow it. It defines
|
|
21
|
+
the design process, the anti-generic-UI rules, and the reference files to load on demand.
|
|
22
|
+
${MARK_END}`
|
|
23
|
+
|
|
24
|
+
const CURSOR_RULE = `---
|
|
25
|
+
description: UI/UX design process for any interface, layout, styling, component, or redesign work
|
|
26
|
+
globs:
|
|
27
|
+
alwaysApply: false
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
Read \`${BODY_DIR}/AGENTS.md\` and follow it for any task touching UI, UX, layout,
|
|
31
|
+
visual design, styling, components, responsive behavior, or a redesign. It defines
|
|
32
|
+
the design process, the anti-generic-UI rules, and reference files to load on demand.
|
|
33
|
+
`
|
|
34
|
+
|
|
35
|
+
// agent registry -------------------------------------------------------------
|
|
36
|
+
|
|
37
|
+
const claudeSkillDir = (ctx) => ctx.global
|
|
38
|
+
? path.join(os.homedir(), '.claude', 'skills', 'soureeui')
|
|
39
|
+
: path.join(ctx.dir, '.claude', 'skills', 'soureeui')
|
|
40
|
+
|
|
41
|
+
const AGENTS = {
|
|
42
|
+
claude: {
|
|
43
|
+
label: 'Claude Code',
|
|
44
|
+
detect: ['.claude', 'CLAUDE.md'],
|
|
45
|
+
install: (ctx) => {
|
|
46
|
+
ctx.copyBody(claudeSkillDir(ctx), { skill: true })
|
|
47
|
+
ctx.note('loads automatically on UI work, or run /soureeui')
|
|
48
|
+
},
|
|
49
|
+
paths: (ctx) => [claudeSkillDir(ctx)],
|
|
50
|
+
},
|
|
51
|
+
codex: {
|
|
52
|
+
label: 'Codex',
|
|
53
|
+
detect: ['AGENTS.md', '.codex'],
|
|
54
|
+
install: (ctx) => {
|
|
55
|
+
ctx.body()
|
|
56
|
+
ctx.pointer(path.join(ctx.dir, 'AGENTS.md'))
|
|
57
|
+
},
|
|
58
|
+
paths: (ctx) => [path.join(ctx.dir, BODY_DIR), path.join(ctx.dir, 'AGENTS.md')],
|
|
59
|
+
},
|
|
60
|
+
antigravity: {
|
|
61
|
+
label: 'Antigravity',
|
|
62
|
+
detect: ['.agents', '.agent'],
|
|
63
|
+
install: (ctx) => {
|
|
64
|
+
ctx.body()
|
|
65
|
+
ctx.pointer(ctx.global
|
|
66
|
+
? path.join(os.homedir(), '.gemini', 'GEMINI.md')
|
|
67
|
+
: path.join(ctx.dir, 'AGENTS.md'))
|
|
68
|
+
ctx.write(path.join(ctx.dir, '.agents', 'rules', 'soureeui.md'),
|
|
69
|
+
POINTER(`${BODY_DIR}/AGENTS.md`) + '\n')
|
|
70
|
+
},
|
|
71
|
+
paths: (ctx) => [
|
|
72
|
+
path.join(ctx.dir, BODY_DIR),
|
|
73
|
+
path.join(ctx.dir, '.agents', 'rules', 'soureeui.md'),
|
|
74
|
+
path.join(ctx.dir, 'AGENTS.md'),
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
cursor: {
|
|
78
|
+
label: 'Cursor',
|
|
79
|
+
detect: ['.cursor'],
|
|
80
|
+
install: (ctx) => {
|
|
81
|
+
ctx.body()
|
|
82
|
+
ctx.write(path.join(ctx.dir, '.cursor', 'rules', 'soureeui.mdc'), CURSOR_RULE)
|
|
83
|
+
},
|
|
84
|
+
paths: (ctx) => [path.join(ctx.dir, BODY_DIR), path.join(ctx.dir, '.cursor', 'rules', 'soureeui.mdc')],
|
|
85
|
+
},
|
|
86
|
+
windsurf: {
|
|
87
|
+
label: 'Windsurf',
|
|
88
|
+
detect: ['.windsurf'],
|
|
89
|
+
install: (ctx) => {
|
|
90
|
+
ctx.body()
|
|
91
|
+
ctx.write(path.join(ctx.dir, '.windsurf', 'rules', 'soureeui.md'),
|
|
92
|
+
POINTER(`${BODY_DIR}/AGENTS.md`) + '\n')
|
|
93
|
+
},
|
|
94
|
+
paths: (ctx) => [path.join(ctx.dir, BODY_DIR), path.join(ctx.dir, '.windsurf', 'rules', 'soureeui.md')],
|
|
95
|
+
},
|
|
96
|
+
gemini: {
|
|
97
|
+
label: 'Gemini CLI',
|
|
98
|
+
detect: ['GEMINI.md', '.gemini'],
|
|
99
|
+
install: (ctx) => {
|
|
100
|
+
ctx.body()
|
|
101
|
+
ctx.pointer(ctx.global
|
|
102
|
+
? path.join(os.homedir(), '.gemini', 'GEMINI.md')
|
|
103
|
+
: path.join(ctx.dir, 'GEMINI.md'))
|
|
104
|
+
},
|
|
105
|
+
paths: (ctx) => [path.join(ctx.dir, BODY_DIR), path.join(ctx.dir, 'GEMINI.md')],
|
|
106
|
+
},
|
|
107
|
+
copilot: {
|
|
108
|
+
label: 'GitHub Copilot',
|
|
109
|
+
detect: [path.join('.github', 'copilot-instructions.md')],
|
|
110
|
+
install: (ctx) => {
|
|
111
|
+
ctx.body()
|
|
112
|
+
ctx.pointer(path.join(ctx.dir, '.github', 'copilot-instructions.md'))
|
|
113
|
+
},
|
|
114
|
+
paths: (ctx) => [path.join(ctx.dir, BODY_DIR), path.join(ctx.dir, '.github', 'copilot-instructions.md')],
|
|
115
|
+
},
|
|
116
|
+
generic: {
|
|
117
|
+
label: 'Any other agent',
|
|
118
|
+
detect: [],
|
|
119
|
+
install: (ctx) => {
|
|
120
|
+
ctx.body()
|
|
121
|
+
ctx.note(`point your agent at ${BODY_DIR}/AGENTS.md`)
|
|
122
|
+
},
|
|
123
|
+
paths: (ctx) => [path.join(ctx.dir, BODY_DIR)],
|
|
124
|
+
},
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const NAMES = Object.keys(AGENTS)
|
|
128
|
+
|
|
129
|
+
// output ---------------------------------------------------------------------
|
|
130
|
+
|
|
131
|
+
const ESC = String.fromCharCode(27)
|
|
132
|
+
const tty = process.stdout.isTTY && !process.env.NO_COLOR
|
|
133
|
+
const paint = (code, s) => (tty ? `${ESC}[${code}m${s}${ESC}[0m` : s)
|
|
134
|
+
const bold = (s) => paint(1, s)
|
|
135
|
+
const dim = (s) => paint(2, s)
|
|
136
|
+
const green = (s) => paint(32, s)
|
|
137
|
+
const yellow = (s) => paint(33, s)
|
|
138
|
+
const red = (s) => paint(31, s)
|
|
139
|
+
|
|
140
|
+
// piping into head/less closes stdout early; that is not an error
|
|
141
|
+
process.stdout.on('error', (err) => { if (err.code === 'EPIPE') process.exit(0) })
|
|
142
|
+
|
|
143
|
+
const log = (s = '') => process.stdout.write(s + '\n')
|
|
144
|
+
const fail = (msg) => { process.stderr.write(red('error: ') + msg + '\n'); process.exit(1) }
|
|
145
|
+
|
|
146
|
+
// args -----------------------------------------------------------------------
|
|
147
|
+
|
|
148
|
+
function parseArgs (argv) {
|
|
149
|
+
const out = { _: [], ai: [], dir: process.cwd(), global: false, force: false, dryRun: false, yes: false }
|
|
150
|
+
for (let i = 0; i < argv.length; i++) {
|
|
151
|
+
const a = argv[i]
|
|
152
|
+
const next = () => {
|
|
153
|
+
const v = argv[++i]
|
|
154
|
+
if (v === undefined || v.startsWith('-')) fail(`${a} needs a value`)
|
|
155
|
+
return v
|
|
156
|
+
}
|
|
157
|
+
const addAi = (v) => out.ai.push(...v.split(',').map((s) => s.trim()).filter(Boolean))
|
|
158
|
+
if (a === '--ai' || a === '-a') addAi(next())
|
|
159
|
+
else if (a.startsWith('--ai=')) addAi(a.slice(5))
|
|
160
|
+
else if (a === '--dir' || a === '-d') out.dir = path.resolve(next())
|
|
161
|
+
else if (a.startsWith('--dir=')) out.dir = path.resolve(a.slice(6))
|
|
162
|
+
else if (a === '--global' || a === '-g') out.global = true
|
|
163
|
+
else if (a === '--force' || a === '-f') out.force = true
|
|
164
|
+
else if (a === '--dry-run' || a === '-n') out.dryRun = true
|
|
165
|
+
else if (a === '--yes' || a === '-y') out.yes = true
|
|
166
|
+
else if (a === '--help' || a === '-h') out.help = true
|
|
167
|
+
else if (a === '--version' || a === '-v') out.version = true
|
|
168
|
+
else if (a.startsWith('-')) fail(`unknown flag: ${a}`)
|
|
169
|
+
else out._.push(a)
|
|
170
|
+
}
|
|
171
|
+
return out
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// fs helpers -----------------------------------------------------------------
|
|
175
|
+
|
|
176
|
+
function copyDir (src, dest) {
|
|
177
|
+
fs.mkdirSync(dest, { recursive: true })
|
|
178
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
179
|
+
const from = path.join(src, entry.name)
|
|
180
|
+
const to = path.join(dest, entry.name)
|
|
181
|
+
if (entry.isDirectory()) copyDir(from, to)
|
|
182
|
+
else fs.copyFileSync(from, to)
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
function makeCtx (opts, actions) {
|
|
187
|
+
const ctx = {
|
|
188
|
+
dir: opts.dir,
|
|
189
|
+
global: opts.global,
|
|
190
|
+
rel: (p) => (p === opts.dir || p.startsWith(opts.dir + path.sep)
|
|
191
|
+
? path.relative(opts.dir, p) || '.'
|
|
192
|
+
: p.replace(os.homedir(), '~')),
|
|
193
|
+
note: (msg) => actions.push({ kind: 'note', msg }),
|
|
194
|
+
write: (file, content) => actions.push({ kind: 'write', file, content }),
|
|
195
|
+
pointer: (file) => actions.push({ kind: 'pointer', file }),
|
|
196
|
+
copyBody: (dest, o = {}) => actions.push({ kind: 'body', dest, skill: !!o.skill }),
|
|
197
|
+
body: () => ctx.copyBody(path.join(opts.dir, BODY_DIR)),
|
|
198
|
+
}
|
|
199
|
+
return ctx
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function applyActions (actions, opts, ctx) {
|
|
203
|
+
for (const a of actions) {
|
|
204
|
+
if (a.kind === 'note') {
|
|
205
|
+
log(' ' + dim(a.msg))
|
|
206
|
+
continue
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
if (a.kind === 'body') {
|
|
210
|
+
if (!opts.dryRun) {
|
|
211
|
+
fs.mkdirSync(a.dest, { recursive: true })
|
|
212
|
+
for (const item of PAYLOAD) {
|
|
213
|
+
const src = path.join(ROOT, item)
|
|
214
|
+
if (!fs.existsSync(src)) continue
|
|
215
|
+
const dest = path.join(a.dest, item)
|
|
216
|
+
if (fs.statSync(src).isDirectory()) {
|
|
217
|
+
fs.rmSync(dest, { recursive: true, force: true })
|
|
218
|
+
copyDir(src, dest)
|
|
219
|
+
} else {
|
|
220
|
+
fs.copyFileSync(src, dest)
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
fs.rmSync(path.join(a.dest, a.skill ? 'AGENTS.md' : 'SKILL.md'), { force: true })
|
|
224
|
+
}
|
|
225
|
+
log(' ' + green('+') + ' skill body ' + dim('->') + ' ' + ctx.rel(a.dest) + '/')
|
|
226
|
+
continue
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (a.kind === 'write') {
|
|
230
|
+
const exists = fs.existsSync(a.file)
|
|
231
|
+
if (exists && !opts.force && fs.readFileSync(a.file, 'utf8') === a.content) {
|
|
232
|
+
log(' ' + dim('=') + ' ' + ctx.rel(a.file) + ' ' + dim('already current'))
|
|
233
|
+
continue
|
|
234
|
+
}
|
|
235
|
+
if (!opts.dryRun) {
|
|
236
|
+
fs.mkdirSync(path.dirname(a.file), { recursive: true })
|
|
237
|
+
fs.writeFileSync(a.file, a.content)
|
|
238
|
+
}
|
|
239
|
+
log(' ' + green(exists ? '~' : '+') + ' ' + ctx.rel(a.file))
|
|
240
|
+
continue
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
if (a.kind === 'pointer') {
|
|
244
|
+
const bodyPath = opts.global
|
|
245
|
+
? path.join(os.homedir(), BODY_DIR, 'AGENTS.md').replace(os.homedir(), '~')
|
|
246
|
+
: `${BODY_DIR}/AGENTS.md`
|
|
247
|
+
const block = POINTER(bodyPath)
|
|
248
|
+
const exists = fs.existsSync(a.file)
|
|
249
|
+
const current = exists ? fs.readFileSync(a.file, 'utf8') : ''
|
|
250
|
+
|
|
251
|
+
if (current.includes(MARK_BEGIN)) {
|
|
252
|
+
const start = current.indexOf(MARK_BEGIN)
|
|
253
|
+
const end = current.indexOf(MARK_END, start)
|
|
254
|
+
const next = current.slice(0, start) + block + current.slice(end + MARK_END.length)
|
|
255
|
+
if (next !== current && !opts.dryRun) fs.writeFileSync(a.file, next)
|
|
256
|
+
log(' ' + dim('=') + ' ' + ctx.rel(a.file) + ' ' +
|
|
257
|
+
dim(next === current ? 'already current' : 'pointer refreshed'))
|
|
258
|
+
continue
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
if (!opts.dryRun) {
|
|
262
|
+
fs.mkdirSync(path.dirname(a.file), { recursive: true })
|
|
263
|
+
const head = current ? current.replace(/\n*$/, '\n\n') : ''
|
|
264
|
+
fs.writeFileSync(a.file, head + block + '\n')
|
|
265
|
+
}
|
|
266
|
+
log(' ' + green(exists ? '~' : '+') + ' ' + ctx.rel(a.file) + ' ' +
|
|
267
|
+
dim(exists ? 'pointer appended' : 'created'))
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// agent selection ------------------------------------------------------------
|
|
273
|
+
|
|
274
|
+
function detect (dir) {
|
|
275
|
+
const found = []
|
|
276
|
+
for (const name of NAMES) {
|
|
277
|
+
for (const marker of AGENTS[name].detect) {
|
|
278
|
+
if (fs.existsSync(path.join(dir, marker))) {
|
|
279
|
+
found.push(name)
|
|
280
|
+
break
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return found
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
function ask (question) {
|
|
288
|
+
return new Promise((resolve) => {
|
|
289
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
290
|
+
rl.question(question, (answer) => {
|
|
291
|
+
rl.close()
|
|
292
|
+
resolve(answer.trim())
|
|
293
|
+
})
|
|
294
|
+
})
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async function pickAgents (opts) {
|
|
298
|
+
if (opts.ai.length) {
|
|
299
|
+
if (opts.ai.includes('all')) return NAMES.filter((n) => n !== 'generic')
|
|
300
|
+
const unknown = opts.ai.filter((n) => !NAMES.includes(n))
|
|
301
|
+
if (unknown.length) fail(`unknown agent: ${unknown.join(', ')}\n known: ${NAMES.join(', ')}, all`)
|
|
302
|
+
return [...new Set(opts.ai)]
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const found = detect(opts.dir)
|
|
306
|
+
const fallback = found.length ? found : ['claude', 'codex']
|
|
307
|
+
|
|
308
|
+
if (opts.yes || !process.stdin.isTTY) {
|
|
309
|
+
if (found.length) log(dim(`detected: ${found.join(', ')}`))
|
|
310
|
+
return fallback
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
log('')
|
|
314
|
+
log(bold('Which agents?') + dim(' numbers, names, or a for all'))
|
|
315
|
+
NAMES.forEach((n, i) => {
|
|
316
|
+
const hit = found.includes(n) ? green(' detected') : ''
|
|
317
|
+
log(` ${dim(String(i + 1) + ')')} ${n.padEnd(12)} ${dim(AGENTS[n].label)}${hit}`)
|
|
318
|
+
})
|
|
319
|
+
const answer = await ask(`\n${dim('default: ' + fallback.join(', '))}\n> `)
|
|
320
|
+
if (!answer) return fallback
|
|
321
|
+
if (/^(a|all)$/i.test(answer)) return NAMES.filter((n) => n !== 'generic')
|
|
322
|
+
|
|
323
|
+
const picked = answer.split(/[\s,]+/).filter(Boolean).map((token) => {
|
|
324
|
+
const i = Number(token)
|
|
325
|
+
if (Number.isInteger(i) && i >= 1 && i <= NAMES.length) return NAMES[i - 1]
|
|
326
|
+
if (NAMES.includes(token)) return token
|
|
327
|
+
return fail(`not an option: ${token}`)
|
|
328
|
+
})
|
|
329
|
+
return [...new Set(picked)]
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
// commands -------------------------------------------------------------------
|
|
333
|
+
|
|
334
|
+
async function cmdInit (opts) {
|
|
335
|
+
if (!fs.existsSync(opts.dir)) fail(`no such directory: ${opts.dir}`)
|
|
336
|
+
const agents = await pickAgents(opts)
|
|
337
|
+
|
|
338
|
+
log('')
|
|
339
|
+
log(`${bold('soureeui')} ${dim('v' + PKG.version)} ${opts.dir}${opts.dryRun ? yellow(' dry run') : ''}`)
|
|
340
|
+
log('')
|
|
341
|
+
|
|
342
|
+
for (const name of agents) {
|
|
343
|
+
log(bold(AGENTS[name].label))
|
|
344
|
+
const actions = []
|
|
345
|
+
const ctx = makeCtx(opts, actions)
|
|
346
|
+
AGENTS[name].install(ctx)
|
|
347
|
+
applyActions(actions, opts, ctx)
|
|
348
|
+
log('')
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
log(green('Done.') + dim(' Ask your agent to build or redesign a screen and it will run the design process.'))
|
|
352
|
+
log('')
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
function cmdList () {
|
|
356
|
+
log('')
|
|
357
|
+
log(bold('Supported agents'))
|
|
358
|
+
NAMES.forEach((n) => log(` ${n.padEnd(12)} ${dim(AGENTS[n].label)}`))
|
|
359
|
+
log('')
|
|
360
|
+
log(dim(' soureeui init --ai cursor'))
|
|
361
|
+
log(dim(' soureeui init --ai claude,codex'))
|
|
362
|
+
log(dim(' soureeui init --ai all'))
|
|
363
|
+
log('')
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function installedPaths (name, ctx) {
|
|
367
|
+
return AGENTS[name].paths(ctx).filter((p) => {
|
|
368
|
+
if (!fs.existsSync(p)) return false
|
|
369
|
+
if (/\.(md|mdc)$/.test(p) && !p.includes('soureeui')) {
|
|
370
|
+
return fs.readFileSync(p, 'utf8').includes(MARK_BEGIN)
|
|
371
|
+
}
|
|
372
|
+
return true
|
|
373
|
+
})
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function cmdDoctor (opts) {
|
|
377
|
+
log('')
|
|
378
|
+
log(`${bold('soureeui doctor')} ${dim(opts.dir)}`)
|
|
379
|
+
log('')
|
|
380
|
+
const ctx = makeCtx(opts, [])
|
|
381
|
+
let any = false
|
|
382
|
+
for (const name of NAMES) {
|
|
383
|
+
const present = installedPaths(name, ctx)
|
|
384
|
+
if (!present.length) continue
|
|
385
|
+
any = true
|
|
386
|
+
log(`${green('installed')} ${bold(AGENTS[name].label)}`)
|
|
387
|
+
present.forEach((p) => log(` ${dim(ctx.rel(p))}`))
|
|
388
|
+
}
|
|
389
|
+
if (!any) log(dim('nothing installed here. run: soureeui init'))
|
|
390
|
+
log('')
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
async function cmdRemove (opts) {
|
|
394
|
+
const agents = opts.ai.length ? (opts.ai.includes('all') ? NAMES : opts.ai) : NAMES
|
|
395
|
+
const ctx = makeCtx(opts, [])
|
|
396
|
+
const targets = [...new Set(agents.flatMap((n) => installedPaths(n, ctx)))]
|
|
397
|
+
|
|
398
|
+
if (!targets.length) {
|
|
399
|
+
log(dim('nothing to remove.'))
|
|
400
|
+
return
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
log('')
|
|
404
|
+
log(bold('Will remove or clean:'))
|
|
405
|
+
targets.forEach((p) => log(' ' + ctx.rel(p)))
|
|
406
|
+
log('')
|
|
407
|
+
|
|
408
|
+
if (!opts.yes && !opts.dryRun && process.stdin.isTTY) {
|
|
409
|
+
const answer = await ask('Proceed? (y/N) ')
|
|
410
|
+
if (!/^y(es)?$/i.test(answer)) {
|
|
411
|
+
log(dim('cancelled.'))
|
|
412
|
+
return
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
for (const p of targets) {
|
|
417
|
+
const isInstructionFile = /\.(md|mdc)$/.test(p) && !p.includes('soureeui')
|
|
418
|
+
if (isInstructionFile) {
|
|
419
|
+
const content = fs.readFileSync(p, 'utf8')
|
|
420
|
+
const start = content.indexOf(MARK_BEGIN)
|
|
421
|
+
const end = content.indexOf(MARK_END, start)
|
|
422
|
+
const cleaned = (content.slice(0, start) + content.slice(end + MARK_END.length)).trim()
|
|
423
|
+
if (!opts.dryRun) {
|
|
424
|
+
if (cleaned) fs.writeFileSync(p, cleaned + '\n')
|
|
425
|
+
else fs.rmSync(p)
|
|
426
|
+
}
|
|
427
|
+
log(' ' + green('-') + ' pointer removed from ' + ctx.rel(p))
|
|
428
|
+
} else {
|
|
429
|
+
if (!opts.dryRun) fs.rmSync(p, { recursive: true, force: true })
|
|
430
|
+
log(' ' + green('-') + ' ' + ctx.rel(p))
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
log('')
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
function help () {
|
|
437
|
+
log(`
|
|
438
|
+
${bold('soureeui')} ${dim('- install the UI Architect design skill into your AI coding agent')}
|
|
439
|
+
|
|
440
|
+
${bold('Usage')}
|
|
441
|
+
soureeui init [--ai <agents>] [options]
|
|
442
|
+
soureeui list
|
|
443
|
+
soureeui doctor
|
|
444
|
+
soureeui remove [--ai <agents>]
|
|
445
|
+
|
|
446
|
+
${bold('Examples')}
|
|
447
|
+
${dim('$')} soureeui init --ai cursor
|
|
448
|
+
${dim('$')} soureeui init --ai claude,codex
|
|
449
|
+
${dim('$')} soureeui init --ai all
|
|
450
|
+
${dim('$')} soureeui init --ai claude --global
|
|
451
|
+
${dim('$')} soureeui init ${dim('detects what the project uses, or asks')}
|
|
452
|
+
|
|
453
|
+
${bold('Agents')}
|
|
454
|
+
${NAMES.join(', ')}, all
|
|
455
|
+
|
|
456
|
+
${bold('Options')}
|
|
457
|
+
-a, --ai <list> agents to install for, comma separated
|
|
458
|
+
-d, --dir <path> target project (default: current directory)
|
|
459
|
+
-g, --global install machine-wide, where the agent supports it
|
|
460
|
+
-f, --force overwrite existing rule files
|
|
461
|
+
-n, --dry-run show what would change, write nothing
|
|
462
|
+
-y, --yes no prompts
|
|
463
|
+
-h, --help this text
|
|
464
|
+
-v, --version version
|
|
465
|
+
`)
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// main -----------------------------------------------------------------------
|
|
469
|
+
|
|
470
|
+
async function main () {
|
|
471
|
+
const opts = parseArgs(process.argv.slice(2))
|
|
472
|
+
if (opts.version) return log(PKG.version)
|
|
473
|
+
const cmd = opts._[0] || (opts.ai.length ? 'init' : null)
|
|
474
|
+
if (opts.help || !cmd) return help()
|
|
475
|
+
|
|
476
|
+
switch (cmd) {
|
|
477
|
+
case 'init':
|
|
478
|
+
case 'install':
|
|
479
|
+
case 'add': return cmdInit(opts)
|
|
480
|
+
case 'update': return cmdInit({ ...opts, force: true })
|
|
481
|
+
case 'list':
|
|
482
|
+
case 'agents': return cmdList()
|
|
483
|
+
case 'doctor':
|
|
484
|
+
case 'status': return cmdDoctor(opts)
|
|
485
|
+
case 'remove':
|
|
486
|
+
case 'uninstall': return cmdRemove(opts)
|
|
487
|
+
default: return fail(`unknown command: ${cmd}\n try: soureeui --help`)
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
main().catch((err) => fail(err && err.stack ? err.stack : String(err)))
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "soureeui",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Install the UI Architect design skill into Claude Code, Codex, Antigravity, Cursor, Windsurf, Gemini CLI, or Copilot, so the agent designs before it codes instead of producing generic AI-looking UI.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"soureeui": "bin/soureeui.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin",
|
|
10
|
+
"references",
|
|
11
|
+
"templates",
|
|
12
|
+
"AGENTS.md",
|
|
13
|
+
"SKILL.md",
|
|
14
|
+
"README.md"
|
|
15
|
+
],
|
|
16
|
+
"engines": {
|
|
17
|
+
"node": ">=18"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"ui",
|
|
21
|
+
"ux",
|
|
22
|
+
"design",
|
|
23
|
+
"design-system",
|
|
24
|
+
"ai",
|
|
25
|
+
"agent",
|
|
26
|
+
"skill",
|
|
27
|
+
"claude-code",
|
|
28
|
+
"codex",
|
|
29
|
+
"antigravity",
|
|
30
|
+
"cursor",
|
|
31
|
+
"windsurf",
|
|
32
|
+
"copilot",
|
|
33
|
+
"agents-md"
|
|
34
|
+
],
|
|
35
|
+
"repository": {
|
|
36
|
+
"type": "git",
|
|
37
|
+
"url": "git+https://github.com/shree2698/pro-ui.git"
|
|
38
|
+
},
|
|
39
|
+
"homepage": "https://github.com/shree2698/pro-ui#readme",
|
|
40
|
+
"bugs": {
|
|
41
|
+
"url": "https://github.com/shree2698/pro-ui/issues"
|
|
42
|
+
},
|
|
43
|
+
"license": "MIT",
|
|
44
|
+
"scripts": {
|
|
45
|
+
"test": "node bin/soureeui.js --help > /dev/null && node bin/soureeui.js list > /dev/null && echo ok"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Accessibility
|
|
2
|
+
|
|
3
|
+
Part of the design, not a pass afterwards. Retrofitting is more expensive than building it correctly.
|
|
4
|
+
|
|
5
|
+
Target: WCAG 2.2 AA unless the project states otherwise.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Structure
|
|
10
|
+
|
|
11
|
+
- semantic elements over styled generic containers: `button`, `a`, `nav`, `main`, `header`, `footer`, `section`, `ul`, `table`, `dialog`
|
|
12
|
+
- one `h1` per page; heading levels descend without gaps. Rank is semantic — size is styling
|
|
13
|
+
- landmarks so a screen reader can jump: main content, navigation, search, footer
|
|
14
|
+
- a skip link to main content when navigation precedes it
|
|
15
|
+
- lists are lists, tables are tables with real headers and scopes
|
|
16
|
+
|
|
17
|
+
## Controls
|
|
18
|
+
|
|
19
|
+
- **a button does something, a link goes somewhere.** Never a clickable `div`
|
|
20
|
+
- icon-only controls carry an accessible name
|
|
21
|
+
- toggles, tabs, menus, comboboxes, and dialogs expose their state (pressed, selected, expanded, current)
|
|
22
|
+
- custom controls follow the established keyboard pattern for that role — if that is expensive, use a primitives library (`packages.md`)
|
|
23
|
+
|
|
24
|
+
## Keyboard
|
|
25
|
+
|
|
26
|
+
- every interactive element is reachable and operable by keyboard
|
|
27
|
+
- tab order follows visual order
|
|
28
|
+
- **visible focus on every focusable element.** Never remove the focus ring without replacing it with something at least as visible
|
|
29
|
+
- focus-visible for keyboard users; do not show rings on mouse click if the project prefers that, but never suppress both
|
|
30
|
+
- modals trap focus, close on Escape, and return focus to the trigger
|
|
31
|
+
- no keyboard traps anywhere else
|
|
32
|
+
|
|
33
|
+
## Contrast
|
|
34
|
+
|
|
35
|
+
| Content | Minimum |
|
|
36
|
+
|---|---|
|
|
37
|
+
| Body text | 4.5:1 |
|
|
38
|
+
| Large text (≥24px, or ≥19px bold) | 3:1 |
|
|
39
|
+
| Icons and meaningful graphics | 3:1 |
|
|
40
|
+
| Control boundaries, focus indicators | 3:1 against adjacent colors |
|
|
41
|
+
|
|
42
|
+
Check at token-definition time. Placeholder text, muted text on tinted surfaces, and text over images are the usual failures — text over an image needs a scrim or a guaranteed-safe zone.
|
|
43
|
+
|
|
44
|
+
**Color is never the only signal.** Pair it with text, icon, shape, or position — for status, validation, chart series, and required fields.
|
|
45
|
+
|
|
46
|
+
## Forms
|
|
47
|
+
|
|
48
|
+
- every field has a persistent visible label. Placeholder is not a label
|
|
49
|
+
- errors are associated with the field, announced, and stated in text next to it
|
|
50
|
+
- required fields marked in text, not by color or an unexplained asterisk alone
|
|
51
|
+
- related controls grouped with a group label
|
|
52
|
+
- appropriate input types and autocomplete hints
|
|
53
|
+
- do not disable submit silently; explain what is missing
|
|
54
|
+
|
|
55
|
+
## Content
|
|
56
|
+
|
|
57
|
+
- alt text describes purpose, not appearance. Decorative images get empty alt
|
|
58
|
+
- link text makes sense alone — "read the routing guide", not "click here"
|
|
59
|
+
- captions and transcripts for media
|
|
60
|
+
- dynamic updates announced through a polite live region; never hijack focus for a background update
|
|
61
|
+
|
|
62
|
+
## Interaction
|
|
63
|
+
|
|
64
|
+
- touch targets ~44px minimum with spacing between adjacent targets
|
|
65
|
+
- do not rely on hover to reveal essential content or actions
|
|
66
|
+
- respect reduced motion, and provide a reduced alternative rather than removing meaning (`motion.md`)
|
|
67
|
+
- page stays usable at 200% zoom and at 320px effective width with no loss of content or function
|
|
68
|
+
- do not suppress zoom
|
|
69
|
+
|
|
70
|
+
## Quick verification
|
|
71
|
+
|
|
72
|
+
1. Unplug the mouse. Complete the primary task.
|
|
73
|
+
2. Tab through. Is focus always visible and in a sensible order?
|
|
74
|
+
3. Zoom to 200%. Does anything break or get clipped?
|
|
75
|
+
4. Check contrast on text, icons, and focus rings.
|
|
76
|
+
5. Read only the headings. Does the page make sense?
|
|
77
|
+
6. Turn on reduced motion. Is everything still understandable?
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# Anti-AI Patterns
|
|
2
|
+
|
|
3
|
+
The failure mode this skill exists to prevent: an interface that is competent, symmetrical, glossy, and indistinguishable from ten thousand others.
|
|
4
|
+
|
|
5
|
+
Read this before finalizing any visual design.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## The pattern list
|
|
10
|
+
|
|
11
|
+
Each is banned as a *default*. Each is allowed when there is a stated product reason.
|
|
12
|
+
|
|
13
|
+
| Pattern | Why it fails | Instead |
|
|
14
|
+
|---|---|---|
|
|
15
|
+
| Purple-to-blue gradient as brand | Belongs to no product; signals "generated" instantly | Derive color from the actual brand, product, or domain |
|
|
16
|
+
| Gradient hero with giant centered heading | Zero information, maximum height, no hierarchy | Lead with the actual value proposition and a real interface or artifact |
|
|
17
|
+
| Floating blobs / abstract shapes | Decoration standing in for content | Real imagery, real data, real product UI, or nothing |
|
|
18
|
+
| Glow and neon halos | Attention without meaning | Contrast and position carry emphasis |
|
|
19
|
+
| Glass everywhere | Layering with nothing to layer over; kills contrast | Glass only over actual imagery or depth |
|
|
20
|
+
| Everything rounded to the same large radius | Erases hierarchy; every element reads the same weight | A radius system with intent (`design-system.md`) |
|
|
21
|
+
| Every section is a card | Cards become the page instead of grouping content | Cards for genuinely discrete, repeated objects only |
|
|
22
|
+
| Repetitive 3-column feature grid | Three features, equal weight, no priority — a lie about the product | Rank features; give the important one more space |
|
|
23
|
+
| Stacked drop shadows | Fake depth with no light source | One elevation scale, used sparingly |
|
|
24
|
+
| "AI Powered" / "Next-Gen" badges | Says nothing; dates instantly | Say what it does |
|
|
25
|
+
| Fake "Trusted by" logo rows | Fabricated social proof | Real customers, or cut the section |
|
|
26
|
+
| Invented statistics ("10x faster", "99.9%") | Fabricated claims | Real numbers, or qualitative specifics |
|
|
27
|
+
| Sparkle icons as decoration | Meaningless glyph noise | Icons that label real actions |
|
|
28
|
+
| Entrance animation on every element | Delays content; motion sickness; nothing is emphasized | Motion where it explains a change |
|
|
29
|
+
| Constant floating / pulsing loops | Permanent distraction | Static, unless movement carries meaning |
|
|
30
|
+
| Generic SaaS section order transplanted onto an unrelated product | Structure unrelated to this product's story | Section order from the actual user journey |
|
|
31
|
+
| Whitespace without hierarchy | Airy but unreadable; nothing dominates | Space that groups and ranks |
|
|
32
|
+
| Identical icon-title-text triplets down the page | Templated rhythm | Vary composition where content differs |
|
|
33
|
+
| Emoji as UI icons | Inconsistent rendering, unprofessional, not accessible | Icon library (`imagery-and-icons.md`) |
|
|
34
|
+
| Lorem ipsum or "Transform your workflow" | Design built on nothing | Real product copy (`content-and-copy.md`) |
|
|
35
|
+
| Centered everything | No reading axis, no tension | Deliberate alignment; asymmetry where it helps |
|
|
36
|
+
| Dark hero, light body, dark footer for no reason | Banded structure copied from templates | Structure from content |
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## The human-design test
|
|
41
|
+
|
|
42
|
+
Run every question. Any "yes" in the wrong direction is a fix, not a note.
|
|
43
|
+
|
|
44
|
+
1. **Does it look like a template?** → Change composition, hierarchy, or type — not just color.
|
|
45
|
+
2. **Could this belong to hundreds of unrelated products?** → Add product-specific decisions.
|
|
46
|
+
3. **Are decorative effects louder than the content?** → Remove effects until content wins.
|
|
47
|
+
4. **Are all the cards identical?** → Rank them, or stop using cards.
|
|
48
|
+
5. **Is everything rounded?** → Rebuild the radius system.
|
|
49
|
+
6. **Is it purple/blue with a gradient?** → Rebuild the palette from the product.
|
|
50
|
+
7. **Does every element animate?** → Cut motion to what explains change.
|
|
51
|
+
8. **Does every section have the same structure?** → Vary deliberately.
|
|
52
|
+
9. **Would removing this element hurt?** → If no, remove it.
|
|
53
|
+
10. **Can a stranger tell what the product does in five seconds?** → If no, the design is failing its first job.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## The originality floor
|
|
58
|
+
|
|
59
|
+
Every interface needs at least **two decisions no competitor's site would share**. Usually one structural and one expressive:
|
|
60
|
+
|
|
61
|
+
- a layout shaped by the actual workflow or data, not by a grid preset
|
|
62
|
+
- a color taken from the physical product, material, domain, or existing brand
|
|
63
|
+
- a type pairing with a reason from the sector's own visual history
|
|
64
|
+
- a signature component that only this product needs
|
|
65
|
+
- a density chosen for how these users actually work
|
|
66
|
+
- a real artifact — screenshot, chart, map, document, photograph — instead of decoration
|
|
67
|
+
|
|
68
|
+
Without those, the direction is a costume.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Self-check before shipping
|
|
73
|
+
|
|
74
|
+
Ask: *if this screenshot appeared in a feed with no caption, would anyone guess which product it belongs to?* If not, go back to the originality floor.
|