@ericrisco/rsc 0.1.10 → 0.1.12
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 +9 -6
- package/manifest.json +1 -1
- package/package.json +1 -1
- package/scripts/lib/ui.js +64 -33
- package/scripts/rsc.js +45 -31
package/README.md
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
3
|
```
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
4
|
+
████████ ███████ ███████
|
|
5
|
+
██ ██ ██ ██ ██
|
|
6
|
+
██ ██ ██ ██
|
|
7
|
+
██ ██ ██ ██
|
|
8
|
+
████████ ██████ ██
|
|
9
|
+
██ ██ ██ ██
|
|
10
|
+
██ ██ ██ ██
|
|
11
|
+
██ ██ ██ ██ ██
|
|
12
|
+
██ ██ ███████ ███████
|
|
10
13
|
```
|
|
11
14
|
|
|
12
15
|
# `rsc` — 231 agent skills, one CLI, zero bloat
|
package/manifest.json
CHANGED
package/package.json
CHANGED
package/scripts/lib/ui.js
CHANGED
|
@@ -36,26 +36,42 @@ const C = {
|
|
|
36
36
|
green: (s) => `\x1b[32m${s}\x1b[39m`,
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
|
|
40
|
-
'
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
39
|
+
// Big block RSC wordmark, built from per-letter grids so rows always align.
|
|
40
|
+
const _R = ['████████', '██ ██', '██ ██', '██ ██', '████████', '██ ██', '██ ██', '██ ██', '██ ██'];
|
|
41
|
+
const _S = [' ███████', '██', '██', '██', ' ██████', ' ██', ' ██', ' ██', '███████'];
|
|
42
|
+
const _C = [' ███████', '██ ██', '██', '██', '██', '██', '██', '██ ██', ' ███████'];
|
|
43
|
+
const _padW = 9;
|
|
44
|
+
const _pad = (s) => s + ' '.repeat(Math.max(0, _padW - [...s].length));
|
|
45
|
+
const ART = _R.map((_, i) => ` ${_pad(_R[i])} ${_pad(_S[i])} ${_pad(_C[i])}`);
|
|
46
|
+
|
|
47
|
+
// HSL → RGB (s=1, l=0.6) for a true rainbow.
|
|
48
|
+
function hsl(h, s = 1, l = 0.6) {
|
|
49
|
+
const c = (1 - Math.abs(2 * l - 1)) * s;
|
|
50
|
+
const x = c * (1 - Math.abs(((h / 60) % 2) - 1));
|
|
51
|
+
const m = l - c / 2;
|
|
52
|
+
let r = 0; let g = 0; let b = 0;
|
|
53
|
+
if (h < 60) { r = c; g = x; } else if (h < 120) { r = x; g = c; }
|
|
54
|
+
else if (h < 180) { g = c; b = x; } else if (h < 240) { g = x; b = c; }
|
|
55
|
+
else if (h < 300) { r = x; b = c; } else { r = c; b = x; }
|
|
56
|
+
return [Math.round((r + m) * 255), Math.round((g + m) * 255), Math.round((b + m) * 255)];
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
//
|
|
59
|
+
// One frame: rainbow per character, diagonal phase, revealed up to `cols`.
|
|
60
|
+
function frame(phase, cols) {
|
|
61
|
+
return ART.map((line, r) => {
|
|
62
|
+
const chars = [...line];
|
|
63
|
+
let out = '';
|
|
64
|
+
for (let c = 0; c < chars.length && c < cols; c++) {
|
|
65
|
+
const ch = chars[c];
|
|
66
|
+
if (ch === ' ') { out += ' '; continue; }
|
|
67
|
+
const [rr, gg, bb] = hsl((c * 5 + r * 14 + phase * 20) % 360);
|
|
68
|
+
out += `\x1b[1;38;2;${rr};${gg};${bb}m${ch}`;
|
|
69
|
+
}
|
|
70
|
+
return `\x1b[2K${out}\x1b[0m`;
|
|
71
|
+
}).join('\n') + '\n';
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Animated ASCII wordmark — the exaggerated "WOW". Static plain text when not a TTY.
|
|
59
75
|
export async function banner() {
|
|
60
76
|
if (!stdout.isTTY) {
|
|
61
77
|
say('');
|
|
@@ -63,23 +79,36 @@ export async function banner() {
|
|
|
63
79
|
say(' 231 skills · one CLI · zero bloat');
|
|
64
80
|
return;
|
|
65
81
|
}
|
|
66
|
-
const
|
|
82
|
+
const rows = ART.length;
|
|
83
|
+
const W = Math.max(...ART.map((l) => [...l].length));
|
|
67
84
|
stdout.write('\x1b[?25l'); // hide cursor
|
|
68
85
|
say('');
|
|
69
|
-
// 1) letters slide in left → right
|
|
86
|
+
// 1) letters slide in left → right
|
|
70
87
|
let first = true;
|
|
71
|
-
for (let
|
|
72
|
-
if (!first) stdout.write(`\x1b[${
|
|
88
|
+
for (let cols = 2; cols <= W; cols += 2) {
|
|
89
|
+
if (!first) stdout.write(`\x1b[${rows}A`);
|
|
73
90
|
first = false;
|
|
74
|
-
stdout.write(
|
|
75
|
-
await sleep(
|
|
91
|
+
stdout.write(frame(0, cols));
|
|
92
|
+
await sleep(12);
|
|
93
|
+
}
|
|
94
|
+
// 2) flowing rainbow diagonal sweeps
|
|
95
|
+
for (let phase = 1; phase <= 30; phase++) {
|
|
96
|
+
stdout.write(`\x1b[${rows}A`);
|
|
97
|
+
stdout.write(frame(phase, W));
|
|
98
|
+
await sleep(26);
|
|
76
99
|
}
|
|
77
|
-
//
|
|
78
|
-
for (let
|
|
79
|
-
stdout.write(`\x1b[${
|
|
80
|
-
stdout.write(ART.map((l
|
|
81
|
-
await sleep(
|
|
100
|
+
// 3) double white flash — the pop
|
|
101
|
+
for (let f = 0; f < 2; f++) {
|
|
102
|
+
stdout.write(`\x1b[${rows}A`);
|
|
103
|
+
stdout.write(ART.map((l) => `\x1b[2K\x1b[1;97m${l}\x1b[0m`).join('\n') + '\n');
|
|
104
|
+
await sleep(60);
|
|
105
|
+
stdout.write(`\x1b[${rows}A`);
|
|
106
|
+
stdout.write(frame(15, W));
|
|
107
|
+
await sleep(60);
|
|
82
108
|
}
|
|
109
|
+
// settle on a final rainbow snapshot
|
|
110
|
+
stdout.write(`\x1b[${rows}A`);
|
|
111
|
+
stdout.write(frame(15, W));
|
|
83
112
|
say(C.dim(' 231 skills · one CLI · zero bloat'));
|
|
84
113
|
stdout.write('\x1b[?25h'); // show cursor
|
|
85
114
|
}
|
|
@@ -114,7 +143,7 @@ function tuiSelect(question, options) {
|
|
|
114
143
|
const paint = makePainter();
|
|
115
144
|
const render = () => paint([
|
|
116
145
|
C.bold(question),
|
|
117
|
-
C.dim(' ↑↓ move · enter select'),
|
|
146
|
+
C.dim(' ↑↓ move · enter select · esc back'),
|
|
118
147
|
...options.map((o, idx) =>
|
|
119
148
|
idx === i ? `${C.cyan('❯')} ${C.cyan(o.label)}` : ` ${o.label}`),
|
|
120
149
|
]);
|
|
@@ -123,7 +152,8 @@ function tuiSelect(question, options) {
|
|
|
123
152
|
if (key.name === 'up' || key.name === 'k') { i = (i - 1 + options.length) % options.length; render(); }
|
|
124
153
|
else if (key.name === 'down' || key.name === 'j') { i = (i + 1) % options.length; render(); }
|
|
125
154
|
else if (key.name === 'return') { stop(); resolve(options[i].key); }
|
|
126
|
-
else if (key.name === 'escape'
|
|
155
|
+
else if (key.name === 'escape') { stop(); resolve(null); } // back / cancel
|
|
156
|
+
else if (key.ctrl && key.name === 'c') { stop(); stdout.write('\n'); process.exit(130); } // hard quit
|
|
127
157
|
});
|
|
128
158
|
});
|
|
129
159
|
}
|
|
@@ -148,7 +178,7 @@ function tuiChecklist(title, items) {
|
|
|
148
178
|
const more = items.length > VISIBLE ? C.dim(` (${cur + 1}/${items.length})`) : '';
|
|
149
179
|
paint([
|
|
150
180
|
C.bold(title),
|
|
151
|
-
C.dim(' ↑↓ move · space toggle · a all · enter confirm') + more,
|
|
181
|
+
C.dim(' ↑↓ move · space toggle · a all · enter confirm · esc back') + more,
|
|
152
182
|
...rows,
|
|
153
183
|
]);
|
|
154
184
|
};
|
|
@@ -159,7 +189,8 @@ function tuiChecklist(title, items) {
|
|
|
159
189
|
else if (key.name === 'space') { sel.has(cur) ? sel.delete(cur) : sel.add(cur); render(); }
|
|
160
190
|
else if (str === 'a') { if (sel.size === items.length) sel.clear(); else items.forEach((_, idx) => sel.add(idx)); render(); }
|
|
161
191
|
else if (key.name === 'return') { stop(); resolve([...sel].sort((x, y) => x - y).map((idx) => items[idx].id)); }
|
|
162
|
-
else if (key.name === 'escape'
|
|
192
|
+
else if (key.name === 'escape') { stop(); resolve(null); } // back without changing
|
|
193
|
+
else if (key.ctrl && key.name === 'c') { stop(); stdout.write('\n'); process.exit(130); } // hard quit
|
|
163
194
|
});
|
|
164
195
|
});
|
|
165
196
|
}
|
package/scripts/rsc.js
CHANGED
|
@@ -31,15 +31,19 @@ async function recommendIds(query, { labeledOnly = false } = {}) {
|
|
|
31
31
|
}
|
|
32
32
|
|
|
33
33
|
// Pick skills by browsing the domains, accumulating across rounds.
|
|
34
|
+
// Returns the chosen skill ids, or null if the user backed out to the main menu.
|
|
34
35
|
async function manualSelect() {
|
|
35
36
|
const chosen = new Set();
|
|
36
37
|
for (;;) {
|
|
37
38
|
const opts = DOMAINS.map((d, i) => ({ key: String(i), label: `${d.title} (${d.ids.length})` }));
|
|
38
|
-
opts.push({ key: 'done', label: chosen.size ? `✅ Finish & install (${chosen.size} chosen)` : 'Finish
|
|
39
|
+
opts.push({ key: 'done', label: chosen.size ? `✅ Finish & install (${chosen.size} chosen)` : 'Finish (install nothing)' });
|
|
40
|
+
opts.push({ key: 'back', label: '← Back to the main menu' });
|
|
39
41
|
const k = await select('\nWhich area do you want to install skills from?', opts);
|
|
40
|
-
if (k === '
|
|
42
|
+
if (k === 'back' || k === null) return null; // esc or Back → main menu
|
|
43
|
+
if (k === 'done') break;
|
|
41
44
|
const d = DOMAINS[parseInt(k, 10)];
|
|
42
|
-
|
|
45
|
+
const picked = await pickFrom(`${d.title}:`, d.ids); // null = esc → leave this area unchanged
|
|
46
|
+
if (picked) picked.forEach((id) => chosen.add(id));
|
|
43
47
|
say(` → ${chosen.size} skills chosen so far.`);
|
|
44
48
|
}
|
|
45
49
|
return [...chosen];
|
|
@@ -54,6 +58,7 @@ async function selectAgents() {
|
|
|
54
58
|
label: `${t.label} (${t.hint})${t.id === detected ? ' ⟵ detected here' : ''}`,
|
|
55
59
|
}));
|
|
56
60
|
const chosen = await pickFrom('Which assistants do you want to install for? (space to toggle, a = all)', items);
|
|
61
|
+
if (chosen === null) return null; // esc → back to the main menu
|
|
57
62
|
return chosen.length ? chosen : [detected];
|
|
58
63
|
}
|
|
59
64
|
|
|
@@ -91,39 +96,48 @@ async function wizard() {
|
|
|
91
96
|
const m = loadManifest();
|
|
92
97
|
await banner();
|
|
93
98
|
say(' the skill catalog for your assistant (Claude Code · Codex · Cursor · Gemini · Antigravity)\n');
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
+
// Navigable loop: esc / "← Back" / "no" all return here instead of quitting.
|
|
100
|
+
for (;;) {
|
|
101
|
+
const choice = await select('What do you want to do?', [
|
|
102
|
+
{ key: 'base', label: 'Base install — the essentials (orient + suggest + harness + init)' },
|
|
103
|
+
{ key: 'sdd', label: 'Base + Spec-Driven Development — the specify → plan → implement → ship flow' },
|
|
104
|
+
{ key: 'manual', label: 'Pick skills by hand, by area' },
|
|
105
|
+
]);
|
|
106
|
+
if (choice === null) { say('\nOK — nothing installed. Anytime: npx @ericrisco/rsc'); return; }
|
|
99
107
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
108
|
+
let ids;
|
|
109
|
+
if (choice === 'base') ids = skillsForProfile(m, 'minimal');
|
|
110
|
+
else if (choice === 'sdd') ids = skillsForProfile(m, 'core');
|
|
111
|
+
else if (choice === 'manual') {
|
|
112
|
+
const picked = await manualSelect();
|
|
113
|
+
if (picked === null) continue; // backed out → re-show this menu
|
|
114
|
+
ids = picked;
|
|
115
|
+
} else continue;
|
|
105
116
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
117
|
+
// The floor is always installed: the compass + the detector.
|
|
118
|
+
ids = [...new Set(['orient', 'suggest', ...ids])];
|
|
119
|
+
if (ids.length <= 2 && choice !== 'base') {
|
|
120
|
+
say('\nNothing chosen — back to the menu.');
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
112
123
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
124
|
+
const targets = await selectAgents();
|
|
125
|
+
if (targets === null) continue; // esc in the assistant picker → back to menu
|
|
126
|
+
say(`\nI'll install ${ids.length} skills for: ${targets.join(', ')}`);
|
|
127
|
+
say(' ' + ids.join(', '));
|
|
128
|
+
say(' (real files live once in .rsc/skills/ — each assistant just links to them)');
|
|
129
|
+
if (!(await confirm('Install it?'))) {
|
|
130
|
+
say('Cancelled — back to the menu.');
|
|
131
|
+
continue; // "no" / esc → back to menu, not quit
|
|
132
|
+
}
|
|
133
|
+
for (const target of targets) {
|
|
134
|
+
await applyInstall({ skillIds: ids, target });
|
|
135
|
+
say(` ✅ ${target}`);
|
|
136
|
+
}
|
|
137
|
+
say(`\n✅ Installed ${ids.length} skills for ${targets.length} assistant(s).`);
|
|
138
|
+
printNextSteps(targets, ids);
|
|
119
139
|
return;
|
|
120
140
|
}
|
|
121
|
-
for (const target of targets) {
|
|
122
|
-
await applyInstall({ skillIds: ids, target });
|
|
123
|
-
say(` ✅ ${target}`);
|
|
124
|
-
}
|
|
125
|
-
say(`\n✅ Installed ${ids.length} skills for ${targets.length} assistant(s).`);
|
|
126
|
-
printNextSteps(targets, ids);
|
|
127
141
|
}
|
|
128
142
|
|
|
129
143
|
async function main() {
|