# seo adapter pack

SEO capability pack operating on PROVIDED html/text (no network — fetching is the 'scraping' pack's job). Generates meta/OpenGraph/Twitter tags, schema.org JSON-LD...


On-page **SEO** capabilities that operate on **provided** html/text. Pure JS, zero npm deps.

> **No network.** This pack never fetches a URL. Pulling remote HTML is the job of the
> **`scraping`** pack — pass its output in here. (`scraping` also *parses* existing
> sitemap.xml / robots.txt; this pack *generates* them.)

## Tools

| Tool | Args | Returns |
|---|---|---|
| `metaTags` | `{ title, description, url?, image?, type='website', twitterCard='summary_large_image', keywords? }` | `{ tags:[], html }` — title + description + canonical + OG + Twitter |
| `openGraph` | `{ title?, description?, url?, image?, type='website' }` | `{ tags:[], html }` |
| `twitterCard` | `{ title?, description?, image?, twitterCard='summary_large_image' }` | `{ tags:[], html }` |
| `jsonLd` | `{ type, data }` | `{ type, json, script }` — schema.org for Article/Product/Organization/BreadcrumbList/FAQPage |
| `sitemap` | `{ urls:[{loc,lastmod?,changefreq?,priority?}] }` | `{ xml, count }` — valid `<urlset>` |
| `robotsTxt` | `{ rules:[{userAgent,allow?,disallow?}], sitemap? }` | `{ txt, blocks }` |
| `audit` | `{ html }` | `{ score, issues:[], checks:{} }` — title/desc/h1/img-alt/canonical checks |
| `keywordDensity` | `{ text, top=10 }` | `{ total, terms:[{term,count,density}] }` |
| `slugify` | `{ text }` | `{ slug }` |
| `readingTime` | `{ text, wpm=200 }` | `{ words, wpm, minutes, text }` |
| `serpSnippet` | `{ title, description }` | `{ title, description, warnings }` — pixel-ish char limits |

## Usage

```js
import seo from './index.js';

seo.adapters.metaTags({ title: 'Hello', description: 'A short but sufficient description of the page.', url: 'https://x.io' });
seo.adapters.sitemap({ urls: [{ loc: 'https://x.io/', changefreq: 'daily', priority: 1 }] });
seo.adapters.audit({ html: '<html><body><p>no title</p></body></html>' }); // → score/issues flag missing title
seo.adapters.jsonLd({ type: 'Article', data: { title: 'T', author: 'A', datePublished: '2026-01-01' } });
```

## DRY boundaries

- **Fetching stays in `scraping`.** This pack is offline-only.
- **`slugify`** also exists in `a-transformation` (`string.slugify`). `seo.slugify` is a convenience
  so an SEO caller need not cross packs; for heavy transliteration use `a-transformation`.


---
Source: shared/engines/adapters/domain/seo/README.md
Canonical: https://docs.leumas.tech/p/adapters/domain/seo
