Skill Store 33

Book To Skill

Converts books and documents (PDF, EPUB, DOCX, HTML, Markdown, plain text, RTF, MOBI/AZW with Cal…

by virgiliojr94 · MIT

The skill

Converts books and documents (PDF, EPUB, DOCX, HTML, Markdown, plain text, RTF, MOBI/AZW with Calibre) into structured agent skills, extracting frameworks, mental models, principles, techniques, and anti-patterns. Use when the user wants to study a document through GitHub Copilot CLI, Amp, Claude Code, Hermes Agent, OpenCode, or OpenClaw, apply an author's frameworks while working, or build a reusable knowledge base from a file.

<!-- Cross-agent notes (informational; ignored by host agents):

  • Compatible skill roots: GitHub Copilot CLI (~/.copilot/skills, ~/.agents/skills, .github/skills, .claude/skills, .agents/skills), Amp (.agents/skills, ~/.config/agents/skills, ~/.config/amp/skills), Claude Code (~/.claude/skills), Hermes Agent ($HERMES_HOME/skills, .hermes/skills, .agents/skills), OpenClaw (${OPENCLAW_STATE_DIR:-~/.openclaw}/skills, .agents/skills, skills/; ~/.agents/skills only with the default state). OpenCode (~/.config/opencode/skills, ~/.claude/skills, .opencode/skills, ~/.agents/skills, .agents/skills).
  • allowed-tools is intentionally omitted to stay agent-neutral: Copilot CLI uses shell/MCP-server names, Claude uses Bash/Read/Write/Glob/Grep, Amp adds shell_command. The skill needs shell (to run extract.py) and file read/write — each host will prompt for those on first use.
  • Argument hint: <path-to-document-folder-or-glob>... [skill-name-slug] -->

Book-to-Skill Converter

Transform written knowledge into actionable agent skills by extracting structure — not producing summaries.

Philosophy

Books contain crystallized expertise: frameworks, principles, and techniques that took years to develop. This skill extracts that knowledge into a format GitHub Copilot CLI, Amp, Claude Code, Hermes Agent, OpenCode, OpenClaw, or another compatible agent can leverage repeatedly. Extract structure, not summaries. A skill isn't a book report. It's a toolkit of:

  • Named frameworks (mental models with clear application)
  • Actionable principles (rules that guide decisions)
  • Techniques (step-by-step methods)
  • Anti-patterns (what to avoid and why)
  • Voice calibration (how the author thinks and communicates)

Preserve the author's precision. Frameworks often have specific names for reasons. "The 5 Whys" isn't interchangeable with "ask why multiple times." Capture the exact formulation.

Layer depth appropriately. Simple books → simple skills. Complex books with 10+ frameworks → skills with reference files and on-demand chapters.


Modes of Operation

Four paths available. Route based on what the user asks:

1. Full Conversion (Default)

Trigger: User provides one or more document/directory/glob paths without special instructions Action: Run all steps below (Steps 0–9) Output: Complete skill with SKILL.md, chapters/, glossary, patterns, cheatsheet

2. Analyze Only

Trigger: User says "analyze", "just extract", or "I want to review before generating" Action: Run Steps 0–3, then produce a structured extraction report (frameworks, principles, techniques found). Stop — do NOT generate skill files. Output: Analysis report for user review

3. Generate from Prior Analysis

Trigger: User has existing analysis notes or previously ran analyze-only Action: Skip Steps 0–3, use the provided analysis as input, run Steps 4–9 Output: Skill files from the provided analysis

4. Update / Fold-in (Existing Skill)

Trigger: User provides one or more new source paths and indicates they want to update an existing skill (either by pointing to the existing skill folder, providing a skill slug that already exists in SKILLS_HOME, or explicitly requesting an update). Action: Run Step 0 (out-of-scope check), Step 1 (validate inputs), Step 1.5 (identify book type), and Step 2 (extract new files). Then skip to Step 5 (identify/detect existing skill path) and run the Update / Fold-in Workflow to merge the new content into the existing skill files. Output: Updated existing skill with new/revised chapter summaries and merged indexes/glossaries.


Skill Locations

This converter can run from multiple skill systems. When looking for this converter's helper script or writing the generated book skill, prefer these locations in order:

  1. GitHub Copilot CLI personal skills: ~/.copilot/skills/
  2. Cross-agent personal skills (Copilot, Amp, Codex; OpenClaw with its default state): ~/.agents/skills/
  3. Claude Code personal skills: ~/.claude/skills/
  4. Project-local Copilot skills: .github/skills/
  5. Project-local Claude skills: .claude/skills/
  6. Project-local Amp / Copilot / OpenClaw skills: .agents/skills/
  7. Amp global skills: ~/.config/agents/skills/
  8. Amp legacy global skills: ~/.config/amp/skills/
  9. Hermes Agent personal skills: $HERMES_HOME/skills/ (defaults to ~/.hermes/skills/)
  10. Hermes Agent project skills: .hermes/skills/ or .agents/skills/
  11. OpenClaw personal skills: ${OPENCLAW_STATE_DIR:-~/.openclaw}/skills/ (active state; ~/.agents/skills/ is shared only with the default state)
  12. OpenClaw project skills: .agents/skills/ or skills/
  13. OpenCode personal skills: ~/.config/opencode/skills/ or ~/.agents/skills/
  14. OpenCode project skills: .opencode/skills/ or .agents/skills/

For generated book skills, prefer the user-level cross-agent root ~/.agents/skills/ — one physical copy serves the cross-agent hosts and OpenClaw when it uses its default state. Copilot CLI and Amp discover it natively; Claude Code needs a symlink from ~/.claude/skills/<skill_name> (created in Step 10, see Step 5 for the rules). Pick a host-private or project-local root only when the user explicitly asks for one. BOOK_TO_SKILL_SCOPE=project or personal can make that choice explicit for automation; do not ask a mandatory scope question merely because both scopes are available.


Step 0 — Out-of-scope check

If no arguments are provided, stop and respond:

"book-to-skill requires a supported document path, folder, or glob pattern. Usage: book-to-skill <path-to-document-folder-or-glob>... [skill-name-slug]"

Throughout the workflow:

  • Resolve the destination root first. Before any existing-skill lookup, determine where this run would write: the personal or project-local root for the user's host, per the Step 5 table and selection rules — a project-local request uses its project-local root, never the personal root. Below, "the resolved root" means that destination.
  • Identify the input paths and the optional skill slug.
  • If the last argument is not a file, folder, or glob that exists or matches any files, and it looks like a skill slug (e.g. lowercase hyphens, alphanumeric), treat it as SKILL_NAME.
  • Treat all other arguments as the list of INPUT_PATHS.
  • If any input path is an existing skill directory (contains SKILL.md and a chapters/ sub-folder), or if SKILL_NAME matches an existing skill slug in the resolved root, flag this run as an Update/Fold-in operation (Mode 4).
  • Re-run guard. Before starting extraction for a Full Conversion, derive the prospective skill slug under Step 5's naming options — the author-concept form, the by-title form, and any SKILL_NAME given — and check the resolved root for an existing match. On a match, STOP and ask the user: "<skill-name> already exists. Choose: (1) Update/Fold-in (Mode 4), (2) verify the existing skill is complete and stop, or (3) force full regeneration." Do not re-extract until the user chooses. On context recovery after an interrupted run (network cut-off, replayed or continued conversation), reuse the extraction work directory that run reported (Workdir -> in its output, or the path you set in BOOK_SKILL_WORKDIR) only if it is still intact and still matches the current inputs and options — the directory exists, its metadata.json is readable, the sources it lists match the files being converted now one-to-one on filename and content fingerprint (the extractor records a per-source sha256 in metadata.json; recompute the fingerprint from each file as it exists now and require an exact match — reuse_is_safe() in book_to_skill/utils.py is the executable form of this check), and its recorded extraction_mode is the mode this run is using (Step 2's "confirm the extraction is the document you asked for" rule). If it is missing (temp cleanup), its metadata.json is gone, the sources no longer match (a different filename, or a changed content fingerprint), the recorded metadata has no sha256 for a source (recorded before fingerprints existed, so freshness cannot be established), or the mode differs, say so and start a fresh extraction instead of resuming. When in doubt, ask the user before discarding or resuming.

Step 1 — Validate input

Verify that there is at least one supported file, directory, or glob pattern among the INPUT_PATHS. For directories and globs, expand them to find matching supported files (.pdf, .epub, .docx, .txt, .md, .markdown, .rst, .adoc, .html, .htm, .rtf, .mobi, .azw, .azw3).

If no supported files are found, stop with a clear error message.


Step 1.5 — Identify content type

Before extracting, ask the user:

"What kind of content do these sources have? This helps me choose the best extraction method.

  1. Technical — has code blocks, tables, formulas, diagrams (e.g. programming books, academic papers, architecture guides)
  2. Text-heavy — mostly prose, few or no tables/code (e.g. management, productivity, narrative non-fiction)
  3. Not sure — I'll use the fast method and warn you if quality seems limited"

Store the answer as BOOK_TYPE:

  • Option 1 → BOOK_TYPE=technical
  • Option 2 → BOOK_TYPE=text
  • Option 3 → BOOK_TYPE=text

If BOOK_TYPE=technical, inform the user before proceeding:

"📐 Technical mode selected — using Docling for structure-aware extraction (tables, code blocks, formulas preserved as markdown). This takes ~1.5s per page, so expect a few minutes for longer sources. Starting now…"

If BOOK_TYPE=text, inform:

"📄 Text mode selected — using the fastest suitable extractor for each file type. Plain text/Markdown/HTML are usually ready in seconds; PDFs use pdftotext when available."


Step 2 — Extract text from the source documents

Run the extraction script, passing the input paths:

SCRIPT_PATH=""
HERMES_HOME_RESOLVED="${HERMES_HOME:-$HOME/.hermes}"
OPENCLAW_STATE_DIR_RESOLVED="${OPENCLAW_STATE_DIR:-$HOME/.openclaw}"
PROJECT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || true)"
HERMES_PROJECT_TRUSTED=false
if [ -n "$PROJECT_ROOT" ] && [ "${HERMES_AGENT:-}" = true ] && \
  command -v hermes >/dev/null 2>&1 && \
  command -v python3 >/dev/null 2>&1 && \
  hermes config get skills.trusted_project_dirs --json 2>/dev/null | PROJECT_ROOT="$PROJECT_ROOT" python3 -c 'import json, os, pathlib, sys; root=pathlib.Path(os.environ["PROJECT_ROOT"]).resolve(); sys.exit(not any(pathlib.Path(p).expanduser().resolve() == root for p in json.load(sys.stdin)))' 2>/dev/null
then
  HERMES_PROJECT_TRUSTED=true
fi

CANDIDATES=(
  "$HOME/.copilot/skills/book-to-skill/scripts/extract.py"
  "$HOME/.agents/skills/book-to-skill/scripts/extract.py"
  "$HOME/.claude/skills/book-to-skill/scripts/extract.py"
  "${OPENCLAW_STATE_DIR_RESOLVED}/skills/book-to-skill/scripts/extract.py"
  "${OPENCLAW_STATE_DIR_RESOLVED}/skills"/*/book-to-skill/scripts/extract.py
  "${OPENCLAW_STATE_DIR_RESOLVED}/skills"/*/*/book-to-skill/scripts/extract.py
  "${OPENCLAW_STATE_DIR_RESOLVED}/skills"/*/*/*/book-to-skill/scripts/extract.py
  "${OPENCLAW_STATE_DIR_RESOLVED}/skills"/*/*/*/*/book-to-skill/scripts/extract.py
  "${OPENCLAW_STATE_DIR_RESOLVED}/skills"/*/*/*/*/*/book-to-skill/scripts/extract.py
  "${OPENCLAW_STATE_DIR_RESOLVED}/skills"/*/*/*/*/*/*/book-to-skill/scripts/extract.py
  "$HOME/.config/opencode/skills/book-to-skill/scripts/extract.py"
  "$HERMES_HOME_RESOLVED/skills/book-to-skill/scripts/extract.py"
  "$HERMES_HOME_RESOLVED"/skills/*/book-to-skill/scripts/extract.py
)
if [ "${HERMES_AGENT:-}" != true ]; then
  # Project-local roots are resolved against the git worktree, not the current
  # directory: an agent can be invoked from anywhere inside the project (e.g.
  # `src/nested`) and these roots still have to be found — that is how OpenCode
  # and the other hosts discover project skills. The CWD-relative forms are kept
  # so the probe also works outside a git repository.
  if [ -n "$PROJECT_ROOT" ]; then
    CANDIDATES+=(
      "$PROJECT_ROOT/.github/skills/book-to-skill/scripts/extract.py"
      "$PROJECT_ROOT/.claude/skills/book-to-skill/scripts/extract.py"
      "$PROJECT_ROOT/.agents/skills/book-to-skill/scripts/extract.py"
      "$PROJECT_ROOT/.opencode/skills/book-to-skill/scripts/extract.py"
    )
  fi
  CANDIDATES+=(
    ".github/skills/book-to-skill/scripts/extract.py"
    ".claude/skills/book-to-skill/scripts/extract.py"
    ".agents/skills/book-to-skill/scripts/extract.py"
    "skills/book-to-skill/scripts/extract.py"
    "skills"/*/book-to-skill/scripts/extract.py
    "skills"/*/*/book-to-skill/scripts/extract.py
    "skills"/*/*/*/book-to-skill/scripts/extract.py
    "skills"/*/*/*/*/book-to-skill/scripts/extract.py
    "skills"/*/*/*/*/*/book-to-skill/scripts/extract.py
    "skills"/*/*/*/*/*/*/book-to-skill/scripts/extract.py
    ".opencode/skills/book-to-skill/scripts/extract.py"
  )
  if [ -n "$PROJECT_ROOT" ]; then
    CANDIDATES+=(
      "$PROJECT_ROOT/skills/book-to-skill/scripts/extract.py"
      "$PROJECT_ROOT/skills"/*/book-to-skill/scripts/extract.py
      "$PROJECT_ROOT/skills"/*/*/book-to-skill/scripts/extract.py
      "$PROJECT_ROOT/skills"/*/*/*/book-to-skill/scripts/extract.py
      "$PROJECT_ROOT/skills"/*/*/*/*/book-to-skill/scripts/extract.py
      "$PROJECT_ROOT/skills"/*/*/*/*/*/book-to-skill/scripts/extract.py
      "$PROJECT_ROOT/skills"/*/*/*/*/*/*/book-to-skill/scripts/extract.py
    )
  fi
fi
CANDIDATES+=(
  "$HOME/.config/agents/skills/book-to-skill/scripts/extract.py"
  "$HOME/.config/amp/skills/book-to-skill/scripts/extract.py"
)
if [ "$HERMES_PROJECT_TRUSTED" = true ]; then
  CANDIDATES=(
    "$PROJECT_ROOT/.hermes/skills/book-to-skill/scripts/extract.py"
    "$PROJECT_ROOT/.hermes/skills"/*/book-to-skill/scripts/extract.py
    "$PROJECT_ROOT/.agents/skills/book-to-skill/scripts/extract.py"
    "$PROJECT_ROOT/.agents/skills"/*/book-to-skill/scripts/extract.py
    "${CANDIDATES[@]}"
  )
fi
for candidate in "${CANDIDATES[@]}"
do
  if [ -f "$candidate" ]; then
    SCRIPT_PATH="$candidate"
    break
  fi
done

if [ -z "$SCRIPT_PATH" ]; then
  echo "Could not find scripts/extract.py for book-to-skill" >&2
  exit 1
fi

PYTHON_BIN="${PYTHON_BIN:-python3}"
if ! command -v "$PYTHON_BIN" >/dev/null 2>&1; then
  PYTHON_BIN="python"
fi

"$PYTHON_BIN" "$SCRIPT_PATH" $INPUT_PATHS --mode <BOOK_TYPE> --install-missing ask

Before extraction, the script checks optional Python packages needed for the detected format. If a better extractor is missing, it prompts the user with the available fallback. Non-interactive sessions default to fallback unless install mode is explicitly yes.

Tip — preflight the environment: run "$PYTHON_BIN" "$SCRIPT_PATH" --check to print a per-format report of which extractors are installed and the exact command to install whatever is missing, without processing any file. Useful when a user reports a setup or quality problem.

This creates a per-run work directory — <tempdir>/book_skill_work-<pid>/ by default, or exactly the path you set in BOOK_SKILL_WORKDIR — containing:

  • full_text.txt — combined extracted text of all sources with clear visually demarcated boundaries.
  • metadata.json — overall combined size, words, pages, token counts, dropped EPUB image counts, the resolved workdir, and a detailed list of individual processed sources.

The run prints all three paths on completion (Workdir ->, Text ->, Meta ->). Take the paths from that output (or from metadata.json's own workdir field) rather than assuming a fixed location — the directory name differs per run so that concurrent extractions on one machine cannot overwrite each other's results.

Read that run's metadata.json to inspect the results.

Always confirm the extraction is the document you asked for before generating anything: check filename / source_file in metadata.json, or the SOURCE: header on the first line of full_text.txt. If you are waiting on a background run, wait on its specific workdir — polling a shared path can surface a different run's output.


Step 2.5 — Pre-flight cost estimate

Read this run's metadata.json (the Meta -> path from the extraction output) and present the user with an estimate before doing any generation:

📖 Sources detected: <total_sources> source(s)
<list each source filename and format from the sources metadata list>
<if images_dropped > 5: warn that N source images were not read>
📄 Combined Pages/Sections: ~<N> | Words: ~<N> | Total tokens: ~<N>K

💰 Estimated token cost (Full Conversion / Update):
   Input  (reading + prompts): ~<N>K tokens
   Output (skill files generated/updated):  ~<N>K tokens
   Total:                           ~<N>K tokens

   Cost: multiply the token counts above by your model's current
   input/output per-1M-token rates (prices and model names change often —
   do not hardcode them; quote today's rate and label it as an estimate).

   ⏱  Estimated time: ~<N> minutes

📁 Files to be generated/updated:
   SKILL.md + chapter files + glossary + patterns + cheatsheet

➡  Proceed with Full Conversion / Update? (or type "analyze only" to preview first)

How to estimate:

  • Input tokens ≈ estimated_tokens from metadata × 1.3 (prompts overhead per chapter pass)
  • Output tokens ≈ chapters × per-chapter budget + 4,000 (SKILL.md) + 4,500 (glossary + patterns + cheatsheet)
    • Per-chapter budget midpoint by BOOK_TYPE (DEPTH is decided later in Step 4 and can raise it): text ≈ 1,000, technical ≈ 1,800. If the user has already indicated reference-only vs deep study, use the matching row of the Step 7 matrix.
  • Cost: report the token counts and multiply by the user's current per-1M-token input/output rates. Do NOT hardcode dollar figures — model names and prices change; if you show one, label it an estimate and date it.

Wait for the user to confirm before proceeding. If they say "analyze only", switch to Mode 2.


Step 2.6 — REPL-style access for large books (> 50k tokens)

Inspired by the Recursive Language Model (RLM) paradigm: treat full_text.txt as a queryable corpus, not a single read. Loading the whole file into context burns budget you will need later for generation.

For books over ~50k tokens, prefer programmatic probes over Read(full_text.txt) without bounds:

# Size check before any Read
wc -w "$FULL_TEXT_PATH"

# Find chapter offsets without loading the whole file
grep -n -E "^\s*(Chapter|CHAPTER)\s+[0-9]+" "$FULL_TEXT_PATH" | head -40

# Pull only the chapter you need (lines start..end inclusive)
sed -n '<start>,<end>p' "$FULL_TEXT_PATH"

# Verify a framework is actually mentioned before claiming it in SKILL.md
grep -c -i "westrum\|dora" "$FULL_TEXT_PATH"

# Targeted Read with offset/limit avoids dumping the full file
# Read(file_path=full_text.txt, offset=<line>, limit=<lines>)

Use this approach for Step 3 (structure analysis), Step 7 (per-chapter summaries), and Step 8 (glossary / patterns extraction). On books under 50k tokens, a single Read is fine.

Why this matters: a 200-page book is ~75k tokens. Re-reading it once per chapter (28 passes) costs ~2M input tokens; using grep + sed to pull only relevant slices keeps generation cost proportional to the output, not the source.


Step 3 — Analyze book structure

Read the first 8,000 characters of the extracted full_text.txt to identify:

  • Book title and author(s)
  • Chapter structure (look for "Chapter N", "PART I", numbered headings, table of contents)
  • Core themes and subject domain
  • Approximate number of chapters

Then read the Table of Contents section if present to map all chapters.

If mode is "Analyze Only": produce the extraction report now and stop. Structure:

## Extraction Report — <Title>

### Author's Core Frameworks
- **<Framework Name>**: <what it is and when to apply>

### Key Principles
- <Principle>: <actionable rule>

### Techniques & Methods
- <Technique>: <step-by-step or how-to>

### Anti-patterns
- <What to avoid>: <why>

### Suggested Skill Name
`{author-lastname}-{core-concept}` — e.g. `cialdini-influence`

### Chapters Detected
| # | Title | Main Frameworks |

Step 4 — Ask purpose (Full Conversion only)

Before generating, ask the user:

"What should this skill help you do? (Pick one or more)

  1. Apply the author's frameworks while working
  2. Think with the author's mental models
  3. Reference specific chapters and concepts
  4. All of the above"

Use the answer to weight what gets highlighted in the SKILL.md Core section.

Derive DEPTH from the answer (no extra prompt):

  • Answer is only option 3 (reference) → DEPTH=reference — lean, fast-lookup chapters.
  • Answer includes option 1, 2, or 4 → DEPTH=study — deeper chapters with more worked detail, examples, and reasoning.

DEPTH and BOOK_TYPE together set the per-chapter token budget in Step 7. Do not ask a separate "study vs reference" question — it is inferred here. (In Modes 2/3, where Step 4 is skipped, default DEPTH=study.)


Step 5 — Determine skill name

If SKILL_NAME was provided, use it as the skill slug. Otherwise, propose two options and let the user choose:

  • By author-concept: {author-lastname}-{core-concept} (e.g. cialdini-influence, meadows-systems)
  • By title: lowercase hyphens from book title (e.g. designing-data-intensive-apps)

Default to author-concept format if the book has a strong methodological identity.

Choose the destination skill root (SKILLS_HOME). First resolve scope from an explicit user request or BOOK_TO_SKILL_SCOPE, then probe host. A request for project-local/project output selects the project-local row; a request for personal/global output selects the personal row. If neither scope is requested, preserve the established personal default (~/.agents/skills for non-Hermes hosts). Do not ask a mandatory scope question solely because project-local roots exist. The selected root may still require host approval before writing.

Host agent Personal skill root Project-local root
GitHub Copilot CLI ~/.agents/skills (discovered natively) .github/skills → .claude/skills → .agents/skills
Amp ~/.agents/skills (discovered natively) .agents/skills
OpenAI Codex ~/.agents/skills (discovered natively; follows symlinks) .agents/skills
Hermes Agent $HERMES_HOME/skills/<category> (defaults to ~/.hermes/skills/<category>) .hermes/skills/<category> → .agents/skills
Claude Code ~/.agents/skills + symlink from ~/.claude/skills/<skill_name> .claude/skills
OpenClaw ${OPENCLAW_STATE_DIR:-~/.openclaw}/skills (active state; ~/.agents/skills only with default state) .agents/skills → skills/

Hermes Agent is the one host that keeps its own personal root: it partitions personal skills by category and does not scan the cross-agent root. Use the active profile's HERMES_HOME and choose a category that matches the generated skill's subject. Do not construct profile paths manually. If the user selects a project-local Hermes root, run hermes skills trust <project-root> after generation and verify discovery with hermes skills list; project skills remain unavailable until the project is trusted.

For OpenClaw, use the active state directory's skills/ root: ${OPENCLAW_STATE_DIR:-~/.openclaw}/skills/. The shared ~/.agents/skills compatibility root is discoverable only when OPENCLAW_STATE_DIR is unset or the default ~/.openclaw; with a non-default state, do not claim that OpenClaw will see a shared-root install. Verify discovery with openclaw skills list after generation. | OpenCode | ~/.agents/skills (discovered natively; ~/.config/opencode/skills and ~/.claude/skills are read too) | .opencode/skills → .agents/skills → .claude/skills |

Hermes Agent is the one host that keeps its own personal root: it partitions personal skills by category and does not scan the cross-agent root. Use the active profile's HERMES_HOME and choose a category that matches the generated skill's subject. Do not construct profile paths manually. If the user selects a project-local Hermes root, run hermes skills trust <project-root> after generation and verify discovery with hermes skills list; project skills remain unavailable until the project is trusted.

OpenCode scans the cross-agent ~/.agents/skills root natively, so the generated-skill default above already serves it — no symlink and no trust step. It also reads its own managed ~/.config/opencode/skills root and the Claude compatibility root ~/.claude/skills; use the OpenCode-managed root only when the user asks for it. Project skills are discovered by walking up from the working directory to the git worktree, so start a new session after generation if the skill does not appear. (~/.cache/opencode/skills is the remote-skill download cache from skills.urls, not an authoring root — never write a generated skill there.)

Selection rules:

  1. Personal install: set SKILLS_HOME to ~/.agents/skills (create the directory if missing). One exception, so the default does not invent a convention in someone else's house: if ~/.agents/skills does not exist and the host's private root already contains skills, use the private root instead and say why in the report.
  2. Claude Code does not scan ~/.agents/skills — after generation completes, Step 10 links the skill in with ln -sfn "$HOME/.agents/skills/<skill_name>" "$HOME/.claude/skills/<skill_name>".
  3. Hermes Agent personal installs use the Hermes row above, not the cross-agent root, and take no symlink.
  4. If the user explicitly asks for a host-private root (~/.copilot/skills, ~/.claude/skills, ~/.config/agents/skills, ~/.config/amp/skills), honor it and skip the symlink.
  5. If the user explicitly asked for project-local output, use the project-local row for their host.
  6. If the choice requires knowing the host (project-local output, the Hermes personal root, the OpenClaw state root, or the Claude Code symlink) and you cannot identify it, ask: "Which agent are you running in — OpenCode, OpenClaw, Hermes Agent, GitHub Copilot CLI, Amp, Codex, or Claude Code?"
  7. For OpenClaw personal output, use ${OPENCLAW_STATE_DIR:-$HOME/.openclaw}/skills. The shared ~/.agents/skills root is a valid OpenClaw destination only when OPENCLAW_STATE_DIR is unset or equals the default $HOME/.openclaw; otherwise use the active state root or a project/extra directory.
  8. If the user explicitly asks for an OpenClaw-managed personal root, use the active state root and verify discovery with openclaw skills list.

Set SKILLS_HOME to the selected root and check if $SKILLS_HOME/<skill_name>/ already exists. On Claude Code, also check whether ~/.claude/skills/<skill_name> exists as a real directory (not a symlink) — a previous install may live there; if so, offer to migrate it (move the directory into ~/.agents/skills/ and replace the original path with the symlink) before continuing. If the skill already exists, prompt the user to choose:

  1. Update / Fold-in (Mode 4) — integrate new files/content into the existing skill components.
  2. Overwrite — delete and regenerate the skill from scratch.
  3. Rename — append -2 or use a different custom slug.

If the user selects Update / Fold-in, proceed immediately to the Update / Fold-in Workflow section after Step 2.5 (skipping Steps 3, 4, 6, 7, 8, 9).


Step 6 — Create skill directory structure

mkdir -p "$SKILLS_HOME/<skill_name>/chapters"

Step 7 — Generate chapter summaries

TOKEN BUDGET RULE — CRITICAL (adaptive):

The per-chapter budget scales with BOOK_TYPE and DEPTH. Technical chapters need room for code and tables; study depth needs room for worked reasoning. Pick the budget from this matrix:

DEPTH=reference DEPTH=study
BOOK_TYPE=text 800–1,200 tokens 1,000–1,800 tokens
BOOK_TYPE=technical 1,200–1,800 tokens 2,000–3,000 tokens
  • These are per-file targets, not hard caps — a dense chapter may run over, a thin one under. Density still beats length (Quality Rule #3): never pad to hit a number.
  • Files are loaded on-demand, so a larger chapter only costs tokens when that chapter is actually read.
  • When in doubt between two cells (e.g. mixed-content book), use the lower budget and let depth come from precision, not volume.

DEPTH=study is earned with content, not a bigger number. The standard section template (Core Idea → Connects To) naturally lands a dense prose chapter around 700–900 tokens. To reach the study budget honestly — not by padding — a study-depth chapter must add concrete material:

  • Reproduce one worked example or artifact from the chapter (e.g. the example press release, a sample dialogue, a filled-in template, a decision the author walks through) under a ## Worked Example section. This is the single biggest lever and the main thing a learner returns for.
  • Expand the "How" of each framework into explicit steps or criteria, not a one-liner.
  • Add a short "Why it works / failure mode" note to the top 1–2 frameworks.

If a chapter genuinely has no worked example and resists expansion, let it land below the study floor rather than padding — and note that the chapter is thin in its Core Idea. A reference-depth chapter, by contrast, deliberately omits worked examples and keeps only the decision-ready essentials.

For EACH chapter/major section identified in Step 3:

Read the corresponding section of the extracted full_text.txt (use character offsets or grep for chapter headings).

Create $SKILLS_HOME/<skill_name>/chapters/ch<NN>-<slug>.md using the structure below.

Adapt emphasis based on BOOK_TYPE:

  • technical → prioritize "Code Examples", "Reference Tables", and "Commands & APIs" sections; preserve exact syntax
  • text → prioritize "Frameworks Introduced", "Mental Models", and "Key Takeaways"; skip empty technical sections
# Chapter N: <Full Title>

## Core Idea
<1–2 sentences: the single most important thing this chapter teaches>

## Frameworks Introduced
- **<Framework Name>**: <exact formulation — preserve the author's naming>
  - When to use: <specific situation>
  - How: <steps or criteria>

## Key Concepts
- **<Term>**: <precise definition in 1 sentence>
(5–10 most important terms from this chapter)

## Mental Models
<2–4 frameworks or thinking tools. Write as "Use X when Y" or "Think of X as Y">

## Anti-patterns
- **<What to avoid>**: <why it fails>

## Code Examples *(technical books only — omit if BOOK_TYPE=text)*
<!-- Copy the most instructive snippet from the chapter. Preserve indentation exactly. -->
```<language>
<key code example from this chapter>
  • What it demonstrates: <one line>

Reference Tables (technical books only — omit if BOOK_TYPE=text)

<!-- Reproduce any comparison matrix, parameter table, or decision table from the chapter in markdown. -->

Worked Example (DEPTH=study only — omit for DEPTH=reference)

<!-- Reproduce or reconstruct one concrete example the author works through: a sample document, a dialogue, a filled-in template, a before/after, or a decision walked end-to-end. This is what makes a study chapter worth its budget. Keep it faithful to the source; never copy long raw passages — reconstruct the example compactly. -->

Key Takeaways

  1. <Actionable insight>
  2. <Actionable insight>
  3. <Actionable insight> (3–7 takeaways a practitioner must remember)

Connects To

  • Ch N: <why this chapter relates>
  • <Concept>: <external concept or standard it connects with>

---

## Step 8 — Generate supporting files

### glossary.md
Create `$SKILLS_HOME/<skill_name>/glossary.md`:
- Every significant term from the book, alphabetically sorted
- Format: `**Term** — definition (Ch N)`
- Max 1,500 tokens

### patterns.md
Create `$SKILLS_HOME/<skill_name>/patterns.md`:
- All concrete techniques, design patterns, algorithms from the book
- Format: `## Pattern Name\n**When to use**: ...\n**How**: ...\n**Trade-offs**: ...`
- Max 2,000 tokens

### cheatsheet.md
Create `$SKILLS_HOME/<skill_name>/cheatsheet.md`:

**This is the most differentiated layer of the skill — treat it as a reasoning aid, not a keyword list.** Anyone can grep the glossary for a term. The cheatsheet captures the author's *judgment*: the decisions they'd make and why. It's the file that turns "I know the words" into "I'd act the way the author would".

Prioritize, in order:
1. **Decision rules** — "When X, do Y, because Z." The if/then logic the author applies, stated so the reader can apply it without re-reading the book.
2. **Decision trees / flowcharts** (as nested bullets or a small table) — for choices with more than two branches.
3. **Trade-off matrices** — competing options scored on the dimensions the author cares about, so the reader can pick under their own constraints.
4. **Thresholds & defaults** — the specific numbers, ratios, or rules of thumb the author commits to (e.g. "keep functions under ~20 lines", "alert when error budget < 10%").
5. **Tells & smells** — fast heuristics for recognizing a situation ("if you see X, you're probably in trouble Y").

Avoid: bare term→definition rows (that's the glossary), and prose paragraphs (that's the chapters). Every line should help the reader *decide* something.

- Format mostly as compact tables and decision rules; the content you'd want on a single printed page kept beside you while working.
- Max 1,200 tokens.

---

## Step 9 — Generate the master SKILL.md

**CRITICAL TOKEN BUDGET: Keep SKILL.md body under 4,000 tokens.**
Compaction truncates from the END — put the most important content FIRST.

Create `$SKILLS_HOME/<skill_name>/SKILL.md`:

```markdown
---
name: <skill_name>
description: "Knowledge base from \"<Full Title>\" by <Author(s)>. Use when applying <author>'s frameworks for <key topics, 3–6 terms>, studying the book, or referencing its concepts."
---

<!-- argument-hint: [topic, framework name, or chapter number] -->

# <Full Title>
**Author**: <Author(s)> | **Pages**: ~<N> | **Chapters**: <N> | **Generated**: <YYYY-MM-DD>

## How to Use This Skill

- **Without arguments** — load core frameworks for reference
- **With a topic** — ask about `replication`, `pricing`, or another indexed topic; I find and read the relevant chapter
- **With chapter** — ask for `ch05`; I load that specific chapter
- **Browse** — ask "what chapters do you have?" to see the full index

When you ask about a topic not covered in Core Frameworks below, I will read
the relevant chapter file before answering.

---

## Core Frameworks & Mental Models
<!-- ~2,000 tokens: the author's most important named frameworks and principles.
     Preserve exact names. Write as "Use X when Y", "Prefer X over Y because Z".
     This is a toolkit, not a summary. -->

<generate 2,000 tokens of the most critical frameworks and insights here>

---

## Chapter Index

| # | Title | Key Frameworks |
|---|-------|----------------|
| [ch01](chapters/ch01-<slug>.md) | <Title> | <framework1>, <framework2> |
| [ch02](chapters/ch02-<slug>.md) | <Title> | <framework1>, <framework2> |
...

## Topic Index

<!-- Alphabetical. Major terms/frameworks → chapter(s) that cover them. -->
- **<Term>** → ch<N>[, ch<N>]
- **<Term>** → ch<N>

## Supporting Files

- [glossary.md](glossary.md) — all key terms with definitions
- [patterns.md](patterns.md) — all techniques and design patterns
- [cheatsheet.md](cheatsheet.md) — quick reference tables and decision guides

---

## Scope & Limits

This skill covers the book content only. For hands-on implementation in your codebase,
combine with project-specific tools. For topics beyond this book, check related skills
or ask the agent directly.
<if images_dropped > 5: state that N source images were not read>

Step 9.5 — Scan the generated skill

Before reporting success, loading the skill in another session, or publishing it, run the advisory security scan:

SKILL_CONVERTER_ROOT="$(cd "$(dirname "$SCRIPT_PATH")/.." && pwd)"
"$PYTHON_BIN" "$SKILL_CONVERTER_ROOT/tools/scan_generated_skill.py" "$SKILLS_HOME/<skill_name>"

If the scanner exits non-zero, stop and ask a human to review its file/line findings. Do not silently rewrite the generated files, and do not load or publish the skill until the findings are resolved or explicitly accepted.


Step 10 — Cleanup and report

If the host is Claude Code and SKILLS_HOME is ~/.agents/skills (the default personal install), expose the skill to Claude Code with a symlink — Claude Code only scans ~/.claude/skills:

mkdir -p "$HOME/.claude/skills"
LINK="$HOME/.claude/skills/<skill_name>"
TARGET="$HOME/.agents/skills/<skill_name>"
if [ -d "$LINK" ] && [ ! -L "$LINK" ]; then
  CLAUDE_STATUS="skipped-realdir"                 # Step 5 migration declined; leave the old dir
else
  ln -sfn "$TARGET" "$LINK" 2>/dev/null || true
  # Read the link back — do NOT trust that `ln` did what was asked. On Windows/MSYS
  # `ln -s` may COPY instead of link (or need Developer Mode / an elevated shell), and
  # PowerShell/cmd have no `ln` at all. The report must reflect what is on disk, not the
  # fact that the command ran.
  if [ -L "$LINK" ] && [ "$(readlink "$LINK")" = "$TARGET" ]; then
    CLAUDE_STATUS="linked"
  elif [ -e "$LINK" ]; then
    CLAUDE_STATUS="copy"                           # a real file/dir landed instead of a link
  else
    CLAUDE_STATUS="absent"                         # ln unavailable or refused
  fi
fi

The real-directory guard is required: ln -sfn into an existing real directory would nest the link inside it (~/.claude/skills/<skill_name>/<skill_name>), leaving Claude Code loading the stale copy. If the user declined the Step 5 migration, skip the symlink and say so in the report — Claude Code keeps using the old directory until it is migrated.

Read the link back before you report anything about it. The symlink is a claim, not a fact: fill the "Discoverable by" line from CLAUDE_STATUS (what is actually on disk), never from "the command was issued". Do not hard-fail when the link is missing or is a copy — the skill exists at the hub and every other host still finds it; the honest report is "written to ~/.agents/skills/<skill_name>; Claude Code will not see it until the link is created", not an abort. (Windows lead, unverified: a directory junction — mklink /J in an elevated cmd, or New-Item -ItemType Junction in PowerShell — needs neither Developer Mode nor a symlink privilege; if you attempt it, it does not change the read-back-then-report rule.)

Skip this when the user chose a host-private or project-local root (Step 5, rules 3-4).

Then clean up the extraction workdir:

PYTHON_BIN="${PYTHON_BIN:-python3}"
if ! command -v "$PYTHON_BIN" >/dev/null 2>&1; then
  PYTHON_BIN="python"
fi

Remove **the work directory this run actually used** — the `Workdir ->` path from the
extraction output, which is also stored as `workdir` in `metadata.json`. Never delete a
directory you did not create: another extraction may be running beside yours.

```bash
# WORKDIR is the path this run reported; quote it in case of spaces.
rm -rf "$WORKDIR"

Equivalently, if you still have the metadata file:

"$PYTHON_BIN" - "$WORKDIR_METADATA_JSON" <<'PY'
import json
import shutil
import sys
from pathlib import Path

meta_path = Path(sys.argv[1])
workdir = json.loads(meta_path.read_text(encoding="utf-8")).get("workdir")
if workdir:
    shutil.rmtree(workdir, ignore_errors=True)
PY

Older copies of this file remove

Use this skill

Connect your agent to the Skill Store MCP server and ask for it by ref: virgiliojr94/book-to-skill-9468075291.

Download SKILL.md · Markdown version

Details

Publisher
virgiliojr94
Category
AI & Agents › Skill & prompt authoring
License
MIT
Updated
2026-10-11