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

@callimacus/thamyr-liquid

v4.8.2

Published

Headless Callimacus runtime for Shopify Liquid themes

Readme

@callimacus/thamyr-liquid

Headless Callimacus runtime for Shopify Liquid themes (Online Store 2.0).

The runtime owns transport, round lifecycle, block dispatch, error containment, and the commerce plumbing (currency, URLs, native AJAX cart). The theme owns every element, every class name, all CSS, and all customer-facing copy. The runtime creates no DOM of its own and ships no CSS.

Theme usage (no build step)

Load the self-contained browser bundle — it auto-mounts every [data-callimacus] element and re-mounts on shopify:section:load:

<script src="https://cdn.jsdelivr.net/npm/@callimacus/thamyr-liquid@4/dist/browser/callimacus-runtime.min.js" defer></script>

The floating @4 picks up minor and patch releases automatically. If you prefer integrity over auto-updates, pin an exact version and add Subresource Integrity (integrity="sha384-…" crossorigin="anonymous") — jsdelivr shows the hash on the file listing. Or skip the CDN entirely: download the file into your theme as assets/callimacus-runtime.min.js and load it with {{ 'callimacus-runtime.min.js' | asset_url }} — first-party hosting, no external script at all (required for Theme Store-listed themes).

Register your renderers from your own file — one entry per block type:

window.callimacusRenderers = window.callimacusRenderers || [];
window.callimacusRenderers.push({
  type: 'custom:giftGuide',
  render(block, ctx) {
    const picks = ctx.picks(block);
    if (!picks.length) return null;
    const grid = document.createElement('ul');
    grid.className = 'gift-guide__grid'; // your class, your CSS
    for (const item of picks) grid.append(card(item, ctx));
    return grid;
  },
});

The full walkthrough — mount-point markup, section settings, renderer contract, and the ctx helpers — lives at docs.callimacus.ai.

Rounds without a message

Not everything a storefront sends is a typed question. The handle on the mount element (element.callimacus) carries the TypeScript SDK's calls, with the same names and options, built by the same helpers:

  • ask(text) — a typed question.
  • sendInteraction(id, {value, payload, block}) — one of the interactions the project declared in its configuration document: a click, a product viewed.
  • openLanding(value?) — the project's custom landing: a round with no message.

The mount supplies the page's language and the visitor's story; pass language or storyId to override them for one call. Every round runs alike — the same data-callimacus-state, the same events, one at a time.

A section opens with one round, declared in its markup:

<div data-callimacus data-client-id="…"
  data-interaction="productDetail"
  data-interaction-value="{{ product.title | escape }}"
  data-interaction-payload='{{ product_payload | json | escape }}'>

data-interaction (with optional data-interaction-value and a JSON object in data-interaction-payload) sends a declared interaction; data-landing opens the custom landing (data-landing="pdp" sends pdp as its value); data-prompt asks a question. Name several and the first in that order wins, with a warning.

Suggestions

element.callimacus.suggest(prefix) resolves with {suggestions}: the project's starter questions on an empty or short prefix, autocomplete while the visitor types — the TypeScript SDK's suggest. It opens no round and changes no state, so it can run beside one; it connects first when the mount has not yet. No connection, a timeout (timeoutMs, 4 s by default) or an error all resolve to no suggestions. Suggestions come back only when the project has them turned on (Thamyr › Configuration › Suggestions).

input.addEventListener('input', async () => {
  const {suggestions} = await root.callimacus.suggest(input.value);
  renderChips(suggestions); // yours
});

Catalogue lookups without a round

Some answers are plain catalogue lookups, not rounds: nothing to compose, nothing to generate. ctx.similar(item, {topK}) and ctx.related(item) — the TypeScript SDK's skesis.similar / skesis.related — read the catalogue directly, in a few hundred milliseconds, and hand items back shaped like ctx.picks, so the same card renderer draws them:

const others = await ctx.similar(item, {topK: 12});
const sameModel = others.filter(other => other.fields.title === item.fields.title);

ctx.related(item) resolves with {related, siblingColors, usedFallback}: what the item references, what references it, and its other colours when the catalogue links them. Both take an item or its id, and read the catalogue in the locale the storefront's route names (/it-it/ reads it-IT, /it/ reads it, the root the page's data-language); pass locale to choose another. They reject with a CallimacusError — lookup_unreachable when the request failed or ran out of time (timeoutMs, 10 s by default), server_<status> when Callimacus refused it.

On-screen context

data-ui-observer on the mount element makes every round carry what the visitor can see — the catalogue items and blocks in view — which the cognitive flow and on-screen conditions read. It is off by default.

Mark each product element with its item's full id, data-product-id="${item.id}" (shopify:product:gid://…, never the handle or a numeric Shopify id). While observing, the runtime marks each rendered block's root element with data-block-id — the block's own id — unless the renderer set one.

The observer reads every [data-product-id] on the page. If the theme's own markup already uses that attribute, give the observer a selector of its own: data-ui-observer="[data-callimacus-product]".

One session across pages

Every ask on a mount continues the same story, so follow-ups carry the conversation behind them. A storefront reloads on every navigation, though, and a fresh mount opens a fresh story — unless the theme names the one it kept:

  • data-story-id on the mount element is the story its first round continues. Empty means none. A story the server no longer has is replaced by a new one.
  • callimacus:story (bubbling, detail {storyId}) fires whenever the server opens a story. element.callimacus.storyId reads the current one.

Liquid cannot read browser storage, so a few lines of inline script carry it over. Place them after the mount element and before the deferred runtime runs:

<script>
  (function () {
    var key = 'callimacus:story';
    var kept;
    try { kept = sessionStorage.getItem(key); } catch (error) {}
    document.querySelectorAll('[data-callimacus]').forEach(function (element) {
      if (kept && !element.dataset.storyId) element.dataset.storyId = kept;
    });
    document.addEventListener('callimacus:story', function (event) {
      try { sessionStorage.setItem(key, event.detail.storyId); } catch (error) {}
    });
  })();
</script>

To start over — a "new conversation" button — call element.callimacus.resetStory() and forget the kept id (sessionStorage.removeItem('callimacus:story')): the next round opens a new story and announces it. The old story is left as it was.

sessionStorage scopes the conversation to the tab; localStorage would keep it across visits. Both can throw (private windows, blocked storage), hence the try: without them the experience still works, one story per page.

Bundler usage

The ESM entry exports the same runtime without the auto-mount side effect:

import {mountAll} from '@callimacus/thamyr-liquid';

mountAll(document);

License

Proprietary — licensed for use by Callimacus customers only. See LICENSE.md.