claude-agent-skills 1.4.0 → 1.5.0
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 +44 -16
- package/lib/inlineSelect.js +88 -0
- package/lib/picker.js +2 -2
- package/lib/prompts.js +7 -7
- package/lib/theme.js +2 -9
- package/package.json +1 -1
- package/skills.json +1 -1
package/commands/hub.js
CHANGED
|
@@ -1,43 +1,67 @@
|
|
|
1
|
-
import { select, isCancel, cancel, outro } from '@clack/prompts';
|
|
2
1
|
import updateNotifier from 'update-notifier';
|
|
3
2
|
import { createRequire } from 'node:module';
|
|
4
3
|
import { fileURLToPath } from 'node:url';
|
|
5
4
|
import { dirname, join } from 'node:path';
|
|
5
|
+
import ansis from 'ansis';
|
|
6
6
|
import { showIntro } from '../lib/banner.js';
|
|
7
7
|
import { CliCancel } from '../lib/prompts.js';
|
|
8
|
+
import { brand, muted, white } from '../lib/theme.js';
|
|
9
|
+
import { inlineSelect } from '../lib/inlineSelect.js';
|
|
8
10
|
|
|
9
11
|
const req = createRequire(import.meta.url);
|
|
10
12
|
const pkg = req(join(dirname(fileURLToPath(import.meta.url)), '..', 'package.json'));
|
|
11
13
|
updateNotifier({ pkg }).notify();
|
|
12
|
-
|
|
14
|
+
|
|
15
|
+
import { runAdd } from './add.js';
|
|
13
16
|
import { runUpdate } from './update.js';
|
|
14
17
|
import { runRemove } from './remove.js';
|
|
15
|
-
import { runList }
|
|
16
|
-
import { runSync }
|
|
17
|
-
import { runCheck }
|
|
18
|
+
import { runList } from './list.js';
|
|
19
|
+
import { runSync } from './sync.js';
|
|
20
|
+
import { runCheck } from './check.js';
|
|
18
21
|
|
|
19
22
|
const SKIP = { skipIntro: true };
|
|
20
23
|
|
|
21
24
|
const MENU = [
|
|
22
|
-
{ value: 'add', label: 'Add Skill(s)' },
|
|
23
|
-
{ value: 'update', label: 'Update Existing Skill(s)' },
|
|
24
|
-
{ value: 'remove', label: 'Remove Existing Skill(s)' },
|
|
25
|
-
{ value: 'list', label: 'List Installed Skill(s)' },
|
|
26
|
-
{ value: 'sync', label: 'Sync/Restore
|
|
27
|
-
{ value: 'check', label: 'Check Skill(s)' },
|
|
25
|
+
{ value: 'add', label: 'Add Skill(s)', hint: 'install new skills' },
|
|
26
|
+
{ value: 'update', label: 'Update Existing Skill(s)', hint: 'pull latest versions' },
|
|
27
|
+
{ value: 'remove', label: 'Remove Existing Skill(s)', hint: 'uninstall skills' },
|
|
28
|
+
{ value: 'list', label: 'List Installed Skill(s)', hint: 'show what\'s installed' },
|
|
29
|
+
{ value: 'sync', label: 'Sync/Restore from Lockfile', hint: 'restore from claude-skills-lock.json' },
|
|
30
|
+
{ value: 'check', label: 'Check Skill(s)', hint: 'verify hashes & lockfile' },
|
|
28
31
|
{ value: 'quit', label: 'Quit' },
|
|
29
32
|
];
|
|
30
33
|
|
|
34
|
+
function printCompactHeader() {
|
|
35
|
+
const silver = s => ansis.rgb(190, 190, 190)(s);
|
|
36
|
+
process.stdout.write('\n');
|
|
37
|
+
process.stdout.write(brand('◈ CLAUDE SKILLS') + ' ' + silver('Agent Skills for Claude Code') + '\n');
|
|
38
|
+
process.stdout.write('\n');
|
|
39
|
+
}
|
|
40
|
+
|
|
31
41
|
export async function runHub() {
|
|
32
42
|
await showIntro();
|
|
33
43
|
|
|
44
|
+
let first = true;
|
|
34
45
|
for (;;) {
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
46
|
+
if (!first) {
|
|
47
|
+
// Clear entire screen and show compact header instead of re-running the banner
|
|
48
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
49
|
+
printCompactHeader();
|
|
50
|
+
}
|
|
51
|
+
first = false;
|
|
39
52
|
|
|
40
53
|
try {
|
|
54
|
+
const choice = await inlineSelect({
|
|
55
|
+
message: 'What do you want to do?',
|
|
56
|
+
hint: '↑↓ navigate · enter select · esc quit',
|
|
57
|
+
options: MENU,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
if (choice === 'quit') {
|
|
61
|
+
process.stdout.write('\n' + muted('Goodbye.') + '\n');
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
41
65
|
if (choice === 'add') await runAdd(SKIP);
|
|
42
66
|
if (choice === 'update') await runUpdate(SKIP);
|
|
43
67
|
if (choice === 'remove') await runRemove(SKIP);
|
|
@@ -45,7 +69,11 @@ export async function runHub() {
|
|
|
45
69
|
if (choice === 'sync') await runSync(SKIP);
|
|
46
70
|
if (choice === 'check') await runCheck(SKIP);
|
|
47
71
|
} catch (e) {
|
|
48
|
-
if (e instanceof CliCancel) continue;
|
|
72
|
+
if (e instanceof CliCancel) continue; // sub-command ESC → back to menu
|
|
73
|
+
if (e?.isCancel) { // hub menu ESC → quit
|
|
74
|
+
process.stdout.write('\n' + muted('Goodbye.') + '\n');
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
49
77
|
throw e;
|
|
50
78
|
}
|
|
51
79
|
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Single-select in-place picker — renders in-place like skillPicker.
|
|
3
|
+
* Resolves with selected value. Throws { isCancel: true } on ESC.
|
|
4
|
+
*/
|
|
5
|
+
import ansis from 'ansis';
|
|
6
|
+
import { brand, white, muted } from './theme.js';
|
|
7
|
+
|
|
8
|
+
const KEY = {
|
|
9
|
+
UP: '\x1b[A',
|
|
10
|
+
DOWN: '\x1b[B',
|
|
11
|
+
ENTER: '\r',
|
|
12
|
+
CTRL_C: '\x03',
|
|
13
|
+
ESC: '\x1b',
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export async function inlineSelect({ message, hint = '↑↓ navigate · enter select · esc back', options }) {
|
|
17
|
+
let cursor = 0;
|
|
18
|
+
let lastLines = 0;
|
|
19
|
+
|
|
20
|
+
function renderLines() {
|
|
21
|
+
const out = [];
|
|
22
|
+
out.push(brand('◆') + ' ' + ansis.bold(white(message)) + ' ' + muted(hint));
|
|
23
|
+
out.push(muted('│'));
|
|
24
|
+
for (let i = 0; i < options.length; i++) {
|
|
25
|
+
const focused = i === cursor;
|
|
26
|
+
const arrow = focused ? ansis.bold(ansis.white('▶')) : ' ';
|
|
27
|
+
const label = focused
|
|
28
|
+
? ansis.bold(ansis.white(options[i].label))
|
|
29
|
+
: muted(options[i].label);
|
|
30
|
+
const desc = options[i].hint ? ' ' + muted(options[i].hint) : '';
|
|
31
|
+
out.push(muted('│') + ' ' + arrow + ' ' + label + desc);
|
|
32
|
+
}
|
|
33
|
+
out.push(muted('│'));
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function draw() {
|
|
38
|
+
const lines = renderLines();
|
|
39
|
+
if (lastLines > 0) process.stdout.write(`\x1b[${lastLines}A`);
|
|
40
|
+
for (const l of lines) process.stdout.write('\x1b[2K' + l + '\n');
|
|
41
|
+
process.stdout.write('\x1b[0J');
|
|
42
|
+
lastLines = lines.length;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function clearBlock() {
|
|
46
|
+
if (lastLines > 0) {
|
|
47
|
+
process.stdout.write(`\x1b[${lastLines}A\x1b[0J`);
|
|
48
|
+
lastLines = 0;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
draw();
|
|
53
|
+
|
|
54
|
+
return new Promise((resolve, reject) => {
|
|
55
|
+
process.stdin.setRawMode(true);
|
|
56
|
+
process.stdin.resume();
|
|
57
|
+
process.stdin.setEncoding('utf8');
|
|
58
|
+
|
|
59
|
+
function cleanup() {
|
|
60
|
+
process.stdin.setRawMode(false);
|
|
61
|
+
process.stdin.pause();
|
|
62
|
+
process.stdin.removeAllListeners('data');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
process.stdin.on('data', key => {
|
|
66
|
+
if (key === KEY.CTRL_C) { cleanup(); process.exit(0); }
|
|
67
|
+
|
|
68
|
+
if (key === KEY.ESC) {
|
|
69
|
+
cleanup();
|
|
70
|
+
clearBlock();
|
|
71
|
+
reject(Object.assign(new Error('cancel'), { isCancel: true }));
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (key === KEY.UP) cursor = Math.max(0, cursor - 1);
|
|
76
|
+
if (key === KEY.DOWN) cursor = Math.min(options.length - 1, cursor + 1);
|
|
77
|
+
|
|
78
|
+
if (key === KEY.ENTER) {
|
|
79
|
+
cleanup();
|
|
80
|
+
clearBlock();
|
|
81
|
+
resolve(options[cursor].value);
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
draw();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
88
|
+
}
|
package/lib/picker.js
CHANGED
|
@@ -70,9 +70,9 @@ export async function skillPicker({ message, options }) {
|
|
|
70
70
|
const focused = idx === cursor;
|
|
71
71
|
const checked = sel.has(idx);
|
|
72
72
|
const box = checked ? success('■') : muted('□');
|
|
73
|
-
const arrow = focused ?
|
|
73
|
+
const arrow = focused ? ansis.bold(ansis.white('▶')) : ' ';
|
|
74
74
|
const label = focused
|
|
75
|
-
? ansis.bold(
|
|
75
|
+
? ansis.bold(ansis.white(opt.label))
|
|
76
76
|
: skillColor(idx)(opt.label);
|
|
77
77
|
const cell = `${arrow}${box} ${label}`;
|
|
78
78
|
// Pad to colW (accounting for invisible ANSI chars: pad by name length, not cell length)
|
package/lib/prompts.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { confirm, isCancel } from '@clack/prompts';
|
|
2
2
|
import ansis from 'ansis';
|
|
3
3
|
import { skillColor, white, muted, divider } from './theme.js';
|
|
4
4
|
import { skillPicker } from './picker.js';
|
|
5
|
+
import { inlineSelect } from './inlineSelect.js';
|
|
5
6
|
|
|
6
7
|
export class CliCancel extends Error {}
|
|
7
8
|
|
|
8
|
-
// Escape/Ctrl+C at any prompt throws CliCancel — hub catches it silently and
|
|
9
|
-
// returns to the main menu (no "Cancelled." noise printed).
|
|
10
9
|
function guard(v) {
|
|
11
10
|
if (isCancel(v)) throw new CliCancel();
|
|
12
11
|
return v;
|
|
@@ -15,13 +14,14 @@ function guard(v) {
|
|
|
15
14
|
export async function pickScope(flags = {}) {
|
|
16
15
|
if (flags.global) return { global: true };
|
|
17
16
|
if (flags.project) return { project: true };
|
|
18
|
-
const v =
|
|
17
|
+
const v = await inlineSelect({
|
|
19
18
|
message: 'Select scope',
|
|
19
|
+
hint: '↑↓ navigate · enter select · esc back',
|
|
20
20
|
options: [
|
|
21
|
-
{ value: 'global', label: 'Global
|
|
22
|
-
{ value: 'project', label: 'Project
|
|
21
|
+
{ value: 'global', label: 'Global', hint: '~/.claude/skills + ~/.agents/skills' },
|
|
22
|
+
{ value: 'project', label: 'Project', hint: '.claude/skills in current directory' },
|
|
23
23
|
],
|
|
24
|
-
}));
|
|
24
|
+
}).catch(e => { if (e?.isCancel) throw new CliCancel(); throw e; });
|
|
25
25
|
return { global: v === 'global' };
|
|
26
26
|
}
|
|
27
27
|
|
package/lib/theme.js
CHANGED
|
@@ -15,15 +15,8 @@ export const muted = t => ok() ? ansis.dim(t) : t;
|
|
|
15
15
|
export const white = t => ok() ? ansis.white(t) : t;
|
|
16
16
|
export const strip = t => ansis.strip(t);
|
|
17
17
|
|
|
18
|
-
//
|
|
19
|
-
const CRIMSON = [
|
|
20
|
-
[235, 60, 60],
|
|
21
|
-
[210, 38, 38],
|
|
22
|
-
[185, 25, 25],
|
|
23
|
-
[160, 15, 15],
|
|
24
|
-
[185, 25, 25],
|
|
25
|
-
[210, 38, 38],
|
|
26
|
-
];
|
|
18
|
+
// Single consistent crimson — all skills the same colour
|
|
19
|
+
const CRIMSON = [[200, 35, 35]];
|
|
27
20
|
|
|
28
21
|
// Pride rainbow — restore for pride month
|
|
29
22
|
const PRIDE_PALETTE = [
|
package/package.json
CHANGED