@booklib/skills 1.8.0 → 1.10.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/AGENTS.md +111 -53
- package/CONTRIBUTING.md +147 -0
- package/PLAN.md +28 -0
- package/README.md +102 -108
- package/bin/skills.js +218 -20
- package/hooks/hooks.json +12 -0
- package/hooks/suggest.js +153 -0
- package/package.json +1 -1
- package/rules/common/clean-code.md +42 -0
- package/rules/java/effective-java.md +42 -0
- package/rules/kotlin/effective-kotlin.md +37 -0
- package/rules/python/effective-python.md +38 -0
- package/rules/rust/rust.md +37 -0
- package/rules/typescript/effective-typescript.md +42 -0
package/README.md
CHANGED
|
@@ -12,100 +12,100 @@
|
|
|
12
12
|
|
|
13
13
|
Book-grounded AI agent skills — each skill packages expert practices from a canonical programming book into reusable instructions that Claude and other AI agents can apply to code generation, code review, and design decisions.
|
|
14
14
|
|
|
15
|
-
```bash
|
|
16
|
-
npx skills add booklib-ai/skills
|
|
17
|
-
```
|
|
18
|
-
|
|
19
15
|

|
|
20
16
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
## Structure
|
|
17
|
+
## Architecture
|
|
24
18
|
|
|
25
|
-
|
|
26
|
-
booklib-ai/skills (repo root)
|
|
27
|
-
├── skills/
|
|
28
|
-
│ ├── clean-code-reviewer/
|
|
29
|
-
│ │ ├── SKILL.md # Required
|
|
30
|
-
│ │ ├── examples/
|
|
31
|
-
│ │ ├── references/
|
|
32
|
-
│ │ ├── scripts/
|
|
33
|
-
│ │ └── evals/
|
|
34
|
-
│ └── [skill-name]/ # One folder per book
|
|
35
|
-
│ └── ...
|
|
36
|
-
├── README.md
|
|
37
|
-
├── LICENSE
|
|
38
|
-
└── package.json
|
|
39
|
-
```
|
|
19
|
+
The library is organized into three tiers that work together:
|
|
40
20
|
|
|
41
|
-
|
|
21
|
+
| Tier | Count | What it does |
|
|
22
|
+
|------|-------|--------------|
|
|
23
|
+
| **Skills** | 22 | Passive context loaded from `.claude/skills/` — triggered automatically when the AI detects a matching file or task |
|
|
24
|
+
| **Commands** | 22 | Explicit slash commands — `/effective-python`, `/design-patterns`, etc. — one per skill |
|
|
25
|
+
| **Agents** | 8 | Autonomous reviewers that combine multiple skills and run end-to-end reviews |
|
|
42
26
|
|
|
43
|
-
|
|
27
|
+
**Profiles** bundle all three tiers by language or domain so you install everything you need in one command.
|
|
44
28
|
|
|
45
|
-
|
|
46
|
-
skill-name/
|
|
47
|
-
├── SKILL.md # Required — YAML frontmatter + markdown instructions
|
|
48
|
-
├── scripts/ # Optional — deterministic code for repeated tasks
|
|
49
|
-
├── references/ # Optional — docs loaded into context as needed
|
|
50
|
-
├── assets/ # Optional — templates, fonts, images
|
|
51
|
-
└── evals/ # Optional — test cases for skill evaluation
|
|
52
|
-
```
|
|
53
|
-
|
|
54
|
-
### SKILL.md Structure
|
|
55
|
-
|
|
56
|
-
```markdown
|
|
57
|
-
---
|
|
58
|
-
name: skill-name
|
|
59
|
-
description: When to trigger this skill and what it does
|
|
60
|
-
---
|
|
61
|
-
|
|
62
|
-
# Skill Title
|
|
63
|
-
|
|
64
|
-
Instructions for the AI agent...
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
## Installation
|
|
68
|
-
|
|
69
|
-
### via skills CLI (recommended)
|
|
29
|
+
## Quick Start
|
|
70
30
|
|
|
71
31
|
```bash
|
|
72
|
-
#
|
|
73
|
-
npx skills add
|
|
32
|
+
# Pick your language or domain
|
|
33
|
+
npx @booklib/skills add --profile=python # Python skills + commands + python-reviewer agent
|
|
34
|
+
npx @booklib/skills add --profile=ts # TypeScript skills + commands + ts-reviewer agent
|
|
35
|
+
npx @booklib/skills add --profile=rust # Rust skills + commands + rust-reviewer agent
|
|
36
|
+
npx @booklib/skills add --profile=jvm # Java/Kotlin skills + commands + jvm-reviewer agent
|
|
37
|
+
npx @booklib/skills add --profile=architecture # DDD/microservices/system design
|
|
38
|
+
npx @booklib/skills add --profile=data # Data pipelines + DDIA
|
|
39
|
+
npx @booklib/skills add --profile=ui # Refactoring UI + animations + data viz
|
|
40
|
+
npx @booklib/skills add --profile=lean # Lean Startup practices
|
|
41
|
+
|
|
42
|
+
# Or install everything
|
|
43
|
+
npx @booklib/skills add --all
|
|
44
|
+
```
|
|
74
45
|
|
|
75
|
-
|
|
76
|
-
npx skills add booklib-ai/skills --skill effective-kotlin
|
|
46
|
+
Skills are installed to `.claude/skills/` in your project, or `~/.claude/skills/` with `--global`.
|
|
77
47
|
|
|
78
|
-
|
|
79
|
-
npx skills add booklib-ai/skills --list
|
|
80
|
-
```
|
|
48
|
+
## Agents
|
|
81
49
|
|
|
82
|
-
|
|
50
|
+
Eight autonomous reviewers that run end-to-end reviews combining the most relevant skills for each domain:
|
|
83
51
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
52
|
+
| Agent | Skills used | Use when |
|
|
53
|
+
|-------|-------------|----------|
|
|
54
|
+
| `booklib-reviewer` | skill-router (auto-selects) | Unsure which skill applies — routes automatically |
|
|
55
|
+
| `python-reviewer` | effective-python · asyncio · web-scraping | Reviewing any Python code |
|
|
56
|
+
| `jvm-reviewer` | effective-java · effective-kotlin · kotlin-in-action · spring-boot | Java or Kotlin code reviews |
|
|
57
|
+
| `rust-reviewer` | programming-with-rust · rust-in-action | Rust ownership, safety, and systems code |
|
|
58
|
+
| `ts-reviewer` | effective-typescript · clean-code-reviewer | TypeScript and TSX reviews |
|
|
59
|
+
| `architecture-reviewer` | domain-driven-design · microservices-patterns · system-design · data-intensive | System design, domain models, service boundaries |
|
|
60
|
+
| `data-reviewer` | data-intensive-patterns · data-pipelines | Schemas, ETL pipelines, stream processing |
|
|
61
|
+
| `ui-reviewer` | refactoring-ui · storytelling-with-data · animation-at-work | UI components, dashboards, data visualizations |
|
|
87
62
|
|
|
88
|
-
|
|
89
|
-
npx @booklib/skills add effective-kotlin
|
|
63
|
+
Invoke an agent in Claude Code with `@booklib-reviewer` or the specific agent name.
|
|
90
64
|
|
|
91
|
-
|
|
92
|
-
npx @booklib/skills list
|
|
93
|
-
```
|
|
65
|
+
## Profiles
|
|
94
66
|
|
|
95
|
-
|
|
67
|
+
| Profile | Skills + agents included |
|
|
68
|
+
|---------|--------------------------|
|
|
69
|
+
| `python` | effective-python · using-asyncio-python · web-scraping-python · python-reviewer |
|
|
70
|
+
| `ts` | effective-typescript · clean-code-reviewer · ts-reviewer |
|
|
71
|
+
| `jvm` | effective-java · effective-kotlin · kotlin-in-action · spring-boot-in-action · jvm-reviewer |
|
|
72
|
+
| `rust` | programming-with-rust · rust-in-action · rust-reviewer |
|
|
73
|
+
| `architecture` | domain-driven-design · microservices-patterns · system-design-interview · data-intensive-patterns · architecture-reviewer |
|
|
74
|
+
| `data` | data-intensive-patterns · data-pipelines · data-reviewer |
|
|
75
|
+
| `ui` | refactoring-ui · storytelling-with-data · animation-at-work · ui-reviewer |
|
|
76
|
+
| `lean` | lean-startup · design-patterns · clean-code-reviewer |
|
|
77
|
+
| `core` | clean-code-reviewer · design-patterns · skill-router · booklib-reviewer |
|
|
96
78
|
|
|
97
|
-
|
|
79
|
+
## Skills
|
|
98
80
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
81
|
+
| Skill | Book |
|
|
82
|
+
|-------|------|
|
|
83
|
+
| [animation-at-work](./skills/animation-at-work/) | *Animation at Work* — Rachel Nabors |
|
|
84
|
+
| [clean-code-reviewer](./skills/clean-code-reviewer/) | *Clean Code* — Robert C. Martin |
|
|
85
|
+
| [data-intensive-patterns](./skills/data-intensive-patterns/) | *Designing Data-Intensive Applications* — Martin Kleppmann |
|
|
86
|
+
| [data-pipelines](./skills/data-pipelines/) | *Data Pipelines Pocket Reference* — James Densmore |
|
|
87
|
+
| [design-patterns](./skills/design-patterns/) | *Head First Design Patterns* |
|
|
88
|
+
| [domain-driven-design](./skills/domain-driven-design/) | *Domain-Driven Design* — Eric Evans |
|
|
89
|
+
| [effective-java](./skills/effective-java/) | *Effective Java* (3rd ed) — Joshua Bloch |
|
|
90
|
+
| [effective-kotlin](./skills/effective-kotlin/) | *Effective Kotlin* (2nd ed) — Marcin Moskała |
|
|
91
|
+
| [effective-python](./skills/effective-python/) | *Effective Python* (2nd ed) — Brett Slatkin |
|
|
92
|
+
| [effective-typescript](./skills/effective-typescript/) | *Effective TypeScript* — Dan Vanderkam |
|
|
93
|
+
| [kotlin-in-action](./skills/kotlin-in-action/) | *Kotlin in Action* (2nd ed) |
|
|
94
|
+
| [lean-startup](./skills/lean-startup/) | *The Lean Startup* — Eric Ries |
|
|
95
|
+
| [microservices-patterns](./skills/microservices-patterns/) | *Microservices Patterns* — Chris Richardson |
|
|
96
|
+
| [programming-with-rust](./skills/programming-with-rust/) | *Programming with Rust* — Donis Marshall |
|
|
97
|
+
| [refactoring-ui](./skills/refactoring-ui/) | *Refactoring UI* — Adam Wathan & Steve Schoger |
|
|
98
|
+
| [rust-in-action](./skills/rust-in-action/) | *Rust in Action* — Tim McNamara |
|
|
99
|
+
| [skill-router](./skills/skill-router/) | Meta-skill — routes to the right skill automatically |
|
|
100
|
+
| [spring-boot-in-action](./skills/spring-boot-in-action/) | *Spring Boot in Action* — Craig Walls |
|
|
101
|
+
| [storytelling-with-data](./skills/storytelling-with-data/) | *Storytelling with Data* — Cole Nussbaumer Knaflic |
|
|
102
|
+
| [system-design-interview](./skills/system-design-interview/) | *System Design Interview* — Alex Xu |
|
|
103
|
+
| [using-asyncio-python](./skills/using-asyncio-python/) | *Using Asyncio in Python* — Caleb Hattingh |
|
|
104
|
+
| [web-scraping-python](./skills/web-scraping-python/) | *Web Scraping with Python* — Ryan Mitchell |
|
|
103
105
|
|
|
104
106
|
## Automatic Skill Routing
|
|
105
107
|
|
|
106
|
-
You don't need to know which skill to apply — the **[skill-router](./skills/skill-router/)** meta-skill does it for you.
|
|
107
|
-
|
|
108
|
-
When an AI agent receives a task, it can invoke `skill-router` first to identify the 1–2 most relevant skills based on the file, language, domain, and work type. The router then returns a ranked recommendation with rationale, so the right expertise is applied automatically.
|
|
108
|
+
You don't need to know which skill to apply — the **[skill-router](./skills/skill-router/)** meta-skill does it for you. When invoked, it reads the file, language, domain, and task type, then returns a ranked recommendation with rationale.
|
|
109
109
|
|
|
110
110
|
```
|
|
111
111
|
User: "Review my order processing service"
|
|
@@ -113,39 +113,11 @@ User: "Review my order processing service"
|
|
|
113
113
|
→ skill-router selects:
|
|
114
114
|
Primary: domain-driven-design — domain model design (Aggregates, Value Objects)
|
|
115
115
|
Secondary: microservices-patterns — service boundaries and inter-service communication
|
|
116
|
-
Skip: clean-code-reviewer — premature at design stage; apply later on implementation code
|
|
117
116
|
```
|
|
118
117
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
**See it in action:** The [`benchmark/`](./benchmark/) folder contains a head-to-head comparison — same buggy Node.js file reviewed by the native PR toolkit vs. `skill-router` routing to `clean-code-reviewer` + `design-patterns`. The skill-router pipeline finds ~47% more unique issues and adds a full refactor roadmap with pattern sequence.
|
|
118
|
+
The `booklib-reviewer` agent wraps this logic end-to-end — invoke it and it handles selection and review automatically.
|
|
122
119
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
| Skill | Description |
|
|
126
|
-
|-------|-------------|
|
|
127
|
-
| 🎬 [animation-at-work](./skills/animation-at-work/) | Apply web animation principles from Rachel Nabors' *Animation at Work* — human perception of motion, 12 principles of animation, and performance |
|
|
128
|
-
| 🧹 [clean-code-reviewer](./skills/clean-code-reviewer/) | Reviews code against Robert C. Martin's *Clean Code* principles with heuristic codes (C1–C5, G1–G36, N1–N7, T1–T9) |
|
|
129
|
-
| 🗄️ [data-intensive-patterns](./skills/data-intensive-patterns/) | Patterns for reliable, scalable, and maintainable systems from Martin Kleppmann's *Designing Data-Intensive Applications* — storage engines, replication, partitioning, and transactions |
|
|
130
|
-
| 🔀 [data-pipelines](./skills/data-pipelines/) | Data pipeline practices from James Densmore's *Data Pipelines Pocket Reference* — ingestion, streaming, transformation, and orchestration |
|
|
131
|
-
| 🏗️ [design-patterns](./skills/design-patterns/) | Apply and review GoF design patterns from *Head First Design Patterns* — creational, structural, and behavioral patterns |
|
|
132
|
-
| 🧩 [domain-driven-design](./skills/domain-driven-design/) | Design and review software using patterns from Eric Evans' *Domain-Driven Design* — tactical and strategic patterns, and Ubiquitous Language |
|
|
133
|
-
| ☕ [effective-java](./skills/effective-java/) | Java best practices from Joshua Bloch's *Effective Java* (3rd Edition) — object creation, generics, enums, lambdas, and concurrency |
|
|
134
|
-
| 🛡️ [effective-kotlin](./skills/effective-kotlin/) | Best practices from Marcin Moskała's *Effective Kotlin* (2nd Ed) — safety, readability, reusability, and abstraction |
|
|
135
|
-
| 🐍 [effective-python](./skills/effective-python/) | Python best practices from Brett Slatkin's *Effective Python* (2nd Edition) — Pythonic thinking, functions, classes, concurrency, and testing |
|
|
136
|
-
| 🔷 [effective-typescript](./skills/effective-typescript/) | TypeScript best practices from Dan Vanderkam's *Effective TypeScript* — type system, type design, avoiding any, type declarations, and migration |
|
|
137
|
-
| 🦀 [programming-with-rust](./skills/programming-with-rust/) | Rust practices from Donis Marshall's *Programming with Rust* — ownership, borrowing, lifetimes, error handling, traits, and fearless concurrency |
|
|
138
|
-
| ⚙️ [rust-in-action](./skills/rust-in-action/) | Systems programming from Tim McNamara's *Rust in Action* — smart pointers, endianness, memory, file formats, TCP networking, concurrency, and OS fundamentals |
|
|
139
|
-
| 🌱 [spring-boot-in-action](./skills/spring-boot-in-action/) | Spring Boot best practices from Craig Walls' *Spring Boot in Action* — auto-configuration, starters, externalized config, profiles, testing with MockMvc, Actuator, and deployment |
|
|
140
|
-
| ⚡ [kotlin-in-action](./skills/kotlin-in-action/) | Practices from *Kotlin in Action* (2nd Ed) — functions, classes, lambdas, nullability, and coroutines |
|
|
141
|
-
| 🚀 [lean-startup](./skills/lean-startup/) | Practices from Eric Ries' *The Lean Startup* — MVP testing, validated learning, Build-Measure-Learn loop, and pivots |
|
|
142
|
-
| 🔧 [microservices-patterns](./skills/microservices-patterns/) | Expert guidance on microservices patterns from Chris Richardson's *Microservices Patterns* — decomposition, sagas, API gateways, event sourcing, CQRS, and service mesh |
|
|
143
|
-
| 🎨 [refactoring-ui](./skills/refactoring-ui/) | UI design principles from *Refactoring UI* by Adam Wathan & Steve Schoger — visual hierarchy, layout, typography, and color |
|
|
144
|
-
| 🗺️ [skill-router](./skills/skill-router/) | **Meta-skill.** Automatically selects the 1–2 most relevant skills for a given file, PR, or task — routes by language, domain, and work type with conflict resolution. Use this when the right skill isn't obvious, or let the AI invoke it automatically before applying any skill |
|
|
145
|
-
| 📊 [storytelling-with-data](./skills/storytelling-with-data/) | Data visualization and storytelling from Cole Nussbaumer Knaflic's *Storytelling with Data* — effective visuals, decluttering, and narrative structure |
|
|
146
|
-
| 🏛️ [system-design-interview](./skills/system-design-interview/) | System design principles from Alex Xu's *System Design Interview* — scaling, estimation, and real-world system designs |
|
|
147
|
-
| 🔄 [using-asyncio-python](./skills/using-asyncio-python/) | Asyncio practices from Caleb Hattingh's *Using Asyncio in Python* — coroutines, event loop, tasks, and signal handling |
|
|
148
|
-
| 🕷️ [web-scraping-python](./skills/web-scraping-python/) | Web scraping practices from Ryan Mitchell's *Web Scraping with Python* — BeautifulSoup, Scrapy, and data storage |
|
|
120
|
+
**Benchmark:** The [`benchmark/`](./benchmark/) folder contains a head-to-head comparison of a native PR review vs. `skill-router` routing to `clean-code-reviewer` + `design-patterns`. The skill-router pipeline finds ~47% more unique issues and produces a full refactor roadmap.
|
|
149
121
|
|
|
150
122
|
## Quality
|
|
151
123
|
|
|
@@ -157,7 +129,7 @@ Skills are evaluated against 6–15 test cases each, run both **with** and **wit
|
|
|
157
129
|
| Skill | Pass Rate | Baseline | Delta | Evals | Last Run |
|
|
158
130
|
|-------|-----------|----------|-------|-------|----------|
|
|
159
131
|
| animation-at-work | — | — | — | — | — |
|
|
160
|
-
| clean-code-reviewer |
|
|
132
|
+
| clean-code-reviewer | 74% ⚠ | 55% | +19pp | 15 | 2026-03-28 |
|
|
161
133
|
| data-intensive-patterns | — | — | — | — | — |
|
|
162
134
|
| data-pipelines | — | — | — | — | — |
|
|
163
135
|
| design-patterns | — | — | — | — | — |
|
|
@@ -182,9 +154,31 @@ Skills are evaluated against 6–15 test cases each, run both **with** and **wit
|
|
|
182
154
|
|
|
183
155
|
Results are stored in each skill's `evals/results.json` and updated by running `npx @booklib/skills eval <name>`.
|
|
184
156
|
|
|
185
|
-
##
|
|
157
|
+
## Structure
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
booklib-ai/skills/
|
|
161
|
+
├── skills/ 22 book-grounded skills (SKILL.md + examples + evals)
|
|
162
|
+
├── commands/ 22 slash commands, one per skill
|
|
163
|
+
├── agents/ 8 autonomous reviewer agents
|
|
164
|
+
├── hooks/ Claude Code hooks (skill suggestion on UserPromptSubmit)
|
|
165
|
+
└── bin/skills.js CLI
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Each skill folder follows the [Agent Skills standard](https://agentskills.io):
|
|
169
|
+
|
|
170
|
+
```
|
|
171
|
+
skill-name/
|
|
172
|
+
├── SKILL.md # Required — YAML frontmatter + instructions
|
|
173
|
+
├── examples/ # before.md and after.md
|
|
174
|
+
├── references/ # Deep reference material loaded on demand
|
|
175
|
+
├── scripts/ # Deterministic helper scripts
|
|
176
|
+
└── evals/ # Test cases for skill evaluation
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Contributing
|
|
186
180
|
|
|
187
|
-
If you've read a book that belongs here, you can add it
|
|
181
|
+
If you've read a book that belongs here, you can add it:
|
|
188
182
|
|
|
189
183
|
```bash
|
|
190
184
|
# 1. Copy an existing skill as a template
|
|
@@ -196,7 +190,7 @@ cp -r skills/clean-code-reviewer skills/your-book-name
|
|
|
196
190
|
npx @booklib/skills check your-book-name
|
|
197
191
|
```
|
|
198
192
|
|
|
199
|
-
The `check` command runs all evals and reports what passes and fails
|
|
193
|
+
The `check` command runs all evals and reports what passes and fails. See [CONTRIBUTING.md](./CONTRIBUTING.md) for the full guide.
|
|
200
194
|
|
|
201
195
|
**Books with open issues** (tagged `good first issue`): [The Pragmatic Programmer](https://github.com/booklib-ai/skills/issues/2) · [Clean Architecture](https://github.com/booklib-ai/skills/issues/3) · [A Philosophy of Software Design](https://github.com/booklib-ai/skills/issues/4) · [Accelerate](https://github.com/booklib-ai/skills/issues/8) · [and more →](https://github.com/booklib-ai/skills/issues?q=is%3Aopen+label%3A%22good+first+issue%22)
|
|
202
196
|
|
package/bin/skills.js
CHANGED
|
@@ -12,6 +12,7 @@ const command = args[0];
|
|
|
12
12
|
const skillsRoot = path.join(__dirname, '..', 'skills');
|
|
13
13
|
const commandsRoot = path.join(__dirname, '..', 'commands');
|
|
14
14
|
const agentsRoot = path.join(__dirname, '..', 'agents');
|
|
15
|
+
const rulesRoot = path.join(__dirname, '..', 'rules');
|
|
15
16
|
|
|
16
17
|
// ─── Installation profiles ────────────────────────────────────────────────────
|
|
17
18
|
const PROFILES = {
|
|
@@ -149,6 +150,9 @@ const commandsTargetDir = isGlobal
|
|
|
149
150
|
const agentsTargetDir = isGlobal
|
|
150
151
|
? path.join(os.homedir(), '.claude', 'agents')
|
|
151
152
|
: path.join(process.cwd(), '.claude', 'agents');
|
|
153
|
+
const rulesTargetDir = isGlobal
|
|
154
|
+
? path.join(os.homedir(), '.claude', 'rules')
|
|
155
|
+
: path.join(process.cwd(), '.claude', 'rules');
|
|
152
156
|
|
|
153
157
|
function copyCommand(skillName) {
|
|
154
158
|
const src = path.join(commandsRoot, `${skillName}.md`);
|
|
@@ -167,6 +171,30 @@ function getAvailableAgents() {
|
|
|
167
171
|
.sort();
|
|
168
172
|
}
|
|
169
173
|
|
|
174
|
+
function parseAgentFrontmatter(agentName) {
|
|
175
|
+
const agentMdPath = path.join(agentsRoot, `${agentName}.md`);
|
|
176
|
+
try {
|
|
177
|
+
const content = fs.readFileSync(agentMdPath, 'utf8');
|
|
178
|
+
const fmMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
179
|
+
if (!fmMatch) return { name: agentName, description: '', model: '' };
|
|
180
|
+
const fm = fmMatch[1];
|
|
181
|
+
|
|
182
|
+
const blockMatch = fm.match(/^description:\s*>\s*\n((?:[ \t]+.+\n?)+)/m);
|
|
183
|
+
const quotedMatch = fm.match(/^description:\s*["'](.+?)["']\s*$/m);
|
|
184
|
+
const plainMatch = fm.match(/^description:\s*(?!>)(.+)$/m);
|
|
185
|
+
const modelMatch = fm.match(/^model:\s*(\S+)/m);
|
|
186
|
+
|
|
187
|
+
let description = '';
|
|
188
|
+
if (blockMatch) description = blockMatch[1].split('\n').map(l => l.trim()).filter(Boolean).join(' ');
|
|
189
|
+
else if (quotedMatch) description = quotedMatch[1];
|
|
190
|
+
else if (plainMatch) description = plainMatch[1].trim();
|
|
191
|
+
|
|
192
|
+
return { name: agentName, description, model: modelMatch?.[1] ?? '' };
|
|
193
|
+
} catch {
|
|
194
|
+
return { name: agentName, description: '', model: '' };
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
170
198
|
function copyAgent(agentName) {
|
|
171
199
|
const src = path.join(agentsRoot, `${agentName}.md`);
|
|
172
200
|
if (!fs.existsSync(src)) return;
|
|
@@ -176,6 +204,72 @@ function copyAgent(agentName) {
|
|
|
176
204
|
console.log(c.green('✓') + ` @${agentName} agent → ${c.dim(dest)}`);
|
|
177
205
|
}
|
|
178
206
|
|
|
207
|
+
// ─── Cursor support ───────────────────────────────────────────────────────────
|
|
208
|
+
function getCursorRulesDir() {
|
|
209
|
+
return isGlobal
|
|
210
|
+
? path.join(os.homedir(), '.cursor', 'rules')
|
|
211
|
+
: path.join(process.cwd(), '.cursor', 'rules');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function copyHooks() {
|
|
215
|
+
const hooksDir = path.join(__dirname, '..', 'hooks');
|
|
216
|
+
if (!fs.existsSync(hooksDir)) return;
|
|
217
|
+
// Copy suggest.js to the .claude/ root as booklib-suggest.js
|
|
218
|
+
const suggestSrc = path.join(hooksDir, 'suggest.js');
|
|
219
|
+
if (fs.existsSync(suggestSrc)) {
|
|
220
|
+
const claudeDir = isGlobal ? path.join(os.homedir(), '.claude') : path.join(process.cwd(), '.claude');
|
|
221
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
222
|
+
const dest = path.join(claudeDir, 'booklib-suggest.js');
|
|
223
|
+
fs.copyFileSync(suggestSrc, dest);
|
|
224
|
+
console.log(c.green('✓') + ` booklib-suggest.js hook → ${c.dim(dest)}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function getAvailableRules() {
|
|
229
|
+
// Returns [{language, name, file}] for each rule file found under rules/
|
|
230
|
+
if (!fs.existsSync(rulesRoot)) return [];
|
|
231
|
+
const result = [];
|
|
232
|
+
for (const lang of fs.readdirSync(rulesRoot).sort()) {
|
|
233
|
+
const langDir = path.join(rulesRoot, lang);
|
|
234
|
+
if (!fs.statSync(langDir).isDirectory()) continue;
|
|
235
|
+
for (const file of fs.readdirSync(langDir).filter(f => f.endsWith('.md')).sort()) {
|
|
236
|
+
result.push({ language: lang, name: file.replace(/\.md$/, ''), file: path.join(langDir, file) });
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
return result;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function copyRules(language) {
|
|
243
|
+
// Copies all rule files for a given language (or 'common') to rulesTargetDir
|
|
244
|
+
const langDir = path.join(rulesRoot, language);
|
|
245
|
+
if (!fs.existsSync(langDir)) {
|
|
246
|
+
console.error(c.red(`✗ No rules for language "${language}".`) + ' Run ' + c.cyan('skills rules') + ' to see available rules.');
|
|
247
|
+
process.exit(1);
|
|
248
|
+
}
|
|
249
|
+
const destDir = path.join(rulesTargetDir, language);
|
|
250
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
251
|
+
for (const file of fs.readdirSync(langDir).filter(f => f.endsWith('.md'))) {
|
|
252
|
+
const dest = path.join(destDir, file);
|
|
253
|
+
fs.copyFileSync(path.join(langDir, file), dest);
|
|
254
|
+
console.log(c.green('✓') + ` ${c.bold(language + '/' + file.replace(/\.md$/, ''))} rule → ${c.dim(dest)}`);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function copyAllRules() {
|
|
259
|
+
const rules = getAvailableRules();
|
|
260
|
+
const languages = [...new Set(rules.map(r => r.language))];
|
|
261
|
+
for (const lang of languages) copyRules(lang);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function copySkillToCursor(skillName) {
|
|
265
|
+
const src = path.join(skillsRoot, skillName, 'SKILL.md');
|
|
266
|
+
if (!fs.existsSync(src)) return;
|
|
267
|
+
const dest = path.join(getCursorRulesDir(), `${skillName}.md`);
|
|
268
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
269
|
+
fs.copyFileSync(src, dest);
|
|
270
|
+
console.log(c.green('✓') + ` ${c.bold(skillName)} → ${c.dim(dest)}`);
|
|
271
|
+
}
|
|
272
|
+
|
|
179
273
|
// ─── CHECK command ────────────────────────────────────────────────────────────
|
|
180
274
|
function checkSkill(skillName) {
|
|
181
275
|
const skillDir = path.join(skillsRoot, skillName);
|
|
@@ -771,12 +865,28 @@ async function main() {
|
|
|
771
865
|
}
|
|
772
866
|
|
|
773
867
|
case 'add': {
|
|
774
|
-
const addAll
|
|
775
|
-
const
|
|
776
|
-
const
|
|
777
|
-
const
|
|
778
|
-
const
|
|
779
|
-
const
|
|
868
|
+
const addAll = args.includes('--all');
|
|
869
|
+
const addHooks = args.includes('--hooks');
|
|
870
|
+
const noCommands = args.includes('--no-commands');
|
|
871
|
+
const noAgents = args.includes('--no-agents');
|
|
872
|
+
const agentArg = args.find(a => a.startsWith('--agent='))?.split('=')[1];
|
|
873
|
+
const profileArg = args.find(a => a.startsWith('--profile='))?.split('=')[1];
|
|
874
|
+
const rulesArg = args.find(a => a === '--rules' || a.startsWith('--rules='));
|
|
875
|
+
const rulesLang = rulesArg?.includes('=') ? rulesArg.split('=')[1] : null;
|
|
876
|
+
const targetArg = (args.find(a => a.startsWith('--target='))?.split('=')[1] ?? 'claude').toLowerCase();
|
|
877
|
+
const toClaude = targetArg === 'claude' || targetArg === 'all';
|
|
878
|
+
const toCursor = targetArg === 'cursor' || targetArg === 'all';
|
|
879
|
+
const skillName = args.find(a => !a.startsWith('--') && a !== 'add');
|
|
880
|
+
|
|
881
|
+
const installSkills = (list) => {
|
|
882
|
+
if (toClaude) list.forEach(s => copySkill(s, targetDir));
|
|
883
|
+
if (toCursor) list.forEach(s => copySkillToCursor(s));
|
|
884
|
+
if (toClaude && !noCommands) list.forEach(s => copyCommand(s));
|
|
885
|
+
};
|
|
886
|
+
const installAgents = (list) => {
|
|
887
|
+
if (toClaude && !noAgents) list.forEach(a => copyAgent(a));
|
|
888
|
+
// agents not applicable to Cursor
|
|
889
|
+
};
|
|
780
890
|
|
|
781
891
|
if (profileArg) {
|
|
782
892
|
const profile = PROFILES[profileArg];
|
|
@@ -784,15 +894,14 @@ async function main() {
|
|
|
784
894
|
console.error(c.red(`✗ Profile "${profileArg}" not found.`) + ' Run ' + c.cyan('skills profiles') + ' to see available profiles.');
|
|
785
895
|
process.exit(1);
|
|
786
896
|
}
|
|
787
|
-
profile.skills
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
const agentStr = profile.agents.length
|
|
897
|
+
installSkills(profile.skills);
|
|
898
|
+
installAgents(profile.agents);
|
|
899
|
+
const targets = [toClaude && '.claude', toCursor && '.cursor/rules'].filter(Boolean).join(' + ');
|
|
900
|
+
const agentStr = (!noAgents && toClaude && profile.agents.length)
|
|
791
901
|
? `, ${profile.agents.length} agent${profile.agents.length > 1 ? 's' : ''}`
|
|
792
902
|
: '';
|
|
793
|
-
console.log(c.dim(`\nInstalled profile "${profileArg}": ${profile.skills.length} skills${agentStr}`));
|
|
903
|
+
console.log(c.dim(`\nInstalled profile "${profileArg}": ${profile.skills.length} skills${agentStr} → ${targets}`));
|
|
794
904
|
} else if (agentArg) {
|
|
795
|
-
// explicit: skills add --agent=booklib-reviewer
|
|
796
905
|
const agents = getAvailableAgents();
|
|
797
906
|
if (!agents.includes(agentArg)) {
|
|
798
907
|
console.error(c.red(`✗ Agent "${agentArg}" not found.`) + ' Available: ' + c.dim(agents.join(', ')));
|
|
@@ -802,14 +911,27 @@ async function main() {
|
|
|
802
911
|
console.log(c.dim(`\nInstalled to ${agentsTargetDir}`));
|
|
803
912
|
} else if (addAll) {
|
|
804
913
|
const skills = getAvailableSkills();
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
914
|
+
const agents = getAvailableAgents();
|
|
915
|
+
installSkills(skills);
|
|
916
|
+
installAgents(agents);
|
|
917
|
+
if (toClaude) { copyHooks(); copyAllRules(); }
|
|
918
|
+
const agentCount = (!noAgents && toClaude) ? agents.length : 0;
|
|
919
|
+
const targets = [toClaude && '.claude', toCursor && '.cursor/rules'].filter(Boolean).join(' + ');
|
|
920
|
+
console.log(c.dim(`\nInstalled ${skills.length} skills, ${agentCount} agents, ${getAvailableRules().length} rules → ${targets}`));
|
|
921
|
+
} else if (addHooks) {
|
|
922
|
+
if (toClaude) copyHooks();
|
|
923
|
+
else console.log(c.yellow(' --hooks only applies to Claude targets. Use without --target=cursor.'));
|
|
924
|
+
break;
|
|
925
|
+
} else if (rulesArg) {
|
|
926
|
+
if (!toClaude) {
|
|
927
|
+
console.log(c.yellow(' --rules only applies to Claude targets (.claude/rules/).'));
|
|
928
|
+
break;
|
|
929
|
+
}
|
|
930
|
+
if (rulesLang) copyRules(rulesLang);
|
|
931
|
+
else copyAllRules();
|
|
932
|
+
console.log(c.dim(`\nInstalled rules → ${rulesTargetDir}`));
|
|
810
933
|
} else if (skillName) {
|
|
811
|
-
|
|
812
|
-
if (!noCommands) copyCommand(skillName);
|
|
934
|
+
installSkills([skillName]);
|
|
813
935
|
console.log(c.dim(`\nInstalled to ${targetDir}`));
|
|
814
936
|
} else {
|
|
815
937
|
console.error(c.red('Usage: skills add <skill-name> | skills add --all | skills add --agent=<name>'));
|
|
@@ -916,6 +1038,74 @@ async function main() {
|
|
|
916
1038
|
break;
|
|
917
1039
|
}
|
|
918
1040
|
|
|
1041
|
+
case 'agents': {
|
|
1042
|
+
const infoArg = args.find(a => a.startsWith('--info='))?.split('=')[1]
|
|
1043
|
+
|| args.find(a => !a.startsWith('--') && a !== 'agents');
|
|
1044
|
+
const available = getAvailableAgents();
|
|
1045
|
+
|
|
1046
|
+
if (infoArg) {
|
|
1047
|
+
if (!available.includes(infoArg)) {
|
|
1048
|
+
console.error(c.red(`✗ Agent "${infoArg}" not found.`) + ' Run ' + c.cyan('skills agents') + ' to see available agents.');
|
|
1049
|
+
process.exit(1);
|
|
1050
|
+
}
|
|
1051
|
+
const { description, model } = parseAgentFrontmatter(infoArg);
|
|
1052
|
+
console.log('');
|
|
1053
|
+
console.log(c.bold(` ${infoArg}`) + c.dim(model ? ` [${model}]` : ''));
|
|
1054
|
+
console.log(' ' + c.line(55));
|
|
1055
|
+
console.log(' ' + description);
|
|
1056
|
+
console.log('');
|
|
1057
|
+
console.log(c.dim(` Install: skills add --agent=${infoArg}`));
|
|
1058
|
+
console.log('');
|
|
1059
|
+
} else {
|
|
1060
|
+
const nameW = Math.max(...available.map(n => n.length)) + 2;
|
|
1061
|
+
console.log('');
|
|
1062
|
+
console.log(c.bold(` @booklib/skills — agents`) + c.dim(` (${available.length})`));
|
|
1063
|
+
console.log(' ' + c.line(60));
|
|
1064
|
+
for (const name of available) {
|
|
1065
|
+
const { description, model } = parseAgentFrontmatter(name);
|
|
1066
|
+
const modelTag = model ? c.dim(` [${model}]`) : '';
|
|
1067
|
+
console.log(` ${c.cyan(name.padEnd(nameW))}${modelTag}`);
|
|
1068
|
+
if (description) console.log(` ${' '.repeat(nameW)}${firstSentence(description, 72)}`);
|
|
1069
|
+
}
|
|
1070
|
+
console.log('');
|
|
1071
|
+
console.log(c.dim(` skills add --agent=<name> install one agent`));
|
|
1072
|
+
console.log(c.dim(` skills agents --info=<name> full description`));
|
|
1073
|
+
console.log('');
|
|
1074
|
+
}
|
|
1075
|
+
break;
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
case 'rules': {
|
|
1079
|
+
const available = getAvailableRules();
|
|
1080
|
+
if (!available.length) {
|
|
1081
|
+
console.log(c.yellow(' No rules found.'));
|
|
1082
|
+
break;
|
|
1083
|
+
}
|
|
1084
|
+
// Group by language
|
|
1085
|
+
const byLang = {};
|
|
1086
|
+
for (const r of available) {
|
|
1087
|
+
if (!byLang[r.language]) byLang[r.language] = [];
|
|
1088
|
+
byLang[r.language].push(r);
|
|
1089
|
+
}
|
|
1090
|
+
console.log('');
|
|
1091
|
+
console.log(c.bold(' @booklib/skills — rules') + c.dim(` (${available.length} always-on)`));
|
|
1092
|
+
console.log(' ' + c.line(60));
|
|
1093
|
+
for (const [lang, rules] of Object.entries(byLang)) {
|
|
1094
|
+
console.log(` ${c.bold(lang)}`);
|
|
1095
|
+
for (const r of rules) {
|
|
1096
|
+
const content = fs.readFileSync(r.file, 'utf8');
|
|
1097
|
+
const descMatch = content.match(/^description:\s*(.+)$/m);
|
|
1098
|
+
const desc = descMatch ? descMatch[1].trim().replace(/^>$/, '') : '';
|
|
1099
|
+
console.log(` ${c.cyan(r.name.padEnd(28))}${c.dim(firstSentence(desc, 55))}`);
|
|
1100
|
+
}
|
|
1101
|
+
console.log('');
|
|
1102
|
+
}
|
|
1103
|
+
console.log(c.dim(` skills add --rules install all rules → .claude/rules/`));
|
|
1104
|
+
console.log(c.dim(` skills add --rules=<language> install rules for one language`));
|
|
1105
|
+
console.log('');
|
|
1106
|
+
break;
|
|
1107
|
+
}
|
|
1108
|
+
|
|
919
1109
|
case 'profiles': {
|
|
920
1110
|
const nameW = Math.max(...Object.keys(PROFILES).map(k => k.length)) + 2;
|
|
921
1111
|
console.log('');
|
|
@@ -939,14 +1129,22 @@ ${c.bold(' @booklib/skills')} — book knowledge distilled into AI agent skills
|
|
|
939
1129
|
|
|
940
1130
|
${c.bold(' Usage:')}
|
|
941
1131
|
${c.cyan('skills list')} list all available skills
|
|
1132
|
+
${c.cyan('skills agents')} list all available agents
|
|
1133
|
+
${c.cyan('skills agents')} ${c.dim('--info=<name>')} full description of an agent
|
|
942
1134
|
${c.cyan('skills profiles')} list available profiles
|
|
1135
|
+
${c.cyan('skills rules')} list always-on rule files
|
|
943
1136
|
${c.cyan('skills info')} ${c.dim('<name>')} full description of a skill
|
|
944
1137
|
${c.cyan('skills demo')} ${c.dim('<name>')} before/after example
|
|
945
1138
|
${c.cyan('skills add')} ${c.dim('--profile=<name>')} install a profile (skills + commands + agent)
|
|
946
1139
|
${c.cyan('skills add')} ${c.dim('<name>')} install a single skill + /command
|
|
947
|
-
${c.cyan('skills add --all')} install everything (skills +
|
|
1140
|
+
${c.cyan('skills add --all')} install everything (skills + agents + rules + hooks)
|
|
948
1141
|
${c.cyan('skills add')} ${c.dim('<name> --global')} install globally (~/.claude/)
|
|
949
1142
|
${c.cyan('skills add')} ${c.dim('--agent=<name>')} install a single agent to .claude/agents/
|
|
1143
|
+
${c.cyan('skills add --rules')} install always-on rules to .claude/rules/
|
|
1144
|
+
${c.cyan('skills add')} ${c.dim('--rules=<language>')} install rules for one language
|
|
1145
|
+
${c.cyan('skills add --hooks')} install the UserPromptSubmit suggestion hook
|
|
1146
|
+
${c.cyan('skills add')} ${c.dim('--target=cursor')} install to .cursor/rules/ (Cursor IDE)
|
|
1147
|
+
${c.cyan('skills add')} ${c.dim('--target=all')} install to both .claude/ and .cursor/
|
|
950
1148
|
${c.cyan('skills add')} ${c.dim('--no-commands')} skip /command installation
|
|
951
1149
|
${c.cyan('skills add')} ${c.dim('--no-agents')} skip agent installation
|
|
952
1150
|
${c.cyan('skills check')} ${c.dim('<name>')} quality check (Bronze/Silver/Gold/Platinum)
|