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 +146 -0
- package/lib/prompts.js +12 -19
- package/package.json +1 -1
- package/skills.json +1 -1
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,
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
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