claude-agent-skills 1.5.2 → 1.5.4
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/README.md +88 -27
- package/commands/hub.js +46 -4
- package/lib/inlineSelect.js +21 -7
- package/lib/picker.js +14 -5
- package/package.json +1 -1
- package/skills.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# claude-agent-skills
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Interactive CLI to install and manage Claude Code agent skills — the prompt files that teach Claude how to perform specialised tasks in your projects.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Quick start
|
|
6
6
|
|
|
7
7
|
```bash
|
|
8
8
|
npx claude-agent-skills
|
|
@@ -17,48 +17,109 @@ claude-agent-skills
|
|
|
17
17
|
|
|
18
18
|
## What it does
|
|
19
19
|
|
|
20
|
-
Launches
|
|
20
|
+
Launches a full-screen interactive TUI (alternate screen buffer — your terminal is restored on exit) with an animated banner, grid-based skill picker, and in-place navigation. Works like `vim` or `less` — nothing leaks into your scrollback.
|
|
21
21
|
|
|
22
22
|
Skills are installed into:
|
|
23
23
|
|
|
24
|
-
| Scope |
|
|
25
|
-
|
|
24
|
+
| Scope | Paths |
|
|
25
|
+
|---------|-------|
|
|
26
26
|
| Global | `~/.claude/skills/` + `~/.agents/skills/` |
|
|
27
|
-
| Project | `.claude/skills/` in current
|
|
27
|
+
| Project | `.claude/skills/` in the current directory |
|
|
28
28
|
|
|
29
|
-
A lockfile (
|
|
29
|
+
A lockfile (`claude-skills-lock.json`) tracks every installed skill with a SHA-256 hash so updates, drift, and broken symlinks can be detected.
|
|
30
|
+
|
|
31
|
+
## Navigation
|
|
32
|
+
|
|
33
|
+
Every screen shows a `►► key action` hint bar at the bottom.
|
|
34
|
+
|
|
35
|
+
| Key | Action |
|
|
36
|
+
|-----|--------|
|
|
37
|
+
| `↑` `↓` | Move up / down in menus |
|
|
38
|
+
| `←` `→` | Move left / right in skill grid |
|
|
39
|
+
| `space` | Toggle skill selection |
|
|
40
|
+
| `a` | Select / deselect all skills |
|
|
41
|
+
| `enter` | Confirm |
|
|
42
|
+
| `esc` | Go back to previous menu |
|
|
43
|
+
| `ctrl+c` | Quit |
|
|
44
|
+
|
|
45
|
+
After any command finishes, a **5-second countdown** is shown (`◂ returning to menu in 5s · press any key`) so you can read the output before the screen resets. Press any key to skip the wait.
|
|
30
46
|
|
|
31
47
|
## Menu options
|
|
32
48
|
|
|
33
|
-
| Option |
|
|
34
|
-
|
|
35
|
-
| **Add Skill(s)** |
|
|
36
|
-
| **Update Existing Skill(s)** |
|
|
37
|
-
| **Remove Existing Skill(s)** |
|
|
38
|
-
| **List Installed Skill(s)** |
|
|
39
|
-
| **Sync/Restore
|
|
40
|
-
| **Check Skill(s)** | Health check — ok
|
|
41
|
-
| **Quit** |
|
|
49
|
+
| Option | What it does |
|
|
50
|
+
|--------|--------------|
|
|
51
|
+
| **Add Skill(s)** | Grid picker of all 54 bundled skills. Select scope → pick skills → pick link type → confirm. Dependencies resolved automatically. |
|
|
52
|
+
| **Update Existing Skill(s)** | Finds skills where the bundled version has a newer hash and re-installs them. |
|
|
53
|
+
| **Remove Existing Skill(s)** | Uninstalls selected skills from disk and removes them from the lockfile. |
|
|
54
|
+
| **List Installed Skill(s)** | Table of all installed skills with their status, link type, and hash. |
|
|
55
|
+
| **Sync/Restore from Lockfile** | Re-materializes any missing or broken skills using the lockfile. Run this after cloning a new machine or if symlinks break. |
|
|
56
|
+
| **Check Skill(s)** | Health check — reports each skill as `ok`, `update available`, `locally modified`, `missing`, or `broken symlink`. |
|
|
57
|
+
| **Quit** | Restore the terminal and exit. |
|
|
58
|
+
|
|
59
|
+
## CLI subcommands
|
|
60
|
+
|
|
61
|
+
All menu actions are also available as direct subcommands (no TUI):
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
claude-agent-skills add --global --all # install all skills globally
|
|
65
|
+
claude-agent-skills add --skill grilling tdd # install specific skills
|
|
66
|
+
claude-agent-skills add --project --copy # copy into project scope
|
|
67
|
+
claude-agent-skills update --global # update all globally
|
|
68
|
+
claude-agent-skills remove --project # interactive remove, project scope
|
|
69
|
+
claude-agent-skills list --global # show installed skills
|
|
70
|
+
claude-agent-skills sync --global # restore from lockfile
|
|
71
|
+
claude-agent-skills check --project # health check, project scope
|
|
72
|
+
claude-agent-skills export --out skills.lock.json # export lockfile for team sharing
|
|
73
|
+
claude-agent-skills import --in skills.lock.json # import and apply a shared lockfile
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Flags
|
|
42
77
|
|
|
43
|
-
|
|
78
|
+
| Flag | Description |
|
|
79
|
+
|------|-------------|
|
|
80
|
+
| `--global` | Target `~/.claude/skills/` (default) |
|
|
81
|
+
| `--project` | Target `.claude/skills/` in cwd |
|
|
82
|
+
| `--all` | Select all available skills |
|
|
83
|
+
| `--skill <name...>` | Specify skill names directly |
|
|
84
|
+
| `--copy` | Materialize as file copies (safe for npx) |
|
|
85
|
+
| `--symlink` | Materialize as symlinks (requires persistent install) |
|
|
86
|
+
| `--yes` | Skip confirmation prompts |
|
|
87
|
+
| `--out <path>` | Export lockfile destination |
|
|
88
|
+
| `--in <path>` | Import lockfile source |
|
|
44
89
|
|
|
45
|
-
##
|
|
90
|
+
## Lockfile
|
|
46
91
|
|
|
47
|
-
|
|
92
|
+
The lockfile (`claude-skills-lock.json`) records each installed skill's SHA-256 hash and link type. Commit it to your repo to let teammates restore the exact same skill set with:
|
|
48
93
|
|
|
49
94
|
```bash
|
|
50
|
-
claude-agent-skills
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
claude-agent-skills
|
|
95
|
+
claude-agent-skills import --in claude-skills-lock.json --global
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Export your current setup:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
claude-agent-skills export --out claude-skills-lock.json
|
|
57
102
|
```
|
|
58
103
|
|
|
59
104
|
## Included skills
|
|
60
105
|
|
|
61
|
-
54 skills
|
|
106
|
+
54 skills across four upstream sources:
|
|
107
|
+
|
|
108
|
+
| Source | Skills |
|
|
109
|
+
|--------|--------|
|
|
110
|
+
| [mattpocock/skills](https://github.com/mattpocock/skills) | `brainstorming`, `codebase-design`, `diagnosing-bugs`, `dispatching-parallel-agents`, `domain-modeling`, `executing-plans`, `finishing-a-development-branch`, `implement`, `improve-codebase-architecture`, `prototype`, `receiving-code-review`, `requesting-code-review`, `subagent-driven-development`, `systematic-debugging`, `tdd`, `teach`, `test-driven-development`, `to-issues`, `to-prd`, `triage`, `using-git-worktrees`, `using-superpowers`, `verification-before-completion`, `writing-plans`, `writing-skills` |
|
|
111
|
+
| [JuliusBrussee/caveman](https://github.com/JuliusBrussee/caveman) | `caveman`, `cavecrew`, `caveman-commit`, `caveman-compress`, `caveman-help`, `caveman-review`, `caveman-stats` |
|
|
112
|
+
| [obra/superpowers](https://github.com/obra/superpowers) | `git-guardrails-claude-code`, `migrate-to-shoehorn`, `resolving-merge-conflicts`, `scaffold-exercises`, `setup-matt-pocock-skills`, `setup-pre-commit` |
|
|
113
|
+
| Personal (Pavi) | `ask-matt`, `council`, `edit-article`, `grill-me`, `grill-with-docs`, `grilling`, `handoff`, `i-am-dumb`, `obsidian-vault`, `ponytail`, `ponytail-audit`, `ponytail-debt`, `ponytail-gain`, `ponytail-help`, `ponytail-review`, `writing-great-skills` |
|
|
114
|
+
|
|
115
|
+
## How bundling works
|
|
116
|
+
|
|
117
|
+
Skills live in a private monorepo (`Pavithran-Francis/skills`). At publish time, `scripts/bundle.js` copies all skill folders into `bundled-skills/` and generates `skills.json` (manifest with skill list, dependency graph, and descriptions). This means `npx` users get all skills without needing access to the private repo.
|
|
118
|
+
|
|
119
|
+
## Requirements
|
|
120
|
+
|
|
121
|
+
- Node.js 18+
|
|
122
|
+
- Claude Code (to use the installed skills)
|
|
62
123
|
|
|
63
124
|
## Author
|
|
64
125
|
|
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,24 +13,38 @@ 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
|
|
|
27
|
+
// Pre-compute max label width so all descriptions align to the same column
|
|
28
|
+
const maxLen = Math.max(...options.map(o => o.label.length));
|
|
29
|
+
|
|
20
30
|
function renderLines() {
|
|
21
31
|
const out = [];
|
|
22
|
-
|
|
32
|
+
// Title line — no hints here (moved to footer bar)
|
|
33
|
+
out.push(brand('◆') + ' ' + ansis.bold(white(message)));
|
|
23
34
|
out.push(muted('│'));
|
|
24
35
|
for (let i = 0; i < options.length; i++) {
|
|
25
|
-
const focused
|
|
26
|
-
const arrow
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
36
|
+
const focused = i === cursor;
|
|
37
|
+
const arrow = focused ? ansis.bold(ansis.white('▶')) : ' ';
|
|
38
|
+
const pad = ' '.repeat(maxLen - options[i].label.length);
|
|
39
|
+
const label = focused
|
|
40
|
+
? ansis.bold(ansis.white(options[i].label)) + pad
|
|
41
|
+
: muted(options[i].label) + pad;
|
|
42
|
+
const desc = options[i].hint ? ' ' + muted(options[i].hint) : '';
|
|
31
43
|
out.push(muted('│') + ' ' + arrow + ' ' + label + desc);
|
|
32
44
|
}
|
|
33
45
|
out.push(muted('│'));
|
|
46
|
+
// Dedicated hint footer bar
|
|
47
|
+
out.push(formatHints(hint));
|
|
34
48
|
return out;
|
|
35
49
|
}
|
|
36
50
|
|
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