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.10.7

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 is a dependency of this package and is installed with it.

The package also exposes the canon-card CLI, with six commands:

canon-card schema                                          # print the canon.card.v1 schema summary
canon-card validate examples/report.card.json
canon-card preview examples/report.card.json               # --format ansi|markdown|json
canon-card send <conversationId> examples/report.card.json # display card, no wait
canon-card request <conversationId> examples/report.card.json --wait
canon-card setup                                           # install the bundled Skill

send and request resolve Canon agent credentials from a registered agent profile, or from CANON_API_KEY — in which case CANON_ENVIRONMENT_ID is also required:

export CANON_ENVIRONMENT_ID=canon-prod-v1   # or canon-dev-v1
export CANON_API_KEY=...

Both commands hard-code a five-minute card expiry; there is no flag to change it. canon-card setup installs the bundled canon-rich-cards Codex Skill into $CODEX_HOME/skills.

Codex plugin

The published package is also an OpenAI-standard Codex plugin. Install the package as a plugin source to load the canonical canon-rich-cards skill; its manifest is .codex-plugin/plugin.json. The plugin adds no duplicate validator or MCP server: validation remains in this package and a Canon-connected Codex host supplies the context-bound codex_app.canon_runtime_control tool.

Within that host, use send_card for an actionless result and request_card when a human response is required. The active conversation and authenticated responder come from Canon rather than model arguments.

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.

A clean canon-card validate does not guarantee the backend will accept the card. Two rules are enforced server-side only: a 32 KiB cap on the whole serialized card, and the mediaPreview URL/MIME allowlist below (the strict validator only checks that URLs are strings of at most 2048 characters).

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 each be the exact url returned by POST /media/upload or resumable finalization — the server pins them to this deployment's own Storage bucket, so another Firebase project's bucket (or any external URL) makes the backend reject the whole card with a 400. Preserve uploadId and sizeBytes for resumable primary media, and thumbnailUploadId for a separately uploaded resumable thumbnail, so Canon can retain the temporary objects with the durable card. Anchor pre-validation on provenance, not on a hostname list. 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: uploadedUrl,            // verbatim from POST /media/upload
      thumbnailUrl: uploadedThumbnailUrl,
      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

@canonmsg/agent-sdk does not depend on this package and performs no card validation — it forwards the document to Canon as-is. Validate with validateCard or the card() builder here, then hand the result to the SDK.

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

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

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 unified interaction endpoints:

  1. Validate the card locally with canon-card validate card.json.
  2. Send it with POST /runtime-interaction/request and interactionKind: "card".
  3. Poll or consume the answer with POST /runtime-interaction/consume and the same interactionKind.
  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 card: waits for the decision
canon-card send <conversationId> card.json             # display card: posts and returns

Action-less cards are display-only — there is no pending response to consume. send is the direct path for them; request --wait also handles them, short-circuiting to { "status": "displayed" } instead of waiting.

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.