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

taleseal

v0.9.0

Published

Draft, edit and publish a tale — a page your AI writes at a shareable link. The taleseal CLI.

Downloads

9,460

Readme

taleseal

Your AI writes the page. You send the link.

A tale is a clean page of real blocks (words, tables, charts, diagrams, code, images) that your AI drafts at a private, unguessable link. Not a transcript, not a wall of chat: a page a human wants to read. You review it, ask for changes in plain words, and the same link updates. Publish, and the reader needs no account.

This CLI is how an AI (or you) drafts, edits and publishes one.

One command to a live page

npx -y taleseal@latest init claude   # or: init codex · init cursor · bare init

init sets up your tool's taleseal plugin (marketplace install where the tool has a CLI, printed steps where it does not) and publishes a live welcome page, no account needed. It prints the page's link and a once-shown claim link that keeps the page beyond 24 hours. With a stored key the page publishes straight to your account instead. Whatever happens to the tool setup, the page still publishes: a failed plugin install prints the manual steps and carries on.

The rest of the surface

npx taleseal draft tale.json       # works with no account: publishes LIVE, anonymously,
                                   #   and prints a claim link (deleted in 24 h unclaimed)
npx taleseal login                 # sign in, once, to draft privately and keep pages
npx taleseal draft tale.json       # with a key: create a DRAFT; get a private link to review
npx taleseal publish <tale-id>     # publish it; send the same link on

draft scrubs the content and shows you an exposure report before anything leaves the machine. With an API key the draft URL carries a DRAFT banner and renders exactly as the published page will, so review happens in the browser, and publishing keeps the link stable: the recipient always sees the latest published revision.

With no key at all (or --anon) there is no draft step: the page is live the moment it is created, immutable, rendered with an unclaimed treatment (no branding, an "Unverified" sender, outbound links as plain text), and hard-deleted after 24 hours unless the printed claim link is used to keep it. Claiming is free, transfers the page to the claiming account, and restores links and branding. A key that is set but empty is refused as a misconfiguration; it never silently goes anonymous.

The flow

compose tale.json  →  validate  →  draft (gate + link)  →  review in the browser
                                        →  publish  →  send  →  (revise → publish)*  →  retract

no account:  compose  →  validate  →  draft (gate)  →  LIVE at once  →  claim within 24 h to keep

Editing an existing draft is id-addressed and atomic: read the outline for the block ids and the draftSeq, then send ops echoing that draftSeq as --base. A stale base is a conflict: nothing applies and you re-base on the outline it hands back; a batch applies whole or not at all.

Login

Bare taleseal login opens the browser: approve there (signing up on the way if needed) and an API key is created and stored in ~/.config/taleseal/config.json ($XDG_CONFIG_HOME respected, file mode 0600). It works from SSH boxes and containers too: open the printed URL on any device and match the confirmation code. Codes last ten minutes and work once.

For CI, scripts and browserless machines, taleseal login --key tk_… stores a key minted by hand in the dashboard instead. It is validated against the server first: a key rejected with 401 is never stored; if the server is unreachable the key is stored anyway, with a warning. taleseal logout removes the stored key.

Usage

Compose and publish:
  validate <tale.json>            Check against the schema: each problem with its exact JSON path.
  draft <tale.json>               Redact, show the exposure gate, then create the tale as a DRAFT
                                    and print its URL. With no key at all (or --anon): publish
                                    LIVE anonymously: no draft step; deleted in 24 h unless claimed.
  publish <tale-id>               Promote the current draft to the revision the recipient sees.
  revise <tale-id> <tale.json>    Store a new whole-tale draft body; the recipient keeps seeing
                                    the published revision until the next publish.
  retract <tale-id>               Destroy the tale and every revision. The URL answers 410 forever.

Read a draft:
  pull <tale-id> [file]           Write the current draft body to <file> (or stdout).
  outline <tale-id>               The draftSeq you echo as --base, plus one line per block.
  get <tale-id> [blockId...]      Full JSON of the named blocks (or all).

Edit a draft (id-addressed, atomic, optimistic concurrency):
  ops <tale-id> <ops.json>        Apply a JSON array of ops in one atomic batch.
  insert <tale-id> <blocks.json>  --after <id> | --before <id> | --start | --end
  replace <tale-id> <blockId> <block.json>
  remove <tale-id> <blockId...>
  move <tale-id> <blockId>        --after <id> | --before <id> | --start | --end
  set-envelope <tale-id>          --title … | --standfirst … | --recipient … | --stationery … |
                                    --sender-name … | --sender-org … | --cta-label … | --cta-url … |
                                    --expires-at … | --clear <fields>

Other:
  upload <path>                   Upload an image and print the reference an "image" block carries.
  login [--key tk_…]              Sign in and store an API key.  logout  Remove it.

Options:
  --base <n>    The draftSeq from `outline`, echoed as an edit's concurrency token. Omit it and the
                CLI reads the current draftSeq itself: convenient, but a concurrent edit would be
                clobbered; pass --base to be safe.
  --idem <key>  Idempotency key: re-sending a batch with the same key never re-applies.
  --anon        Draft only: publish anonymously even when a key is available; live at once,
                immutable, deleted after 24 hours unless claimed.
  --yes         Skip the pre-draft / pre-publish prompt (hooks, CI).
  --json        Print machine-readable results.
  -h, --help    Show this help.

On success the tale URL is printed to stdout. Errors go to stderr with exit code 1.

The exposure gate

Secrets travel nowhere without a human seeing what would ship. When draft (or revise) is about to send a body, it first scrubs it client-side (AWS keys, GitHub/Slack tokens, sk- API keys, JWTs, Bearer headers, PEM private-key blocks and password=/secret:-style assignments become [REDACTED:<kind>] markers) and prints an exposure report counting what it found. Then:

  • Interactive terminal: the report is shown and you are asked to confirm. Anything but yes cancels; nothing leaves the machine.
  • Not a TTY (git hooks, CI, piped output): sending is refused with exit code 1 unless --yes is passed. Automation must opt in explicitly.

Configuration

Both values resolve in the same order everywhere: env var first, then the config file written by taleseal login, then the default:

| Value | Resolution order | | --- | --- | | API key | TALESEAL_API_KEY env var → config "apiKey" | | Base URL | TALESEAL_URL env var → config "url" → https://taleseal.com |

The config file is ~/.config/taleseal/config.json ($XDG_CONFIG_HOME/taleseal/config.json when set), shape {"apiKey": "tk_…", "url"?: "https://…"}, mode 0600. Publish, revise and retract require a key. draft with no key at all publishes anonymously (--anon forces it even with one); a key that is set but empty is refused as a misconfiguration, never treated as anonymous. validate needs none.

Branding

Colours and a logo are not a tale or CLI concern. Branding is a paid add-on the account owner configures once at taleseal.com/dashboard/brand, and every tale the account publishes picks it up automatically. Anonymous pages carry no branding while unclaimed; claiming one applies the claiming account's brand from then on.

Programmatic API

The same surface as the CLI, from the SDK:

import { applyTaleOps, composeTale, draftTale, getTaleOutline, publishTale } from "@taleseal/sdk";

// Compose a whole tale and create it as a draft (reads TALESEAL_API_KEY;
// TALESEAL_URL defaults to https://taleseal.com).
const tale = composeTale({ title: "Brightloop upgraded to Growth", blocks: [/* … */] });
const { id } = await draftTale(tale);

// Or edit an existing draft, block by block, atomically.
const { draftSeq } = await getTaleOutline(id);
await applyTaleOps(id, { baseRevision: draftSeq, ops: [/* … */] });

await publishTale(id); // promote the draft to the revision the recipient sees

// Or with no account at all: published live at once, deleted in 24 h unless claimed.
const anon = await draftTale(tale, { anonymous: true }); // anon.claimUrl, anon.claimExpiresAt

Requires Node ≥ 22.