claude-agent-skills 1.3.3 → 1.3.5

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/lib/picker.js ADDED
@@ -0,0 +1,146 @@
1
+ import ansis from 'ansis';
2
+ import { skillColor, white, muted, success, brand } from './theme.js';
3
+
4
+ const UP = '\x1b[A';
5
+ const DOWN = '\x1b[B';
6
+ const SPACE = ' ';
7
+ const ENTER = '\r';
8
+ const CTRL_C = '\x03';
9
+ const ESC = '\x1b';
10
+
11
+ const clr = () => process.stdout.write('\x1b[2K');
12
+ const up = n => process.stdout.write(`\x1b[${n}A`);
13
+ const nl = () => process.stdout.write('\n');
14
+
15
+ function wordWrap(text, width) {
16
+ const words = text.split(' ');
17
+ const lines = [];
18
+ let line = '';
19
+ for (const w of words) {
20
+ if (line && line.length + 1 + w.length > width) { lines.push(line); line = w; }
21
+ else line = line ? `${line} ${w}` : w;
22
+ }
23
+ if (line) lines.push(line);
24
+ return lines;
25
+ }
26
+
27
+ function writeLine(s = '') {
28
+ clr();
29
+ process.stdout.write(s + '\n');
30
+ }
31
+
32
+ export async function skillPicker({ message, options }) {
33
+ const termW = process.stdout.columns || 100;
34
+ const termH = process.stdout.rows || 30;
35
+
36
+ const DESC_LINES = 3;
37
+ const FOOTER_LINES = 2;
38
+ const HEADER_LINES = 2;
39
+ const VISIBLE = Math.min(options.length, Math.max(6, termH - HEADER_LINES - 1 - DESC_LINES - FOOTER_LINES));
40
+
41
+ let cursor = 0;
42
+ let scrollTop = 0;
43
+ const sel = new Set();
44
+
45
+ function ensureVisible() {
46
+ if (cursor < scrollTop) scrollTop = cursor;
47
+ if (cursor >= scrollTop + VISIBLE) scrollTop = cursor - VISIBLE + 1;
48
+ }
49
+
50
+ function render(first = false) {
51
+ const totalLines = HEADER_LINES + VISIBLE + 1 + DESC_LINES + FOOTER_LINES;
52
+ if (!first) { up(totalLines); }
53
+
54
+ // Header
55
+ writeLine(brand('◆') + ' ' + white(message));
56
+ writeLine(muted('│'));
57
+
58
+ // Skill list
59
+ for (let i = 0; i < VISIBLE; i++) {
60
+ const idx = scrollTop + i;
61
+ if (idx >= options.length) { writeLine(muted('│')); continue; }
62
+ const opt = options[idx];
63
+ const focused = idx === cursor;
64
+ const checked = sel.has(idx);
65
+ const bullet = checked ? success('■') : muted('□');
66
+ const nameStr = focused
67
+ ? ansis.bold(skillColor(idx)(opt.label))
68
+ : skillColor(idx)(opt.label);
69
+ const cursor_ = focused ? brand('▶') : ' ';
70
+ writeLine(`${muted('│')} ${cursor_} ${bullet} ${nameStr}`);
71
+ }
72
+
73
+ // Separator + description panel
74
+ const divW = Math.min(termW - 2, 72);
75
+ writeLine(muted('├' + '─'.repeat(divW) + '┤'));
76
+
77
+ const focused = options[cursor];
78
+ const desc = focused?.description || '';
79
+ const descW = termW - 6;
80
+ const wrapped = desc ? wordWrap(desc, descW) : [];
81
+
82
+ for (let i = 0; i < DESC_LINES; i++) {
83
+ const line = wrapped[i] ?? '';
84
+ writeLine(muted('│ ') + white(line));
85
+ }
86
+
87
+ // Footer
88
+ const scrollHint = options.length > VISIBLE
89
+ ? muted(` ${scrollTop + 1}–${Math.min(scrollTop + VISIBLE, options.length)} of ${options.length}`)
90
+ : '';
91
+ writeLine(muted('│'));
92
+ writeLine(
93
+ muted(' ↑↓ navigate') + ' ' +
94
+ muted('space toggle') + ' ' +
95
+ muted('a = all') + ' ' +
96
+ success(`${sel.size} selected`) + ' ' +
97
+ muted('enter confirm') + ' ' +
98
+ muted('esc back') +
99
+ scrollHint
100
+ );
101
+ }
102
+
103
+ // Reserve space
104
+ process.stdout.write('\n'.repeat(HEADER_LINES + VISIBLE + 1 + DESC_LINES + FOOTER_LINES));
105
+ render(false);
106
+
107
+ return new Promise((resolve, reject) => {
108
+ process.stdin.setRawMode(true);
109
+ process.stdin.resume();
110
+ process.stdin.setEncoding('utf8');
111
+
112
+ function done(result) {
113
+ process.stdin.setRawMode(false);
114
+ process.stdin.pause();
115
+ process.stdin.removeAllListeners('data');
116
+ if (result !== null) {
117
+ nl();
118
+ process.stdout.write(success('◆') + ' ' + white(`${result.length} skill(s) selected`) + '\n');
119
+ }
120
+ resolve(result);
121
+ }
122
+
123
+ process.stdin.on('data', key => {
124
+ if (key === CTRL_C) { done(null); reject(Object.assign(new Error('cancel'), { isCancel: true })); return; }
125
+ if (key === ESC) { done(null); reject(Object.assign(new Error('cancel'), { isCancel: true })); return; }
126
+
127
+ if (key === UP) {
128
+ if (cursor > 0) { cursor--; ensureVisible(); }
129
+ } else if (key === DOWN) {
130
+ if (cursor < options.length - 1) { cursor++; ensureVisible(); }
131
+ } else if (key === SPACE) {
132
+ sel.has(cursor) ? sel.delete(cursor) : sel.add(cursor);
133
+ } else if (key === 'a' || key === 'A') {
134
+ sel.size === options.length
135
+ ? sel.clear()
136
+ : options.forEach((_, i) => sel.add(i));
137
+ } else if (key === ENTER) {
138
+ if (!sel.size) return;
139
+ done([...sel].sort((a, b) => a - b).map(i => options[i].value));
140
+ return;
141
+ }
142
+
143
+ render(false);
144
+ });
145
+ });
146
+ }
package/lib/prompts.js CHANGED
@@ -1,6 +1,7 @@
1
- import { select, multiselect, confirm, isCancel } from '@clack/prompts';
1
+ import { select, confirm, isCancel } from '@clack/prompts';
2
2
  import ansis from 'ansis';
3
3
  import { skillColor, white, muted, divider } from './theme.js';
4
+ import { skillPicker } from './picker.js';
4
5
 
5
6
  export class CliCancel extends Error {}
6
7
 
@@ -28,25 +29,17 @@ export async function pickSkills(names, flags = {}, descriptions = {}) {
28
29
  if (flags.all) return names;
29
30
  if (flags.skill?.length) return flags.skill;
30
31
 
31
- // Calculate description width from actual terminal columns.
32
- // prefix=4 (clack's "□ "), nameCol=32, divider=3 (" │ ")
33
- const termWidth = process.stdout.columns || 100;
34
- const descWidth = Math.max(20, termWidth - 4 - 32 - 3);
35
-
36
- function fitDesc(s) {
37
- if (!s) return '';
38
- return s.length <= descWidth ? s : s.slice(0, descWidth - 1) + '…';
32
+ try {
33
+ const result = await skillPicker({
34
+ message: 'Select skills',
35
+ options: names.map(n => ({ value: n, label: n, description: descriptions[n] || '' })),
36
+ });
37
+ if (result === null) throw new CliCancel();
38
+ return result;
39
+ } catch (e) {
40
+ if (e?.isCancel) throw new CliCancel();
41
+ throw e;
39
42
  }
40
-
41
- return guard(await multiselect({
42
- message: 'Select skills (space to toggle, a for all, enter to confirm)',
43
- options: names.map((n, i) => {
44
- const name = skillColor(i)(ansis.bold(n.padEnd(32)));
45
- const desc = descriptions[n] ? white(fitDesc(descriptions[n])) : '';
46
- return { value: n, label: name + divider + desc };
47
- }),
48
- required: true,
49
- }));
50
43
  }
51
44
 
52
45
  export async function pickLinkType(flags = {}) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-agent-skills",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
4
4
  "description": "Install and manage Pavi's Claude Code agent skills",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/skills.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schema_version": 1,
3
3
  "name": "claude-agent-skills",
4
- "version": "1.3.3",
4
+ "version": "1.3.5",
5
5
  "skills": [
6
6
  "ask-matt",
7
7
  "brainstorming",