@booklib/skills 1.6.0 → 1.7.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.
@@ -1,129 +0,0 @@
1
- # skill-router benchmark: book-based review vs. native PR toolkit
2
-
3
- **skill-router** analyzes your code and picks the right book-based skill for the job — so instead of one AI trying to apply four books superficially, you get a deep, focused review from the correct authority. This benchmark runs the same intentionally broken JavaScript file through two parallel pipelines to compare what each finds.
4
-
5
- | | Native | skill-router |
6
- |---|---|---|
7
- | **Pipeline** | `pr-review-toolkit:code-reviewer` | `skill-router` → `clean-code-reviewer` + `design-patterns` |
8
- | **Output file** | `order-processing.pr-toolkit.js` | `order-processing.skill-router.js` |
9
-
10
- **Bottom line:** Native wins on security depth (catches PCI violation, highest confidence scores). skill-router wins on architectural insight and principle education — finds ~47% more unique issues when both pipelines are combined.
11
-
12
- ---
13
-
14
- ## The code under test
15
-
16
- `order-processing.original.js` — 157-line Node.js order processing module with: one god function (`process`), no error handling, SQL injection on every query, global mutable state shared across all requests, and `eval()` used to return a plain variable.
17
-
18
- ---
19
-
20
- ## How skill-router picks its skills
21
-
22
- The router commits to a routing decision before any review begins:
23
-
24
- ```
25
- Primary: clean-code-reviewer — god function, cryptic names, magic numbers (Ch.2/3/7)
26
- Secondary: design-patterns — duplicated payment blocks = Strategy pattern
27
- Don't use: domain-driven-design — implementation-level code, not a model design problem
28
- ```
29
-
30
- This is the core value: rather than spreading one review thin across four books, the router picks the right lens and goes deep. The `Don't use` line matters — it avoids premature or irrelevant advice.
31
-
32
- ---
33
-
34
- ## Issues found
35
-
36
- > **Notation:** `C1`, `I9`, `S4` are clean-code-reviewer's severity tiers (Critical / Important / Suggestion). `G30`, `N1`, `Ch.7` etc. are chapter/guideline references from *Clean Code* by Robert C. Martin. `Strategy(1)`, `Singleton(4)` etc. are pattern references from *Head First Design Patterns*.
37
-
38
- ### Critical / High severity
39
-
40
- | Issue | Native | clean-code | design-patterns |
41
- |---|---|---|---|
42
- | SQL injection — every query (7 locations) | ✅ Confidence 100 | ✅ C1 — G30 | — |
43
- | `eval("stats")` — unnecessary, disables JIT | ✅ Confidence 100 | ✅ C3 — G30 | ✅ Singleton(4) |
44
- | Global mutable `usr`/`items`/`total` — cross-request leak | ✅ Confidence 98 | ✅ C2 — G18 | ✅ Singleton(4) |
45
- | `items` unbounded memory leak | ✅ Confidence 95 | ⚠️ implied | — |
46
- | `refund()` null dereference crash | ✅ Confidence 95 | ✅ C4 — Ch.7 | — |
47
- | PCI violation — card data logged to stdout | ✅ Confidence 92 | ❌ missed | — |
48
- | Payment fall-through returns `undefined` not `false` | ✅ Confidence 91 | ✅ I9 | ✅ Strategy(1) |
49
- | Duplicated card/paypal confirmation block | ✅ Confidence 85 | ✅ C5 — G5 | ✅ Strategy(1) |
50
- | Wrong-recipient email via global `usr` in cancel/refund | ✅ Confidence 89 | ✅ C4/I7 | — |
51
-
52
- ### Important / Improvement
53
-
54
- | Issue | Native | clean-code | design-patterns |
55
- |---|---|---|---|
56
- | `==` instead of `===` throughout | ✅ Confidence 82 | ✅ I5 — G15 | — |
57
- | `var` instead of `const`/`let` | ✅ Confidence 80 | ✅ I6 — G29 | — |
58
- | Magic discount numbers (0.1, 0.2, 0.5) | ✅ | ✅ I3 — G25 | ✅ Strategy(3) |
59
- | `discount` variable declared, never used | ✅ Confidence 88 | ✅ I3 | — |
60
- | Discount should be data-driven, not if-chain | — | ✅ I4 — G23 | ✅ Strategy(3) |
61
- | God function — 10 responsibilities | ⚠️ via nesting | ✅ I1 — F1/F2 | ✅ Template(5) |
62
- | Six-level arrow-head nesting | ✅ Confidence 82 | ✅ I2 | — |
63
- | No error handling on db/mailer calls | ✅ Confidence 85 | ✅ I8 — Ch.7 | — |
64
- | `cancel()` never updates stats | ✅ Confidence 80 | — | — |
65
- | Stats inconsistency — two separate objects | ✅ Confidence 82 | ✅ S4 | ✅ Singleton(4) |
66
- | Cryptic names (`o`, `u`, `pay`, `s`, `rsn`) | ✅ Confidence 80 | ✅ S1 — N1/N2 | — |
67
- | Noise/lying comments | — | ✅ S2 — C2/C3 | — |
68
- | `formatMoney` floating-point rounding bug | — | ✅ S3 | — |
69
- | Stubs always return `true` — lying | — | ✅ S5 — C3 | — |
70
- | State machine needed for order lifecycle | — | — | ✅ State(2) |
71
- | Side effects hardcoded — use Observer | — | — | ✅ Observer(6) |
72
- | Flat module export — use Facade | — | — | ✅ Facade(7) |
73
-
74
- ### Totals
75
-
76
- | | Native | clean-code | design-patterns | Combined |
77
- |---|---|---|---|---|
78
- | Critical/High | 9 | 5 | 4 | **9 unique** |
79
- | Important/Improvement | 10 | 9 | 3 | **14 unique** |
80
- | Suggestion/Low | 0 | 5 | 0 | **5** |
81
- | **Total** | **19** | **19** | **7 patterns** | **~28 unique** |
82
-
83
- Overlap: ~89% of native issues were also found by skill-router.
84
-
85
- ---
86
-
87
- ## Pattern opportunities identified (skill-router only)
88
-
89
- The native pipeline finds bugs. The skill-router pipeline additionally surfaces structural opportunities — places where a known pattern *could* reduce complexity — and explains the problem each one solves. Whether to apply a pattern is a judgment call: the skill flags the opportunity and the reasoning; you decide if the trade-off is worth it in your context.
90
-
91
- | # | Pattern | Impact | Problem it would solve |
92
- |---|---|---|---|
93
- | 1 | Strategy — payments | HIGH | Copy-pasted if-block per payment method |
94
- | 2 | State — order lifecycle | HIGH | Status strings checked in every function |
95
- | 3 | Strategy — discounts | HIGH | Magic-number if-chain, no extensibility |
96
- | 4 | Singleton (broken) | HIGH | Module-scope mutable state crossing requests |
97
- | 5 | Template Method | MEDIUM | Arrow pyramid duplicated in process+cancel |
98
- | 6 | Observer | MEDIUM | Email/stats hardcoded into business logic |
99
- | 7 | Facade | MEDIUM | Unrelated concerns in one flat export |
100
-
101
- **Suggested refactor sequence if you choose to act:** 4 → 1 → 3 → 2 → 5 → 6 → 7
102
-
103
- ---
104
-
105
-
106
- ## Updates since this benchmark
107
-
108
- **skill-router now outputs severity tiers.** One gap this benchmark exposed was that native's confidence scores (≥80 threshold) act as a noise filter — skill-router had no equivalent. The router has since been updated to instruct selected skills to classify every finding as **HIGH** (correctness/security/data loss), **MEDIUM** (design/maintainability), or **LOW** (style/naming), and to skip LOW findings for standard code reviews. This closes the signal-to-noise gap without re-running the benchmark — detection quality hasn't changed, output actionability has.
109
-
110
- ---
111
-
112
- ## When to use each
113
-
114
- | Situation | Use |
115
- |---|---|
116
- | Pre-merge PR review, security audit | **Native** — pre-merge gate: fast, confidence-filtered, adapts to CLAUDE.md project conventions |
117
- | Larger refactor, architecture planning | **skill-router** — patterns, principles, refactor roadmap |
118
- | Both together | ~95% total issue coverage vs ~80% for either alone |
119
-
120
- ---
121
-
122
- ## Files in this benchmark
123
-
124
- | File | Description |
125
- |---|---|
126
- | `order-processing.original.js` | Original bad code — unchanged input |
127
- | `order-processing.pr-toolkit.js` | Rewritten applying all `pr-review-toolkit` findings |
128
- | `order-processing.skill-router.js` | Rewritten applying `clean-code-reviewer` + `design-patterns` |
129
- | `review-report.md` | This file |
package/demo.gif DELETED
Binary file
package/demo.tape DELETED
@@ -1,40 +0,0 @@
1
- Output demo.gif
2
-
3
- Set Shell "bash"
4
- Set FontSize 15
5
- Set Width 1100
6
- Set Height 650
7
- Set Theme "Dracula"
8
- Set Padding 24
9
- Set Framerate 30
10
- Set PlaybackSpeed 1
11
- Env PATH "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin"
12
-
13
- Hide
14
- Type "cd /Users/fvst/other/fp/skills && clear"
15
- Enter
16
- Sleep 500ms
17
- Show
18
-
19
- Sleep 500ms
20
-
21
- Type "node bin/skills.js list"
22
- Sleep 300ms
23
- Enter
24
- Sleep 5s
25
-
26
- Sleep 1500ms
27
-
28
- Type "node bin/skills.js info clean-code-reviewer"
29
- Sleep 300ms
30
- Enter
31
- Sleep 3s
32
-
33
- Sleep 1500ms
34
-
35
- Type "node bin/skills.js demo clean-code-reviewer"
36
- Sleep 300ms
37
- Enter
38
- Sleep 5s
39
-
40
- Sleep 3s
package/docs/index.html DELETED
@@ -1,411 +0,0 @@
1
- <!DOCTYPE html>
2
- <html lang="en">
3
- <head>
4
- <meta charset="UTF-8"/>
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
6
- <title>booklib-ai/skills — Book-grounded AI agent skills</title>
7
- <meta name="description" content="22 AI agent skills grounded in canonical programming books. Give your AI coding assistant expert knowledge from Clean Code, Effective Java, DDD, and more."/>
8
- <meta property="og:title" content="booklib-ai/skills"/>
9
- <meta property="og:description" content="Book-grounded AI agent skills for Claude Code, Cursor, Copilot, and Windsurf."/>
10
- <meta property="og:image" content="https://booklib-ai.github.io/skills/og.png"/>
11
- <meta property="og:image:width" content="1200"/>
12
- <meta property="og:image:height" content="630"/>
13
- <meta name="twitter:card" content="summary_large_image"/>
14
- <meta name="twitter:image" content="https://booklib-ai.github.io/skills/og.png"/>
15
- <style>
16
- *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
17
-
18
- body {
19
- background: #0d0d1a;
20
- color: #e2e8f0;
21
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
22
- line-height: 1.6;
23
- }
24
-
25
- a { color: #818cf8; text-decoration: none; }
26
- a:hover { text-decoration: underline; }
27
-
28
- /* Hero */
29
- .hero {
30
- text-align: center;
31
- padding: 80px 24px 64px;
32
- max-width: 720px;
33
- margin: 0 auto;
34
- }
35
- .hero img.logo { width: 96px; height: 96px; margin-bottom: 24px; }
36
- .hero h1 { font-size: 2.5rem; font-weight: 700; color: #f1f5f9; letter-spacing: -0.03em; }
37
- .hero p {
38
- font-size: 1.125rem;
39
- color: #94a3b8;
40
- margin-top: 16px;
41
- max-width: 520px;
42
- margin-left: auto;
43
- margin-right: auto;
44
- }
45
-
46
- /* Install */
47
- #install {
48
- display: flex;
49
- align-items: center;
50
- justify-content: center;
51
- gap: 12px;
52
- margin-top: 36px;
53
- flex-wrap: wrap;
54
- }
55
- .install-box {
56
- background: #161625;
57
- border: 1px solid #2d2d4a;
58
- border-radius: 10px;
59
- padding: 12px 20px;
60
- font-family: "SF Mono", "Fira Code", monospace;
61
- font-size: 0.9rem;
62
- color: #a5b4fc;
63
- cursor: pointer;
64
- user-select: all;
65
- transition: border-color 0.2s;
66
- }
67
- .install-box.highlighted {
68
- border-color: #6366f1;
69
- color: #c7d2fe;
70
- }
71
- .copy-btn {
72
- background: #6366f1;
73
- color: white;
74
- border: none;
75
- border-radius: 8px;
76
- padding: 12px 20px;
77
- font-size: 0.875rem;
78
- font-weight: 600;
79
- cursor: pointer;
80
- transition: background 0.15s;
81
- }
82
- .copy-btn:hover { background: #4f46e5; }
83
-
84
- /* Badges */
85
- .badges {
86
- display: flex;
87
- justify-content: center;
88
- gap: 8px;
89
- margin-top: 28px;
90
- flex-wrap: wrap;
91
- }
92
- .badges img { height: 20px; }
93
-
94
- /* Skills grid */
95
- .skills-section {
96
- max-width: 1100px;
97
- margin: 64px auto;
98
- padding: 0 24px;
99
- }
100
- .skills-section h2 {
101
- font-size: 1.5rem;
102
- font-weight: 700;
103
- color: #f1f5f9;
104
- margin-bottom: 8px;
105
- text-align: center;
106
- }
107
- .skills-section .subtitle {
108
- text-align: center;
109
- color: #64748b;
110
- font-size: 0.9rem;
111
- margin-bottom: 32px;
112
- }
113
- .skills-grid {
114
- display: grid;
115
- grid-template-columns: repeat(auto-fill, minmax(130px, 1fr));
116
- gap: 14px;
117
- }
118
-
119
- /* Card */
120
- .skill-card {
121
- background: #11111f;
122
- border: 1px solid #1e1e35;
123
- border-radius: 12px;
124
- overflow: hidden;
125
- cursor: pointer;
126
- transition: border-color 0.15s, transform 0.15s, box-shadow 0.15s;
127
- display: flex;
128
- flex-direction: column;
129
- text-decoration: none;
130
- color: inherit;
131
- }
132
- .skill-card:hover {
133
- border-color: #6366f1;
134
- transform: translateY(-3px);
135
- box-shadow: 0 8px 24px rgba(99, 102, 241, 0.15);
136
- text-decoration: none;
137
- }
138
- .skill-card.active {
139
- border-color: #818cf8;
140
- box-shadow: 0 0 0 2px rgba(99, 102, 241, 0.3);
141
- }
142
-
143
- /* Book cover area */
144
- .cover-wrap {
145
- width: 100%;
146
- aspect-ratio: 2 / 3;
147
- background: #161625;
148
- display: flex;
149
- align-items: center;
150
- justify-content: center;
151
- overflow: hidden;
152
- position: relative;
153
- }
154
- .cover-wrap img {
155
- width: 100%;
156
- height: 100%;
157
- object-fit: cover;
158
- display: block;
159
- }
160
- .cover-wrap img.loading {
161
- background: linear-gradient(90deg, #161625 25%, #1e1e38 50%, #161625 75%);
162
- background-size: 200% 100%;
163
- animation: shimmer 1.5s infinite linear;
164
- }
165
- @keyframes shimmer {
166
- 0% { background-position: 200% 0; }
167
- 100% { background-position: -200% 0; }
168
- }
169
- .cover-fallback {
170
- width: 100%;
171
- height: 100%;
172
- display: flex;
173
- flex-direction: column;
174
- align-items: center;
175
- justify-content: center;
176
- padding: 16px;
177
- text-align: center;
178
- gap: 8px;
179
- }
180
- .cover-fallback .initials {
181
- font-size: 2rem;
182
- font-weight: 700;
183
- color: #6366f1;
184
- line-height: 1;
185
- }
186
- .cover-fallback .fallback-title {
187
- font-size: 0.75rem;
188
- color: #64748b;
189
- line-height: 1.3;
190
- }
191
-
192
- /* Card body */
193
- .card-body {
194
- padding: 10px 12px 12px;
195
- flex: 1;
196
- display: flex;
197
- flex-direction: column;
198
- gap: 3px;
199
- }
200
- .card-body .skill-name {
201
- font-size: 0.7rem;
202
- font-weight: 700;
203
- color: #a5b4fc;
204
- font-family: "SF Mono", "Fira Code", monospace;
205
- word-break: break-all;
206
- }
207
- .card-body .book-title {
208
- font-size: 0.75rem;
209
- font-weight: 600;
210
- color: #e2e8f0;
211
- line-height: 1.3;
212
- margin-top: 2px;
213
- }
214
- .card-body .book-author {
215
- font-size: 0.68rem;
216
- color: #64748b;
217
- }
218
- .card-body .card-desc {
219
- font-size: 0.68rem;
220
- color: #475569;
221
- line-height: 1.4;
222
- margin-top: 4px;
223
- }
224
-
225
- /* Footer */
226
- footer {
227
- text-align: center;
228
- padding: 48px 24px;
229
- color: #475569;
230
- font-size: 0.875rem;
231
- border-top: 1px solid #1e1e35;
232
- }
233
- footer a { color: #6366f1; }
234
- </style>
235
- </head>
236
- <body>
237
-
238
- <section class="hero">
239
- <img class="logo" src="https://raw.githubusercontent.com/booklib-ai/skills/main/assets/logo.svg" alt="booklib-ai skills logo"/>
240
- <h1>Skills</h1>
241
- <p>Book-grounded AI agent skills — expert knowledge from canonical programming books, packaged for Claude Code, Cursor, Copilot, and Windsurf.</p>
242
-
243
- <div id="install">
244
- <div class="install-box" id="cmd">npx skills add booklib-ai/skills --all -g</div>
245
- <button class="copy-btn" onclick="copyCmd()">Copy</button>
246
- </div>
247
-
248
- <div class="badges">
249
- <img src="https://img.shields.io/npm/v/@booklib/skills.svg" alt="npm version"/>
250
- <img src="https://img.shields.io/npm/dw/@booklib/skills.svg" alt="downloads"/>
251
- <img src="https://img.shields.io/github/stars/booklib-ai/skills?style=flat" alt="stars"/>
252
- <img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="license"/>
253
- </div>
254
- </section>
255
-
256
- <section class="skills-section">
257
- <h2>22 skills, 22 books</h2>
258
- <p class="subtitle">Click any skill to see its install command</p>
259
- <div class="skills-grid" id="grid"></div>
260
- </section>
261
-
262
- <footer>
263
- <p>MIT License &middot; <a href="https://github.com/booklib-ai/skills">GitHub</a> &middot; <a href="https://github.com/booklib-ai/skills/blob/main/CONTRIBUTING.md">Contributing</a> &middot; <a href="https://github.com/booklib-ai/skills/blob/main/AGENTS.md">Agent Setup</a></p>
264
- </footer>
265
-
266
- <script>
267
- const SKILLS = [
268
- { name: "animation-at-work", book: "Animation at Work", author: "Rachel Nabors", isbn: "9781937557942", desc: "Motion perception, 12 animation principles, CSS transitions, and performance." },
269
- { name: "clean-code-reviewer", book: "Clean Code", author: "Robert C. Martin", isbn: "9780132350884", desc: "Naming, functions, comments, classes, and the Boy Scout Rule." },
270
- { name: "data-intensive-patterns",book: "Designing Data-Intensive Applications",author: "Martin Kleppmann", isbn: "9781449373320", desc: "Storage engines, replication, transactions, and distributed systems." },
271
- { name: "data-pipelines", book: "Data Pipelines Pocket Reference", author: "James Densmore", isbn: "9781492087601", desc: "Ingestion, streaming, transformation, and orchestration." },
272
- { name: "design-patterns", book: "Head First Design Patterns", author: "Freeman & Robson", isbn: "9781492078005", desc: "GoF creational, structural, and behavioral patterns in depth." },
273
- { name: "domain-driven-design", book: "Domain-Driven Design", author: "Eric Evans", isbn: "9780321125217", desc: "Aggregates, Value Objects, Bounded Contexts, Ubiquitous Language." },
274
- { name: "effective-java", book: "Effective Java", author: "Joshua Bloch", isbn: "9780134685991", desc: "Object creation, generics, enums, lambdas, and concurrency." },
275
- { name: "effective-kotlin", book: "Effective Kotlin", author: "Marcin Moskala", isbn: "9781803248776", desc: "Safety, readability, reusability, and abstraction." },
276
- { name: "effective-python", book: "Effective Python", author: "Brett Slatkin", isbn: "9780134853987", desc: "Pythonic thinking, functions, classes, concurrency, and testing." },
277
- { name: "effective-typescript", book: "Effective TypeScript", author: "Dan Vanderkam", isbn: "9781492053743", desc: "Type system, type design, avoiding any, declarations, and migration." },
278
- { name: "kotlin-in-action", book: "Kotlin in Action", author: "Elizarov & Isakova", isbn: "9781617293290", desc: "Functions, classes, lambdas, nullability, coroutines, and flows." },
279
- { name: "lean-startup", book: "The Lean Startup", author: "Eric Ries", isbn: "9780307887894", desc: "MVP testing, validated learning, Build-Measure-Learn loop." },
280
- { name: "microservices-patterns", book: "Microservices Patterns", author: "Chris Richardson", isbn: "9781617294549", desc: "Decomposition, sagas, API gateways, event sourcing, and CQRS." },
281
- { name: "programming-with-rust", book: "Programming with Rust", author: "Donis Marshall", isbn: "9780137889655", desc: "Ownership, borrowing, lifetimes, error handling, traits, concurrency." },
282
- { name: "refactoring-ui", book: "Refactoring UI", author: "Wathan & Schoger", isbn: null, desc: "Visual hierarchy, layout, typography, color, and spacing." },
283
- { name: "rust-in-action", book: "Rust in Action", author: "Tim McNamara", isbn: "9781617294556", desc: "Systems programming — smart pointers, memory, networking, OS." },
284
- { name: "skill-router", book: "Meta-skill", author: "booklib-ai", isbn: null, desc: "Automatically routes to the most relevant skill for any task." },
285
- { name: "spring-boot-in-action", book: "Spring Boot in Action", author: "Craig Walls", isbn: "9781617292545", desc: "Auto-configuration, starters, testing, Actuator, and deployment." },
286
- { name: "storytelling-with-data", book: "Storytelling with Data", author: "Cole Nussbaumer Knaflic",isbn: "9781119002253", desc: "Effective visuals, decluttering, and narrative structure." },
287
- { name: "system-design-interview",book: "System Design Interview", author: "Alex Xu", isbn: "9798664653403", desc: "Scaling, estimation, load balancing, caching, and sharding." },
288
- { name: "using-asyncio-python", book: "Using Asyncio in Python", author: "Caleb Hattingh", isbn: "9781492075325", desc: "Coroutines, event loop, tasks, and signal handling." },
289
- { name: "web-scraping-python", book: "Web Scraping with Python", author: "Ryan Mitchell", isbn: "9781491985564", desc: "BeautifulSoup, Scrapy, Selenium, and data storage." },
290
- ];
291
-
292
- const ALL_CMD = "npx skills add booklib-ai/skills --all -g";
293
- let activeCard = null;
294
-
295
- function initials(name) {
296
- return name.split(/[\s-]+/).map(w => w[0]).join("").toUpperCase().slice(0, 3);
297
- }
298
-
299
- // Fetch cover URL from Google Books JSON API.
300
- // Using the API (not direct hotlinking) avoids false-positive placeholder images
301
- // ("No preview available" hatch / "image not available" text) that Google returns
302
- // as HTTP 200 responses — the API simply omits imageLinks when no cover exists.
303
- function showFb(img) {
304
- img.style.display = "none";
305
- const fb = img.nextElementSibling;
306
- if (fb) fb.style.display = "flex";
307
- }
308
-
309
- async function loadCover(img) {
310
- const isbn = img.dataset.isbn;
311
- try {
312
- const url = `https://www.googleapis.com/books/v1/volumes?q=isbn:${isbn}&fields=items(volumeInfo(imageLinks))&maxResults=1`;
313
- const d = await fetch(url).then(r => r.json());
314
- const links = d.items?.[0]?.volumeInfo?.imageLinks;
315
- // thumbnail/smallThumbnail are the only sizes returned by a search query.
316
- // Replacing zoom=1 with zoom=0 fetches the largest available version of the same image.
317
- const src = (links?.thumbnail || links?.smallThumbnail || "")
318
- .replace(/^http:/, "https:")
319
- .replace("zoom=1", "zoom=5")
320
- .replace("&edge=curl", "");
321
- if (src) {
322
- img.onload = () => img.classList.remove("loading");
323
- img.onerror = () => showFb(img);
324
- img.src = src;
325
- } else {
326
- showFb(img);
327
- }
328
- } catch {
329
- showFb(img);
330
- }
331
- }
332
-
333
- function buildCard(skill) {
334
- const card = document.createElement("a");
335
- card.className = "skill-card";
336
- card.href = `https://github.com/booklib-ai/skills/tree/main/skills/${skill.name}`;
337
- card.target = "_blank";
338
- card.rel = "noopener";
339
-
340
- const coverHtml = skill.isbn
341
- ? `<img
342
- class="loading"
343
- data-isbn="${skill.isbn}"
344
- alt="${skill.book} cover"
345
- />
346
- <div class="cover-fallback" style="display:none">
347
- <div class="initials">${initials(skill.book)}</div>
348
- <div class="fallback-title">${skill.book}</div>
349
- </div>`
350
- : `<div class="cover-fallback">
351
- <div class="initials">${initials(skill.book)}</div>
352
- <div class="fallback-title">${skill.book}</div>
353
- </div>`;
354
-
355
- card.innerHTML = `
356
- <div class="cover-wrap">${coverHtml}</div>
357
- <div class="card-body">
358
- <div class="skill-name">${skill.name}</div>
359
- <div class="book-title">${skill.book}</div>
360
- <div class="book-author">${skill.author}</div>
361
- <div class="card-desc">${skill.desc}</div>
362
- </div>
363
- `;
364
-
365
- card.addEventListener("click", (e) => {
366
- e.preventDefault();
367
- selectSkill(skill, card);
368
- });
369
-
370
- return card;
371
- }
372
-
373
- function selectSkill(skill, card) {
374
- // Deactivate previous
375
- if (activeCard && activeCard !== card) {
376
- activeCard.classList.remove("active");
377
- }
378
- const cmdEl = document.getElementById("cmd");
379
-
380
- if (activeCard === card) {
381
- // Deselect — back to install all
382
- activeCard.classList.remove("active");
383
- activeCard = null;
384
- cmdEl.textContent = ALL_CMD;
385
- cmdEl.classList.remove("highlighted");
386
- } else {
387
- activeCard = card;
388
- card.classList.add("active");
389
- cmdEl.textContent = `npx skills add booklib-ai/skills ${skill.name} -g`;
390
- cmdEl.classList.add("highlighted");
391
- document.getElementById("install").scrollIntoView({ behavior: "smooth", block: "center" });
392
- }
393
- }
394
-
395
- function copyCmd() {
396
- const text = document.getElementById("cmd").textContent;
397
- navigator.clipboard.writeText(text);
398
- const btn = document.querySelector(".copy-btn");
399
- btn.textContent = "Copied!";
400
- setTimeout(() => btn.textContent = "Copy", 2000);
401
- }
402
-
403
- // Build grid
404
- const grid = document.getElementById("grid");
405
- SKILLS.forEach(skill => grid.appendChild(buildCard(skill)));
406
- // Kick off async cover loading after DOM is populated
407
- document.querySelectorAll("img[data-isbn]").forEach(loadCover);
408
- </script>
409
-
410
- </body>
411
- </html>