npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@sidhxntt/seo-meta

v2.0.0

Published

Interactive CLI that merges SEO / social-meta tags into any HTML file. No AI calls — pure stdin prompts.

Readme

@sidhxntt/seo-meta

Interactive Node.js CLI that merges every SEO / social-meta tag into an existing HTML file. No AI calls. No network. Pure stdin prompts.


Features

| Category | Tags managed | |---|---| | Primary meta | <title>, description, keywords, author, canonical, robots, hreflang, charset, viewport | | Favicons | 16px, 32px, 180px (apple), 192px, 512px, .ico | | Open Graph | All standard OG tags + article:* tags | | Twitter / X Card | All card variants (validated against the four legal values) | | Theme / PWA | theme-color, color-scheme, apple-mobile-web-app-* | | Geo | geo.region, geo.placename | | Performance | preconnect, dns-prefetch | | JSON-LD | Person, Organization, or Article — plus an optional WebSite node | | Scripts | Extra <script src> tags with optional defer |

Everything is escaped (&, <, >, ", ') before it reaches an attribute or text node, and JSON-LD is serialised with < escaped as < so no value can break out of the <script> block.


Requirements


Install

npm install -g @sidhxntt/seo-meta
# or run it without installing
npx @sidhxntt/seo-meta --help

From a clone of this repo:

cd seo_meta
npm install
node src/index.js --help

Usage

# Interactive — prompts for everything
seo-meta

# Skip the file-path prompts (both --flag value and --flag=value work)
seo-meta --input ./index.html --output ./dist/index.html
seo-meta --input=./index.html --output=./dist/index.html

# Preview only: prints the resulting <head> to stdout, writes nothing
seo-meta --input ./index.html --dry-run

# Replay a previous run with zero prompts (CI-friendly)
seo-meta --config ./dist/index.seo-config.json --yes

Options

| Flag | Description | |---|---| | -i, --input <path> | Input HTML file. Validated before any other prompt. | | -o, --output <path> | Output HTML file. Defaults to <name>.seo.html, never the input. | | -c, --config <path> | Load a saved .seo-config.json and use it as prompt defaults. | | -y, --yes | Non-interactive: take every value from --config, skip all prompts. | | --dry-run | Print the resulting <head> to stdout; write nothing. | | -h, --help | Show help. | | -v, --version | Print the version. |

Unknown flags are an error, not silently ignored.


What happens to my HTML?

The <head> is parsed and merged — never replaced.

  1. The document is parsed with node-html-parser (so a <head> mentioned inside a comment or an inline JS string is never mistaken for the real one).
  2. If there is no <head>, one is inserted as the first child of <html> — after <!DOCTYPE html>, inside the document, in the correct place. If there is no <html> either, a <head> is synthesised before <body>.
  3. <meta charset> and <meta name="viewport"> are guaranteed to be present, charset first.
  4. For each tag the tool generates, the matching existing tag is replaced in place (matched on name=, property=, rel= + discriminator, src=, or the tag name for <title>). Anything else — stylesheets, scripts, preloads, custom meta — is preserved byte-for-byte.
  5. Tags with no existing counterpart are appended at the end of <head> under a <!-- SEO meta tags managed by @sidhxntt/seo-meta --> marker.

Re-running the tool on its own output is idempotent: nothing is duplicated.

Safety

  • The default output path is <name>.seo.html — pressing Enter never overwrites your source.
  • Whenever an existing file is about to be overwritten, a .bak copy is written first and the path is printed.
  • --dry-run writes nothing at all — not even the config sidecar.

Saved config

After writing the HTML, a <output-name>.seo-config.json sidecar is written next to the output. Feed it back in to replay the run:

seo-meta --config ./dist/index.seo-config.json --yes

personEmail is never persisted — it is PII, and this file is meant to be committed. The email still lands in the generated JSON-LD if you supply one.


Validation

Values are validated and re-prompted on failure:

  • canonicalUrl, og:url, image URLs, sameAs entries → must be absolute http(s) URLs
  • theme-color → must be a hex colour
  • twitter:card → must be summary, summary_large_image, app, or player
  • og:image:width / height → whole numbers
  • language tags → BCP-47-ish shape
  • description → hard limit 320 chars, soft warning outside the 120–160 sweet spot

Empty values are omitted from the output entirely, so JSON-LD never ships "name": "".

--yes mode reports invalid config values as errors instead of prompting.


Non-TTY environments

If stdin is not a TTY (CI, piped input) and --yes is not set, the tool exits with a clear message instead of hanging on readline.


Prompt reference

📁  File Paths            — input / output HTML paths
🔤  Primary Meta          — title, description, keywords, author, canonical, robots, lang, charset, viewport
📣  Open Graph            — og:* tags + optional article:* tags (case-insensitive og:type match)
𝕏   Twitter / X Card      — twitter:* tags
🎨  Theme & PWA           — theme-color, PWA meta
🌍  Geo / Regional        — geo.region, geo.placename
🖼️  Favicons              — paths for all icon sizes
🧩  JSON-LD               — Person / Organization / Article + optional WebSite
⚡  Performance Hints     — preconnect, dns-prefetch URLs
📦  Extra Scripts         — arbitrary <script src> tags

Sample

sample/input.html is a document with no <head>. Running:

node src/index.js --input sample/input.html --output sample/output.html

produces sample/output.html — a valid document with <head> correctly nested inside <html>, after the doctype — plus sample/output.seo-config.json.


License

MIT