{"_id":"@12signals/html-distill","_rev":"3-c82be25a0e022b10357e8c250b5cb628","name":"@12signals/html-distill","dist-tags":{"latest":"0.1.2"},"versions":{"0.1.0":{"name":"@12signals/html-distill","version":"0.1.0","keywords":["html","html-cleaner","clean-html","llm","ai","prompt","web-scraping","scraping","content-extraction","readability","sanitize","rehype","unified","landing-page","markdown","preprocessing"],"author":{"name":"12signals"},"license":"MIT","_id":"@12signals/html-distill@0.1.0","maintainers":[{"name":"mellors","email":"info@mellors.de"}],"homepage":"https://github.com/12signals/html-distill#readme","bugs":{"url":"https://github.com/12signals/html-distill/issues"},"dist":{"shasum":"34b3dabe3a7983d8e6e2bc584ed7f197d7f88109","tarball":"https://registry.npmjs.org/@12signals/html-distill/-/html-distill-0.1.0.tgz","fileCount":11,"integrity":"sha512-WSPyFYz7c1FnaQhxhHxnqBlhDZv3SFwSZj0DDWjfHGVglOpzAJT3/UuZwsSjEdmgJhdnip2LQvHYvesDZCLMXg==","signatures":[{"sig":"MEUCIExFM38jfhFe5T+W3tc1AtRRg1ovzH5UEPnt/JhcvONNAiEAuBwnzsV184nKJTmBzLUlMEM4wGeX4dgezqlmU8LOiA0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":61392},"main":"./src/index.js","type":"module","types":"./types/index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./types/index.d.ts","default":"./src/index.js"}},"gitHead":"60bd9f4b5a406d3d0217c65e9723746ed57b6082","scripts":{"lint":"eslint src test","test":"vitest run","test:watch":"vitest","build:types":"tsc -p tsconfig.json","prepublishOnly":"npm run test && npm run build:types"},"_npmUser":{"name":"mellors","email":"info@mellors.de"},"repository":{"url":"git+https://github.com/12signals/html-distill.git","type":"git"},"_npmVersion":"10.9.4","description":"Structure-preserving HTML normalizer for LLM input. Turns real-world marketing pages, landing pages and SPA output into stable, semantically-hinted HTML — without throwing away the boilerplate that carries the signal.","directories":{},"sideEffects":false,"_nodeVersion":"22.22.0","dependencies":{"unified":"^11.0.5","rehype-parse":"^9.0.0","rehype-format":"^5.0.0","rehype-sanitize":"^6.0.0","rehype-stringify":"^10.0.0","hast-util-sanitize":"^5.0.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.1.0","typescript":"^5.6.0"},"_npmOperationalInternal":{"tmp":"tmp/html-distill_0.1.0_1785479170045_0.34037254787957316","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@12signals/html-distill","version":"0.1.1","keywords":["html","html-cleaner","clean-html","llm","ai","prompt","web-scraping","scraping","content-extraction","readability","sanitize","rehype","unified","landing-page","markdown","preprocessing"],"author":{"name":"12signals"},"license":"MIT","_id":"@12signals/html-distill@0.1.1","maintainers":[{"name":"mellors","email":"info@mellors.de"}],"homepage":"https://github.com/12signals/html-distill#readme","bugs":{"url":"https://github.com/12signals/html-distill/issues"},"dist":{"shasum":"8bd0d8945041418af40d6cd8be60ecf65c95f1b4","tarball":"https://registry.npmjs.org/@12signals/html-distill/-/html-distill-0.1.1.tgz","fileCount":11,"integrity":"sha512-/BMZss0tevN4AN/eG/hGxT2XogMIhVrQam4Gev2v8rMoke1tTNofyXrr8srI0z62TGfkvg9FoQ5iEjnKiLpcqg==","signatures":[{"sig":"MEUCIFvqcK1Gg5Ul7JR7fMwBcSOEE1tUIrwMo/4JQzzvylEPAiEA6VeOXwQ7QFNygPRQISENPyCxsCcNjqtB/ljjLIDzE1Y=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@12signals%2fhtml-distill@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":57402},"main":"./src/index.js","type":"module","types":"./types/index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./types/index.d.ts","default":"./src/index.js"}},"gitHead":"13b8facd8a12b66716182277934b5935f3ceee39","scripts":{"lint":"eslint src test","test":"vitest run","test:watch":"vitest","build:types":"tsc -p tsconfig.json","prepublishOnly":"npm run test && npm run build:types"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:9a229b1d-0960-4c4c-8e12-5816573cd3ea"}},"repository":{"url":"git+https://github.com/12signals/html-distill.git","type":"git"},"_npmVersion":"11.16.0","description":"Structure-preserving HTML normalizer for LLM input. Turns real-world marketing pages, landing pages and SPA output into stable, semantically-hinted HTML — without throwing away the boilerplate that carries the signal.","directories":{},"sideEffects":false,"_nodeVersion":"24.18.0","dependencies":{"unified":"^11.0.5","rehype-parse":"^9.0.0","rehype-format":"^5.0.0","rehype-sanitize":"^6.0.0","rehype-stringify":"^10.0.0","hast-util-sanitize":"^5.0.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^2.1.0","typescript":"^5.6.0"},"_npmOperationalInternal":{"tmp":"tmp/html-distill_0.1.1_1785566584855_0.37086825661389144","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@12signals/html-distill","version":"0.1.2","description":"Structure-preserving HTML normalizer for LLM input. Turns real-world marketing pages, landing pages and SPA output into stable, semantically-hinted HTML — without throwing away the boilerplate that carries the signal.","type":"module","exports":{".":{"types":"./types/index.d.ts","default":"./src/index.js"}},"main":"./src/index.js","types":"./types/index.d.ts","sideEffects":false,"publishConfig":{"access":"public"},"scripts":{"test":"vitest run","test:watch":"vitest","build:types":"tsc -p tsconfig.json","lint":"eslint src test","prepublishOnly":"npm run test && npm run build:types"},"keywords":["html","html-cleaner","clean-html","llm","ai","prompt","web-scraping","scraping","content-extraction","readability","sanitize","rehype","unified","landing-page","markdown","preprocessing"],"author":{"name":"12signals"},"license":"MIT","homepage":"https://github.com/12signals/html-distill#readme","repository":{"type":"git","url":"git+https://github.com/12signals/html-distill.git"},"bugs":{"url":"https://github.com/12signals/html-distill/issues"},"engines":{"node":">=18"},"dependencies":{"hast-util-sanitize":"^5.0.1","rehype-format":"^5.0.0","rehype-parse":"^9.0.0","rehype-sanitize":"^6.0.0","rehype-stringify":"^10.0.0","unified":"^11.0.5"},"devDependencies":{"typescript":"^5.6.0","vitest":"^2.1.0"},"gitHead":"1642107b759fe51eaa826d25af395cbd336db51f","_id":"@12signals/html-distill@0.1.2","_nodeVersion":"24.18.0","_npmVersion":"11.16.0","dist":{"integrity":"sha512-UjlPfpXHG/kAD91sIityRs689IF7pRbafr8OIbF+B53VSIPNv0sBcBGGayGqfR97asGfU5PeNjYOurtD2niRnA==","shasum":"3d172c34e9d1c083e135292f551a45c28143dfae","tarball":"https://registry.npmjs.org/@12signals/html-distill/-/html-distill-0.1.2.tgz","fileCount":11,"unpackedSize":59088,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@12signals%2fhtml-distill@0.1.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQD8NKrANd7IQMlMa65Jm4mK7dmXAvFoX2JTXsB3P9SG+gIgJXxcrJr+fPCXmi6rTAkujkJdwlHca+M4sgFs3qkLXpo="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:9a229b1d-0960-4c4c-8e12-5816573cd3ea"}},"directories":{},"maintainers":[{"name":"mellors","email":"info@mellors.de"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/html-distill_0.1.2_1786205668790_0.053892439773663314"},"_hasShrinkwrap":false}},"time":{"created":"2026-07-31T06:26:09.852Z","modified":"2026-08-08T16:14:29.288Z","0.1.0":"2026-07-31T06:26:10.186Z","0.1.1":"2026-08-01T06:43:05.006Z","0.1.2":"2026-08-08T16:14:28.937Z"},"bugs":{"url":"https://github.com/12signals/html-distill/issues"},"author":{"name":"12signals"},"license":"MIT","homepage":"https://github.com/12signals/html-distill#readme","keywords":["html","html-cleaner","clean-html","llm","ai","prompt","web-scraping","scraping","content-extraction","readability","sanitize","rehype","unified","landing-page","markdown","preprocessing"],"repository":{"type":"git","url":"git+https://github.com/12signals/html-distill.git"},"description":"Structure-preserving HTML normalizer for LLM input. Turns real-world marketing pages, landing pages and SPA output into stable, semantically-hinted HTML — without throwing away the boilerplate that carries the signal.","maintainers":[{"name":"mellors","email":"info@mellors.de"}],"readme":"# html-distill\n\n[![npm](https://img.shields.io/npm/v/@12signals/html-distill.svg)](https://www.npmjs.com/package/@12signals/html-distill)\n[![install size](https://packagephobia.com/badge?p=@12signals/html-distill)](https://packagephobia.com/result?p=@12signals/html-distill)\n[![license](https://img.shields.io/npm/l/@12signals/html-distill.svg)](./LICENSE)\n\nClean real-world HTML into stable, LLM-ready markup.\n\nReadability-style extractors hunt for an \"article\" and throw the rest away. On\nlanding pages, marketing sites and SPAs, the rest (the hero, the pricing, the\nCTAs, the nav) is exactly what you want. html-distill keeps the whole page and\nstrips the noise instead: cookie banners, hidden nodes, framework cruft and\ntracking params go, and a few semantic hints go in so an LLM can tell a headline\nfrom a button.\n\nBuilt at [12signals](https://12signals.com), where it cleans thousands of\ncompetitor homepages, career pages and product pages for LLM analysis every day.\n\n```bash\nnpm install @12signals/html-distill\n```\n\n```js\nimport { cleanHtml } from '@12signals/html-distill';\n\nconst clean = await cleanHtml(rawHtml);\n```\n\n```html\n<!-- in -->\n<div class=\"cookie-banner\">We value your privacy…</div>\n<section><div><div>\n  <h1 class=\"text-6xl font-bold tracking-tight\">Ship faster</h1>\n  <a class=\"btn\" href=\"/pricing?utm_source=hn&page=2\">See plans</a>\n</div></div></section>\n\n<!-- out -->\n<section>\n  <h1 class=\"hero\">Ship faster</h1>\n  <a class=\"btn\" href=\"/pricing?page=2\">See plans</a>\n</section>\n```\n\n## Why\n\n- **Whole page, not an article.** Nothing is discarded for being \"boilerplate\".\n- **Semantic hints.** Visual hierarchy (big headings, hero sections, category\n  kickers) is normalized into stable `hero` / `kicker` cues that survive\n  sanitization, so the model reads structure the same way across Tailwind,\n  WordPress, Bootstrap, Framer and inline styles.\n- **Noise removed, carefully.** Cookie-consent banners, hidden nodes, embedded\n  SPA JSON and anti-bot-proxy leakage are stripped by rules hardened on real\n  pages (they keep screen-reader content, responsive-shown elements and body\n  copy that merely mentions \"cookies\").\n- **Stable output.** Redundant wrappers collapse and tracking params are\n  dropped, so the same page cleans to the same markup run to run: good for\n  diffing and caching.\n\n## API\n\n### `cleanHtml(html, options?) => Promise<string>`\n\n| option | type | default | |\n|--------|------|---------|--|\n| `policy` | `object` | `defaultPolicy` | Which classes to keep and how hero/kicker hints are detected. |\n| `linkParams` | `string[]` | `defaultLinkParams` | Query params kept on `<a href>`. |\n| `removeHeadArtifacts` | `boolean` | `true` | Drop document-head elements (`title`/`meta`) that fragment-parsing leaves in the body. |\n| `schema` | `object` | built-in | rehype-sanitize schema (advanced). |\n\n```js\nimport { cleanHtml, defaultPolicy } from '@12signals/html-distill';\n\nawait cleanHtml(rawHtml, {\n  policy: { ...defaultPolicy, allowedClassNames: [...defaultPolicy.allowedClassNames, 'newsletter'] },\n  linkParams: ['page', 'q', 'ref'],\n});\n```\n\n### Composable transforms\n\nEvery pipeline stage is also exported as a [unified](https://unifiedjs.com/) /\n[rehype](https://github.com/rehypejs/rehype) plugin, so you can build your own\npipeline (e.g. keep the hero detection, skip the cookie-banner removal). See\n[`src/transforms.js`](./src/transforms.js) for the full list and per-stage docs.\n\n```js\nimport { detectHeroFromAttributesAndStyles, filterClassNames } from '@12signals/html-distill';\n```\n\n## Markdown\n\nhtml-distill returns HTML. Pipe it through any HTML-to-Markdown converter for a\nMarkdown prompt:\n\n```js\nimport { cleanHtml } from '@12signals/html-distill';\nimport { htmlToText } from 'html-to-text';\n\nconst md = htmlToText(await cleanHtml(rawHtml));\n```\n\n## License\n\nMIT © [12signals](https://12signals.com)\n","readmeFilename":"README.md"}