Install
$ agentstack add skill-roots-and-fruit-skills-web-scrape-to-md ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Web Scrape to Markdown
Purpose
Collect high-signal website content into local markdown files using WebSearch and WebFetch — markdown-first, goal-aligned extraction, minimal token waste.
Not this skill:
- Site inventory only — use [Site Content Catalog](../../marketing/skills/site-content-catalog/) for URL lists,
page_typelabels, and SEO signals without full content extraction. - Batch reproducible crawls — use a dedicated scraper CLI when you need
raw/,manifest.jsonl, or resume logic at hundreds of URLs. - Live SERP scoring — use [Information Gain Evaluator](../../marketing/skills/information-gain-evaluator/) for citation-fit analysis.
Reference: REFERENCE.md · Examples: EXAMPLES.md
Input policy (non-negotiable)
- Domain or URL list — from the user only. Never infer from workspace, examples, or prior runs.
- Goal — from the user (audit, research, brief, competitor analysis, corpus). If missing, ask once.
- Coverage target — user number or default 10 pages.
- Output directory — user path when saving files; if omitted, return handoff + page markdown in chat only.
If missing domain/URLs, ask once:
> Which domain or URLs should I scrape? What is the goal, and how many pages should I target? (Optional: output folder path.)
Modes
| Mode | When | Output | |------|------|--------| | corpusrun | User wants local .md files | Per-page files + INDEX.md + handoff JSON | | summaryonly | Quick triage, no files | Handoff JSON + run summary in chat |
Completion criterion for mode: handoff.mode matches what you did; corpus_files[] populated only in corpus_run.
Quick start
- Confirm domain/URLs, goal, coverage target, mode, optional output_dir.
- Step 1 — markdown-first discovery + sitemap/blog fallbacks (
REFERENCE.md). - Step 1b — freshness pass when goal requires recent posts (
REFERENCE.md§ Freshness). - Step 2 — curate URL set with intent + source tags.
- Step 3 — fetch with WebFetch tactics; batch parallel independent URLs.
- Step 4 — write page markdown per template; assess quality (Q-layer).
- Step 5 — emit handoff JSON v1.1 +
INDEX.mdwhencorpus_run.
Workflow
Web Scrape to MD Progress
- [ ] Step 1: Markdown-first discovery (+ sitemap/blog fallbacks)
- [ ] Step 1b: Freshness pass (when goal requires)
- [ ] Step 2: URL curation
- [ ] Step 3: Fetch (efficient, parallel)
- [ ] Step 4: Page markdown + quality pass
- [ ] Step 5: Handoff + INDEX
Step 1 — Markdown-first discovery
Before bulk HTML page fetches:
WebFetchin order:/llms.txt,/llms-full.txt,/llms.md,/sitemap.xml.- Record
discovery_summary.llms_endpoints_checked: trueandsitemap_root_status(ok|http_error|missing). - Sitemap root failure — if
/sitemap.xmlreturns HTTP error, try child sitemaps fromnode scripts/wp-sitemap-fallback.mjs --domain(REFERENCE.md§ Sitemap fallback). - llms.txt blog/archive links — when llms lists a blog or content archive URL, fetch that index for child post links even though other archive indexes are normally excluded (
REFERENCE.md§ llms archive rule). - Blog archive fallback — when root + child sitemaps fail or omit posts, fetch archive URLs: llms-seeded archive links first, then
blogArchiveCandidates(domain, seeds)(REFERENCE.md). WebSearchonly when llms, sitemap, and archive passes still leave gaps.
Completion criterion: sitemap_child_urls_tried[] populated when root sitemap failed; llms-seeded archive URLs fetched for discovery when present.
Step 1b — Freshness pass (when required)
Set discovery_summary.freshness_goal: true when the goal mentions blog, articles, corpus, research, content, publish, recent, or latest (scripts/wp-sitemap-fallback.mjs goalNeedsFreshness()).
When freshness_goal is true:
- Prefer newest posts from child post sitemap `` order or archive index (top 3 by default).
- Archive link extraction — on each archive index fetch, run
extractArchivePostUrls()on the raw response (REFERENCE.md§ Archive link extraction). Use discoveredhrefs before any slug guessing. - Add discovered post URLs to
recent_posts_discovered[]and the curated fetch set. - Do not skip the archive index solely as "low-signal" when freshness is required.
- Slug recovery (last resort only) — when link extraction finds teasers but no URLs (
REFERENCE.md§ Slug recovery):
- Derive
pathPrefixfromblog_archive_urlviablogPathPrefixFromArchiveUrl()— never hardcode a site path. - Run
slugCandidatesFromTeaser()andpostUrlsFromSlugs(domain, candidates, pathPrefix). - If slug attempts fail,
WebSearchwith the exact teaser sentence in quotes.
Completion criterion: recent_posts_discovered[] lists every post visible on the archive index; unresolved only after link extraction + slug candidates + exact-title search.
Step 2 — URL curation
- Dedupe URLs; tag source and intent (
REFERENCE.md). - Exclude tag/author archives, auth/checkout, faceted noise — except llms-seeded blog indexes and URLs needed for
freshness_goal. - Trim to coverage target — highest-signal URLs first; include newest posts when freshness_goal.
Completion criterion: every planned fetch URL has source_tag + intent; completed_total equals curated attempt count.
Step 3 — Fetch (efficient, parallel)
Apply REFERENCE.md § WebFetch:
- Batch independent URLs in parallel WebFetch calls; retry individually if a batch times out.
- Follow redirects; store
final_url. - Prefer markdown page variants when llms or sitemap hints allow.
- Set
fetch_formatper page fetch only (markdown|html). - Status:
ok|http_error|blocked_or_empty|unresolved(after one retry + alternative path).
Completion criterion: each url_results[] row has status, fetch_format when ok, and final_url when ok.
Step 4 — Page markdown + quality pass
For corpus_run, one file per page ({slug}.md) using REFERENCE.md template.
- Extract for the declared goal — not full page dumps.
- Q-layer checklist in
REFERENCE.md. - Cap ~80–120 lines per page unless user requests full capture.
- Prefer post body files over archive index files when coverage is tight — document discovery in
limitations[]instead of a dedicated archive-index corpus file unless the user asks for one.
Completion criterion: every ok URL in corpus_run has a corpus_files[] entry passing Q-layer; archive post URLs extracted before slug recovery when freshness applies.
Step 5 — Handoff + INDEX
Emit handoff JSON v1.1 (REFERENCE.md). Reconcile counts:
completed_ok= count ofurl_resultswithstatus: okcompleted_total=url_results.lengthmarkdown_direct_count/html_fallback_count= ok page fetches byfetch_formatonly (not llms.txt discovery)
For corpus_run, write INDEX.md. Every recommended_keep URL should appear in successful url_results or be documented in limitations[].
Return in chat: attempted vs succeeded, efficiency summary, keep/drop, next step.
Decision rules
- WebSearch/WebFetch default for quick and medium runs.
- Skip extra search rounds when llms + sitemap + archive already map the goal-aligned set.
- Never skip an llms.txt Blog/archive link without fetching it once for child URLs.
- Escalate to programmatic scraping only for large page counts, resume needs, or explicit batch artifact requests.
- Do not claim full-site coverage unless planned URL classes were attempted.
Quality guardrails
- Separate found URL from successfully fetched content.
- Do not treat discovery snippets as page body.
- If a URL fails twice, mark
unresolvedand document one alternative inlimitations[].
Ship bar (regression)
node scripts/verify-handoff.mjs
node scripts/verify-scorecard.mjs
node scripts/wp-sitemap-fallback.mjs --domain example.com
Golden fixtures: examples/example-saas.handoff.fixture.json, examples/example-sitemap-fail.handoff.fixture.json. See examples/SCORECARD-example-saas.md.
Done definition
- [ ] User inputs echoed in
handoff.inputs— no invented domain - [ ]
llms_endpoints_checkedtrue;sitemap_root_statusrecorded - [ ] Sitemap root HTTP error → child sitemaps tried and/or blog archive fetched
- [ ] Archive fetch →
extractArchivePostUrls()on raw response before slug recovery - [ ] Every curated URL in
url_results[]; counts reconciled (completed_ok, format counts) - [ ]
markdown_direct_countcounts page fetches only — not llms.txt - [ ]
corpus_run: eachokURL has corpus file meeting Q-layer - [ ] Child verifiers exit 0 on golden fixtures
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Roots-and-Fruit
- Source: Roots-and-Fruit/skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.