@ericrisco/rsc 0.1.11 → 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/manifest.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.1.11",
2
+ "version": "0.1.12",
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.11",
3
+ "version": "0.1.12",
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
@@ -143,7 +143,7 @@ function tuiSelect(question, options) {
143
143
  const paint = makePainter();
144
144
  const render = () => paint([
145
145
  C.bold(question),
146
- C.dim(' ↑↓ move · enter select'),
146
+ C.dim(' ↑↓ move · enter select · esc back'),
147
147
  ...options.map((o, idx) =>
148
148
  idx === i ? `${C.cyan('❯')} ${C.cyan(o.label)}` : ` ${o.label}`),
149
149
  ]);
@@ -152,7 +152,8 @@ function tuiSelect(question, options) {
152
152
  if (key.name === 'up' || key.name === 'k') { i = (i - 1 + options.length) % options.length; render(); }
153
153
  else if (key.name === 'down' || key.name === 'j') { i = (i + 1) % options.length; render(); }
154
154
  else if (key.name === 'return') { stop(); resolve(options[i].key); }
155
- else if (key.name === 'escape' || (key.ctrl && key.name === 'c')) { stop(); stdout.write('\n'); process.exit(130); }
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
156
157
  });
157
158
  });
158
159
  }
@@ -177,7 +178,7 @@ function tuiChecklist(title, items) {
177
178
  const more = items.length > VISIBLE ? C.dim(` (${cur + 1}/${items.length})`) : '';
178
179
  paint([
179
180
  C.bold(title),
180
- C.dim(' ↑↓ move · space toggle · a all · enter confirm') + more,
181
+ C.dim(' ↑↓ move · space toggle · a all · enter confirm · esc back') + more,
181
182
  ...rows,
182
183
  ]);
183
184
  };
@@ -188,7 +189,8 @@ function tuiChecklist(title, items) {
188
189
  else if (key.name === 'space') { sel.has(cur) ? sel.delete(cur) : sel.add(cur); render(); }
189
190
  else if (str === 'a') { if (sel.size === items.length) sel.clear(); else items.forEach((_, idx) => sel.add(idx)); render(); }
190
191
  else if (key.name === 'return') { stop(); resolve([...sel].sort((x, y) => x - y).map((idx) => items[idx].id)); }
191
- else if (key.name === 'escape' || (key.ctrl && key.name === 'c')) { stop(); stdout.write('\n'); process.exit(130); }
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
192
194
  });
193
195
  });
194
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 without choosing anything' });
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 === 'done' || k === null) break;
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
- (await pickFrom(`${d.title}:`, d.ids)).forEach((id) => chosen.add(id));
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
- const choice = await select('What do you want to do?', [
95
- { key: 'base', label: 'Base install — the essentials (orient + suggest + harness + init)' },
96
- { key: 'sdd', label: 'Base + Spec-Driven Development the specify → plan → implement → ship flow' },
97
- { key: 'manual', label: 'Pick skills by hand, by area' },
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
- let ids = [];
101
- if (choice === 'base') ids = skillsForProfile(m, 'minimal');
102
- else if (choice === 'sdd') ids = skillsForProfile(m, 'core');
103
- else if (choice === 'manual') ids = await manualSelect();
104
- else { say("Didn't catch that. Run again: npx @ericrisco/rsc"); return; }
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
- // The floor is always installed: the compass + the detector.
107
- ids = [...new Set(['orient', 'suggest', ...ids])];
108
- if (ids.length <= 2 && choice !== 'base') {
109
- say('\nNo skills were chosen. Anytime: npx @ericrisco/rsc');
110
- return;
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
- const targets = await selectAgents();
114
- say(`\nI'll install ${ids.length} skills for: ${targets.join(', ')}`);
115
- say(' ' + ids.join(', '));
116
- say(' (real files live once in .rsc/skills/ — each assistant just links to them)');
117
- if (!(await confirm('Install it?'))) {
118
- say('No problem. Anytime: npx @ericrisco/rsc');
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() {