claude-agent-skills 1.3.5 → 1.3.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/lib/picker.js +123 -97
- package/package.json +1 -1
- package/skills.json +1 -1
package/lib/picker.js
CHANGED
|
@@ -1,146 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom skill picker — grid layout, full description panel for focused skill.
|
|
3
|
+
* Navigation: ↑↓←→ space=toggle a=all enter=confirm esc=back
|
|
4
|
+
*/
|
|
1
5
|
import ansis from 'ansis';
|
|
2
|
-
import { skillColor, white, muted, success, brand } from './theme.js';
|
|
3
|
-
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
6
|
+
import { skillColor, white, muted, success, brand, warn } from './theme.js';
|
|
7
|
+
|
|
8
|
+
const KEY = {
|
|
9
|
+
UP: '\x1b[A',
|
|
10
|
+
DOWN: '\x1b[B',
|
|
11
|
+
RIGHT: '\x1b[C',
|
|
12
|
+
LEFT: '\x1b[D',
|
|
13
|
+
SPACE: ' ',
|
|
14
|
+
ENTER: '\r',
|
|
15
|
+
CTRL_C: '\x03',
|
|
16
|
+
ESC: '\x1b',
|
|
17
|
+
};
|
|
14
18
|
|
|
15
19
|
function wordWrap(text, width) {
|
|
16
20
|
const words = text.split(' ');
|
|
17
21
|
const lines = [];
|
|
18
|
-
let
|
|
22
|
+
let cur = '';
|
|
19
23
|
for (const w of words) {
|
|
20
|
-
if (
|
|
21
|
-
else
|
|
24
|
+
if (cur && cur.length + 1 + w.length > width) { lines.push(cur); cur = w; }
|
|
25
|
+
else cur = cur ? `${cur} ${w}` : w;
|
|
22
26
|
}
|
|
23
|
-
if (
|
|
27
|
+
if (cur) lines.push(cur);
|
|
24
28
|
return lines;
|
|
25
29
|
}
|
|
26
30
|
|
|
27
|
-
function writeLine(s = '') {
|
|
28
|
-
clr();
|
|
29
|
-
process.stdout.write(s + '\n');
|
|
30
|
-
}
|
|
31
|
-
|
|
32
31
|
export async function skillPicker({ message, options }) {
|
|
33
|
-
const termW
|
|
34
|
-
const termH
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
32
|
+
const termW = process.stdout.columns || 100;
|
|
33
|
+
const termH = process.stdout.rows || 40;
|
|
34
|
+
|
|
35
|
+
// Grid dimensions
|
|
36
|
+
const maxName = Math.max(...options.map(o => o.label.length));
|
|
37
|
+
const colW = maxName + 6; // □ name + padding
|
|
38
|
+
const numCols = Math.max(1, Math.floor((termW - 2) / colW));
|
|
39
|
+
const numRows = Math.ceil(options.length / numCols);
|
|
40
|
+
|
|
41
|
+
// Fixed layout heights
|
|
42
|
+
const HEADER = 2;
|
|
43
|
+
const GRID_H = numRows;
|
|
44
|
+
const SEP = 1;
|
|
45
|
+
const DESC_H = Math.max(4, Math.min(8, termH - HEADER - GRID_H - SEP - 3));
|
|
46
|
+
const FOOTER = 1;
|
|
47
|
+
const TOTAL = HEADER + GRID_H + SEP + DESC_H + FOOTER;
|
|
48
|
+
|
|
49
|
+
let cursor = 0;
|
|
50
|
+
const sel = new Set();
|
|
51
|
+
let lastLines = 0;
|
|
52
|
+
|
|
53
|
+
function clamp(c) {
|
|
54
|
+
return Math.max(0, Math.min(options.length - 1, c));
|
|
48
55
|
}
|
|
49
56
|
|
|
50
|
-
function
|
|
51
|
-
const
|
|
52
|
-
if (!first) { up(totalLines); }
|
|
57
|
+
function renderGrid() {
|
|
58
|
+
const lines = [];
|
|
53
59
|
|
|
54
60
|
// Header
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
61
|
+
lines.push(brand('◆') + ' ' + ansis.bold(white(message)) +
|
|
62
|
+
muted(' ↑↓←→ navigate · space toggle · a=all · enter confirm · esc back'));
|
|
63
|
+
lines.push(muted('│'));
|
|
64
|
+
|
|
65
|
+
// Grid rows
|
|
66
|
+
for (let r = 0; r < numRows; r++) {
|
|
67
|
+
let row = muted('│') + ' ';
|
|
68
|
+
for (let c = 0; c < numCols; c++) {
|
|
69
|
+
const idx = r * numCols + c;
|
|
70
|
+
if (idx >= options.length) break;
|
|
71
|
+
const opt = options[idx];
|
|
72
|
+
const focused = idx === cursor;
|
|
73
|
+
const checked = sel.has(idx);
|
|
74
|
+
const box = checked ? success('■') : muted('□');
|
|
75
|
+
const arrow = focused ? brand('▶') : ' ';
|
|
76
|
+
const label = focused
|
|
77
|
+
? ansis.bold(skillColor(idx)(opt.label))
|
|
78
|
+
: skillColor(idx)(opt.label);
|
|
79
|
+
const cell = `${arrow}${box} ${label}`;
|
|
80
|
+
// Pad to colW (accounting for invisible ANSI chars: pad by name length, not cell length)
|
|
81
|
+
const visLen = 3 + opt.label.length; // arrow(1) + box(1) + space(1) + name
|
|
82
|
+
const pad = Math.max(0, colW - visLen);
|
|
83
|
+
row += cell + ' '.repeat(pad);
|
|
84
|
+
}
|
|
85
|
+
lines.push(row);
|
|
71
86
|
}
|
|
72
87
|
|
|
73
|
-
// Separator + description
|
|
74
|
-
|
|
75
|
-
writeLine(muted('├' + '─'.repeat(divW) + '┤'));
|
|
88
|
+
// Separator + description
|
|
89
|
+
lines.push(muted('├' + '─'.repeat(Math.min(termW - 2, 78)) + '┤'));
|
|
76
90
|
|
|
77
91
|
const focused = options[cursor];
|
|
78
|
-
const desc = focused?.description
|
|
79
|
-
const
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
for (let i = 0; i < DESC_LINES; i++) {
|
|
92
|
+
const desc = focused?.description ?? '';
|
|
93
|
+
const wrapped = desc ? wordWrap(desc, termW - 6) : [];
|
|
94
|
+
for (let i = 0; i < DESC_H; i++) {
|
|
83
95
|
const line = wrapped[i] ?? '';
|
|
84
|
-
|
|
96
|
+
lines.push(muted('│ ') + white(line));
|
|
85
97
|
}
|
|
86
98
|
|
|
87
99
|
// Footer
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
: ''
|
|
91
|
-
|
|
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
|
+
const selCount = sel.size;
|
|
101
|
+
lines.push(
|
|
102
|
+
' ' + (selCount > 0 ? success(`${selCount} selected`) : muted('0 selected')) +
|
|
103
|
+
muted(` · ${cursor + 1} of ${options.length}`)
|
|
100
104
|
);
|
|
105
|
+
|
|
106
|
+
return lines;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function draw() {
|
|
110
|
+
const lines = renderGrid();
|
|
111
|
+
// Go up by however many lines we drew last time
|
|
112
|
+
if (lastLines > 0) {
|
|
113
|
+
process.stdout.write(`\x1b[${lastLines}A`);
|
|
114
|
+
}
|
|
115
|
+
for (const line of lines) {
|
|
116
|
+
process.stdout.write('\x1b[2K' + line + '\n');
|
|
117
|
+
}
|
|
118
|
+
lastLines = lines.length;
|
|
101
119
|
}
|
|
102
120
|
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
render(false);
|
|
121
|
+
// Initial draw
|
|
122
|
+
draw();
|
|
106
123
|
|
|
107
124
|
return new Promise((resolve, reject) => {
|
|
108
125
|
process.stdin.setRawMode(true);
|
|
109
126
|
process.stdin.resume();
|
|
110
127
|
process.stdin.setEncoding('utf8');
|
|
111
128
|
|
|
112
|
-
function
|
|
129
|
+
function cleanup() {
|
|
113
130
|
process.stdin.setRawMode(false);
|
|
114
131
|
process.stdin.pause();
|
|
115
132
|
process.stdin.removeAllListeners('data');
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function cancel() {
|
|
136
|
+
cleanup();
|
|
137
|
+
reject(Object.assign(new Error('cancel'), { isCancel: true }));
|
|
121
138
|
}
|
|
122
139
|
|
|
123
140
|
process.stdin.on('data', key => {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
if (key ===
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
141
|
+
const row = Math.floor(cursor / numCols);
|
|
142
|
+
const col = cursor % numCols;
|
|
143
|
+
|
|
144
|
+
if (key === KEY.CTRL_C) { cleanup(); process.exit(0); }
|
|
145
|
+
if (key === KEY.ESC) { cancel(); return; }
|
|
146
|
+
|
|
147
|
+
if (key === KEY.UP) cursor = clamp((row - 1) * numCols + col);
|
|
148
|
+
if (key === KEY.DOWN) cursor = clamp((row + 1) * numCols + col);
|
|
149
|
+
if (key === KEY.LEFT) cursor = clamp(cursor - 1);
|
|
150
|
+
if (key === KEY.RIGHT) cursor = clamp(cursor + 1);
|
|
151
|
+
|
|
152
|
+
if (key === KEY.SPACE) {
|
|
132
153
|
sel.has(cursor) ? sel.delete(cursor) : sel.add(cursor);
|
|
133
|
-
}
|
|
154
|
+
}
|
|
155
|
+
if (key === 'a' || key === 'A') {
|
|
134
156
|
sel.size === options.length
|
|
135
157
|
? sel.clear()
|
|
136
158
|
: options.forEach((_, i) => sel.add(i));
|
|
137
|
-
}
|
|
159
|
+
}
|
|
160
|
+
if (key === KEY.ENTER) {
|
|
138
161
|
if (!sel.size) return;
|
|
139
|
-
|
|
162
|
+
cleanup();
|
|
163
|
+
process.stdout.write('\x1b[2K' + success('◆') + ' ' +
|
|
164
|
+
white(`${sel.size} skill(s) selected`) + '\n');
|
|
165
|
+
resolve([...sel].sort((a, b) => a - b).map(i => options[i].value));
|
|
140
166
|
return;
|
|
141
167
|
}
|
|
142
168
|
|
|
143
|
-
|
|
169
|
+
draw();
|
|
144
170
|
});
|
|
145
171
|
});
|
|
146
172
|
}
|
package/package.json
CHANGED