claude-agent-skills 1.5.2 → 1.5.3
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/commands/hub.js +46 -4
- package/lib/inlineSelect.js +11 -1
- package/lib/picker.js +14 -5
- package/package.json +1 -1
- package/skills.json +1 -1
package/commands/hub.js
CHANGED
|
@@ -4,6 +4,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
4
4
|
import { dirname, join } from 'node:path';
|
|
5
5
|
import { showIntro, showIntroStatic } from '../lib/banner.js';
|
|
6
6
|
import { CliCancel } from '../lib/prompts.js';
|
|
7
|
+
import { brand, muted } from '../lib/theme.js';
|
|
7
8
|
import { inlineSelect } from '../lib/inlineSelect.js';
|
|
8
9
|
|
|
9
10
|
const req = createRequire(import.meta.url);
|
|
@@ -33,6 +34,45 @@ function restoreScreen() {
|
|
|
33
34
|
process.stdout.write('\x1b[?1049l');
|
|
34
35
|
}
|
|
35
36
|
|
|
37
|
+
/** Show a live countdown then return — lets the user read command output before menu clears. */
|
|
38
|
+
async function pauseBeforeReturn(seconds = 5) {
|
|
39
|
+
let remaining = seconds;
|
|
40
|
+
const render = () => process.stdout.write(
|
|
41
|
+
`\r\x1b[2K ${brand('◂')} ${muted(`returning to menu in ${remaining}s · press any key`)}`
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
process.stdout.write('\n');
|
|
45
|
+
render();
|
|
46
|
+
|
|
47
|
+
return new Promise(resolve => {
|
|
48
|
+
let done = false;
|
|
49
|
+
function cleanup() {
|
|
50
|
+
if (done) return;
|
|
51
|
+
done = true;
|
|
52
|
+
clearInterval(tick);
|
|
53
|
+
process.stdin.setRawMode(false);
|
|
54
|
+
process.stdin.pause();
|
|
55
|
+
process.stdin.removeAllListeners('data');
|
|
56
|
+
process.stdout.write('\n');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const tick = setInterval(() => {
|
|
60
|
+
remaining--;
|
|
61
|
+
if (remaining <= 0) { cleanup(); resolve(); }
|
|
62
|
+
else render();
|
|
63
|
+
}, 1000);
|
|
64
|
+
|
|
65
|
+
process.stdin.setRawMode(true);
|
|
66
|
+
process.stdin.resume();
|
|
67
|
+
process.stdin.setEncoding('utf8');
|
|
68
|
+
process.stdin.on('data', key => {
|
|
69
|
+
if (key === '\x03') { cleanup(); process.exit(0); }
|
|
70
|
+
cleanup();
|
|
71
|
+
resolve();
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
36
76
|
export async function runHub() {
|
|
37
77
|
// Alternate screen buffer — isolated viewport, no scrollback. Restored on exit like vim/less.
|
|
38
78
|
process.stdout.write('\x1b[?1049h\x1b[2J\x1b[H');
|
|
@@ -43,9 +83,8 @@ export async function runHub() {
|
|
|
43
83
|
let first = true;
|
|
44
84
|
for (;;) {
|
|
45
85
|
if (!first) {
|
|
46
|
-
// Clear alt screen and re-render the static banner so it's always visible
|
|
47
86
|
process.stdout.write('\x1b[2J\x1b[H');
|
|
48
|
-
await showIntroStatic();
|
|
87
|
+
await showIntroStatic(); // static banner — always visible on return to menu
|
|
49
88
|
}
|
|
50
89
|
first = false;
|
|
51
90
|
|
|
@@ -64,9 +103,12 @@ export async function runHub() {
|
|
|
64
103
|
if (choice === 'list') await runList(SKIP);
|
|
65
104
|
if (choice === 'sync') await runSync(SKIP);
|
|
66
105
|
if (choice === 'check') await runCheck(SKIP);
|
|
106
|
+
|
|
107
|
+
// Pause so user can read command output before the screen clears
|
|
108
|
+
await pauseBeforeReturn();
|
|
67
109
|
} catch (e) {
|
|
68
|
-
if (e instanceof CliCancel) continue; // sub-
|
|
69
|
-
if (e?.isCancel) { restoreScreen(); return; }
|
|
110
|
+
if (e instanceof CliCancel) continue; // ESC from sub-step → back to menu immediately
|
|
111
|
+
if (e?.isCancel) { restoreScreen(); return; }
|
|
70
112
|
restoreScreen();
|
|
71
113
|
throw e;
|
|
72
114
|
}
|
package/lib/inlineSelect.js
CHANGED
|
@@ -13,13 +13,21 @@ const KEY = {
|
|
|
13
13
|
ESC: '\x1b',
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
function formatHints(str) {
|
|
17
|
+
return ' ' + brand('►► ') + str.split(' · ').map(part => {
|
|
18
|
+
const [key, ...rest] = part.trim().split(' ');
|
|
19
|
+
return ansis.bold(ansis.white(key)) + (rest.length ? muted(' ' + rest.join(' ')) : '');
|
|
20
|
+
}).join(muted(' · '));
|
|
21
|
+
}
|
|
22
|
+
|
|
16
23
|
export async function inlineSelect({ message, hint = '↑↓ navigate · enter select · esc back', options }) {
|
|
17
24
|
let cursor = 0;
|
|
18
25
|
let lastLines = 0;
|
|
19
26
|
|
|
20
27
|
function renderLines() {
|
|
21
28
|
const out = [];
|
|
22
|
-
|
|
29
|
+
// Title line — no hints here (moved to footer bar)
|
|
30
|
+
out.push(brand('◆') + ' ' + ansis.bold(white(message)));
|
|
23
31
|
out.push(muted('│'));
|
|
24
32
|
for (let i = 0; i < options.length; i++) {
|
|
25
33
|
const focused = i === cursor;
|
|
@@ -31,6 +39,8 @@ export async function inlineSelect({ message, hint = '↑↓ navigate · enter s
|
|
|
31
39
|
out.push(muted('│') + ' ' + arrow + ' ' + label + desc);
|
|
32
40
|
}
|
|
33
41
|
out.push(muted('│'));
|
|
42
|
+
// Dedicated hint footer bar
|
|
43
|
+
out.push(formatHints(hint));
|
|
34
44
|
return out;
|
|
35
45
|
}
|
|
36
46
|
|
package/lib/picker.js
CHANGED
|
@@ -3,7 +3,14 @@
|
|
|
3
3
|
* Navigation: ↑↓←→ space=toggle a=all enter=confirm esc=back
|
|
4
4
|
*/
|
|
5
5
|
import ansis from 'ansis';
|
|
6
|
-
import { skillColor, white, muted, success, brand
|
|
6
|
+
import { skillColor, white, muted, success, brand } from './theme.js';
|
|
7
|
+
|
|
8
|
+
function formatHints(str) {
|
|
9
|
+
return ' ' + brand('►► ') + str.split(' · ').map(part => {
|
|
10
|
+
const [key, ...rest] = part.trim().split(' ');
|
|
11
|
+
return ansis.bold(ansis.white(key)) + (rest.length ? muted(' ' + rest.join(' ')) : '');
|
|
12
|
+
}).join(muted(' · '));
|
|
13
|
+
}
|
|
7
14
|
|
|
8
15
|
const KEY = {
|
|
9
16
|
UP: '\x1b[A',
|
|
@@ -52,12 +59,13 @@ export async function skillPicker({ message, options }) {
|
|
|
52
59
|
return Math.max(0, Math.min(options.length - 1, c));
|
|
53
60
|
}
|
|
54
61
|
|
|
62
|
+
const HINT = '↑↓←→ navigate · space toggle · a=all · enter confirm · esc back';
|
|
63
|
+
|
|
55
64
|
function renderGrid() {
|
|
56
65
|
const lines = [];
|
|
57
66
|
|
|
58
|
-
// Header
|
|
59
|
-
lines.push(brand('◆') + ' ' + ansis.bold(white(message))
|
|
60
|
-
muted(' ↑↓←→ navigate · space toggle · a=all · enter confirm · esc back'));
|
|
67
|
+
// Header — title only, hints moved to footer bar
|
|
68
|
+
lines.push(brand('◆') + ' ' + ansis.bold(white(message)));
|
|
61
69
|
lines.push(muted('│'));
|
|
62
70
|
|
|
63
71
|
// Grid rows
|
|
@@ -94,12 +102,13 @@ export async function skillPicker({ message, options }) {
|
|
|
94
102
|
lines.push(muted('│ ') + white(line));
|
|
95
103
|
}
|
|
96
104
|
|
|
97
|
-
// Footer
|
|
105
|
+
// Footer — count + hint bar
|
|
98
106
|
const selCount = sel.size;
|
|
99
107
|
lines.push(
|
|
100
108
|
' ' + (selCount > 0 ? success(`${selCount} selected`) : muted('0 selected')) +
|
|
101
109
|
muted(` · ${cursor + 1} of ${options.length}`)
|
|
102
110
|
);
|
|
111
|
+
lines.push(formatHints(HINT));
|
|
103
112
|
|
|
104
113
|
return lines;
|
|
105
114
|
}
|
package/package.json
CHANGED