@booklib/core 2.0.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/.cursor/rules/booklib-standards.mdc +40 -0
- package/.gemini/context.md +372 -0
- package/AGENTS.md +166 -0
- package/CHANGELOG.md +226 -0
- package/CLAUDE.md +81 -0
- package/CODE_OF_CONDUCT.md +31 -0
- package/CONTRIBUTING.md +304 -0
- package/LICENSE +21 -0
- package/PLAN.md +28 -0
- package/README.ja.md +198 -0
- package/README.ko.md +198 -0
- package/README.md +503 -0
- package/README.pt-BR.md +198 -0
- package/README.uk.md +241 -0
- package/README.zh-CN.md +198 -0
- package/SECURITY.md +9 -0
- package/agents/architecture-reviewer.md +136 -0
- package/agents/booklib-reviewer.md +90 -0
- package/agents/data-reviewer.md +107 -0
- package/agents/jvm-reviewer.md +146 -0
- package/agents/python-reviewer.md +128 -0
- package/agents/rust-reviewer.md +115 -0
- package/agents/ts-reviewer.md +110 -0
- package/agents/ui-reviewer.md +117 -0
- package/assets/logo.svg +36 -0
- package/bin/booklib-mcp.js +304 -0
- package/bin/booklib.js +1705 -0
- package/bin/skills.cjs +1292 -0
- package/booklib-router.mdc +36 -0
- package/booklib.config.json +19 -0
- package/commands/animation-at-work.md +10 -0
- package/commands/clean-code-reviewer.md +10 -0
- package/commands/data-intensive-patterns.md +10 -0
- package/commands/data-pipelines.md +10 -0
- package/commands/design-patterns.md +10 -0
- package/commands/domain-driven-design.md +10 -0
- package/commands/effective-java.md +10 -0
- package/commands/effective-kotlin.md +10 -0
- package/commands/effective-python.md +10 -0
- package/commands/effective-typescript.md +10 -0
- package/commands/kotlin-in-action.md +10 -0
- package/commands/lean-startup.md +10 -0
- package/commands/microservices-patterns.md +10 -0
- package/commands/programming-with-rust.md +10 -0
- package/commands/refactoring-ui.md +10 -0
- package/commands/rust-in-action.md +10 -0
- package/commands/skill-router.md +10 -0
- package/commands/spring-boot-in-action.md +10 -0
- package/commands/storytelling-with-data.md +10 -0
- package/commands/system-design-interview.md +10 -0
- package/commands/using-asyncio-python.md +10 -0
- package/commands/web-scraping-python.md +10 -0
- package/community/registry.json +1616 -0
- package/hooks/hooks.json +23 -0
- package/hooks/posttooluse-capture.mjs +67 -0
- package/hooks/suggest.js +153 -0
- package/lib/agent-behaviors.js +40 -0
- package/lib/agent-detector.js +96 -0
- package/lib/config-loader.js +39 -0
- package/lib/conflict-resolver.js +148 -0
- package/lib/context-builder.js +574 -0
- package/lib/discovery-engine.js +298 -0
- package/lib/doctor/hook-installer.js +83 -0
- package/lib/doctor/usage-tracker.js +87 -0
- package/lib/engine/ai-features.js +253 -0
- package/lib/engine/auditor.js +103 -0
- package/lib/engine/bm25-index.js +178 -0
- package/lib/engine/capture.js +120 -0
- package/lib/engine/corrections.js +198 -0
- package/lib/engine/doctor.js +195 -0
- package/lib/engine/graph-injector.js +137 -0
- package/lib/engine/graph.js +161 -0
- package/lib/engine/handoff.js +405 -0
- package/lib/engine/indexer.js +242 -0
- package/lib/engine/parser.js +53 -0
- package/lib/engine/query-expander.js +42 -0
- package/lib/engine/reranker.js +40 -0
- package/lib/engine/rrf.js +59 -0
- package/lib/engine/scanner.js +151 -0
- package/lib/engine/searcher.js +139 -0
- package/lib/engine/session-coordinator.js +306 -0
- package/lib/engine/session-manager.js +429 -0
- package/lib/engine/synthesizer.js +70 -0
- package/lib/installer.js +70 -0
- package/lib/instinct-block.js +33 -0
- package/lib/mcp-config-writer.js +88 -0
- package/lib/paths.js +57 -0
- package/lib/profiles/design.md +19 -0
- package/lib/profiles/general.md +16 -0
- package/lib/profiles/research-analysis.md +22 -0
- package/lib/profiles/software-development.md +23 -0
- package/lib/profiles/writing-content.md +19 -0
- package/lib/project-initializer.js +916 -0
- package/lib/registry/skills.js +102 -0
- package/lib/registry-searcher.js +99 -0
- package/lib/rules/rules-manager.js +169 -0
- package/lib/skill-fetcher.js +333 -0
- package/lib/well-known-builder.js +70 -0
- package/lib/wizard/index.js +404 -0
- package/lib/wizard/integration-detector.js +41 -0
- package/lib/wizard/project-detector.js +100 -0
- package/lib/wizard/prompt.js +156 -0
- package/lib/wizard/registry-embeddings.js +107 -0
- package/lib/wizard/skill-recommender.js +69 -0
- package/llms-full.txt +254 -0
- package/llms.txt +70 -0
- package/package.json +45 -0
- package/research-reports/2026-04-01-current-architecture.md +160 -0
- package/research-reports/IDEAS.md +93 -0
- 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/scripts/gen-llms-full.mjs +36 -0
- package/scripts/gen-og.mjs +142 -0
- package/scripts/validate-frontmatter.js +25 -0
- package/skills/animation-at-work/SKILL.md +270 -0
- package/skills/animation-at-work/assets/example_asset.txt +1 -0
- package/skills/animation-at-work/evals/evals.json +44 -0
- package/skills/animation-at-work/evals/results.json +13 -0
- package/skills/animation-at-work/examples/after.md +64 -0
- package/skills/animation-at-work/examples/before.md +35 -0
- package/skills/animation-at-work/references/api_reference.md +369 -0
- package/skills/animation-at-work/references/review-checklist.md +79 -0
- package/skills/animation-at-work/scripts/audit_animations.py +295 -0
- package/skills/animation-at-work/scripts/example.py +1 -0
- package/skills/clean-code-reviewer/SKILL.md +444 -0
- package/skills/clean-code-reviewer/audit.json +35 -0
- package/skills/clean-code-reviewer/evals/evals.json +185 -0
- package/skills/clean-code-reviewer/evals/results.json +13 -0
- package/skills/clean-code-reviewer/examples/after.md +48 -0
- package/skills/clean-code-reviewer/examples/before.md +33 -0
- package/skills/clean-code-reviewer/references/api_reference.md +158 -0
- package/skills/clean-code-reviewer/references/practices-catalog.md +282 -0
- package/skills/clean-code-reviewer/references/review-checklist.md +254 -0
- package/skills/clean-code-reviewer/scripts/pre-review.py +206 -0
- package/skills/data-intensive-patterns/SKILL.md +267 -0
- package/skills/data-intensive-patterns/assets/example_asset.txt +1 -0
- package/skills/data-intensive-patterns/evals/evals.json +54 -0
- package/skills/data-intensive-patterns/evals/results.json +13 -0
- package/skills/data-intensive-patterns/examples/after.md +61 -0
- package/skills/data-intensive-patterns/examples/before.md +38 -0
- package/skills/data-intensive-patterns/references/api_reference.md +34 -0
- package/skills/data-intensive-patterns/references/patterns-catalog.md +551 -0
- package/skills/data-intensive-patterns/references/review-checklist.md +193 -0
- package/skills/data-intensive-patterns/scripts/adr.py +213 -0
- package/skills/data-intensive-patterns/scripts/example.py +1 -0
- package/skills/data-pipelines/SKILL.md +259 -0
- package/skills/data-pipelines/assets/example_asset.txt +1 -0
- package/skills/data-pipelines/evals/evals.json +45 -0
- package/skills/data-pipelines/evals/results.json +13 -0
- package/skills/data-pipelines/examples/after.md +97 -0
- package/skills/data-pipelines/examples/before.md +37 -0
- package/skills/data-pipelines/references/api_reference.md +301 -0
- package/skills/data-pipelines/references/review-checklist.md +181 -0
- package/skills/data-pipelines/scripts/example.py +1 -0
- package/skills/data-pipelines/scripts/new_pipeline.py +444 -0
- package/skills/design-patterns/SKILL.md +271 -0
- package/skills/design-patterns/assets/example_asset.txt +1 -0
- package/skills/design-patterns/evals/evals.json +46 -0
- package/skills/design-patterns/evals/results.json +13 -0
- package/skills/design-patterns/examples/after.md +52 -0
- package/skills/design-patterns/examples/before.md +29 -0
- package/skills/design-patterns/references/api_reference.md +1 -0
- package/skills/design-patterns/references/patterns-catalog.md +726 -0
- package/skills/design-patterns/references/review-checklist.md +173 -0
- package/skills/design-patterns/scripts/example.py +1 -0
- package/skills/design-patterns/scripts/scaffold.py +807 -0
- package/skills/domain-driven-design/SKILL.md +142 -0
- package/skills/domain-driven-design/assets/example_asset.txt +1 -0
- package/skills/domain-driven-design/evals/evals.json +48 -0
- package/skills/domain-driven-design/evals/results.json +13 -0
- package/skills/domain-driven-design/examples/after.md +80 -0
- package/skills/domain-driven-design/examples/before.md +43 -0
- package/skills/domain-driven-design/references/api_reference.md +1 -0
- package/skills/domain-driven-design/references/patterns-catalog.md +545 -0
- package/skills/domain-driven-design/references/review-checklist.md +158 -0
- package/skills/domain-driven-design/scripts/example.py +1 -0
- package/skills/domain-driven-design/scripts/scaffold.py +421 -0
- package/skills/effective-java/SKILL.md +227 -0
- package/skills/effective-java/assets/example_asset.txt +1 -0
- package/skills/effective-java/evals/evals.json +46 -0
- package/skills/effective-java/evals/results.json +13 -0
- package/skills/effective-java/examples/after.md +83 -0
- package/skills/effective-java/examples/before.md +37 -0
- package/skills/effective-java/references/api_reference.md +1 -0
- package/skills/effective-java/references/items-catalog.md +955 -0
- package/skills/effective-java/references/review-checklist.md +216 -0
- package/skills/effective-java/scripts/checkstyle_setup.py +211 -0
- package/skills/effective-java/scripts/example.py +1 -0
- package/skills/effective-kotlin/SKILL.md +271 -0
- package/skills/effective-kotlin/assets/example_asset.txt +1 -0
- package/skills/effective-kotlin/audit.json +29 -0
- package/skills/effective-kotlin/evals/evals.json +45 -0
- package/skills/effective-kotlin/evals/results.json +13 -0
- package/skills/effective-kotlin/examples/after.md +36 -0
- package/skills/effective-kotlin/examples/before.md +38 -0
- package/skills/effective-kotlin/references/api_reference.md +1 -0
- package/skills/effective-kotlin/references/practices-catalog.md +1228 -0
- package/skills/effective-kotlin/references/review-checklist.md +126 -0
- package/skills/effective-kotlin/scripts/example.py +1 -0
- package/skills/effective-python/SKILL.md +441 -0
- package/skills/effective-python/evals/evals.json +44 -0
- package/skills/effective-python/evals/results.json +13 -0
- package/skills/effective-python/examples/after.md +56 -0
- package/skills/effective-python/examples/before.md +40 -0
- package/skills/effective-python/ref-01-pythonic-thinking.md +202 -0
- package/skills/effective-python/ref-02-lists-and-dicts.md +146 -0
- package/skills/effective-python/ref-03-functions.md +186 -0
- package/skills/effective-python/ref-04-comprehensions-generators.md +211 -0
- package/skills/effective-python/ref-05-classes-interfaces.md +188 -0
- package/skills/effective-python/ref-06-metaclasses-attributes.md +209 -0
- package/skills/effective-python/ref-07-concurrency.md +213 -0
- package/skills/effective-python/ref-08-robustness-performance.md +248 -0
- package/skills/effective-python/ref-09-testing-debugging.md +253 -0
- package/skills/effective-python/ref-10-collaboration.md +175 -0
- package/skills/effective-python/references/api_reference.md +218 -0
- package/skills/effective-python/references/practices-catalog.md +483 -0
- package/skills/effective-python/references/review-checklist.md +190 -0
- package/skills/effective-python/scripts/lint.py +173 -0
- package/skills/effective-typescript/SKILL.md +262 -0
- package/skills/effective-typescript/audit.json +29 -0
- package/skills/effective-typescript/evals/evals.json +37 -0
- package/skills/effective-typescript/evals/results.json +13 -0
- package/skills/effective-typescript/examples/after.md +70 -0
- package/skills/effective-typescript/examples/before.md +47 -0
- package/skills/effective-typescript/references/api_reference.md +118 -0
- package/skills/effective-typescript/references/practices-catalog.md +371 -0
- package/skills/effective-typescript/scripts/review.py +169 -0
- package/skills/kotlin-in-action/SKILL.md +261 -0
- package/skills/kotlin-in-action/assets/example_asset.txt +1 -0
- package/skills/kotlin-in-action/evals/evals.json +43 -0
- package/skills/kotlin-in-action/evals/results.json +13 -0
- package/skills/kotlin-in-action/examples/after.md +53 -0
- package/skills/kotlin-in-action/examples/before.md +39 -0
- package/skills/kotlin-in-action/references/api_reference.md +1 -0
- package/skills/kotlin-in-action/references/practices-catalog.md +436 -0
- package/skills/kotlin-in-action/references/review-checklist.md +204 -0
- package/skills/kotlin-in-action/scripts/example.py +1 -0
- package/skills/kotlin-in-action/scripts/setup_detekt.py +224 -0
- package/skills/lean-startup/SKILL.md +160 -0
- package/skills/lean-startup/assets/example_asset.txt +1 -0
- package/skills/lean-startup/evals/evals.json +43 -0
- package/skills/lean-startup/evals/results.json +13 -0
- package/skills/lean-startup/examples/after.md +80 -0
- package/skills/lean-startup/examples/before.md +34 -0
- package/skills/lean-startup/references/api_reference.md +319 -0
- package/skills/lean-startup/references/review-checklist.md +137 -0
- package/skills/lean-startup/scripts/example.py +1 -0
- package/skills/lean-startup/scripts/new_experiment.py +286 -0
- package/skills/microservices-patterns/SKILL.md +384 -0
- package/skills/microservices-patterns/evals/evals.json +45 -0
- package/skills/microservices-patterns/evals/results.json +13 -0
- package/skills/microservices-patterns/examples/after.md +69 -0
- package/skills/microservices-patterns/examples/before.md +40 -0
- package/skills/microservices-patterns/references/patterns-catalog.md +391 -0
- package/skills/microservices-patterns/references/review-checklist.md +169 -0
- package/skills/microservices-patterns/scripts/new_service.py +583 -0
- package/skills/programming-with-rust/SKILL.md +209 -0
- package/skills/programming-with-rust/evals/evals.json +37 -0
- package/skills/programming-with-rust/evals/results.json +13 -0
- package/skills/programming-with-rust/examples/after.md +107 -0
- package/skills/programming-with-rust/examples/before.md +59 -0
- package/skills/programming-with-rust/references/api_reference.md +152 -0
- package/skills/programming-with-rust/references/practices-catalog.md +335 -0
- package/skills/programming-with-rust/scripts/review.py +142 -0
- package/skills/refactoring-ui/SKILL.md +362 -0
- package/skills/refactoring-ui/assets/example_asset.txt +1 -0
- package/skills/refactoring-ui/evals/evals.json +45 -0
- package/skills/refactoring-ui/evals/results.json +13 -0
- package/skills/refactoring-ui/examples/after.md +85 -0
- package/skills/refactoring-ui/examples/before.md +58 -0
- package/skills/refactoring-ui/references/api_reference.md +355 -0
- package/skills/refactoring-ui/references/review-checklist.md +114 -0
- package/skills/refactoring-ui/scripts/audit_css.py +250 -0
- package/skills/refactoring-ui/scripts/example.py +1 -0
- package/skills/rust-in-action/SKILL.md +350 -0
- package/skills/rust-in-action/evals/evals.json +38 -0
- package/skills/rust-in-action/evals/results.json +13 -0
- package/skills/rust-in-action/examples/after.md +156 -0
- package/skills/rust-in-action/examples/before.md +56 -0
- package/skills/rust-in-action/references/practices-catalog.md +346 -0
- package/skills/rust-in-action/scripts/review.py +147 -0
- package/skills/skill-router/SKILL.md +186 -0
- package/skills/skill-router/evals/evals.json +38 -0
- package/skills/skill-router/evals/results.json +13 -0
- package/skills/skill-router/examples/after.md +63 -0
- package/skills/skill-router/examples/before.md +39 -0
- package/skills/skill-router/references/api_reference.md +24 -0
- package/skills/skill-router/references/routing-heuristics.md +89 -0
- package/skills/skill-router/references/skill-catalog.md +174 -0
- package/skills/skill-router/scripts/route.py +266 -0
- package/skills/spring-boot-in-action/SKILL.md +340 -0
- package/skills/spring-boot-in-action/evals/evals.json +39 -0
- package/skills/spring-boot-in-action/evals/results.json +13 -0
- package/skills/spring-boot-in-action/examples/after.md +185 -0
- package/skills/spring-boot-in-action/examples/before.md +84 -0
- package/skills/spring-boot-in-action/references/practices-catalog.md +403 -0
- package/skills/spring-boot-in-action/scripts/review.py +184 -0
- package/skills/storytelling-with-data/SKILL.md +241 -0
- package/skills/storytelling-with-data/assets/example_asset.txt +1 -0
- package/skills/storytelling-with-data/evals/evals.json +47 -0
- package/skills/storytelling-with-data/evals/results.json +13 -0
- package/skills/storytelling-with-data/examples/after.md +50 -0
- package/skills/storytelling-with-data/examples/before.md +33 -0
- package/skills/storytelling-with-data/references/api_reference.md +379 -0
- package/skills/storytelling-with-data/references/review-checklist.md +111 -0
- package/skills/storytelling-with-data/scripts/chart_review.py +301 -0
- package/skills/storytelling-with-data/scripts/example.py +1 -0
- package/skills/system-design-interview/SKILL.md +233 -0
- package/skills/system-design-interview/assets/example_asset.txt +1 -0
- package/skills/system-design-interview/evals/evals.json +46 -0
- package/skills/system-design-interview/evals/results.json +13 -0
- package/skills/system-design-interview/examples/after.md +94 -0
- package/skills/system-design-interview/examples/before.md +27 -0
- package/skills/system-design-interview/references/api_reference.md +582 -0
- package/skills/system-design-interview/references/review-checklist.md +201 -0
- package/skills/system-design-interview/scripts/example.py +1 -0
- package/skills/system-design-interview/scripts/new_design.py +421 -0
- package/skills/using-asyncio-python/SKILL.md +290 -0
- package/skills/using-asyncio-python/assets/example_asset.txt +1 -0
- package/skills/using-asyncio-python/evals/evals.json +43 -0
- package/skills/using-asyncio-python/evals/results.json +13 -0
- package/skills/using-asyncio-python/examples/after.md +68 -0
- package/skills/using-asyncio-python/examples/before.md +39 -0
- package/skills/using-asyncio-python/references/api_reference.md +267 -0
- package/skills/using-asyncio-python/references/review-checklist.md +149 -0
- package/skills/using-asyncio-python/scripts/check_blocking.py +270 -0
- package/skills/using-asyncio-python/scripts/example.py +1 -0
- package/skills/web-scraping-python/SKILL.md +280 -0
- package/skills/web-scraping-python/assets/example_asset.txt +1 -0
- package/skills/web-scraping-python/evals/evals.json +46 -0
- package/skills/web-scraping-python/evals/results.json +13 -0
- package/skills/web-scraping-python/examples/after.md +109 -0
- package/skills/web-scraping-python/examples/before.md +40 -0
- package/skills/web-scraping-python/references/api_reference.md +393 -0
- package/skills/web-scraping-python/references/review-checklist.md +163 -0
- package/skills/web-scraping-python/scripts/example.py +1 -0
- package/skills/web-scraping-python/scripts/new_scraper.py +231 -0
- package/skills/writing-plans/audit.json +34 -0
- package/tests/agent-detector.test.js +83 -0
- package/tests/corrections.test.js +245 -0
- package/tests/doctor/hook-installer.test.js +72 -0
- package/tests/doctor/usage-tracker.test.js +140 -0
- package/tests/engine/benchmark-eval.test.js +31 -0
- package/tests/engine/bm25-index.test.js +85 -0
- package/tests/engine/capture-command.test.js +35 -0
- package/tests/engine/capture.test.js +17 -0
- package/tests/engine/graph-augmented-search.test.js +107 -0
- package/tests/engine/graph-injector.test.js +44 -0
- package/tests/engine/graph.test.js +216 -0
- package/tests/engine/hybrid-searcher.test.js +74 -0
- package/tests/engine/indexer-bm25.test.js +37 -0
- package/tests/engine/mcp-tools.test.js +73 -0
- package/tests/engine/project-initializer-mcp.test.js +99 -0
- package/tests/engine/query-expander.test.js +36 -0
- package/tests/engine/reranker.test.js +51 -0
- package/tests/engine/rrf.test.js +49 -0
- package/tests/engine/srag-prefix.test.js +47 -0
- package/tests/instinct-block.test.js +23 -0
- package/tests/mcp-config-writer.test.js +60 -0
- package/tests/project-initializer-new-agents.test.js +48 -0
- package/tests/rules/rules-manager.test.js +230 -0
- package/tests/well-known-builder.test.js +40 -0
- package/tests/wizard/integration-detector.test.js +31 -0
- package/tests/wizard/project-detector.test.js +51 -0
- package/tests/wizard/prompt-session.test.js +61 -0
- package/tests/wizard/prompt.test.js +16 -0
- package/tests/wizard/registry-embeddings.test.js +35 -0
- package/tests/wizard/skill-recommender.test.js +34 -0
- package/tests/wizard/slot-count.test.js +25 -0
- package/vercel.json +21 -0
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import os from 'os';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import https from 'https';
|
|
5
|
+
import { execSync } from 'child_process';
|
|
6
|
+
import { fileURLToPath } from 'url';
|
|
7
|
+
import { resolveBookLibPaths } from './paths.js';
|
|
8
|
+
import { BookLibIndexer } from './engine/indexer.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Thrown when a skill source is not trusted and no `onPrompt` handler is provided
|
|
12
|
+
* (or the user declined the prompt).
|
|
13
|
+
*/
|
|
14
|
+
export class RequiresConfirmationError extends Error {
|
|
15
|
+
constructor(skill) {
|
|
16
|
+
super(`Skill "${skill.name}" requires user confirmation before indexing (untrusted source).`);
|
|
17
|
+
this.skill = skill;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Fetches a skill from its source (npm or GitHub raw URL) and indexes it locally.
|
|
23
|
+
*
|
|
24
|
+
* Usage:
|
|
25
|
+
* const fetcher = new SkillFetcher();
|
|
26
|
+
* await fetcher.fetch(skillDescriptor, {
|
|
27
|
+
* onPrompt: async (skill) => confirm(`Index "${skill.name}" from ${skill.source.type}?`),
|
|
28
|
+
* });
|
|
29
|
+
*/
|
|
30
|
+
export class SkillFetcher {
|
|
31
|
+
constructor(options = {}) {
|
|
32
|
+
this.paths = resolveBookLibPaths(options.projectCwd);
|
|
33
|
+
this.indexer = new BookLibIndexer(this.paths.indexPath);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Fetches and indexes a single skill.
|
|
38
|
+
*
|
|
39
|
+
* @param {object} skill - Skill descriptor (from SKILL_REGISTRY or DiscoveryEngine)
|
|
40
|
+
* @param {object} opts
|
|
41
|
+
* @param {Function} [opts.onPrompt] - async (skill) => boolean; called for untrusted sources.
|
|
42
|
+
* If omitted and source is untrusted, throws RequiresConfirmationError.
|
|
43
|
+
*/
|
|
44
|
+
async fetch(skill, { onPrompt } = {}) {
|
|
45
|
+
if (!skill.trusted) {
|
|
46
|
+
if (typeof onPrompt !== 'function') {
|
|
47
|
+
throw new RequiresConfirmationError(skill);
|
|
48
|
+
}
|
|
49
|
+
const confirmed = await onPrompt(skill);
|
|
50
|
+
if (!confirmed) {
|
|
51
|
+
throw new RequiresConfirmationError(skill);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const content = await this._fetchContent(skill);
|
|
56
|
+
const skillDir = this._skillDir(skill);
|
|
57
|
+
|
|
58
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
59
|
+
fs.writeFileSync(path.join(skillDir, 'SKILL.md'), content);
|
|
60
|
+
|
|
61
|
+
// Sync to ~/.claude/skills/ so Claude Code's native Skill tool can use it
|
|
62
|
+
this._syncToClaudeSkills(skill, skillDir);
|
|
63
|
+
|
|
64
|
+
console.log(`Indexing ${skill.name}...`);
|
|
65
|
+
await this.indexer.indexDirectory(skillDir);
|
|
66
|
+
console.log(`${skill.name} is ready.`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Returns true if the skill is already fetched locally.
|
|
71
|
+
*/
|
|
72
|
+
isCached(skill) {
|
|
73
|
+
return fs.existsSync(path.join(this._skillDir(skill), 'SKILL.md'));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ── Private helpers ────────────────────────────────────────────────────────
|
|
77
|
+
|
|
78
|
+
_skillDir(skill) {
|
|
79
|
+
const folderName = skill.name.replace(/[@/]/g, '_');
|
|
80
|
+
return path.join(this.paths.cachePath, 'skills', folderName);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Writes a clean SKILL.md to ~/.claude/skills/<name>/ so Claude Code's
|
|
85
|
+
* native Skill tool (and obra/superpowers, ruflo) can load it directly.
|
|
86
|
+
*
|
|
87
|
+
* Strips BookLib's <framework> indexing wrapper — orchestrators expect
|
|
88
|
+
* plain frontmatter + content, not BookLib's internal format.
|
|
89
|
+
* Safe to call multiple times — overwrites only BookLib-managed files.
|
|
90
|
+
*/
|
|
91
|
+
_syncToClaudeSkills(skill, skillDir) {
|
|
92
|
+
try {
|
|
93
|
+
const claudeSkillsDir = path.join(os.homedir(), '.claude', 'skills');
|
|
94
|
+
const slugName = skill.name.replace(/["']/g, '').replace(/\s+/g, '-').toLowerCase();
|
|
95
|
+
const destDir = path.join(claudeSkillsDir, slugName);
|
|
96
|
+
const destFile = path.join(destDir, 'SKILL.md');
|
|
97
|
+
const srcFile = path.join(skillDir, 'SKILL.md');
|
|
98
|
+
|
|
99
|
+
if (!fs.existsSync(srcFile)) return;
|
|
100
|
+
|
|
101
|
+
// Don't overwrite user-managed directories (not created by us)
|
|
102
|
+
const markerFile = path.join(destDir, '.booklib');
|
|
103
|
+
if (fs.existsSync(destDir) && !fs.existsSync(markerFile)) return;
|
|
104
|
+
|
|
105
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
106
|
+
|
|
107
|
+
// Strip BookLib's <framework> wrapper for a clean Skill-tool-compatible file
|
|
108
|
+
const raw = fs.readFileSync(srcFile, 'utf8');
|
|
109
|
+
const clean = this._stripFrameworkWrapper(raw, skill);
|
|
110
|
+
fs.writeFileSync(destFile, clean);
|
|
111
|
+
fs.writeFileSync(markerFile, ''); // marker so we know we own this dir
|
|
112
|
+
} catch {
|
|
113
|
+
// Non-fatal — BookLib still works without the sync
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Removes the ~/.claude/skills/<name> directory written by _syncToClaudeSkills.
|
|
119
|
+
* Only removes BookLib-managed directories (those containing a .booklib marker).
|
|
120
|
+
*/
|
|
121
|
+
desyncFromClaudeSkills(skill) {
|
|
122
|
+
try {
|
|
123
|
+
const claudeSkillsDir = path.join(os.homedir(), '.claude', 'skills');
|
|
124
|
+
const slugName = skill.name.replace(/["']/g, '').replace(/\s+/g, '-').toLowerCase();
|
|
125
|
+
const destDir = path.join(claudeSkillsDir, slugName);
|
|
126
|
+
if (!fs.existsSync(path.join(destDir, '.booklib'))) return; // not ours
|
|
127
|
+
fs.rmSync(destDir, { recursive: true, force: true });
|
|
128
|
+
} catch { /* non-fatal */ }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Extracts clean content from BookLib's internal <framework>-wrapped format.
|
|
133
|
+
* Produces a minimal frontmatter + raw content file suitable for the Skill tool.
|
|
134
|
+
*/
|
|
135
|
+
_stripFrameworkWrapper(content, skill) {
|
|
136
|
+
const desc = skill.description ? `description: ${skill.description.replace(/\n/g, ' ').slice(0, 200)}\n` : '';
|
|
137
|
+
const frontmatter = `---\nname: ${skill.name}\n${desc}---\n\n`;
|
|
138
|
+
|
|
139
|
+
// Extract content between <framework>...</framework> if present
|
|
140
|
+
const match = content.match(/<framework>([\s\S]*?)<\/framework>/);
|
|
141
|
+
if (match) return frontmatter + match[1].trim() + '\n';
|
|
142
|
+
|
|
143
|
+
// Already clean (native SKILL.md from github-skills-dir sources) — strip our frontmatter and re-emit
|
|
144
|
+
const withoutHeader = content.replace(/^---[\s\S]*?---\s*\n(#[^\n]+\n(>[^\n]*\n)?)?/, '').trim();
|
|
145
|
+
return frontmatter + withoutHeader + '\n';
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async _fetchContent(skill) {
|
|
149
|
+
const { source } = skill;
|
|
150
|
+
|
|
151
|
+
let raw;
|
|
152
|
+
if (source.type === 'github') {
|
|
153
|
+
raw = await this._fetchUrl(source.url);
|
|
154
|
+
} else if (source.type === 'npm') {
|
|
155
|
+
raw = await this._fetchFromNpm(source.package, source.path ?? 'SKILL.md');
|
|
156
|
+
} else {
|
|
157
|
+
throw new Error(`Unsupported source type: ${source.type}`);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// If content already has BookLib XML structure, return as-is.
|
|
161
|
+
// Otherwise apply Universal Adapter: wrap in <framework> so the parser
|
|
162
|
+
// produces structured chunks instead of one giant blob.
|
|
163
|
+
if (raw.includes('<framework>') || raw.includes('<core_principles>')) {
|
|
164
|
+
return raw;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const descFm = skill.description ? `description: ${skill.description.replace(/\n/g, ' ').slice(0, 200)}\n` : '';
|
|
168
|
+
const descBody = skill.description ? `\n> ${skill.description}\n` : '';
|
|
169
|
+
return `---
|
|
170
|
+
name: ${skill.name}
|
|
171
|
+
${descFm}source: ${source.url ?? source.package}
|
|
172
|
+
stars: ${skill.stars ?? 0}
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
# ${skill.name}
|
|
176
|
+
${descBody}
|
|
177
|
+
<framework>
|
|
178
|
+
${raw}
|
|
179
|
+
</framework>
|
|
180
|
+
`;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Extracts a file from an npm package tarball without installing node_modules.
|
|
185
|
+
* Uses `npm pack --dry-run` to get the tarball, then `tar` to extract one file.
|
|
186
|
+
*/
|
|
187
|
+
_fetchFromNpm(packageName, filePath) {
|
|
188
|
+
return new Promise((resolve, reject) => {
|
|
189
|
+
try {
|
|
190
|
+
const tmpDir = fs.mkdtempSync(path.join(this.paths.cachePath, 'npm-tmp-'));
|
|
191
|
+
const tarball = execSync(`npm pack ${packageName} --quiet`, { cwd: tmpDir }).toString().trim();
|
|
192
|
+
const tarballPath = path.join(tmpDir, tarball);
|
|
193
|
+
const extracted = execSync(
|
|
194
|
+
`tar -xOf "${tarballPath}" "package/${filePath}" 2>/dev/null || tar -xOf "${tarballPath}" "${filePath}"`,
|
|
195
|
+
{ cwd: tmpDir }
|
|
196
|
+
).toString();
|
|
197
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
198
|
+
resolve(extracted);
|
|
199
|
+
} catch (err) {
|
|
200
|
+
reject(new Error(`Failed to extract ${filePath} from npm package ${packageName}: ${err.message}`));
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
_fetchUrl(url) {
|
|
206
|
+
return new Promise((resolve, reject) => {
|
|
207
|
+
const options = { headers: { 'User-Agent': 'booklib-fetcher/1.0' } };
|
|
208
|
+
https.get(url, options, res => {
|
|
209
|
+
if (res.statusCode >= 300 && res.statusCode < 400 && res.headers.location) {
|
|
210
|
+
return resolve(this._fetchUrl(res.headers.location));
|
|
211
|
+
}
|
|
212
|
+
if (res.statusCode !== 200) {
|
|
213
|
+
return reject(new Error(`HTTP ${res.statusCode} fetching ${url}`));
|
|
214
|
+
}
|
|
215
|
+
let data = '';
|
|
216
|
+
res.on('data', chunk => (data += chunk));
|
|
217
|
+
res.on('end', () => resolve(data));
|
|
218
|
+
}).on('error', reject);
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ── Three-tier skill install ─────────────────────────────────────────────────
|
|
224
|
+
|
|
225
|
+
const PACKAGE_ROOT = path.resolve(fileURLToPath(import.meta.url), '..', '..');
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Installs a skill using three-tier lookup:
|
|
229
|
+
* 1. Already installed in ~/.claude/skills/<name>/ with .booklib marker → 'already-installed'
|
|
230
|
+
* 2. Bundled in <packageRoot>/skills/<name>/SKILL.md → install via installBundledSkill → 'installed'
|
|
231
|
+
* 3. Cached in ~/.booklib/skills/<name>/SKILL.md → copy to ~/.claude/skills/ → 'installed'
|
|
232
|
+
* 4. None found → 'not-found'
|
|
233
|
+
*
|
|
234
|
+
* @param {string} skillName - skill directory name
|
|
235
|
+
* @returns {'already-installed' | 'installed' | 'not-found'}
|
|
236
|
+
*/
|
|
237
|
+
export function installSkill(skillName) {
|
|
238
|
+
const claudeDir = path.join(os.homedir(), '.claude', 'skills', skillName);
|
|
239
|
+
|
|
240
|
+
// Already installed with BookLib marker
|
|
241
|
+
if (fs.existsSync(path.join(claudeDir, '.booklib'))) return 'already-installed';
|
|
242
|
+
|
|
243
|
+
// Try bundled
|
|
244
|
+
const bundledPath = path.join(PACKAGE_ROOT, 'skills', skillName, 'SKILL.md');
|
|
245
|
+
if (fs.existsSync(bundledPath)) {
|
|
246
|
+
installBundledSkill(skillName);
|
|
247
|
+
return 'installed';
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Try cache
|
|
251
|
+
const { cachePath } = resolveBookLibPaths();
|
|
252
|
+
const cachedPath = path.join(cachePath, 'skills', skillName, 'SKILL.md');
|
|
253
|
+
if (fs.existsSync(cachedPath)) {
|
|
254
|
+
fs.mkdirSync(claudeDir, { recursive: true });
|
|
255
|
+
fs.copyFileSync(cachedPath, path.join(claudeDir, 'SKILL.md'));
|
|
256
|
+
fs.writeFileSync(path.join(claudeDir, '.booklib'), '');
|
|
257
|
+
return 'installed';
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
return 'not-found';
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// ── Slot management ───────────────────────────────────────────────────────────
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* Counts ALL skill directories in claudeSkillsDir (BookLib-managed or not).
|
|
267
|
+
* Used for health-check warnings because Claude loads every skill at startup.
|
|
268
|
+
*
|
|
269
|
+
* @param {string} [claudeSkillsDir] - override for testing
|
|
270
|
+
*/
|
|
271
|
+
export function countAllSlots(claudeSkillsDir) {
|
|
272
|
+
const dir = claudeSkillsDir ?? path.join(os.homedir(), '.claude', 'skills');
|
|
273
|
+
if (!fs.existsSync(dir)) return 0;
|
|
274
|
+
try {
|
|
275
|
+
return fs.readdirSync(dir, { withFileTypes: true })
|
|
276
|
+
.filter(d => d.isDirectory())
|
|
277
|
+
.length;
|
|
278
|
+
} catch { return 0; }
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Counts BookLib-managed skill slots in claudeSkillsDir.
|
|
283
|
+
* Only counts dirs containing a `.booklib` marker (written by _syncToClaudeSkills).
|
|
284
|
+
*
|
|
285
|
+
* @param {string} [claudeSkillsDir] - override for testing
|
|
286
|
+
*/
|
|
287
|
+
export function countInstalledSlots(claudeSkillsDir) {
|
|
288
|
+
const dir = claudeSkillsDir ?? path.join(os.homedir(), '.claude', 'skills');
|
|
289
|
+
if (!fs.existsSync(dir)) return 0;
|
|
290
|
+
try {
|
|
291
|
+
return fs.readdirSync(dir, { withFileTypes: true })
|
|
292
|
+
.filter(d => d.isDirectory() && fs.existsSync(path.join(dir, d.name, '.booklib')))
|
|
293
|
+
.length;
|
|
294
|
+
} catch { return 0; }
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Returns names of all BookLib-managed installed skills.
|
|
299
|
+
*
|
|
300
|
+
* @param {string} [claudeSkillsDir] - override for testing
|
|
301
|
+
*/
|
|
302
|
+
export function listInstalledSkillNames(claudeSkillsDir) {
|
|
303
|
+
const dir = claudeSkillsDir ?? path.join(os.homedir(), '.claude', 'skills');
|
|
304
|
+
if (!fs.existsSync(dir)) return [];
|
|
305
|
+
try {
|
|
306
|
+
return fs.readdirSync(dir, { withFileTypes: true })
|
|
307
|
+
.filter(d => d.isDirectory() && fs.existsSync(path.join(dir, d.name, '.booklib')))
|
|
308
|
+
.map(d => d.name);
|
|
309
|
+
} catch { return []; }
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Installs a bundled skill (from the package's skills/ dir) to ~/.claude/skills/.
|
|
314
|
+
* No-ops if the destination exists and is not BookLib-managed.
|
|
315
|
+
*
|
|
316
|
+
* @param {string} skillName - directory name under skills/
|
|
317
|
+
* @param {string} [claudeSkillsDir] - override for testing
|
|
318
|
+
*/
|
|
319
|
+
export function installBundledSkill(skillName, claudeSkillsDir) {
|
|
320
|
+
const packageRoot = path.resolve(fileURLToPath(import.meta.url), '..', '..');
|
|
321
|
+
const srcFile = path.join(packageRoot, 'skills', skillName, 'SKILL.md');
|
|
322
|
+
if (!fs.existsSync(srcFile)) throw new Error(`Bundled skill not found: ${skillName}`);
|
|
323
|
+
|
|
324
|
+
const destBase = claudeSkillsDir ?? path.join(os.homedir(), '.claude', 'skills');
|
|
325
|
+
const destDir = path.join(destBase, skillName);
|
|
326
|
+
const marker = path.join(destDir, '.booklib');
|
|
327
|
+
|
|
328
|
+
if (fs.existsSync(destDir) && !fs.existsSync(marker)) return; // user-managed, skip
|
|
329
|
+
|
|
330
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
331
|
+
fs.copyFileSync(srcFile, path.join(destDir, 'SKILL.md'));
|
|
332
|
+
fs.writeFileSync(marker, '');
|
|
333
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import matter from 'gray-matter';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
|
|
6
|
+
const PACKAGE_ROOT = path.resolve(fileURLToPath(import.meta.url), '..', '..');
|
|
7
|
+
const BUNDLED_SKILLS_DIR = path.join(PACKAGE_ROOT, 'skills');
|
|
8
|
+
const DEFAULT_OUT = path.join(PACKAGE_ROOT, 'docs', '.well-known', 'skills', 'default');
|
|
9
|
+
|
|
10
|
+
export class WellKnownBuilder {
|
|
11
|
+
constructor({ outDir = DEFAULT_OUT } = {}) {
|
|
12
|
+
this.outDir = outDir;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async build() {
|
|
16
|
+
const skills = this._readBundledSkills();
|
|
17
|
+
const content = this._render(skills);
|
|
18
|
+
fs.mkdirSync(this.outDir, { recursive: true });
|
|
19
|
+
const outPath = path.join(this.outDir, 'skill.md');
|
|
20
|
+
fs.writeFileSync(outPath, content);
|
|
21
|
+
return outPath;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
_readBundledSkills() {
|
|
25
|
+
return fs.readdirSync(BUNDLED_SKILLS_DIR)
|
|
26
|
+
.map(name => {
|
|
27
|
+
const file = path.join(BUNDLED_SKILLS_DIR, name, 'SKILL.md');
|
|
28
|
+
if (!fs.existsSync(file)) return null;
|
|
29
|
+
const { data } = matter(fs.readFileSync(file, 'utf8'));
|
|
30
|
+
return { name: data.name ?? name, description: data.description ?? '', tags: data.tags ?? [] };
|
|
31
|
+
})
|
|
32
|
+
.filter(Boolean);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
_render(skills) {
|
|
36
|
+
const list = skills
|
|
37
|
+
.map(s => `- **${s.name}**: ${String(s.description).replace(/\s+/g, ' ').slice(0, 120)}`)
|
|
38
|
+
.join('\n');
|
|
39
|
+
|
|
40
|
+
return `---
|
|
41
|
+
name: booklib-skills
|
|
42
|
+
description: >
|
|
43
|
+
BookLib — curated skills from canonical programming books. Covers Kotlin,
|
|
44
|
+
Python, Java, TypeScript, Rust, architecture, DDD, data-intensive systems,
|
|
45
|
+
UI design, and more. Install individual skills via npx skillsadd booklib-ai/booklib/<name>.
|
|
46
|
+
version: "1.0"
|
|
47
|
+
license: MIT
|
|
48
|
+
tags: [books, knowledge, all-languages, architecture, best-practices]
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
# BookLib Skills
|
|
52
|
+
|
|
53
|
+
Book knowledge distilled into structured AI skills. Install any skill with:
|
|
54
|
+
|
|
55
|
+
\`\`\`
|
|
56
|
+
npx skillsadd booklib-ai/booklib/<skill-name>
|
|
57
|
+
\`\`\`
|
|
58
|
+
|
|
59
|
+
## Available Skills
|
|
60
|
+
|
|
61
|
+
${list}
|
|
62
|
+
|
|
63
|
+
## Install Everything
|
|
64
|
+
|
|
65
|
+
\`\`\`bash
|
|
66
|
+
npm install -g @booklib/skills && booklib init
|
|
67
|
+
\`\`\`
|
|
68
|
+
`;
|
|
69
|
+
}
|
|
70
|
+
}
|