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

@aeptus/aexeo-emdash

v0.8.17

Published

Aexeo SEO/GEO content evaluator as an emdash plugin

Readme

@aeptus/aexeo-emdash

Aexeo SEO/GEO content evaluator as an emdash plugin.

Adds a Findings admin page, per-document panel, and dashboard intelligence-score widget to an emdash site. Saves auto-evaluate the changed document; the Refresh button re-evaluates the whole site.

Install

npm install @aeptus/aexeo-emdash vite-plugin-wasm
// astro.config.mjs
import { aexeoPlugin } from "@aeptus/aexeo-emdash";
import wasm from "vite-plugin-wasm";

export default defineConfig({
  vite: {
    plugins: [wasm()],
    optimizeDeps: { exclude: ["@aeptus/aexeo-emdash"] },
  },
  integrations: [
    emdash({
      database: d1({ binding: "DB" }),
      storage: r2({ binding: "MEDIA" }),
      plugins: [aexeoPlugin()],
    }),
  ],
});

That's it. No environment variables, no auth tokens, no admin Setup page, no separate sidecar to deploy.

For the full install runbook, sandboxed-mode alternative, and troubleshooting list, see INSTALL.md.

What it does

  • Findings page at /_emdash/admin/plugins/aexeo-emdash/findings: rule violations across the site, filterable by severity.
  • Document panel at /document: findings scoped to one document.
  • Dashboard widget: top-line intelligence score (citation, truth, answer-pack, external-trust dimensions). Banner appears below 60.
  • Refresh button: re-evaluate every document in the configured collections (posts, pages by default).
  • Auto-evaluate on save: emdash's content:afterSave hook fires, re-evaluates the saved document, updates the findings table.

Compatibility

  • emdash >= 0.7.0 (peer dep)
  • Cloudflare Workers via @astrojs/cloudflare. The plugin uses Cloudflare-specific APIs and silently no-ops on the Node adapter.
  • WASM eval ships with the package; the consumer's Vite/Wrangler bundler handles compilation at build time. vite-plugin-wasm is a hard requirement on the consumer side.

Configuring collections

The plugin sweeps ["posts", "pages"] by default — the slugs the @emdash-cms/template-blog-cloudflare template ships with. Three knobs to override, in precedence order:

// Full override — replace the default entirely.
// Use when your schema has none of the default slugs.
aexeoPlugin({ collections: ["blog_posts", "pages"] })

// Extend the default — add slugs alongside posts + pages.
aexeoPlugin({ includeCollections: ["guides", "faqs"] })

// Subtract from the default — remove a slug.
aexeoPlugin({ excludeCollections: ["posts"] })

// Combine include + exclude (subtract first, then add).
// `collections` ignores both — it's the explicit-list override.
aexeoPlugin({
  excludeCollections: ["posts"],
  includeCollections: ["blog_posts", "guides"],
})

Pointing any of these at a slug that doesn't exist is non-fatal: the bridge's content.list returns empty and the missing collection shows up in the Refresh summary's errors field.

Suppressing findings

When a finding is intentional (legal pages with non-standard SEO, localized routes where a rule doesn't apply, draft documents whose findings shouldn't surface yet), silence it with a suppression rule. Suppressions are applied before findings are persisted to KV — suppressed findings never reach the dashboard, /findings, or the per-document panel.

aexeoPlugin({
  suppressions: [
    // route + rule
    { routePattern: "/privacy", ruleIds: ["RULE001"] },

    // recursive route subtree
    { routePattern: "/fr-fr/**", ruleIds: ["RULE002"] },

    // applies to every route
    { ruleIds: ["RULE999"] },

    // by collection
    { collections: ["drafts"], ruleIds: ["RULE042"] },

    // by document status
    { statuses: ["draft"] }, // silence ALL findings on drafts
  ],
})

Glob syntax for routePattern

| Pattern | Matches | |---|---| | * | any chars except / | | ** | any chars including / (recursive) | | ? | exactly one char except / |

Patterns are anchored. /about matches only /about, not /about/team — use /about/** if you want a subtree.

Migrating from a local patch: if your previous setup used looser patterns (e.g. one-asterisk-means-everything), update them on bump. * is intentionally single-segment so editors can scope rules to a level of the path hierarchy without recursive surprises.

Selector semantics

A rule may set any combination of routePattern, ruleIds, collections, and statuses. Matching is AND across selectors, OR across rules: a finding is suppressed when at least one rule matches it; a rule matches when every selector it sets matches the finding's context.

Empty rules ({} with no selectors) are rejected at plugin construction. That's a kill-switch for all findings everywhere and almost certainly an editor mistake.

collections and statuses only meaningful for per-document findings. Sitewide findings (cross-document audit issues) are matched only by routePattern and ruleIds.

Suppressions are plugin-only by design. The CLI's check is the canonical strict audit; if you want to silence findings at the CLI layer, use aexeo.toml's [ignore] block instead.

Two factories

The package exports two factories for different trust contexts:

  • aexeoPlugin()configured mode (recommended). In-process plugin. Use for first-party emdash sites.
  • aexeoPluginSandboxed({ evaluatorHost })sandboxed mode. Requires a separately-deployed sidecar Worker. Use only when shipping to third-party emdash sites that shouldn't trust the plugin code with full host access.

See INSTALL.md for the sandboxed setup runbook.

License

MIT — see LICENSE.