Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Read any web page, document, or YouTube video as clean Markdown using PullMD. Use this skill whenever you need to fetch, read, extract, or summarize content from a URL — web articles, Reddit threads, PDF/Word/PowerPoint/Excel/EPUB documents, or YouTube transcripts. This includes when the user says 'read this page', 'what does this URL say', 'fetch this article', 'summarize this PDF', 'get the transcript of this video', or when you need web content as context for another task. Also use this when
.claude/skills/aeternalabshq-pullmd/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-22 | ✗→✓ | ▲ Improved | 120% | 0% |
| case-23 | ✗→✓ | ▲ Improved | 612% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 72% | 0% |
| case-07 | ✗→✓ | ▲ Improved | 60% | 0% |
| case-08 | ✗→✓ | ▲ Improved | 57% | 0% |
Read web pages, documents, and YouTube videos as clean, structured Markdown via the self-hosted PullMD service. Falls back gracefully to WebFetch if PullMD is unavailable.
PullMD routes each URL through the extraction path that fits it:
/news, /newest, /ask, /show, /jobs, /best) go through the HN API and come back as a clean nested comment tree.Accept: text/markdown get native Markdown directly.The result is much cleaner than the raw HTML that WebFetch returns, and it works on JavaScript-heavy sites and binary formats that WebFetch can't handle at all.
Use Bash to curl the PullMD API. This is preferred over WebFetch because it returns clean Markdown directly:
bashcurl -s "__PULLMD_URL__/api?url=<URL>"
The response is text/markdown — ready to use as-is.
Available parameters:
| Param | Default | Notes | | --------------- | ------- | --------------------------------------------------------------------------- | | url | — | Required. | | comments | true | Include Reddit / Hacker News comments. Ignored for other URLs. | | comment_depth | 3 | Comment nesting depth (1–10), Reddit and Hacker News. | | comment_limit | none | Max top-level Reddit comments (Reddit returns ~200 without a cap). | | frontmatter | false | Prepend YAML metadata (title, source, quality, share id, …). | | format | md | text strips Markdown; json returns a structured response with metadata. | | nocache | false | Bypass the 1-hour cache and refetch from source. | | render | auto | force → always render via Playwright. skip → never render. Bypasses cache. | | extractor | auto | Force readability / trafilatura / playwright, skipping the quality pick. Bypasses cache. | | pdf | — | ocr → high-quality OCR conversion for PDFs (table-grade output; needs a server-side OCR key). Bypasses cache. | | yt_timecodes | links | YouTube transcripts: links (clickable timestamps), plain ([MM:SS]), none. | | yt_chunk | 30 | YouTube transcript block size in seconds; 0 = per original snippet. | | query | — | Set this when you need specific information from a page rather than the whole document: pass the question you are trying to answer, in natural language, and get back only the matching sections - typically 70-95% fewer tokens on long pages. No LLM involved. Empty/absent = full page, unchanged. | | max_tokens | 600 | Token budget for query (64–20000). No effect without query. Raise it when the answer likely spans several sections; leave the default for single-fact lookups. Only validated when query is set. | | lang | de | Language for the comments-section header (de or en). |
Response headers worth checking:
X-Source — reddit · hackernews · cloudflare · readability · readability-fallback · trafilatura · playwright · recipe-content · coverage-guard · markitdown · youtube · image-caption · audio-transcript · pdf-ocrX-Quality — 0.0–1.0 extraction confidence (low values mean the static extraction was thin or noisy)X-Share-Id — 8-hex permalink, openable as __PULLMD_URL__/s/<id> (absent for /api/html — local conversions are never cached or shared)X-Suggested-Filename — a ready-made filename for this conversion (e.g. YT-some-talk-dQw4w9WgXcQ.md); use it when you save the output to a file instead of inventing a name.X-Transcript-Status — YouTube only: ok / none / blocked / error. blocked and error are transient (rate limit) and not cached — retry later; none means the video has no transcript at all.X-Extracted / X-Extract-Confidence / X-Extract-Sections / X-Extract-Original-Tokens / X-Extract-Returned-Tokens — only when query is active; the last two show how much context the extraction saved.Example calls:
bash# Read an article curl -s "__PULLMD_URL__/api?url=https://example.com/article" # Read a Reddit post with comments curl -s "__PULLMD_URL__/api?url=https://reddit.com/r/node/comments/abc/title/&comments=true" # Convert a PDF / Office document by URL curl -s "__PULLMD_URL__/api?url=https://example.com/report.pdf" # Table-heavy PDF via the OCR tier (if enabled on the instance) curl -s "__PULLMD_URL__/api?url=https://example.com/report.pdf&pdf=ocr" # YouTube transcript with clickable timecodes (if enabled on the instance) curl -s "__PULLMD_URL__/api?url=https://www.youtube.com/watch?v=dQw4w9WgXcQ" # Long page, but you only need one thing: get just the relevant sections curl -s "__PULLMD_URL__/api?url=https://example.com/long-doc&query=rate+limit+headers&max_tokens=800" # Get fresh (uncached) content curl -s "__PULLMD_URL__/api?url=https://example.com/news&nocache=true" # Force the Playwright fallback for a JS-rendered page that didn't trigger # the auto-detection (or where you want to be sure) curl -s "__PULLMD_URL__/api?url=https://mistral.ai/pricing&render=force" # Convert a local HTML file you already have (never cached, no share link; X-Filename keeps the name out of access logs) curl -s -X POST --data-binary @page.html -H 'Content-Type: text/html' -H 'X-Filename: page.html' "__PULLMD_URL__/api/html" # Upload a local document (PDF/DOCX/…, max 25 MB) curl -s -X POST --data-binary @report.pdf -H 'Content-Type: application/pdf' -H 'X-Filename: report.pdf' "__PULLMD_URL__/api/file"
If curl returns valid Markdown (starts with # or contains readable text), use that content. The X-Source response header tells you which extraction method was used. If X-Source: playwright, the page needed JavaScript rendering — that's normal for SPAs (Next.js, React, Vue dashboards, …).
If PullMD fails (network error, timeout, empty response), fall back to the built-in WebFetch tool:
WebFetch(url="<URL>", prompt="Extract the main content of this page")This still works but produces noisier output since it processes raw HTML. (For document and YouTube URLs there is no WebFetch equivalent — report the failure instead.)
Need to read a URL?
├── Is it a GitHub URL? → Use `gh` CLI instead
├── Is it a JSON API? → Use curl/fetch directly
└── Anything else (web page, PDF/Office doc, YouTube, image, audio):
├── Try: curl PullMD API
│ ├── Success (got Markdown) → Use it
│ └── Failed (error/timeout/empty) → Fallback below
└── Fallback: WebFetch tool (web pages only)nocache=true if you need the latest version. render=force|skip, extractor=, pdf=ocr, and explicit yt_* params also bypass the cache.comments=true to include the discussion below the post. Reddit and Hacker News URLs are auto-detected and use dedicated pipelines; comment_depth controls how deep the tree goes.query=<the question you are trying to answer>, phrased in natural language - it returns just the matching sections (typically 70-95% fewer tokens on long pages) and reports the saving in X-Extract-*. It falls back to the full page when nothing matches, so it is safe to try. Omit it only when you genuinely need the complete document - summarizing, translating, archiving.render=force re-extracts via headless Chromium.redd.it short links and /r/<sub>/s/<id> share links) and use a specialized extraction pipeline that handles posts, comments, galleries, and videos.frontmatter=true when you want metadata: extraction source and quality always; for Reddit posts also subreddit, author, upvotes, and publish date; for media/YouTube/OCR results duration, image size, and LLM token usage (cost tracking)./api/history endpoint shows recent conversions — useful for checking what's been fetched: curl -s "__PULLMD_URL__/api/history?limit=5".share_id. GET __PULLMD_URL__/s/<id> returns the cached markdown and re-fetches from source if older than one hour — useful as a stable URL that always returns fresh content.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-21 | pass→pass | 9,173 | 2,578 | -72% | 1 | 1 | 0% | 1,515 | 3,038 | +101% | 0 | 0 | — |
case-22 | fail→pass | 8,199 | 7,968 | -3% | 1 | 1 | 0% | 1,377 | 3,034 | +120% | 0 | 0 | — |
case-23 | fail→pass | 8,104 | 2,077 | -74% | 1 | 1 | 0% | 416 | 2,962 | +612% | 0 | 0 | — |
case-01 | fail→fail | 12,930 | 14,094 | +9% | 1 | 1 | 0% | 2,106 | 3,175 | +51% | 0 | 0 | — |
case-02 | fail→fail | 13,968 | 20,508 | +47% | 1 | 1 | 0% | 1,319 | 3,230 | +145% | 0 | 0 | — |
case-03 | fail→fail | 3,948 | 17,653 | +347% | 1 | 1 | 0% | 600 | 3,149 | +425% | 0 | 0 | — |
case-04 | pass→pass | 9,636 | 2,948 | -69% | 1 | 1 | 0% | 799 | 3,080 | +285% | 0 | 0 | — |
case-05 | pass→pass | 8,120 | 8,078 | -1% | 1 | 1 | 0% | 1,460 | 3,208 | +120% | 0 | 0 | — |
case-06 | fail→pass | 11,142 | 7,838 | -30% | 1 | 1 | 0% | 1,821 | 3,125 | +72% | 0 | 0 | — |
case-07 | fail→pass | 14,184 | 3,424 | -76% | 1 | 1 | 0% | 2,075 | 3,311 | +60% | 0 | 0 | — |
case-08 | fail→pass | 12,348 | 5,604 | -55% | 1 | 1 | 0% | 2,184 | 3,427 | +57% | 0 | 0 | — |
case-09 | fail→pass | 15,699 | 2,763 | -82% | 1 | 1 | 0% | 1,867 | 3,166 | +70% | 0 | 0 | — |
case-10 | fail→pass | 14,152 | 2,365 | -83% | 1 | 1 | 0% | 2,154 | 3,084 | +43% | 0 | 0 | — |
case-11 | fail→pass | 13,788 | 8,360 | -39% | 1 | 1 | 0% | 2,492 | 3,329 | +34% | 0 | 0 | — |
case-12 | fail→pass | 18,548 | 4,326 | -77% | 1 | 1 | 0% | 2,014 | 3,279 | +63% | 0 | 0 | — |
case-13 | fail→pass | 17,049 | 2,399 | -86% | 1 | 1 | 0% | 2,898 | 3,090 | +7% | 0 | 0 | — |
case-14 | fail→pass | 15,310 | 9,641 | -37% | 1 | 1 | 0% | 2,445 | 3,399 | +39% | 0 | 0 | — |
case-15 | fail→pass | 20,483 | 9,165 | -55% | 1 | 1 | 0% | 3,053 | 3,421 | +12% | 0 | 0 | — |
case-16 | fail→pass | 11,753 | 2,516 | -79% | 1 | 1 | 0% | 1,128 | 2,955 | +162% | 0 | 0 | — |
case-17 | fail→pass | 15,587 | 7,819 | -50% | 1 | 1 | 0% | 1,872 | 2,980 | +59% | 0 | 0 | — |
case-18 | fail→pass | 11,969 | 3,267 | -73% | 1 | 1 | 0% | 1,056 | 3,220 | +205% | 0 | 0 | — |
case-19 | pass→pass | 14,459 | 8,613 | -40% | 1 | 1 | 0% | 1,723 | 3,287 | +91% | 0 | 0 | — |
case-20 | fail→pass | 9,403 | 2,910 | -69% | 1 | 1 | 0% | 1,624 | 3,084 | +90% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted, and 20 counted toward the lift figure. The other 3 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +70 percentage points is the difference between those two pass rates over the 20 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.