book-to-skill
CLI Toolvirgiliojr94/book-to-skill
Turn any technical book PDF into a skill for AI agents.
Overview
A CLI tool that distills technical books, document folders, or source collections into structured agent skills. Works with GitHub Copilot CLI, Amp, and Claude Code. Reduces token usage by 24x-51x compared to dumping the book into context.
README Preview
\n \n\n\nbook-to-skill\n\n\n Turn any technical book, document folder, or collection of sources into a unified agent skill — ready to study, reference, and use while you work in GitHub Copilot CLI, Amp, or Claude Code.\n\n\n\n \n \n \n \n \n\n\n\n \n\n\n\n 🏆 #10 Python Repository of the Day and #25 Repository of the Day on Trendshift (May 23, 2026)\n\n\n\n Why ·\n What it generates ·\n Beyond books ·\n Usage ·\n Requirements ·\n How it works ·\n Discovery Loop Tax ·\n FAQ ·\n Install ·\n Changelog ·\n Performance ·\n Architecture\n\n\n\n 24×–51× fewer tokens than dumping the book into context to answer one question, measured on real books (how it's measured).\n\n\n**How it works, in 3 steps:**\n\n1. **Point** it at a file, folder, or glob — `/book-to-skill ./my-book.pdf`\n2. **It distills** the book into a skill — frameworks, decision rules, anti-patterns, and per-chapter files. Structure, not a summary.\n3. **Your agent loads it on demand** — ask `/my-book replication` and it reads the right chapter and answers from the real content, no hallucination.\n\n---\n\n## 🤔 Why\n\nYou buy a great technical book. You read it once. Three months later you can't remember chapter 7 existed.\n\nThe usual workarounds don't help:\n- 📄 "Let me just search the PDF" → you get a list of pages, not answers\n- 🧠 "I'll ask the agent about this book" → it either hallucinates or says it doesn't have the content\n- 📝 "I'll take notes as I read" → you end up with a 200-line doc you never open again\n\n**book-to-skill solves this by turning the book into a structured skill your agent loads on demand.**\n\nOnce installed, you just type `/your-book-slug replication` and the agent reads the right chapter and answers from the actual content. No hallucination. No digging through PDFs. The book becomes part of your workflow.\n\nWorks with any host that supports the open [Agent Skills](https://github.com/agentskills/agentskills) standard — GitHub Copilot CLI, Amp, and Clau
FAQ (5)
TroubleshootingHow do I fix DOCX tables being moved to the end of extracted text when python-docx is installed?
This happens when the parser reads document.paragraphs first and document.tables second, which loses their interleaved order in the Word body. Upgrade book-to-skill to the release containing the DOCX order fix; the corrected parser iterates body blocks in document order, typically with document.iter_inner_content(), handling Paragraph and Table objects as they appear. On older python-docx versions without ordered block iteration, it falls back to the stdlib ZIP/XML parser. As an immediate workaround, force the fallback by running pip uninstall python-docx in your virtual environment, or call extract_docx_with_zipfile("document.docx") directly. Verify that a table appearing before Chapter 2 is extracted before the Chapter 2 heading.
TroubleshootingHow do I fix pytest showing 5 skipped tests for bs4 and trafilatura in CI?
The tests are skipped because they use pytest.importorskip to require optional packages. Install the test-only dependencies in a dedicated CI job:
optional-deps:
name: test (optional deps installed)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: "3.12"
- run: pip install pytest beautifulsoup4 trafilatura
- run: pytest tests/ -q
Keep the existing dependency-free job unchanged. To verify locally:
pip install pytest beautifulsoup4 trafilatura
python3 -m pytest tests/ -q
With both packages installed, the result changes from 698 passed, 5 skipped to 703 passed, 0 skipped. The affected tests are the bs4-gated test in tests/test_html_block_boundaries.py and four trafilatura-gated tests in tests/test_html_boilerplate_extraction.py.
TroubleshootingWhy is chapter_headings_sample missing for each source in metadata.json?
The CLI metadata projection in book_to_skill/utils.py copied chapters_detected and chapters_method but omitted the per-source chapter_headings_sample; only the consolidated top-level sample was serialized. Upgrade to a build containing PR #245, commit b27360ccfc2f92e0a6571dac03e32389a6e4cfe4:
pip install git+https://github.com/virgiliojr94/book-to-skill.git@b27360ccfc2f92e0a6571dac03e32389a6e4cfe4
Then rerun extraction, for example:
BOOK_SKILL_WORKDIR="$PWD/repro-output" python3 scripts/extract.py alpha.md beta.md --mode text --install-missing no
If patching version 1.4.0 locally, add the existing extraction result to each source object in main() next to the other chapter fields, using the variable that holds extract_single_file() output:
"chapter_headings_sample": source.get("chapter_headings_sample", []),
Do not recompute the sample or assign the consolidated top-level sample, because that can attach the first source's headings to later sources. Empty lists and Unicode headings must be preserved. Validate with pytest -q and ruff check ..
TroubleshootingHow to fix the stdlib EPUB fallback silently skipping chapters when OPF manifest hrefs contain fragments or percent-encoded filenames?
Upgrade to a release containing the PR fix(epub): resolve encoded manifest hrefs; the bug is present on commit f08d343. When patching the fallback, do not pass the raw OPF href to ZipFile.read(). First resolve it relative to the OPF directory, remove query/fragment components, and decode percent escapes:
import posixpath
from urllib.parse import urlsplit, unquote
def manifest_member_name(opf_path, href):
href_path = urlsplit(href).path # drops ?query and #fragment
href_path = unquote(href_path) # Chapter%201.xhtml -> Chapter 1.xhtml
return posixpath.normpath(posixpath.join(posixpath.dirname(opf_path), href_path))
Then call zf.read(manifest_member_name(opf_path, href)). XML parsers such as ElementTree already decode XML entities in attribute values. Thus chapter.xhtml#section-2 resolves to chapter.xhtml, and Text/Chapter%201.xhtml from OEBPS/content.opf resolves to OEBPS/Text/Chapter 1.xhtml.
TroubleshootingHow do I fix invisible Unicode Default_Ignorable characters such as Mongolian FVS, Khmer inherent vowels, and Shorthand format controls not being stripped or detected?
Patch book_to_skill/sanitize.py::is_invisible_codepoint() to include the complete missing set of assigned Default_Ignorable_Code_Point=Yes code points: U+17B4, U+17B5, U+180B, U+180C, U+180D, U+1BCA0, U+1BCA1, U+1BCA2, and U+1BCA3. For example, add their scalar values to the shared invisible-code-point set: 0x17B4, 0x17B5, 0x180B, 0x180C, 0x180D, 0x1BCA0, 0x1BCA1, 0x1BCA2, 0x1BCA3. Do not maintain a separate scanner list: tools/scan_generated_skill.py imports the same predicate, so updating it makes extraction strip these characters and the scanner report the same set consistently. The issue is verified on Python 3.9.5 with Unicode 13.0.0; all nine code points are already assigned there. After patching, rerun the test suite and regenerate/rescan affected skills.