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

@peristyle/grocery-cart-widget

v0.6.1

Published

Chat, recipe search, and one-click ingredient ordering (Kroger/Walmart via Peristyle) for EmDash-powered food blogs

Downloads

866

Readme

@peristyle/grocery-cart-widget

An EmDash CMS plugin that puts a grocery concierge on every page of a food blog: readers chat, search the blog's recipes, and order the ingredients at Kroger or Walmart — powered by the Peristyle Grocery Cart API.

Iframe architecture (0.5.0): the plugin injects a ~1.4KB embed script that mounts a cross-origin <iframe> pointed at the Peristyle-served /widget page. Everything sensitive — the reader's pk_ key, chat (including pantry-aware AI replies for signed-in readers), match data — lives inside the iframe on the Peristyle origin, where host-page scripts, analytics, and ads cannot reach it (the same isolation model claude.ai uses for artifacts and MCP-app widgets). The widget page is CSP-hardened: inline scripts hashed, connect-src 'self', and frame-ancestors locked to the creator's verified domains when a widget token is present. The blog's brand colors are read from its CSS custom properties and passed through sanitized URL params, so the widget still matches the site. The embed script is the only code that runs in the page, which makes any future CMS integration (e.g. WordPress) trivial.

After changing widget UI code, run pnpm build && pnpm run sync:api to refresh the bundle the API serves (peristyle-grocery-list/src/peristyle_grocery_cart/assets/widget.js).

What readers get

  • A floating Grocery chat launcher on every page
  • Chat — Peristyle's concierge, grounded on the blog's recipe index (never invents recipes). With a widget token configured, search and chat are scoped to this blog's recipes and the assistant speaks as this blog's concierge. AI vs. deterministic-search mode is decided server-side by Peristyle — no LLM key in the plugin.
  • Recipe cards with a Shop button per result
  • Kroger — one-time account connect (OAuth via Peristyle; the reader's pk_… key lives only in their browser, layered across localStorage → sessionStorage → memory so sign-in survives privacy modes as long as the browser allows), ingredient → product matching, server-side cart add
  • A compact Sign in / ● Signed in chip in the panel header — always-visible status, tap to sign in anytime mid-session (it joins an already-open sign-in card rather than duplicating it) or, when signed in, to see status and sign out. Lives in the existing header bar, so chat space is untouched, including on mobile
  • Walmart — no sign-in; matching plus an add-to-cart checkout link
  • A reviewable match list before anything is added, in one of two tiers:
    • Simple list (default) — untick what you have, see prices, add
    • Full review — the Peristyle Grocery Cart MCP app experience, ported to the blog: product photos, sale/stock badges, quantity steppers (minus at 1 removes a line), alternative-product swaps, a full-recipe reference section with one-tap add-back, a running estimated total, a proper cart confirmation panel (including an "Already added" state when the server dedupes a repeat add), and — for readers who opted into the Peristyle pantry — a one-tap post-checkout purchase confirmation that keeps their pantry current

Install

pnpm add @peristyle/grocery-cart-widget
// astro.config.mjs
import emdash from "emdash/astro";
import { groceryCartWidgetPlugin } from "@peristyle/grocery-cart-widget";

export default defineConfig({
  integrations: [
    emdash({
      plugins: [groceryCartWidgetPlugin()], // or sandboxed: [groceryCartWidgetPlugin()]
    }),
  ],
});

The widget is inlined into pages via the page:fragments hook. Configure under Plugins → Grocery List in the admin:

| Setting | Default | Notes | | ----------------------- | -------------------------- | ---------------------------------------------------------------------- | | Show the grocery widget | on | | | Peristyle widget token | — | wt_… from your Peristyle creator dashboard (GET /v1/creator/widget-token); scopes search + chat to your recipes and attributes widget traffic to you | | Default store | Kroger | Readers can switch in the widget | | Cart review experience | Simple list | "Full review" enables the MCP-app-style match review + pantry loop | | Peristyle API base URL | https://api.peristyle.io | Point at a staging API if needed |

For matching to work end-to-end, the blog's recipes should be indexed in Peristyle (see peristyle.io partner onboarding).

Capabilities & privacy

  • Declares only hooks.page-fragments:register — the plugin makes no server-side network calls
  • The reader's browser calls the Peristyle API directly: match routes return the full normalized shape (suggested product + alternatives, regular/promo prices, stock, pantry-staple flags); cart-add items carry optional description / ingredient_name / price context so pantry confirmations and order-value analytics stay readable upstream
  • The reader's Kroger key is issued by the Peristyle connect flow directly to their browser and sent per-request as a bearer; the plugin stores no reader credentials and no LLM keys anywhere

Development

pnpm install
pnpm build       # builds the widget IIFE first, then the plugin (see tsup configs)
pnpm typecheck
pnpm test        # drives the built IIFE in jsdom against a stubbed Peristyle API, both tiers

The widget (src/widget.ts) is compiled to dist/widget.iife.js and inlined as a string into the sandbox entry — no CDN request, always the same version as the installed package.

Server-side counterparts live in the peristyle-grocery-list repo: /v1/concierge/chat (chat orchestration), widget tokens (/v1/creator/widget-token), creator-scoped /v1/recipes/search, and CORS for browser-direct calls.

Releasing

  1. Bump "version" in package.json (single source of truth; the build injects it into the descriptor).
  2. npm publish (runs typecheck + fresh build + tests via prepublishOnly).
  3. Commit, tag, push; bump consumers (e.g. food-blog-base).