super-ux 0.3.0 → 0.5.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/CHANGELOG.md +24 -0
- package/README.md +29 -3
- package/bin/super-ux.js +230 -16
- package/package.json +25 -5
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,30 @@ All notable changes to this project are documented in this file. The format
|
|
|
4
4
|
follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions
|
|
5
5
|
follow [SemVer](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.5.0] - 2026-07-19
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Installer menu is now a real multi-select: arrow keys / j k to move, space
|
|
12
|
+
or number to toggle, `a` selects all three targets at once, enter installs
|
|
13
|
+
the whole selection in one run (own questions asked up front, the external
|
|
14
|
+
skills-CLI picker runs last). Non-TTY stdin gets a text fallback
|
|
15
|
+
(`1,3` / `all` / `q`). Zero dependencies (stdlib raw-mode).
|
|
16
|
+
|
|
17
|
+
## [0.4.0] - 2026-07-19
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- Interactive installer menu: bare `npx super-ux` now offers (1) skills for
|
|
22
|
+
any of 70+ agents via the `skills` CLI picker, (2) Cursor rules into a
|
|
23
|
+
project, (3) the Claude Code plugin user-globally (runs the `claude plugin`
|
|
24
|
+
CLI when available). Flag paths (`--cursor [dir] [--force]`) unchanged.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
|
|
28
|
+
- Prompt handling with piped stdin (persistent line buffer instead of
|
|
29
|
+
sequential `rl.question`, which dropped pre-buffered answers).
|
|
30
|
+
|
|
7
31
|
## [0.3.0] - 2026-07-19
|
|
8
32
|
|
|
9
33
|
### Added
|
package/README.md
CHANGED
|
@@ -60,13 +60,39 @@ Then in your project: `/ux`. That's it — it installs the hard rule, seeds
|
|
|
60
60
|
`docs/ux/`, builds the scenario base if empty, and on every later run just
|
|
61
61
|
reports status and the next action.
|
|
62
62
|
|
|
63
|
+
### Any agent via the skills CLI (70+ agents)
|
|
64
|
+
|
|
65
|
+
```sh
|
|
66
|
+
npx skills add ssheleg/super-ux # both skills, current project
|
|
67
|
+
npx skills add ssheleg/super-ux -g # user-global
|
|
68
|
+
npx skills add ssheleg/super-ux --skill ux-audit # one skill
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
[vercel-labs/skills](https://github.com/vercel-labs/skills) discovers the
|
|
72
|
+
skills through this repo's marketplace manifest and installs them for Claude
|
|
73
|
+
Code, Cursor, Codex, OpenCode and others. Note: this installs the two skills
|
|
74
|
+
only — the `/ux` commands and the Cursor always-on hard rule come with the
|
|
75
|
+
methods below.
|
|
76
|
+
|
|
77
|
+
### Interactive (pick agent + scope)
|
|
78
|
+
|
|
79
|
+
```sh
|
|
80
|
+
npx super-ux
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Multi-select menu (space to toggle, `a` = everything at once, enter to
|
|
84
|
+
install): skills for any of 70+ agents (delegates to the `skills` CLI picker
|
|
85
|
+
— choose agents and global/project there), Cursor rules into a project, and
|
|
86
|
+
the Claude Code plugin user-globally — any combination in one run.
|
|
87
|
+
|
|
63
88
|
### Cursor
|
|
64
89
|
|
|
65
90
|
```sh
|
|
66
|
-
npx
|
|
91
|
+
npx super-ux --cursor /path/to/your/project
|
|
67
92
|
```
|
|
68
93
|
|
|
69
|
-
(
|
|
94
|
+
(also works: `npx github:ssheleg/super-ux --cursor <dir>` straight from the
|
|
95
|
+
repo, or clone and run `./install.sh --cursor <dir>` — same behavior.) Copies the
|
|
70
96
|
three rules into `.cursor/rules/` and seeds `docs/ux/scenarios.md`. An
|
|
71
97
|
existing scenario base is never overwritten; re-run with `--force` to update
|
|
72
98
|
rules after a new release.
|
|
@@ -100,7 +126,7 @@ is semver; bump `marketplace.json` + `plugin.json` + `CHANGELOG.md` together
|
|
|
100
126
|
чек-листом для регулярных аудитов кода (`/ux-audit`) с вердиктами
|
|
101
127
|
PASS/PARTIAL/FAIL/BLOCKED и доказательствами `file:line`. Установка: в
|
|
102
128
|
Claude Code — `/plugin marketplace add ssheleg/super-ux`, в Cursor —
|
|
103
|
-
`npx
|
|
129
|
+
`npx super-ux --cursor <проект>`. Дальше одна команда — `/ux`: сама ставит
|
|
104
130
|
правило и базу, а при повторных запусках показывает статус и следующий шаг.
|
|
105
131
|
|
|
106
132
|
## License
|
package/bin/super-ux.js
CHANGED
|
@@ -1,33 +1,46 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/*
|
|
3
3
|
* super-ux installer CLI.
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
*
|
|
5
|
+
* No arguments: interactive multi-select menu (arrow keys + space, `a` for
|
|
6
|
+
* all) covering: skills for any agent via the `skills` CLI picker, Cursor
|
|
7
|
+
* rules into a project, Claude Code plugin user-globally. Non-TTY stdin gets
|
|
8
|
+
* a text fallback ("1,3" / "all"). Flags keep the non-interactive paths:
|
|
9
|
+
* --cursor [dir] [--force].
|
|
7
10
|
*/
|
|
8
11
|
'use strict';
|
|
9
12
|
|
|
10
13
|
const fs = require('fs');
|
|
11
14
|
const path = require('path');
|
|
15
|
+
const readline = require('readline');
|
|
16
|
+
const { spawnSync } = require('child_process');
|
|
12
17
|
|
|
13
18
|
const ROOT = path.resolve(__dirname, '..');
|
|
19
|
+
const REPO = 'ssheleg/super-ux';
|
|
20
|
+
|
|
21
|
+
const MENU_ITEMS = [
|
|
22
|
+
{ key: 'skills', label: 'Skills for any AI agent (Claude Code, Codex, Cursor, 70+ — opens agent picker)' },
|
|
23
|
+
{ key: 'cursor', label: 'Cursor rules (always-on hard rule + docs/ux skeleton) into a project' },
|
|
24
|
+
{ key: 'claude', label: 'Claude Code plugin (skills + /ux commands, user-global)' },
|
|
25
|
+
];
|
|
14
26
|
|
|
15
27
|
function usage() {
|
|
16
28
|
console.log(`super-ux installer
|
|
17
29
|
|
|
18
30
|
Usage:
|
|
19
|
-
npx
|
|
20
|
-
npx super-ux --cursor [project-dir] [--force]
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
+
npx super-ux interactive menu (multi-select)
|
|
32
|
+
npx super-ux --cursor [project-dir] [--force] Cursor rules, non-interactive
|
|
33
|
+
npx super-ux --help
|
|
34
|
+
|
|
35
|
+
Menu items (select any combination, 'a' = all):
|
|
36
|
+
1. Skills for any AI agent (Claude Code, Codex, Cursor, 70+) — delegates to
|
|
37
|
+
'npx skills add ${REPO}' with its agent/global/project picker.
|
|
38
|
+
2. Cursor rules: cursor/rules/*.mdc -> <project>/.cursor/rules/ plus the
|
|
39
|
+
docs/ux skeleton. Existing scenario base is NEVER overwritten; existing
|
|
40
|
+
rule files are skipped unless --force.
|
|
41
|
+
3. Claude Code plugin (skills + /ux commands, user-global) — runs
|
|
42
|
+
'claude plugin marketplace add ${REPO}' + 'claude plugin install' when
|
|
43
|
+
the claude CLI is available, otherwise prints the /plugin commands.`);
|
|
31
44
|
}
|
|
32
45
|
|
|
33
46
|
function fail(message) {
|
|
@@ -73,12 +86,213 @@ function installCursor(target, force) {
|
|
|
73
86
|
console.log(`done: ${installed} installed, ${skipped} skipped`);
|
|
74
87
|
}
|
|
75
88
|
|
|
89
|
+
function run(cmd, args) {
|
|
90
|
+
const result = spawnSync(cmd, args, { stdio: 'inherit' });
|
|
91
|
+
if (result.error && result.error.code === 'ENOENT') return 'missing';
|
|
92
|
+
return result.status === 0 ? 'ok' : 'failed';
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function installSkillsCli() {
|
|
96
|
+
console.log(`\n--- Skills for any agent: delegating to the skills CLI picker ---`);
|
|
97
|
+
const status = run('npx', ['--yes', 'skills', 'add', REPO]);
|
|
98
|
+
if (status !== 'ok') console.error(`warning: 'npx skills add ${REPO}' ${status}`);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function installClaudePlugin() {
|
|
102
|
+
console.log(`\n--- Claude Code plugin ---`);
|
|
103
|
+
const probe = spawnSync('claude', ['--version'], { stdio: 'ignore' });
|
|
104
|
+
if (probe.error && probe.error.code === 'ENOENT') {
|
|
105
|
+
console.log(`claude CLI not found. Run inside Claude Code instead:
|
|
106
|
+
/plugin marketplace add ${REPO}
|
|
107
|
+
/plugin install super-ux@super-ux`);
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
if (run('claude', ['plugin', 'marketplace', 'add', REPO]) !== 'ok') {
|
|
111
|
+
console.log('(marketplace may already be added — continuing)');
|
|
112
|
+
}
|
|
113
|
+
if (run('claude', ['plugin', 'install', 'super-ux@super-ux']) === 'ok') {
|
|
114
|
+
console.log('Claude Code plugin installed (scope: user). Restart sessions to pick it up; then run /ux in any project.');
|
|
115
|
+
} else {
|
|
116
|
+
console.error('warning: claude plugin install failed — see output above');
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function makePrompter() {
|
|
121
|
+
// A persistent 'line' listener with a buffer: with piped stdin, lines that
|
|
122
|
+
// arrive between two questions are kept instead of being lost.
|
|
123
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
124
|
+
const buffered = [];
|
|
125
|
+
let pending = null;
|
|
126
|
+
let closed = false;
|
|
127
|
+
rl.on('line', (line) => {
|
|
128
|
+
if (pending) {
|
|
129
|
+
const resolve = pending;
|
|
130
|
+
pending = null;
|
|
131
|
+
resolve(line);
|
|
132
|
+
} else {
|
|
133
|
+
buffered.push(line);
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
rl.on('close', () => {
|
|
137
|
+
closed = true;
|
|
138
|
+
if (pending) {
|
|
139
|
+
const resolve = pending;
|
|
140
|
+
pending = null;
|
|
141
|
+
resolve('');
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
return {
|
|
145
|
+
ask(question) {
|
|
146
|
+
process.stdout.write(question);
|
|
147
|
+
if (buffered.length > 0) return Promise.resolve(buffered.shift());
|
|
148
|
+
if (closed) return Promise.resolve('');
|
|
149
|
+
return new Promise((resolve) => {
|
|
150
|
+
pending = resolve;
|
|
151
|
+
});
|
|
152
|
+
},
|
|
153
|
+
close() {
|
|
154
|
+
rl.close();
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
function parseSelection(input, count) {
|
|
160
|
+
const value = input.trim().toLowerCase();
|
|
161
|
+
if (value === '' || value === 'q' || value === 'quit') return [];
|
|
162
|
+
if (value === 'a' || value === 'all' || value === '*') {
|
|
163
|
+
return Array.from({ length: count }, (_, i) => i);
|
|
164
|
+
}
|
|
165
|
+
const picked = new Set();
|
|
166
|
+
for (const part of value.split(/[\s,]+/)) {
|
|
167
|
+
if (part === '') continue;
|
|
168
|
+
const n = Number(part);
|
|
169
|
+
if (!Number.isInteger(n) || n < 1 || n > count) return null;
|
|
170
|
+
picked.add(n - 1);
|
|
171
|
+
}
|
|
172
|
+
return [...picked].sort();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function selectInteractive(items) {
|
|
176
|
+
// Raw-mode checkbox list: up/down or j/k move, space or 1..9 toggle,
|
|
177
|
+
// a = toggle all, enter = confirm, q/esc/ctrl+c = quit.
|
|
178
|
+
return new Promise((resolve) => {
|
|
179
|
+
const selected = new Set();
|
|
180
|
+
let cursor = 0;
|
|
181
|
+
let rendered = false;
|
|
182
|
+
|
|
183
|
+
const line = (i) =>
|
|
184
|
+
`${i === cursor ? '❯' : ' '} ${selected.has(i) ? '◉' : '◯'} ${i + 1}) ${items[i].label}`;
|
|
185
|
+
|
|
186
|
+
const render = () => {
|
|
187
|
+
if (rendered) process.stdout.write(`\x1b[${items.length + 1}A`);
|
|
188
|
+
for (let i = 0; i < items.length; i += 1) {
|
|
189
|
+
process.stdout.write(`\x1b[2K${line(i)}\n`);
|
|
190
|
+
}
|
|
191
|
+
process.stdout.write(
|
|
192
|
+
'\x1b[2K ↑/↓ move · space/number toggle · a all · enter confirm · q quit\n'
|
|
193
|
+
);
|
|
194
|
+
rendered = true;
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const finish = (result) => {
|
|
198
|
+
process.stdin.setRawMode(false);
|
|
199
|
+
process.stdin.pause();
|
|
200
|
+
process.stdin.removeListener('keypress', onKeypress);
|
|
201
|
+
resolve(result);
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const onKeypress = (str, key) => {
|
|
205
|
+
const name = key && key.name;
|
|
206
|
+
if ((key && key.ctrl && name === 'c') || name === 'escape' || str === 'q') {
|
|
207
|
+
finish([]);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (name === 'up' || str === 'k') cursor = (cursor - 1 + items.length) % items.length;
|
|
211
|
+
else if (name === 'down' || str === 'j') cursor = (cursor + 1) % items.length;
|
|
212
|
+
else if (name === 'space') {
|
|
213
|
+
if (selected.has(cursor)) selected.delete(cursor);
|
|
214
|
+
else selected.add(cursor);
|
|
215
|
+
} else if (str === 'a') {
|
|
216
|
+
if (selected.size === items.length) selected.clear();
|
|
217
|
+
else for (let i = 0; i < items.length; i += 1) selected.add(i);
|
|
218
|
+
} else if (str && /^[1-9]$/.test(str) && Number(str) <= items.length) {
|
|
219
|
+
const idx = Number(str) - 1;
|
|
220
|
+
if (selected.has(idx)) selected.delete(idx);
|
|
221
|
+
else selected.add(idx);
|
|
222
|
+
cursor = idx;
|
|
223
|
+
} else if (name === 'return') {
|
|
224
|
+
finish([...selected].sort());
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
render();
|
|
228
|
+
};
|
|
229
|
+
|
|
230
|
+
readline.emitKeypressEvents(process.stdin);
|
|
231
|
+
process.stdin.setRawMode(true);
|
|
232
|
+
process.stdin.resume();
|
|
233
|
+
process.stdin.on('keypress', onKeypress);
|
|
234
|
+
render();
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
async function selectFallback(items, prompter) {
|
|
239
|
+
for (let i = 0; i < items.length; i += 1) {
|
|
240
|
+
console.log(` ${i + 1}) ${items[i].label}`);
|
|
241
|
+
}
|
|
242
|
+
const answer = await prompter.ask(`Select [e.g. 1,3 | all | q]: `);
|
|
243
|
+
const picked = parseSelection(answer, items.length);
|
|
244
|
+
if (picked === null) fail(`invalid selection '${answer.trim()}'`);
|
|
245
|
+
return picked;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function menu() {
|
|
249
|
+
console.log('super-ux — scenario-driven UI development. Select what to install:\n');
|
|
250
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
251
|
+
|
|
252
|
+
// ONE prompter for the whole flow: with piped stdin, all pending lines are
|
|
253
|
+
// buffered by its persistent listener; a second prompter would lose them.
|
|
254
|
+
let prompter = null;
|
|
255
|
+
let picked;
|
|
256
|
+
if (interactive) {
|
|
257
|
+
picked = await selectInteractive(MENU_ITEMS);
|
|
258
|
+
} else {
|
|
259
|
+
prompter = makePrompter();
|
|
260
|
+
picked = await selectFallback(MENU_ITEMS, prompter);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (picked.length === 0) {
|
|
264
|
+
if (prompter) prompter.close();
|
|
265
|
+
console.log('Nothing selected.');
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const keys = picked.map((i) => MENU_ITEMS[i].key);
|
|
270
|
+
|
|
271
|
+
// Gather all our own questions BEFORE running anything, so they don't
|
|
272
|
+
// interleave with the external skills-CLI picker.
|
|
273
|
+
let cursorDir = null;
|
|
274
|
+
if (keys.includes('cursor')) {
|
|
275
|
+
if (!prompter) prompter = makePrompter();
|
|
276
|
+
const dir = (await prompter.ask('Cursor rules — project directory [.]: ')).trim() || '.';
|
|
277
|
+
cursorDir = path.resolve(dir);
|
|
278
|
+
}
|
|
279
|
+
if (prompter) prompter.close();
|
|
280
|
+
|
|
281
|
+
if (keys.includes('cursor')) installCursor(cursorDir, false);
|
|
282
|
+
if (keys.includes('claude')) installClaudePlugin();
|
|
283
|
+
if (keys.includes('skills')) installSkillsCli();
|
|
284
|
+
}
|
|
285
|
+
|
|
76
286
|
function main() {
|
|
77
287
|
const args = process.argv.slice(2);
|
|
78
|
-
if (args
|
|
288
|
+
if (args[0] === '--help' || args[0] === '-h') {
|
|
79
289
|
usage();
|
|
80
290
|
return;
|
|
81
291
|
}
|
|
292
|
+
if (args.length === 0) {
|
|
293
|
+
menu();
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
82
296
|
if (args[0] !== '--cursor') {
|
|
83
297
|
console.error(`error: unknown mode '${args[0]}'`);
|
|
84
298
|
usage();
|
package/package.json
CHANGED
|
@@ -1,13 +1,33 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "super-ux",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Scenario-driven UI development for AI agents (Claude Code + Cursor): scenario base, scenario-first hard rule, evidence-backed UX audits. This package is the installer CLI.",
|
|
5
|
-
"bin": {
|
|
6
|
-
|
|
5
|
+
"bin": {
|
|
6
|
+
"super-ux": "bin/super-ux.js"
|
|
7
|
+
},
|
|
8
|
+
"files": [
|
|
9
|
+
"bin",
|
|
10
|
+
"cursor",
|
|
11
|
+
"templates",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE",
|
|
14
|
+
"CHANGELOG.md"
|
|
15
|
+
],
|
|
7
16
|
"repository": "github:ssheleg/super-ux",
|
|
8
17
|
"homepage": "https://github.com/ssheleg/super-ux",
|
|
9
18
|
"license": "MIT",
|
|
10
19
|
"author": "ssheleg",
|
|
11
|
-
"engines": {
|
|
12
|
-
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=16"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"ux",
|
|
25
|
+
"scenarios",
|
|
26
|
+
"audit",
|
|
27
|
+
"ui",
|
|
28
|
+
"user-flows",
|
|
29
|
+
"scenario-driven",
|
|
30
|
+
"claude-code",
|
|
31
|
+
"cursor"
|
|
32
|
+
]
|
|
13
33
|
}
|