@ericrisco/rsc 0.1.5 โ†’ 0.1.6

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 CHANGED
@@ -80,13 +80,15 @@ assistant proposes new skills on its own from then on.
80
80
 
81
81
  ```
82
82
  $ rsc
83
- ๐Ÿ‘‹ rsc โ€” the skill catalog for your assistant.
83
+ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ† animated gradient wordmark
84
+ โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•
85
+ โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘
86
+ 231 skills ยท one CLI ยท zero bloat
84
87
 
85
88
  What do you want to do? โ†‘โ†“ move ยท enter select
86
89
  โฏ Base install โ€” the essentials (orient + suggest + harness + init)
87
90
  Base + Spec-Driven Development โ€” specify โ†’ plan โ†’ implement โ†’ ship
88
91
  Pick skills by hand, by area
89
- Describe my project and let rsc choose
90
92
  ```
91
93
 
92
94
  Pick **by area** and you get a checkbox list โ€” **โ†‘โ†“ to move, space to toggle,
package/manifest.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.1.5",
2
+ "version": "0.1.6",
3
3
  "counts": {
4
4
  "skills": 231
5
5
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ericrisco/rsc",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Eric Risco's agent-skills catalog as a granular, self-recommending CLI installer.",
5
5
  "type": "module",
6
6
  "bin": {
package/scripts/lib/ui.js CHANGED
@@ -1,6 +1,9 @@
1
- import { createInterface, emitKeypressEvents } from 'node:readline';
1
+ import { createInterface } from 'node:readline/promises';
2
+ import { emitKeypressEvents } from 'node:readline';
2
3
  import { stdin, stdout } from 'node:process';
3
4
 
5
+ const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
6
+
4
7
  export async function ask(question) {
5
8
  const rl = createInterface({ input: stdin, output: stdout });
6
9
  const a = await rl.question(question);
@@ -33,20 +36,52 @@ const C = {
33
36
  green: (s) => `\x1b[32m${s}\x1b[39m`,
34
37
  };
35
38
 
36
- // ASCII wordmark shown at the top of the wizard.
37
- export function banner() {
38
- const tty = Boolean(stdout.isTTY);
39
- const art = [
40
- '',
41
- ' โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—',
42
- ' โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•',
43
- ' โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ ',
44
- ' โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ•šโ•โ•โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ ',
45
- ' โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—',
46
- ' โ•šโ•โ• โ•šโ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•โ•โ•',
47
- ];
48
- for (const l of art) say(tty ? C.cyan(l) : l);
49
- say((tty ? C.dim(' 231 skills ยท one CLI ยท zero bloat') : ' 231 skills ยท one CLI ยท zero bloat'));
39
+ const ART = [
40
+ ' โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•— โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—',
41
+ ' โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•โ–ˆโ–ˆโ•”โ•โ•โ•โ•โ•',
42
+ ' โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•”โ•โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—โ–ˆโ–ˆโ•‘ ',
43
+ ' โ–ˆโ–ˆโ•”โ•โ•โ–ˆโ–ˆโ•—โ•šโ•โ•โ•โ•โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ•‘ ',
44
+ ' โ–ˆโ–ˆโ•‘ โ–ˆโ–ˆโ•‘โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•‘โ•šโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ•—',
45
+ ' โ•šโ•โ• โ•šโ•โ•โ•šโ•โ•โ•โ•โ•โ•โ• โ•šโ•โ•โ•โ•โ•โ•',
46
+ ];
47
+
48
+ // Vertical truecolor gradient (sky โ†’ violet), phase-shiftable for the wave.
49
+ function gradientLine(text, row, phase) {
50
+ const a = [56, 189, 248]; const b = [167, 139, 250];
51
+ const t = ((row + phase) % ART.length) / (ART.length - 1);
52
+ const r = Math.round(a[0] + (b[0] - a[0]) * t);
53
+ const g = Math.round(a[1] + (b[1] - a[1]) * t);
54
+ const bl = Math.round(a[2] + (b[2] - a[2]) * t);
55
+ return `\x1b[38;2;${r};${g};${bl}m${text}\x1b[39m`;
56
+ }
57
+
58
+ // Animated ASCII wordmark โ€” the "wooow". Static plain text when not a TTY.
59
+ export async function banner() {
60
+ if (!stdout.isTTY) {
61
+ say('');
62
+ for (const l of ART) say(l);
63
+ say(' 231 skills ยท one CLI ยท zero bloat');
64
+ return;
65
+ }
66
+ const W = Math.max(...ART.map((l) => l.length));
67
+ stdout.write('\x1b[?25l'); // hide cursor
68
+ say('');
69
+ // 1) letters slide in left โ†’ right, column by column
70
+ let first = true;
71
+ for (let w = 1; w <= W; w++) {
72
+ if (!first) stdout.write(`\x1b[${ART.length}A`);
73
+ first = false;
74
+ stdout.write(ART.map((l, i) => `\x1b[2K${gradientLine(l.slice(0, w), i, 0)}`).join('\n') + '\n');
75
+ await sleep(20);
76
+ }
77
+ // 2) one flowing color wave to settle
78
+ for (let phase = 1; phase <= ART.length; phase++) {
79
+ stdout.write(`\x1b[${ART.length}A`);
80
+ stdout.write(ART.map((l, i) => `\x1b[2K${gradientLine(l, i, phase)}`).join('\n') + '\n');
81
+ await sleep(45);
82
+ }
83
+ say(C.dim(' 231 skills ยท one CLI ยท zero bloat'));
84
+ stdout.write('\x1b[?25h'); // show cursor
50
85
  }
51
86
 
52
87
  // Repaint a fixed block of lines in place (cursor ends just below the block).
@@ -142,6 +177,18 @@ export async function select(question, options) {
142
177
  return byKey ? byKey.key : null;
143
178
  }
144
179
 
180
+ // Public: yes/no confirmation. Arrow TUI when interactive, typed prompt otherwise.
181
+ export async function confirm(question) {
182
+ if (interactive()) {
183
+ const k = await tuiSelect(question, [
184
+ { key: 'yes', label: 'Yes, install it' },
185
+ { key: 'no', label: 'No, cancel' },
186
+ ]);
187
+ return k === 'yes';
188
+ }
189
+ return yes(await ask(`${question} (yes / no) > `));
190
+ }
191
+
145
192
  // Public: multi-select. items: array of strings or { id, label }. Returns ids.
146
193
  export async function pickFrom(title, items) {
147
194
  const norm = items.map((it) => (typeof it === 'string' ? { id: it, label: it } : it));
package/scripts/rsc.js CHANGED
@@ -6,7 +6,7 @@ import { rank } from './consult.js';
6
6
  import { expandRecommends, toOutcomes, hasOutcome } from './lib/recommend.js';
7
7
  import { applyInstall, listInstalled, uninstall } from './install-apply.js';
8
8
  import { doctor } from './doctor.js';
9
- import { ask, say, yes, select, pickFrom, banner } from './lib/ui.js';
9
+ import { say, select, pickFrom, banner, confirm } from './lib/ui.js';
10
10
  import { refreshRegistry, registryStatus } from './lib/registry.js';
11
11
  import { DOMAINS } from './lib/domains.js';
12
12
 
@@ -45,19 +45,6 @@ async function manualSelect() {
45
45
  return [...chosen];
46
46
  }
47
47
 
48
- // Describe-your-project flow: rsc reads repo + words and proposes skills.
49
- async function describeFlow() {
50
- const goal = await ask('\nTell me in one sentence what you want to build or run:\n> ');
51
- const ids = await recommendIds(goal, { labeledOnly: true });
52
- if (!ids.length) {
53
- say("I'm not sure. Try describing it in more detail, or pick by hand (option 3).");
54
- return [];
55
- }
56
- say('\nI recommend installing:');
57
- for (const o of toOutcomes(ids)) say(` โ€ข ${o.label}`);
58
- return ids;
59
- }
60
-
61
48
  // After installing, remind the user how to actually start โ€” per IDE โ€” and that
62
49
  // rsc keeps recommending skills as they work. The harness/SDD *init* runs INSIDE
63
50
  // the assistant (with the user present), never blindly from this CLI.
@@ -94,20 +81,18 @@ function printNextSteps(target, ids) {
94
81
 
95
82
  async function wizard() {
96
83
  const m = loadManifest();
97
- banner();
84
+ await banner();
98
85
  say(' the skill catalog for your assistant (Claude Code ยท Cursor ยท Codex ยท Gemini)\n');
99
86
  const choice = await select('What do you want to do?', [
100
87
  { key: 'base', label: 'Base install โ€” the essentials (orient + suggest + harness + init)' },
101
88
  { key: 'sdd', label: 'Base + Spec-Driven Development โ€” the specify โ†’ plan โ†’ implement โ†’ ship flow' },
102
89
  { key: 'manual', label: 'Pick skills by hand, by area' },
103
- { key: 'describe', label: 'Describe my project and let rsc choose' },
104
90
  ]);
105
91
 
106
92
  let ids = [];
107
93
  if (choice === 'base') ids = skillsForProfile(m, 'minimal');
108
94
  else if (choice === 'sdd') ids = skillsForProfile(m, 'core');
109
95
  else if (choice === 'manual') ids = await manualSelect();
110
- else if (choice === 'describe') ids = await describeFlow();
111
96
  else { say("Didn't catch that. Run again: npx @ericrisco/rsc"); return; }
112
97
 
113
98
  // The floor is always installed: the compass + the detector.
@@ -120,7 +105,7 @@ async function wizard() {
120
105
  const target = detectTarget();
121
106
  say(`\nI'll install ${ids.length} skills into ${target}:`);
122
107
  say(' ' + ids.join(', '));
123
- if (!yes(await ask('\nInstall it? (yes / no) > '))) {
108
+ if (!(await confirm('Install it?'))) {
124
109
  say('No problem. Anytime: npx @ericrisco/rsc');
125
110
  return;
126
111
  }