super-ux 0.4.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 CHANGED
@@ -4,6 +4,16 @@ 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
+
7
17
  ## [0.4.0] - 2026-07-19
8
18
 
9
19
  ### Added
package/README.md CHANGED
@@ -80,9 +80,10 @@ methods below.
80
80
  npx super-ux
81
81
  ```
82
82
 
83
- Menu: skills for any of 70+ agents (delegates to the `skills` CLI picker
84
- choose agents and global/project there), Cursor rules into a project, or the
85
- Claude Code plugin user-globally.
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.
86
87
 
87
88
  ### Cursor
88
89
 
package/bin/super-ux.js CHANGED
@@ -2,9 +2,11 @@
2
2
  /*
3
3
  * super-ux installer CLI.
4
4
  *
5
- * No arguments: interactive menu (skills for any agent via the `skills` CLI
6
- * picker, Cursor rules into a project, Claude Code plugin user-globally).
7
- * Flags keep the non-interactive paths: --cursor [dir] [--force].
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].
8
10
  */
9
11
  'use strict';
10
12
 
@@ -16,15 +18,21 @@ const { spawnSync } = require('child_process');
16
18
  const ROOT = path.resolve(__dirname, '..');
17
19
  const REPO = 'ssheleg/super-ux';
18
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
+ ];
26
+
19
27
  function usage() {
20
28
  console.log(`super-ux installer
21
29
 
22
30
  Usage:
23
- npx super-ux interactive menu
31
+ npx super-ux interactive menu (multi-select)
24
32
  npx super-ux --cursor [project-dir] [--force] Cursor rules, non-interactive
25
33
  npx super-ux --help
26
34
 
27
- Menu options (also available directly):
35
+ Menu items (select any combination, 'a' = all):
28
36
  1. Skills for any AI agent (Claude Code, Codex, Cursor, 70+) — delegates to
29
37
  'npx skills add ${REPO}' with its agent/global/project picker.
30
38
  2. Cursor rules: cursor/rules/*.mdc -> <project>/.cursor/rules/ plus the
@@ -85,12 +93,13 @@ function run(cmd, args) {
85
93
  }
86
94
 
87
95
  function installSkillsCli() {
88
- console.log(`Delegating to the skills CLI (agent/global/project picker)...`);
96
+ console.log(`\n--- Skills for any agent: delegating to the skills CLI picker ---`);
89
97
  const status = run('npx', ['--yes', 'skills', 'add', REPO]);
90
- if (status !== 'ok') fail(`'npx skills add ${REPO}' ${status}`);
98
+ if (status !== 'ok') console.error(`warning: 'npx skills add ${REPO}' ${status}`);
91
99
  }
92
100
 
93
101
  function installClaudePlugin() {
102
+ console.log(`\n--- Claude Code plugin ---`);
94
103
  const probe = spawnSync('claude', ['--version'], { stdio: 'ignore' });
95
104
  if (probe.error && probe.error.code === 'ENOENT') {
96
105
  console.log(`claude CLI not found. Run inside Claude Code instead:
@@ -104,14 +113,13 @@ function installClaudePlugin() {
104
113
  if (run('claude', ['plugin', 'install', 'super-ux@super-ux']) === 'ok') {
105
114
  console.log('Claude Code plugin installed (scope: user). Restart sessions to pick it up; then run /ux in any project.');
106
115
  } else {
107
- fail('claude plugin install failed — see output above');
116
+ console.error('warning: claude plugin install failed — see output above');
108
117
  }
109
118
  }
110
119
 
111
120
  function makePrompter() {
112
121
  // A persistent 'line' listener with a buffer: with piped stdin, lines that
113
- // arrive between two questions are kept instead of being lost (which is
114
- // what plain sequential rl.question() does).
122
+ // arrive between two questions are kept instead of being lost.
115
123
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
116
124
  const buffered = [];
117
125
  let pending = null;
@@ -148,30 +156,131 @@ function makePrompter() {
148
156
  };
149
157
  }
150
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
+
151
248
  async function menu() {
152
- const prompter = makePrompter();
153
- console.log(`super-ux scenario-driven UI development. What do you want to install?
154
-
155
- 1) Skills for any AI agent (Claude Code, Codex, Cursor, 70+ interactive picker)
156
- 2) Cursor rules (always-on hard rule + docs/ux skeleton) into a project
157
- 3) Claude Code plugin (skills + /ux commands, user-global)
158
- q) Quit
159
- `);
160
- const choice = (await prompter.ask('Choice [1/2/3/q]: ')).trim().toLowerCase();
161
- if (choice === '1') {
162
- prompter.close();
163
- installSkillsCli();
164
- } else if (choice === '2') {
165
- const dir = (await prompter.ask('Project directory [.]: ')).trim() || '.';
166
- prompter.close();
167
- installCursor(path.resolve(dir), false);
168
- } else if (choice === '3') {
169
- prompter.close();
170
- installClaudePlugin();
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);
171
258
  } else {
172
- prompter.close();
173
- if (choice !== 'q' && choice !== '') fail(`unknown choice '${choice}'`);
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);
174
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();
175
284
  }
176
285
 
177
286
  function main() {
package/package.json CHANGED
@@ -1,13 +1,33 @@
1
1
  {
2
2
  "name": "super-ux",
3
- "version": "0.4.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": { "super-ux": "bin/super-ux.js" },
6
- "files": ["bin", "cursor", "templates", "README.md", "LICENSE", "CHANGELOG.md"],
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": { "node": ">=16" },
12
- "keywords": ["ux", "scenarios", "audit", "ui", "user-flows", "scenario-driven", "claude-code", "cursor"]
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
  }