metafetch 0.2.0 Draft

Metafetch 0.2.0: the image comes through, socials fill themselves, and any API fills the rest

The share image comes through, a tool's socials fill themselves from its homepage, funding rounds land in the note, and any model or data API fills the rest.

Your link cards get their pictures back. Direct Fetch, the command that needs no API key, now finds a page's share image in all the places pages put it. When a page has no image, it no longer erases the one your note already had.

Why care?

A note about a tool, a company, or an article usually becomes a card: Bases views, Dataview tables, or a published site's link previews. A card without its image looks broken.

Direct Fetch from Script left og_image empty on pages that have a share image. Two problems compounded:

  • It only looked in one place, and only one way. It read <meta property="og:image"> with quoted attributes. Pages also write it as name="og:image" (Wondershare's Filmora), with unquoted attributes in minified builds, as og:image:secure_url, as schema.org microdata (Google), or only in JSON-LD. All of those came back empty.

  • A miss erased what you had. When a page had no image, the command wrote og_image: null over the image your note already held, whether it came from an earlier fetch, from OpenGraph.io, or from you.

Both are fixed in 0.2.0.

What's new

The share image, from everywhere pages put it

In order, first found wins:

  1. og:image, then og:image:url and og:image:secure_url

  2. twitter:image, then the older twitter:image:src

  3. <meta itemprop="image"> (schema.org microdata)

  4. <link rel="image_src">

  5. The image of the page's JSON-LD article or web-page node

Steps 1 and 2 accept either property= or name=, since pages mix them up. Relative paths are resolved against the page, so you always get a full URL.

Nothing gets blanked

If a page doesn't supply an image, a title, or a description, your note keeps what it had. A note that never had an image still gets the og_image: null placeholder, so batch tools can still find unfetched notes.

Cleaner metadata overall

Direct Fetch shares its extractor with Cite Wide, which fixed several bugs in it while enriching the Lossless vault's citations. Those fixes are now in Metafetch too:

  • Apostrophes survive. Kyle O'Brien used to come back as Kyle O.

  • Named HTML entities decode. You get BMW’s … — a look, not BMW&rsquo;s … &mdash; a look.

  • The favicon is the favicon, not the large apple-touch-icon a page happened to list first.

  • More authors are found. When there's no author tag, Metafetch checks JSON-LD, Medium-style "Written by" labels, and the visible byline. Reading times ("3 minutes") and CMS placeholders ("Super User") no longer land in authors.

  • Bot-check pages are a failed fetch, not metadata. "Just a moment…" will never become your note's title; the note is left alone and you get a notice.

New command: Fill frontmatter from folder profile. A profile is an ordinary note in zz-cf-lib/frontmatter/ that says which properties a folder's notes should carry. Create example frontmatter profile writes one for Tooling/: the company's GitHub org and repo, LinkedIn page, X, YouTube, Discord, Instagram, Facebook, Bluesky, and Crunchbase.

Metafetch reads the company's homepage and fills the fields that are still empty. Finding social links is easy; choosing the company's own is not. A homepage also links customer logos, partners, and share buttons. So links the site lists in its structured data win, then footer links, then navigation. A body link that doesn't resemble the brand is never used, and when two links tie, nothing is written and you're told both.

On 20 random notes from our own Tooling folder, 16 gained links on the first run. The other four were one page with no links to find and three sites that refuse automated fetches.

Any model for the fields a page can't fill

Some properties need judgment, not a link: a one-line zinger, a pricing model, a category from your own list. Mark a profile field from: [model] with an instruction, and the model fills it from the page's text after the free fields are done. Its answer is checked against the field: a value outside your list is dropped, and the model is told an empty field beats a guess.

Use whichever model you like. Claude (claude-opus-5-5), OpenAI (gpt-6-astra), TrustedRouter, and any OpenAI-compatible endpoint (LM Studio, Ollama, OpenRouter, Groq) are built in. Any other API is a short recipe file in zz-cf-lib/recipes/; Create example model recipe writes one for Google Gemini to copy from.

Keys stay in Obsidian's keychain, picked by name in settings and never stored in Metafetch's files. Each key goes only to its own provider, and a recipe file's key goes nowhere until you approve its host.

Funding rounds, written into the note

New command: Fetch via Fireround. Run it on a company note and its funding rounds land under ## Fundraising History as a json block, exactly as Fireround's verified feed returned them: amounts, dates, lead and other investors, and the articles each round was checked against. Running it again replaces the block. All of it stays out of your frontmatter, where a few rounds would run to thousands of characters.

Two rules keep the wrong company out. Rounds are kept only when the company's domain matches the note's exactly, so a similar name never gets in. And the note's url has to be a homepage: a note on openai.com/sora is about something hosted there, so OpenAI's rounds aren't written to it, and a notice says why.

Any data API, as a recipe file

Fireround is one recipe, not a special case. A recipe can describe any JSON API, and there are two ways to use one:

  • Its own command. A recipe with write-json-under: "Some Heading" gets a Fetch via command that writes its answer under that heading. This is for answers too large to be properties.

  • A profile field. Name the recipe in from: and one of its outputs in value:. The recipe owns its output names and your profile owns your vault's keys, so neither depends on the other:

YAML
og_image:    { page: image, from: [direct, firecrawl] }
brand_color: { from: [firecrawl-branding], value: primary_color }

The first line reads the page directly, and asks Firecrawl only when the site refuses. That's the fix for those three sites above. The second reads a site's colours, fonts, and logo. Sources run cheapest first: the free parse, then data recipes, then a model, which fills only what's left. Each recipe is called at most once per note.

One Firecrawl key covers all of it, and that key only ever goes to api.firecrawl.dev. A recipe file with provider: firecrawl borrows it, so anything in Firecrawl's provider catalogue is one file away, with no new release. Pointing that recipe at any other host is refused before anything is sent.

The settings tab now uses Obsidian 1.13's declarative settings API. Every Metafetch setting appears in Obsidian's settings search, and the tab is grouped by provider: OpenGraph.io, Microlink, Direct fetch, Field names, and Vault identity code. Every setting from 0.1.x is still there.

Upgrade notes

  • Requires Obsidian 1.13 or newer. The declarative settings API arrived in 1.13. Obsidian 1.12 and earlier keep 0.1.7.

  • Your settings carry over untouched. Nothing in your notes is rewritten on upgrade.

  • Command names are now in sentence case, for example "Direct fetch from script". Their IDs didn't change, so your hotkeys still work.

  • Firecrawl features need a Firecrawl key, chosen under Settings → Metafetch → Firecrawl. Without one, everything else works as usual. A Fireround lookup used 5 Firecrawl credits in our test calls.

  • The example profile is unchanged in what it fetches. Its Fireround and brand fields ship commented out, so nothing calls Firecrawl until you ask it to.

  • Notes fetched before 0.2.0 with an empty og_image can be re-run. Many pages that came back empty will now return an image. Some pages really publish none (Founders Fund, semver.org, electronjs.org), and those stay empty.

Under the hood

We probed the 50 vault notes that had been fetched with no image. Most pages simply have no og:image. The rest came back empty for the reasons above, and each one is now a test fixture.

  • Network: api.firecrawl.dev is new, and is only contacted when you run a Firecrawl or Fireround recipe with a key set. Model providers are only contacted when a profile field asks for one.

  • Recipes: two kinds, model and data. A data recipe reads named outputs by path ([*] for lists, records and match so only an exact match is read). Only data recipes can borrow a key, and only from a recipe that holds its own, so a chain can't widen where a key goes. Only text, numbers, yes/no, and lists of those reach frontmatter; objects and inline data: images are dropped.

  • Tests: 183, run by a zero-dependency node:test harness copied from Cite Wide. They cover every image location, every social platform and ranking case, every recipe's request and key handling, the "never blank a stored value" rule end to end through both commands, settings definitions, and command registration.

  • Lint: clean (0 errors, 0 warnings) under the Obsidian community review bot's own rules, eslint-plugin-obsidianmd.

  • Dependencies: Obsidian API 1.13.1, ESLint 10.12, typescript-eslint 8.71.1. Still zero runtime dependencies.

Every release asset is cryptographically attested:

BASH
gh attestation verify main.js --repo lossless-group/metafetch