Crawl fetch ingest
Skill lossless-group/lossless-agent-skills/crawl-fetch-ingest
Pi & Agent-Skills-standard skills used by The Lossless Group. Starting with context-vigilance.
npx -y skills add lossless-group/lossless-agent-skills --skill crawl-fetch-ingestAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 4 stars4 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
What its author says it does
Copied from the file, not written here
The Lossless Group's workflow for filling in team and portfolio metadata for VC firms and the operating companies they back — crawl a firm's site, fetch structured data + brand assets for people and companies referenced in a deck/PDF, ingest as canonical .md files with YAML frontmatter. Supports two starting anchors — firm-anchored (one VC → its team → its portfolio → portco CEOs) and company-anchored (one operating company → its backer firms → each backer's team + portfolio, stopping there) — for credibility-card use. Use whenever you need to recreate VC team pages, advisor sections, or portfolio company sections in HTML/Tailwind/Reveal slideshows; whenever the input is "here's a PDF and/or a firm URL, fill in the people and companies"; whenever you need headshots, LinkedIn URLs, company logos (SVG preferred), CEO metadata; whenever you need to "ingest our backers" or "make these investors legible to readers"; whenever the user mentions "fill out the team", "find the headshots", "credibility ingest", "we need their portfolio companies", or names this skill directly. Encodes the four-checkpoint cascade (VC team → advisors → portfolio companies → portco CEOs), the cross-tool fallback pattern (Firecrawl → Tavily → OpenGraph.io), the global-cache-per-firm convention so the same firm's data is reused across multiple decks/memos, and the loose canonical schema that sites converge toward on refactor (not enforced on ingest).
SKILL.md
26.9 KB, as published. Nobody here has run it
Crawl, Fetch, Ingest
Given a deck PDF and/or a firm URL, fill in the team and portfolio metadata so a designer can drop it into HTML/Tailwind/Reveal slides without hand-collecting every headshot, title, and logo.
When to use this skill
- Recreating a VC firm's team / advisor / portfolio sections for a redesigned deck or web presentation
- Filling team metadata for an investment memo, fund one-pager, or fundraise deck
- Asset hunt: SVG logos, favicons, headshots, LinkedIn URLs for people who appear in a PDF or on a firm's site
- Any "here's a PDF, give me a clean dataset of who's in it" task at the org/portfolio level
- Any 2nd / 3rd-order crawl: VC firm → portfolio companies → those companies' CEOs
- Credibility ingest for an operating company's fundraise — walk outward through its backers to make those firms legible to readers starting from near-zero context
Anchor types — two starting points, same cascade
The skill supports two starting anchors. Both are first-class. They share the same fetch cascade (Jina / Firecrawl / OpenGraph.io / Brandfetch / SVG-tier fallback / bg-strip) and the same schema. Only the entry point and stop condition differ.
Anchor: VC firm Anchor: Operating company
→ team → list of backer firms
→ portfolio cos → for each backer:
→ portco CEOs → team
→ portfolio cos
(stop — no portco CEOs)
Firm-anchored (the original walk): one VC firm is the root. Used when you're rebuilding a firm's own deck, memo, or website. See ## The four checkpoints below.
Company-anchored (credibility-card walk): one operating company is the root, and we walk outward through its named backers. Used when an operating company's deck/site needs to make its investor list legible to readers who don't know those firms. The traversal stops at the backers' portfolio companies — portco CEOs add no legibility at credibility-card distance. See routines/investor-credibility-ingest.md.
Both walks can run inside the same project. A typical fundraise repo ends up with:
<project>/data/
team/ # the operating company's own employees (company-anchored, CP1-only)
investors/ # backer firms (investor-credibility-ingest routine)
{firm-a}/firm.md + team/ + portfolio/
firms/ # if the project also did the firm-anchored walk on some specific VC
{firm}/firm.md + team/ + portfolio/ + portco-ceos/
Inputs (improvise with as little as possible)
The skill should accept any of:
- PDF only — extract names, titles, role labels, company logos visually present; infer firm context from cover/footer; ask user to confirm the firm before fetching
- URL only — treat as the firm's homepage; discover
/team,/portfolio,/aboutfrom sitemap or anchor crawl; build entity roster from the site - PDF + URL — best case; PDF defines who's in the deck (the authoritative list), URL drives discovery and asset-fetch
If only a name is given (e.g., "fill out Sequoia Capital"), search for the homepage with Tavily, confirm with user, then proceed as URL-only.
Output layout
Per-project content + assets land here:
<cwd>/data/firms/{firm-slug}/
firm.md # firm-level metadata
team/{person-slug}.md # CP1 (VC team) + CP2 (advisors) outputs
team/{person-slug}.{jpg|png} # headshot
portfolio/{co-slug}.md # CP3 output
portfolio/{co-slug}.svg # logo (svg preferred, png/jpg fallback)
portfolio/{co-slug}-ceo.md # CP4 output (CEO of that portco)
portfolio/{co-slug}-ceo.{jpg|png}
Raw API responses are cached globally so the same firm's data is reused across decks/memos:
~/.claude/skills/crawl-fetch-ingest/cache/{firm-slug}/
firecrawl/{url-hash}.json
tavily/{query-hash}.json
og/{url-hash}.json
Cache lookup is the first step before any paid call. Delete the firm's cache folder to force re-fetch.
The four checkpoints
Each checkpoint is a human-confirmation gate. Run discovery, present the roster/list to the user, wait for "go" before paid fetches.
CP1 — Everyone on the firm's own site
Goal: match deck names to firm-site bios; enrich with LinkedIn and other public profiles.
Important: firms scatter their people across multiple sub-pages by role, not just one /team page. A pure /team crawl misses venture partners, operating partners, EIRs, supporting partners, and the advisory board — all of whom may appear in the deck. CP1 must enumerate all of the firm's people-bearing pages, then categorize each person into the right role_class.
Page-discovery cascade. Try each path; treat 404 / missing-link as "this firm doesn't use that section." Keep going.
| Path family | Maps to role_class | Common path variants |
|---|---|---|
| Core team | vc-team, managing-partner (top of list usually) | /team, /people, /who-we-are, /about/team |
| Venture partners | venture-partner | /venture-partners, /partners, /team#venture |
| Operating partners | operating-partner | /operating-partners, /platform, /team#operating |
| Entrepreneurs in residence | entrepreneur-in-residence | /eir, /residents, /entrepreneurs-in-residence |
| Supporting partners / mentors | supporting-partner | /support, /supporting-partners, /mentors, /community, /network |
| Advisory board / LPAC | advisor | A separate section on /team, or dedicated /lpac, /advisors, /board pages |
For paths that paginate (e.g., /support?page=2), iterate until an empty page or a Load more button stops appearing.
Per-person fetch cascade. For each person discovered above:
- Jina Reader the bio sub-page (
/team/{slug},/supporting-partners/{slug}, etc.) → markdown. - If the markdown is thin or missing structured fields, escalate to Firecrawl with a
{name, title, bio, headshot, profile_links}[]extraction schema. - Cross-reference with deck names (if PDF was provided). Names that match a deck entry get priority + the deck's role-label preserved in
deck_role_label. - OpenGraph.io on each linked profile URL to pull a higher-quality
og:imageheadshot when the firm site's<img>is small. - For raw HTML scraping, the headshot is usually in
<meta property="og:image">— but some firms (e.g., Webflow sites) put it inbackground-image:url(...)on a sibling<div>. Check both. - Firecrawl each person's LinkedIn URL (LinkedIn is JS-gated; Jina Reader usually returns thin content).
Output: team/{person-slug}.md with the appropriate role_class (see schema/person.md for the full list of 9 recognized values + disambiguation tips). All people from the firm's site go in team/, regardless of role_class — the file location stays uniform; the role_class field carries the meaning.
Note on terminology mapping. Firms use idiosyncratic labels — Calm/Storm calls their founder-mentor ecosystem "Supporting Partners," Sequoia calls it "Scouts," Bessemer has "Operating Advisors." Pick the closest matching role_class from the schema; preserve the firm's literal label in deck_role_label and title. Don't spawn new role_classes for one-off labels.
CP2 — People in the deck NOT found on the firm's site
Goal: people named in the deck who didn't match anywhere in CP1's expanded discovery (any sub-page, any role). Resolve them via search.
If you've already broadened CP1 to cover all the role-bearing sub-pages above, CP2 will be smaller than it used to be — many "advisors" turn out to live on the firm's /support or /lpac page and get caught in CP1. CP2 is now genuinely "people the firm doesn't list publicly" — usually external advisors, occasional collaborators, or people the deck mentions by reputation.
Cascade:
- Tavily search with query like
"{Name}" "{Firm Name}" advisoror"{Name}" "{Title from deck}" - Jina Reader the top result for non-LinkedIn pages (personal site, news article, board-of-directors page)
- Firecrawl for LinkedIn profiles (Jina Reader is thin on LinkedIn)
- OpenGraph.io on the resolved profile URL for headshot
- If no high-confidence match, write the file with
status: flaggedandconfidence: low— never skip silently
Output: team/{person-slug}.md with role_class: advisor (if the deck's role label suggests a formal advisory/governance role) or external (if no clear category). Role-label hint from the deck preserved in deck_role_label.
CP3 — Portfolio companies in the deck
Goal: for each company referenced in the deck, gather profile metadata + brand assets.
Principle: match the asset role to the render context. A single "the logo" doesn't exist — every company has multiple brand assets at different aspect ratios for different uses. Fetching just one and forcing it everywhere produces bad layouts (a horizontal wordmark squeezed into a square chip becomes invisible-tiny-text; a square favicon stretched to a wide header looks pixelated). The skill captures three asset roles per company by default, then the rendering layer picks whichever fits the slot:
| Role | Aspect ratio | Use case | Fetch helper |
|---|---|---|---|
trademark (or wordmark + appIcon) | wide / horizontal | inline header, wordmark display, hero ribbon | scripts/logo-hunt.sh + scripts/brandfetch.ts |
favicon | square (1:1) | small chip, tile, list-row icon, OS app icon | scripts/favicon-hunt.sh |
og:image (URL only, no download by default) | 1.91:1 social card | portfolio detail-page hero, deck banner, social share preview | scripts/og-image-hunt.sh |
The CP3 fetch loop should run all three for every portfolio company — they're cheap, they hit different paths on the company site, and they cover the most common rendering contexts a downstream deck/site will need.
Cascade:
- Jina Reader the firm's
/portfolio(or/companies,/investments) → markdown - If structured fields needed, Firecrawl with
{name, website, sector, stage, description}[]schema - Cross-reference with logos visually present in the deck PDF (often the deck has 6–12 portfolio logos on a "selected investments" slide)
- Tavily search to fill in any company named in the deck but not on the firm's portfolio page
- OpenGraph.io on each company homepage for favicon +
og:image+ description - Crunchbase / LinkedIn discovery via Tavily (
"{Co} crunchbase","{Co} linkedin") — fetch via Firecrawl (both are JS-gated)
Trademark / wordmark cascade. SVG quality matters: rasters cause three recurring problems in slide / card layouts — opaque backgrounds that clash with surfaces, low-resolution that pixelates when scaled, and inconsistent margin-to-glyph ratio across brands so logos in the same-size container look visually uneven. Always try harder for an SVG before settling for a raster.
Run the cascade in this order, stopping at the first SVG (or first usable raster on the final tier). Record asset_strategy in the company's frontmatter.
- Inline
<svg>scrape — fetch the company homepage HTML, look for<svg>elements inside<header>/<nav>/[class*="logo"]. Many modern sites inline the nav logo as SVG. If found, extract and serialize. - Site SVG paths — try in order:
/logo.svg,/assets/logo.svg,/img/logo.svg,/static/logo.svg,/images/logo.svg,/brand/logo.svg,/favicon.svg,/apple-touch-icon.svg. Also upgrade any<img src="...png">in the nav to.svgand try. - Brand / press kit pages — fetch
/brand,/press,/media,/kit,/brand-assets,/about/press,/company/brand,/legal/brand. These pages often link to a downloadable SVG explicitly. Jina Reader the page; grep for.svg. - Brandfetch API (if
BRANDFETCH_API_KEYis set) —https://api.brandfetch.io/v2/brands/{domain}returns brand assets keyed by format. Preferformat=svg. Free tier: 1k req/mo. Seescripts/brandfetch.ts(when added). - Tavily site-search across SVG repos — query
"{Co} logo svg" site:worldvectorlogo.com OR site:seeklogo.com OR site:vectorlogo.zone OR site:wikimedia.org OR site:upload.wikimedia.org. These are SVG-first directories; a hit is usually clean. - Google Custom Search with
fileType=svg(last resort, costs money) — only if you haveGOOGLE_CSE_KEY+GOOGLE_CSE_CXin~/.secrets. Returns top URLs; download and validate it's actually an SVG. - Raster fallback + background strip — if all SVG paths fail: fetch the best raster (Brandfetch PNG > OpenGraph.io og:image > favicon), then run
scripts/bg-strip.shto remove the background. Setlogo_bg_stripped: trueandasset_strategy: site-raster-stripped(or whichever tier).
SVG validation — after fetching anything claiming to be SVG: file must contain <svg, have a viewBox or width/height, and be > 200 bytes (filters out 1×1 tracking pixels). Strip embedded <script> tags for safety.
Background-strip rule for rasters — sample the four corner pixels: if they're a uniform color (all same RGB within ~5%) and not already transparent, run scripts/bg-strip.sh automatically. The script does ImageMagick flood-fill from each corner first (fast, deterministic, ~70% of cases); falls back to rembg (U²-Net model) for non-uniform / soft-edge backgrounds if installed.
Favicon cascade (separate from the trademark cascade — runs in parallel):
<link rel="apple-touch-icon" href="...">from the homepage HTML — 180×180 polished square, designed for tile rendering. Best tier for chip/grid displays.<link rel="icon" href="...">from homepage HTML — any size; biggest preferred.- Common paths:
/apple-touch-icon.png,/apple-touch-icon-precomposed.png, then/favicon.svg,/favicon-32x32.png,/favicon.png,/favicon.ico. - PWA manifest icons via
<link rel="manifest">. - Google S2 favicon service (
https://www.google.com/s2/favicons?domain={d}&sz=128) — last-resort fallback that almost always returns something for resolvable domains.
Run via scripts/favicon-hunt.sh <domain> — returns JSON with tier, url, format. ICO outputs need converting to PNG before use in pipelines that don't accept ICO (Astro's <Image>, etc.) — use magick "ico:input.ico" -resize 256x256\> output.png.
OG-image cascade (URL only, no download by default):
<meta property="og:image">from homepage<meta property="og:image:url"><meta name="twitter:image"><meta name="twitter:image:src">
Run via scripts/og-image-hunt.sh <domain> — captures a URL into the company's og_image_url frontmatter field. The URL is preserved for downstream consumers (a deck might pull it in for a hero slide; a portfolio detail page might use it as a banner). No bytes downloaded by default.
Output: portfolio/{co-slug}.md. Brand assets use the role-prefixed convention:
- Default trademark:
portfolio/trademark__{Company-Name}.svg(or.pngfallback with alpha guaranteed if bg-strip ran) - When the brand has BOTH a wordmark and a separate icon:
portfolio/wordmark__{Company-Name}.svg+portfolio/appIcon__{Company-Name}.svg - Always:
portfolio/favicon__{Company-Name}.{svg|png}(ICO converted to PNG) - og:image URL recorded in frontmatter as
og_image_url:(no file saved unless explicitly requested)
Company-Name is Train-Case (e.g. Foundation-Health, 9am-Health, Inne). Full naming spec in schema/company.md → "Asset filename convention". The company's .md has separate logo: and favicon: fields pointing to their respective files.
CP4 — CEOs of the CP3 companies
Goal: for each portfolio company resolved in CP3, find the CEO and gather their metadata.
Cascade:
- Tavily search
"CEO of {Company}"or"{Company} founder" - Jina Reader the company
/teamor/aboutpage if not LinkedIn - Firecrawl the resolved LinkedIn profile
- OpenGraph.io for headshot
Output: portfolio/{co-slug}-ceo.md (next to the company file).
Tool inventory
| Tool | Type | Use for | When to prefer |
|---|---|---|---|
| Jina Reader | REST helper script (scripts/jina-reader.ts) | URL → clean markdown | Default first call when you just need a page's content. Fast, cheap, JS-rendering-aware. |
| Firecrawl MCP | MCP server | Structured extraction (URL + JSON schema → filled JSON) | When you need structured output (e.g., extract {name, title, bio, headshot}[] from a team page) or when Jina Reader returns thin content for a JS-heavy site. |
| Tavily MCP | MCP server | AI-native search — replaces "Google + scrape" | Advisor / CEO discovery, finding LinkedIn URLs by name+context, finding the right firm homepage from a name. |
| OpenGraph.io | REST helper script (scripts/og-fetch.ts) | OG metadata + image for any URL | Quick favicon / og:image / description without spinning up Firecrawl. |
| Bash + curl | built-in | Downloading assets to disk | After you've resolved the asset URL. |
| Brandfetch API | REST helper script (scripts/brandfetch.ts — to be added) | SVG logo + brand colors + fonts for a domain | Tier 4 in the logo cascade — when site SVG hunt fails. Free tier 1k/mo. Requires BRANDFETCH_API_KEY in ~/.secrets. |
| Tavily MCP (SVG repo search) | Tavily, with site: filters | Find SVG logos hosted on worldvectorlogo.com / seeklogo.com / vectorlogo.zone / wikimedia.org | Tier 5 in the logo cascade — when Brandfetch returns nothing. |
| Google Custom Search | REST (helper to be added) | fileType=svg web-wide SVG search | Tier 6 (last resort). Costs money beyond 100 free queries/day. Requires GOOGLE_CSE_KEY + GOOGLE_CSE_CX. |
ImageMagick (magick) | local CLI (brew install imagemagick) | Background removal via flood-fill from corners; also color sampling | Tier 1 of scripts/bg-strip.sh. Deterministic, no model deps, handles white + brand-color backgrounds. |
| rembg | local CLI (pipx install rembg) | AI-based background removal (U²-Net) for non-uniform / soft-edge logos | Tier 2 of scripts/bg-strip.sh. ~170MB model on first run, then offline. Optional — script gracefully degrades without it. |
Decision flow per URL:
- Need just text content? → Jina Reader.
- Need a structured object filled from the page? → Firecrawl with extraction schema.
- Need favicon / og:image / description only? → OpenGraph.io.
- Don't have a URL yet, just a name? → Tavily to find one, then go back to step 1.
Cache before call: every Jina / Firecrawl / Tavily / OpenGraph.io response is keyed on its URL or query hash and stored at ~/.claude/skills/crawl-fetch-ingest/cache/{firm-slug}/. Always check the cache before invoking the tool. Document the hashing convention in setup.md.
Schema
The skill's output schema is canonical and loosely enforced. Sites already have divergent shapes (mpstaton-site, fullstack-vc, hypernova-site, memopop-ai); adapting per-site happens on refactor, not on ingest. See:
schema/firm.md— firm-level frontmatterschema/person.md— CP1 + CP2 + CP4 (all people are the same shape, distinguished byrole_class)schema/company.md— CP3
Every file is .md with YAML frontmatter and a free-form body for prose bio / description / notes.
Confidence + flagging
Every output file should have a confidence field in frontmatter: high, medium, low, or flagged. Use:
high— direct match on firm site or LinkedIn with all required fields filledmedium— match via search, plausible but not confirmed (e.g., common name)low— partial data, asset hunt failed, ambiguous matchflagged— should be reviewed before publishing
Never silently drop an entity. If it appears in the deck and can't be resolved, write the file with status: unresolved + confidence: flagged and notes about what was tried.
Workflow for Claude
When invoked:
- Identify inputs — what does the user have? PDF? URL? Just a firm name? Confirm the firm slug (kebab-case, e.g.,
sequoia-capital). - Discovery (Phase 1, no paid calls) — extract names + companies from PDF if provided; identify firm site sections (
/team,/portfolio) from URL if provided. - Present roster — show the user what was found and wait for confirmation before running paid fetches. Don't run all four checkpoints autonomously.
- CP1 → confirm → CP2 → confirm → CP3 → confirm → CP4 → confirm. Each gate is a chance for the user to refine, deduplicate, or skip.
- Cache aggressively — every API response goes to
cache/{firm-slug}/{tool}/. Check before calling. - Write files as you go — don't accumulate in memory. The output dir is the state.
- Report at the end — counts per checkpoint, list of
flaggedentities, list of assets that fell back from SVG → PNG, anything the user should manually review.
Subroutines
Subroutines are sub-workflows that operate on the output of the main four-checkpoint ingest. They are invoked after (or independently of) the main ingest, typically as quality-assurance or human-in-the-loop steps. Each lives as its own markdown file in routines/ with optional helper scripts in scripts/.
routines/triage-brand-assets.md
Invoke when: the user asks to "triage", "review", "classify", or "audit" the brand assets fetched for a firm. Typically run after CP3 + bg-strip + rename are complete, before publishing.
What it does: for each portfolio company, runs an auto-classifier (scripts/triage-classify.py) that scores the logo across existence / format / bg-strip success / foreground luminance / resolution / file size / flagged-status, then walks the user through each ambiguous case asking for one of good-to-go / not-urgent-passable / urgent-rework / deferred-for-now. Persists the choice as review_status in the company's frontmatter.
Output: updated frontmatter on each portfolio/{co-slug}.md plus an end-of-routine summary listing the urgent-rework items with suggested next moves (e.g., "try Brandfetch tier 4," "manual cleanup in Figma").
routines/investor-credibility-ingest.md
Invoke when: the user asks to "ingest our backers", "fill out the investor section", "do credibility ingest", "make these VCs legible", or supplies a list of firm names + the operating company they back. This is the company-anchored walk — root is the operating company, traversal goes outward through its named backers.
What it does: for each backer firm, runs CP1 (team across every role-bearing sub-page) + CP3 (portfolio + brand assets via the SVG cascade). Skips CP4 — at credibility-card distance, portco CEOs add no legibility. Writes to <cwd>/data/investors/{firm-slug}/ so multiple backers coexist alongside the operating company's own data/team/.
Output: data/investors/{firm-slug}/firm.md + team/{person}.md + team/{person}.jpg + portfolio/{co}.md + portfolio/trademark__{Co}.svg + portfolio/favicon__{Co}.png per backer firm. End-of-routine summary covers per-firm team/portfolio counts, SVG-vs-raster asset success rates, and cross-firm portfolio overlaps (co-investments are a credibility multiplier the rendering layer may want to highlight).
Adding new subroutines
The pattern: drop a new routines/{name}.md with frontmatter (name, description), then add a one-paragraph entry in this section that tells the agent when to invoke it. Helper scripts go in scripts/{name}.py|.sh|.ts and are referenced from the routine doc, not from SKILL.md directly. This keeps SKILL.md as a stable map and lets subroutines evolve independently.
Examples of future subroutines that fit this pattern: triage-person-assets.md (same idea but for headshots — wrong person, low-res, missing), re-fetch-flagged.md (re-run the cascade with stricter parameters on entities marked confidence: flagged), export-to-site.md (per-site adapter that converts data/firms/... into a target site's content-collection schema).
Important: this skill never adapts to a specific site's content collection
The skill writes only under <cwd>/data/ — data/firms/{firm-slug}/ (firm-anchored), data/investors/{firm-slug}/ (company-anchored, via investor-credibility-ingest), or data/team/ + data/portfolio/ (flat operating-company variant). Wiring any of these outputs into a specific site's content collection (e.g., mpstaton-site's src/content/team/) is a separate, per-site task. That separation is intentional — sites diverge in schema, asset paths, and routing; the skill stays neutral.
See also
setup.md— one-time install (.secrets, MCP servers, npm packages, ImageMagick, rembg)schema/{firm,person,company}.md— canonical frontmatter (incl. logo asset_strategy fields)scripts/jina-reader.ts— URL → markdownscripts/og-fetch.ts— OpenGraph.io REST wrapperscripts/brandfetch.ts— Brandfetch API wrapper (tier 4 of the logo cascade);--best-svg/--best-raster/--save-allmodesscripts/bg-strip.sh— auto-strip backgrounds from raster logos (ImageMagick → rembg cascade)scripts/logo-hunt.sh— run tiers 1–3 of the trademark/wordmark cascade locally for a domainscripts/favicon-hunt.sh— run the favicon cascade for a domain (apple-touch-icon → site icons → Google S2 fallback)scripts/og-image-hunt.sh— extract og:image / twitter:image URL from a homepagefuture-work.md— PDF OCR (port from memopop-orchestrator), Google CSE wrapper, design-system-viewer-style preview UI