Best for
- User explicitly asks to convert a textbook or chapter
- You need to search textbook content and want to avoid per-query PDF
- Building up a knowledge base from a personal library of reference books
drpwchen/textbook-to-note/skills/textbook-to-md/SKILL.md
Convert PDF/EPUB textbooks to searchable markdown files for an AI agent's own reference. Use this skill whenever: (1) the user asks to convert a textbook/PDF chapter to markdown, (2) you need to search textbook content and no markdown version exists yet, (3) batch-converting a set of reference books into a knowledge base. This is a 0-token local conversion — no vision model needed.
Decision brief
This skill calls scripts in your clone of the textbook-to-note repo. At install time, replace {REPO} below with the absolute path of the clone.
Compatibility matrix
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Not declared | No explicit evidence | Portability before use |
| Claude Code | Not declared | No explicit evidence | Portability before use |
| Cursor | Not declared | No explicit evidence | Portability before use |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/drpwchen/textbook-to-note --skill "skills/textbook-to-md"Inspect the Agent Skill "textbook-to-md" from https://github.com/drpwchen/textbook-to-note/blob/65e76902d6d68c0f467fbe1ab1412c4c4798baa4/skills/textbook-to-md/SKILL.md at commit 65e76902d6d68c0f467fbe1ab1412c4c4798baa4. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.
Workflow
After conversion, check: - Total words 500K words → chapter detection failed, re-split needed - 0 chars extracted from sample pages → pure scan; batch-dir handles this automatically. Single-file mode does not auto-route — use batch-dir on a temp folder for OCR needs - Garbled gl…
Call the figure-remap skill's public entrypoint (figureremap.py extract — not the internal gate script). The entrypoint runs deterministic geometric matching by default and returns a stable contract:
Convert PDF or EPUB textbooks into searchable markdown that the agent can grep/read directly, eliminating the need for PDF-library extraction at every query.
User explicitly asks to convert a textbook or chapter
Review the “Quick reference” section in the pinned source before continuing.
Permission review
The documentation asks the agent to run terminal commands or scripts.
python {REPO}/converter/convert.py "path/to/chapter.pdf"The documentation asks the agent to run terminal commands or scripts.
python {REPO}/converter/convert.py "path/to/chapter.pdf" "output.md" --book-label "Author Title 2e — Ch32"Evidence record
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 91/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 99 | Source | Repository attention, not individual Skill quality |
| Compatibility | 0 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
This skill calls scripts in your clone of the textbook-to-note repo. At install time, replace
{REPO}below with the absolute path of the clone.
Convert PDF or EPUB textbooks into searchable markdown that the agent can grep/read directly, eliminating the need for PDF-library extraction at every query.
Output lives outside the note vault, at the path configured by
OUTPUT_DIR in shared/config.py (default ./output/), and is
for the agent's consumption, not for the user's reading.
Figures are on-demand, not pre-extracted. This skill produces markdown text only. Figures are extracted one at a time when a note needs them, via the
figure-remapskill's entrypoint (QC-gated). Do not batch-extract a whole book's figures into afigures/folder — that approach does not scale and is unnecessary since the on-demand path already handles it. A legacyfigures/folder may exist for books converted before this design; new conversions are markdown-only.
# Single file
python {REPO}/converter/convert.py "path/to/chapter.pdf"
# Single file with custom output and label
python {REPO}/converter/convert.py "path/to/chapter.pdf" "output.md" --book-label "Author Title 2e — Ch32"
# Batch-dir: convert ALL PDFs in a directory tree (auto chapter split + PDF bookmarks)
python {REPO}/converter/convert.py --batch-dir "path/to/your/textbook/folder"
# Force re-convert (ignore existing md)
python {REPO}/converter/convert.py --batch-dir "path/to/your/textbook/folder" --force
# Force OCR for ALL PDFs in batch-dir (bypass the text-extraction path entirely).
# Use when the text layer "looks" healthy but quality is actually bad (OCR-overlay
# scans, some digitized reprints) — auto-detection won't trigger because the text
# layer passes the shallow check.
python {REPO}/converter/convert.py --batch-dir DIR --force --force-surya
# EPUB → markdown (the 2nd arg is a FOLDER, not a .md file)
python {REPO}/converter/convert.py "path/to/book.epub" "<OUTPUT_DIR>/Author_Title_2e_2022"
Batch mode skips files whose markdown already exists and is newer than the
source PDF. Batch-dir mode saves progress to batch_progress.json — if
interrupted, re-running resumes where it stopped. --batch-dir also picks
up .epub files automatically.
pandoc (epub → gfm). EPUBs are reflowable, so there are no
<!-- page N --> markers; files carry a <!-- SOURCE: epub --> marker
instead.chNN_*.md + full_text.md (same shape as a PDF
book) — the single-file 2nd argument is the output directory, not a
.md path.pandoc on PATH. Figures are not extracted from EPUB (the
on-demand figure flow is PDF-only).<OUTPUT_DIR>/{PDF_stem}/
(OUTPUT_DIR from shared/config.py; default ./output)full_text.md (complete with <!-- page N --> markers) plus a
chapter splitdocs/ocr-ladder.md). Falls back to
skip-with-explanation if the OCR environment is missing. Output is
full_text.md with page markers, and a chapter split is attempted on the
OCR'd text too (numbered-heading patterns often survive OCR); it is
best-effort and simply yields no chapters when heading detection fails<OUTPUT_DIR>/ ← default ./output (shared/config.py)
├── Author_Title_Edition_Year/
│ ├── ch01_Chapter_Title.md ← conversion produces md only
│ ├── ch02_....md
│ ├── full_text.md
│ └── figures/ ← LEGACY ONLY — not produced by conversion
└── Another_Book/ ← single-file conversions use the same <book>/full_text.md layout
Figure-registry generation (figure_registry.json) is an optional external
hook, not a shipped script — set the FIGURE_REGISTRY_SCRIPT env var to a
generator script if you have one; if unset, post_convert.py skips that
step.
==Conversion writes .md files only.== A figures/ subdirectory is legacy —
present only on books converted before the on-demand switch. New conversions
never create one; figures are pulled on demand by figure-remap.
Each markdown file contains:
<!-- page X --> at every page boundary, to map content
back to the source PDFFig. 32.1, Table 31.2, etc.), an HTML comment
<!-- REF: Fig. 32.1 → see PDF page X --> is inserted so the agent knows
where to look in the original PDFTwo search methods:
grep -r "your search term" "$OUTPUT_DIR" # default ./output
If you've set up the optional semantic index (LanceDB + a local embedding
model — see docs/architecture.md), a textbook_search tool becomes
available for concept-level queries that don't depend on exact wording.
After converting new books, run the post-pipeline:
# Full pipeline: verify quality + refresh index.md + refresh figure registry + semantic index
python {REPO}/converter/post_convert.py
# Or individual steps:
python {REPO}/converter/post_convert.py --verify # check quality only
python {REPO}/converter/post_convert.py --index # semantic index only
python {REPO}/converter/post_convert.py --audit # report index coverage (no backfill)
The pipeline does not extract figures — it only verifies page markers,
refreshes index.md + figure_registry.json from whatever figures already
exist, and (optionally) builds the semantic index. Figure extraction is
never a batch post-step.
Missing page markers do not block indexing by design — EPUBs (reflowable)
and some vector-glyph PDFs legitimately lack <!-- page N -->; the indexer
handles this gracefully (page metadata = 0). After indexing, a coverage
audit compares the markdown corpus against the index and auto-backfills any
book that has markdown but no index rows, so every converted book ends up
searchable even if a step was skipped.
No pre-extraction step. A whole-book batch figure dump is retired and should
not be run on new books. When a note needs a figure, figure-remap extracts
that single figure on demand with QC. See "Using figures in notes" below.
Chapter N / CHAPTER N — standardPart N / PART N — with roman numerals (Part III)Section N / SECTION N / Unit N1 Introduction, 23 Shoulder (digit + title-case
text)Many textbooks repeat Chapter N Title as a running header on every page.
Only count the first occurrence of each unique chapter number — track a
seen_chapters set and skip duplicates.
pages_0001-0030.md).pdf (use an
Author_Title_Edition_Year convention)ch01_Chapter_Title.md, ch00_Front_Matter.mdpages_0001-0030.mdAfter conversion, check:
Always cite with year when referencing converted content:
(Author 2e, 2021), not just (Author).
When writing or supplementing a note that needs a figure (anatomy,
classification, imaging, algorithm), extract that one figure on demand —
never batch-dump a book. The markdown's
<!-- REF: Fig. 5.1 → see PDF page 42 --> markers tell you which figure
exists and what PDF page it's on; hand that to the gate.
Call the figure-remap skill's public entrypoint (figure_remap.py extract — not the internal gate script). The entrypoint runs deterministic
geometric matching by default and returns a stable contract:
python {REPO}/figures/figure_remap.py extract \
--book "{Book}" \
--fig-id "5-1" \
--caption "<caption text from the REF marker / md>" \
--out "path/to/your/vault/attachments/Fig_5-1_{BookShort}.jpeg" \
--pdf "<source PDF path>" \
--page {1-indexed PDF page from the REF marker}
Contract: {status: pass|fail|escalate, match_quality: exact|uncertain|failed, hard_fail, file, fig_id, reason, qc_degraded, qc_skipped} — those eight keys
exactly (figures/figure_remap.py CONTRACT_KEYS; the validator raises on any
extra or missing key, so branching on the engine's internal match_method is
not just discouraged, it is impossible).
status:fail (exit 1) is a deterministic miss — a correct refusal, not a
wrong crop; fix --page, escalate to vision, or leave a <!-- TODO -->.
Read the real caption to confirm the figure depicts what you intend.
On pass (exit 0), embed the --out path (result.file) in the note:
![[Fig_5-1_{BookShort}.jpeg|400]]
*Fig 5.1 — description (Author 2e, p.42)*
When writing a fresh note via the note-writing workflow (see
workflows/note-writing.md), figure harvest is Phase 3.5 — don't call the
gate manually there, the workflow does it. The manual call above is for
ad-hoc figure needs outside that workflow. Full fallback ladder + per-book
calibration: see the figure-remap skill.
figure_registry.json (produced by the post-conversion pipeline) records
each book's figure status. A status of "not extracted" or "lazy-only" is
the expected normal state — it does not mean a batch extraction has to
run first; the on-demand gate handles extraction from the PDF regardless.
{FigID}_{BookShort}.{ext} — e.g. Fig_5-1_AuthorName.jpeg,
Fig_32-1_AuthorName.jpeg. Avoids filename collisions across books in your
attachments folder.
<!-- page X -->.docs/ocr-ladder.md.dump_all figures: books where caption detection failed get
page-based filenames (page_0042.jpeg) without captions. Still usable,
but you must identify content by reading the image.The script accepts any PDF, not just your priority set. For ad-hoc conversions:
python {REPO}/converter/convert.py "path/to/any.pdf" "path/to/output.md" --book-label "Book Name — Chapter"
If no output path is specified, output goes to OUTPUT_DIR/<pdf name>/full_text.md — the same layout --batch-dir uses, so a later batch run skips it as already converted.
Frequently asked questions
This skill calls scripts in your clone of the textbook-to-note repo. At install time, replace {REPO} below with the absolute path of the clone.
The source record exposes this install command: npx skills add https://github.com/drpwchen/textbook-to-note --skill "skills/textbook-to-md". Inspect the command and pinned source before running it.
Static rules flagged exec-script in the source; the page lists the matching lines and excerpts.