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

@canonmsg/rich-cards

v0.8.1

Published

Canon rich runtime card schema, builders, CLI, examples, and bundled agent skill

Readme

Canon Rich Cards

Schema, strict authoring validation, examples, bundled Skill, and canon-card CLI for canon.card.v1 runtime cards.

Canon rich cards are declarative JSON documents that Canon renders natively in chat. They are for structured reports, summaries, tables, charts, action buttons, and small action-bound forms. Canon renders and routes responses; the runtime decides what each action means and enforces the result.

Install

npm install @canonmsg/rich-cards @canonmsg/core

The package also exposes the canon-card CLI:

canon-card validate examples/report.card.json
canon-card preview examples/report.card.json
canon-card request <conversationId> examples/report.card.json --wait

Run canon-card setup to install the bundled canon-rich-cards Codex Skill into $CODEX_HOME/skills.

Card shape

{
  "schema": "canon.card.v1",
  "title": "Review draft",
  "fallbackText": "Review draft: approve or request changes.",
  "blocks": [
    { "kind": "summary", "text": "The runtime prepared a draft and needs a decision." },
    {
      "kind": "actions",
      "actions": [
        { "id": "approve", "label": "Approve", "tone": "positive" },
        {
          "id": "revise",
          "label": "Request changes",
          "fields": [
            { "id": "note", "label": "What should change?", "type": "textarea", "required": true }
          ]
        }
      ]
    }
  ]
}

Supported block kinds: summary, metricGrid, chart, table, list, callout, mediaPreview, details, and actions.

Supported action field types: text, textarea, select, multiSelect, boolean, date, number, currency, searchSelect, and lineItems.

Authoring limits enforced by this package include 24 blocks, 12 total actions, 8 fields per action, 12 choices per field (100 for searchSelect), 32 rows per details block, 8 columns and 50 submitted rows per lineItems field, and one actions block per card. The backend accepts a more lenient compatibility envelope, but strict validation is the developer-facing contract for predictable rendering.

Document review and richer fields

Two display-only block kinds and five extended field types support inline document review (the AP invoice-review pattern, but the primitives are generic):

| Block kind | Use | |---|---| | mediaPreview | Inline image, or a PDF/file thumbnail plus a safe "open" link. media.url and media.thumbnailUrl MUST be Canon Storage URLs — upload the document with POST /media/upload first and use the returned URL. media.mimeType must be image/* or application/pdf (allowlist enforced server-side). Always set the block fallbackText (and the card-level fallbackText) so clients without the block still get the document name and open link. media also accepts fileName, page (1-based), and pageCount. | | details | Read-only label/value rows for extracted fields. Each row may carry tone, confidence (0–1), and hint (e.g. "OCR low confidence"). Up to 32 rows. |

| Field type | Value shape | Constraints | |---|---|---| | date | YYYY-MM-DD string | min/max (also YYYY-MM-DD strings) | | number | finite number | min, max, step, precision (max decimal places) | | currency | finite number | currencyCode (ISO-4217 display metadata), precision (default 2), min, max | | searchSelect | choice value string | filterable single-select; same choices as select, cap raised to 100; degrades to select on older clients | | lineItems | [{ colId: cell, ... }, ...] | columns (each text/number/currency/date/select, with required and per-type constraints), minRows, maxRows (cap 50, up to 8 columns) |

A required correction/rejection reason is not a new field type — use a textarea field with required: true on the reject/correct action.

These primitives are additive. On clients that do not yet recognize them, an unknown block degrades to its fallbackText and an unknown field type degrades to a plain text input; the approve/reject path never depends on a new block or field, so it always works. See references/examples.md in the bundled skill for a full worked AP invoice-review card and a small example per block/field.

Builder methods

The fluent card(...) builder exposes .mediaPreview(media, { title?, fallbackText? }) and .details(rows, title?) alongside the existing .summary(), .metricGrid(), .chart(), .table(), .list(), .callout(), and .actions():

import { card } from '@canonmsg/rich-cards';

const built = card('Invoice 4471 — review', 'Invoice 4471 from Acme — open the PDF to review.')
  .mediaPreview(
    {
      kind: 'file',
      mimeType: 'application/pdf',
      url: canonStorageUrl,        // from POST /media/upload
      thumbnailUrl: thumbStorageUrl,
      fileName: 'ACME-4471.pdf',
    },
    { title: 'Source invoice', fallbackText: 'ACME-4471.pdf — open to review.' },
  )
  .details([
    { label: 'Supplier', value: 'Acme Ltd', confidence: 0.98 },
    { label: 'Total', value: '$12,480.00', tone: 'warning', confidence: 0.71, hint: 'OCR low confidence' },
  ], 'Extracted fields')
  .actions([
    { id: 'approve', label: 'Approve', tone: 'positive' },
    {
      id: 'correct',
      label: 'Submit corrections',
      fields: [
        { id: 'total', label: 'Total', type: 'currency', currencyCode: 'USD', min: 0 },
        {
          id: 'lines',
          label: 'Line items',
          type: 'lineItems',
          columns: [
            { id: 'desc', label: 'Description', type: 'text', required: true },
            { id: 'qty', label: 'Qty', type: 'number', min: 0 },
            { id: 'amount', label: 'Amount', type: 'currency', currencyCode: 'USD' },
          ],
        },
        { id: 'note', label: 'Reason for correction', type: 'textarea', required: true },
      ],
    },
    { id: 'reject', label: 'Reject', tone: 'danger', fields: [{ id: 'reason', label: 'Reason', type: 'textarea', required: true }] },
  ], 'Decision')
  .build(); // strictly validated; throws on an invalid card

The backend also enforces runtime boundaries such as card size, responder identity, expiry, JSON safety, and pending-state consumption. Do not put secrets, tokens, passwords, or hidden raw tool output in cards.

SDK example

import { CanonAgent } from '@canonmsg/agent-sdk';

const agent = new CanonAgent({ apiKey: process.env.CANON_API_KEY! });

agent.on('message', async ({ requestCard, replyFinal }) => {
  const result = await requestCard({
    card: {
      schema: 'canon.card.v1',
      title: 'Review draft',
      fallbackText: 'Review draft: approve or request changes.',
      blocks: [
        { kind: 'summary', text: 'The runtime prepared a draft and needs a decision.' },
        {
          kind: 'actions',
          actions: [
            { id: 'approve', label: 'Approve', tone: 'positive' },
            {
              id: 'revise',
              label: 'Request changes',
              fields: [
                { id: 'note', label: 'What should change?', type: 'textarea', required: true },
              ],
            },
          ],
        },
      ],
    },
  });

  if (result.status === 'submitted' && result.actionId === 'revise') {
    await replyFinal(`Revision requested: ${String(result.values?.note ?? '')}`);
    return;
  }

  await replyFinal(result.status === 'submitted' ? 'Approved.' : 'No decision received.');
});

Raw REST / CLI flow

Raw REST/SSE runtimes use Canon's runtime-card endpoints:

  1. Validate the card locally with canon-card validate card.json.
  2. Send it with POST /runtime-card/request.
  3. Poll or consume the answer with POST /runtime-card/consume.
  4. Switch on actionId and read action-field values.

The CLI wraps that flow when Canon agent credentials are available:

canon-card request <conversationId> card.json --wait

Action-less cards are display-only. There is no pending response to consume.

Cards vs runtime input vs approval

  • Use rich cards for structured display, action buttons, and small action-bound forms. Responses return { actionId, values }.
  • Use runtime input for standalone clarification, sudo, and secret prompts. Structured question responses return answers.
  • Use runtime approval for allow/deny decisions before a tool or action.

Card values are ephemeral runtime-control data. They are not written into visible chat text or card metadata. If a value is truly secret, use the runtime-input secret path instead of an action field.