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

@dcl-regenesislabs/artifacts

v1.1.0

Published

Publish static artifacts to artifacts.dclregenesislabs.xyz — sign in with a work email, push a folder, get a link back

Readme

ia-generated-artifacts

Self-hosted artifact host — a Claude-Artifacts-like place to drop a folder of static files and get a link back. Runs as a Cloudflare Worker in front of an R2 bucket on the dclregenesislabs account.

https://artifacts.dclregenesislabs.xyz/{id}        latest version
https://artifacts.dclregenesislabs.xyz/{id}@2      one version, pinned
https://artifacts.dclregenesislabs.xyz/p/{alias}   public link, no login

id is a UUID v4 minted on the first push and kept for life. Every later push to the same id is a new version; the link never changes. Every gated page gets a bar at the top, injected by the Worker: the artifact's name, a version picker, the public link switch, copy link. Checkboxes marked data-persist remember their state on the host, for everyone.

Every /{id} URL is behind a Cloudflare Access login that only accepts @dclregenesislabs.xyz and @decentraland.org addresses. Making something public is done from the bar, by a signed-in person — never by the upload token. See Visibility.

Publish something

The CLI is on npm as @dcl-regenesislabs/artifacts, so nothing needs installing:

npx @dcl-regenesislabs/artifacts login    # once per machine, work email in a browser
npx @dcl-regenesislabs/artifacts push ./my-demo --name "my demo"

From a checkout of this repo, the same commands are node cli/artifacts.mjs …:

node cli/artifacts.mjs login
node cli/artifacts.mjs push ./my-demo --name "my demo"
# → https://artifacts.dclregenesislabs.xyz/3f2b9c1e-4d5a-4b6c-8e7f-9a0b1c2d3e4f

node cli/artifacts.mjs push ./my-demo --id https://artifacts.dclregenesislabs.xyz/3f2b9c1e-…   # new version, same link

index.html is the entry point. A single file is served at the bare /{id}. A folder is served under /{id}/ (the bare URL redirects there), so reference its assets relatively (./assets/app.js) — absolute paths break.

node cli/artifacts.mjs push ./report.html     # single-file artifact
node cli/artifacts.mjs push ./demos/*/        # several new artifacts at once
node cli/artifacts.mjs info <id or url>       # name, versions, visibility
node cli/artifacts.mjs ls                     # everything on the host
node cli/artifacts.mjs rm   <id or url>       # every version, state and public link
node cli/artifacts.mjs --help

Pushing bytes identical to the latest version records nothing. Blobs are stored by digest, so a new version uploads only the files that changed. commit carries the version number the client last saw and is refused if someone pushed in between.

House template

templates/artifact/ is the look every page on the host shares — IBM Plex, indigo accent, three-state dark mode, and a set of blocks (callouts, cards, tables, phases, checklist, diff columns). It was lifted from a Claude-designed artifact so pages here match the ones Claude makes elsewhere.

node cli/artifacts.mjs new ./my-page --name "Q3 numbers"   # scaffold
$EDITOR my-page/index.html                                 # write
node cli/artifacts.mjs push ./my-page                      # publish

new writes one self-contained index.html: the CSS and JS in templates/artifact/assets/ stay editable as files and are inlined at scaffold time, so the result is the same single-file shape as a Claude artifact and works anywhere. The scaffolded page is a live reference of every block. The one knob per page is --accent in its small <style>. The skill tells Claude to start here. (docs/guide/index.html is generated by scripts/build-guide.mjs from the same assets — re-run it when dcl.css changes. The guide carries the install line with the token filled in, so it stays team-only: never flip it public.)

The bar

The Worker injects <dcl-bar> plus /__ui/bar.js at the top of <body> of every HTML response on a gated URL (src/serve.ts, HTMLRewriter). The script renders into a shadow root, so page CSS can't touch it and vice versa. It talks to a small in-page API under the artifact:

| Call | Who | Does | | --- | --- | --- | | GET /{id}/__ui/meta | anyone through the gate | name, versions, public link, viewer, checkbox state | | PUT /{id}/__ui/public | a signed-in person | mints the public alias; records who and when | | DELETE /{id}/__ui/public | a signed-in person | deletes the alias — the link dies at once | | PUT /{id}/__ui/state | a signed-in person | { "key": true } merges into the checkbox state | | GET /p/{alias}/__ui/state | anyone with the link | the state, read-only |

"Signed-in person" means the Worker verified the Cloudflare Access JWT (src/access.ts: signature against the team's public keys, audience, issuer, expiry) and took the email from it. That is the answer to who can make a document public: anyone who can open it. The public view (/p/…) gets no bar and only a tiny script that paints the saved checkbox state.

Checkboxes. <input type="checkbox" data-persist="deploy-staging">. State is keyed by artifact, not version, so ticks survive a re-push as long as the keys do. The bar shows who ticked each box. Public viewers see them read-only.

Visibility

| | URL | Who can open it | | --- | --- | --- | | team (always) | /{id}, /{id}@n | @dclregenesislabs.xyz and @decentraland.org only, via a work-email login | | public (switch in the bar) | /p/{alias} | anyone holding the link, no login; latest version; no bar |

The public link is additive — it mints a second URL and the gated one keeps working. Everything under /p/ is exempt from the Access policy, so the alias is the only credential: 128 bits of randomness, unrelated to the artifact id.

Switching it off deletes the alias immediately — aliases are resolved against R2 on every request rather than cached — and it is permanent: switching on again mints a different alias, so a link already sent out stays dead. Neither the CLI nor the upload API can do any of this; there is no endpoint for it.

Claude picks all of this up through the project skill in .claude/skills/dcl-artifacts/.

Install the skill (one line)

npx @dcl-regenesislabs/artifacts setup

Same line on macOS, Linux and Windows. It writes the skill — SKILL.md and its style reference, both shipped inside the npm package — to ~/.claude/skills/dcl-artifacts/, with every command rewritten to npx -y @dcl-regenesislabs/artifacts …, and then runs login unless you are already signed in. Run it again to update; it only rewrites what changed.

login on its own offers to install the skill afterwards when run in a terminal, and stays quiet otherwise. --skill-dir <dir> puts the skill somewhere else — any agent that reads the SKILL.md format, e.g. ~/.codex/skills/dcl-artifacts, or .claude/skills/dcl-artifacts inside a project to share it with a team.

Needs Node 20+, nothing else — no git, no repo access, no npm install. The old curl … | bash and irm … | iex lines still work: they are stubs that run the line above.

Inside this repository, the project-level skill at .claude/skills/dcl-artifacts/ is the source of truth and runs node cli/artifacts.mjs directly — npx cannot resolve a package from inside its own checkout.

Signing in

dcl-artifacts login binds a browser login to the terminal that asked for it:

  1. The CLI holds a loopback port, then opens /cli/login with the port, a random state, and sha256(verifier) — the verifier never leaves the process.
  2. Cloudflare Access gates that page, so reaching it proves a work-email login. The page shows a code that must match the terminal, and minting only happens on a POST from that page.
  3. The Worker mints a one-time code (60s, single use, R2) and redirects to http://127.0.0.1:<port>/cb — a target it builds itself from a validated integer, never from the query string.
  4. The CLI checks state, then exchanges the code plus the verifier at /_api/cli/exchange for a token, and writes it to ~/.config/dcl-artifacts/credentials.json (mode 600).

Tokens are dcla_v1_…, live 90 days, and are stored only as sha256 in KV. Revoke one from /cli/tokens or with dcl-artifacts logout [--all]. Nothing is ever pasted anywhere, and the token is never printed.

Layout

| Path | What it is | | --- | --- | | src/index.ts | router: /_api/* → upload API, everything else → site | | src/api.ts | status / blobs / commit / info / list / delete, all bearer-authed | | src/serve.ts | reads: version resolution, @n, redirects, index.html + SPA fallback, ETag/Range/cache, bar injection | | src/ui.ts | the in-page API the bar calls | | src/access.ts | Cloudflare Access JWT verification | | src/bar-script.ts | the injected client code (bar, and the public-view state painter) | | src/store.ts | meta / state / manifest records, compare-and-swap on the R2 ETag | | src/auth.ts, src/mime.ts, src/types.ts, src/urls.ts | token comparison, content types, bindings and record shapes, URL helpers | | shared/artifact-id.js | id / alias / hash / manifest rules — imported by both the Worker and the CLI | | cli/artifacts.mjs | zero-dependency Node uploader | | scripts/smoke.sh | end-to-end check against a local or deployed host | | scripts/setup-access.mjs | reconciles the Cloudflare Access apps and policies | | scripts/build-guide.mjs | regenerates docs/guide/index.html from the template assets | | scripts/install.sh, scripts/install.ps1 | legacy installer stubs served at /install.sh and /install.ps1; they run npx … setup |

R2 layout, per artifact:

a/{id}/__meta.json      name, versions[], current, public {alias, at, by}   (mutable, CAS)
a/{id}/__state.json     checkbox state {key: {checked, by, at}}             (mutable, CAS)
a/{id}/m/{n}.json       file list of version n                              (immutable)
a/{id}/b/{sha256}       blob, one per distinct content                      (immutable)
p/{alias}               → {id}                                              (exists only while public)

Nothing under a/ is ever served by path — the Worker resolves a URL through the meta record and the version's manifest to a blob. Blobs and manifests are cached at the edge under keys the router 404s.

How an upload works

The client hashes locally and uploads straight to blob keys, so the Worker never streams objects through itself:

  1. POST /_api/artifacts/{id}/status — sends the file list, gets back {exists, current, missing: [sha256…]}.
  2. PUT /_api/artifacts/{id}/blobs/{sha256} — raw bytes. The Worker checks the digest and hands the same digest to R2, which checks it again.
  3. POST /_api/artifacts/{id}/commit{files, base, name?, author?}. The Worker confirms every blob is present, writes the version manifest, then updates the meta record — refusing if current !== base. Only then is the version live.

All /_api/* calls require Authorization: Bearer <token> — either a per-user token from login or, until it is retired, the shared UPLOAD_TOKEN. The two exceptions are POST /_api/cli/exchange, which is how the CLI gets a token in the first place (PKCE, not a bearer), and GET /_api/whoami, which reports what the caller is signed in as.

commit records by from the token's own email; the client-supplied author is honoured only for the shared token, which carries no identity.

Who can read what

Cloudflare Access sits in front of the hostname. It matches the most specific path first, which gives these rules:

| Path | Policy | Why | | --- | --- | --- | | /_api/* | bypass | already guarded by the bearer token | | /_health | bypass | exposes nothing, keeps uptime monitors working | | /p/* | bypass | opt-in public aliases carry their own secret | | /install.sh, /install.ps1 | bypass | legacy installer stubs; no secrets in them | | everything else | allow @dclregenesislabs.xyz, @decentraland.org | the gate — pages, /__ui/bar.js, the in-page API |

Login is Cloudflare's built-in one-time PIN: the visitor types their email, gets a 6-digit code, and the policy checks the domain. No identity provider to configure. Sessions last a week. A service-token policy also lets CI reach the gated path — scripts/setup-access.mjs mints that token; the Worker attributes its in-page writes to the token's name.

The Worker also verifies the Access JWT itself for in-page writes. ACCESS_AUD in wrangler.jsonc is the AUD tag of the catch-all "Artifacts" application — the kid in its login redirect. If that application is ever recreated, update it.

workers_dev and preview_urls are pinned to false in wrangler.jsonc. That is load-bearing: a *.workers.dev URL would answer on a hostname Access does not cover and would hand anyone a way straight past the gate.

Develop

npm install
printf 'UPLOAD_TOKEN=%s\nPUBLIC_BASE_URL=http://127.0.0.1:8787\nLOCAL_DEV=1\n' "$(openssl rand -hex 32)" > .dev.vars
npm run dev                                    # wrangler dev, local R2
./scripts/smoke.sh http://127.0.0.1:8787 "$(sed -n 's/^UPLOAD_TOKEN=//p' .dev.vars)"
npm run typecheck

LOCAL_DEV=1 stands in for Access locally: in-page writes are attributed to dev@localhost. It lives only in .dev.vars, which is gitignored and never deployed.

Deploy

Wrangler must be logged into the dclregenesislabs account (wrangler whoami should show a2b29bacd555c6fc78becaad8b183e9c; wrangler login to switch).

npx wrangler r2 bucket create dcl-artifacts
npx wrangler r2 bucket create dcl-artifacts-preview     # used by `wrangler dev --remote`
npx wrangler kv namespace create TOKENS                 # put the id in wrangler.jsonc
npx wrangler kv namespace create TOKENS --preview        # and the preview_id
npx wrangler secret put UPLOAD_TOKEN                     # the shared token, until it is retired
npm run deploy                                          # provisions the custom domain

# Cloudflare Access — needs an API token with Access: Apps and Policies = Edit
CLOUDFLARE_API_TOKEN=… node scripts/setup-access.mjs --dry-run
CLOUDFLARE_API_TOKEN=… node scripts/setup-access.mjs

CF_ACCESS_CLIENT_ID=… CF_ACCESS_CLIENT_SECRET=… \
  ./scripts/smoke.sh https://artifacts.dclregenesislabs.xyz "$DCL_ARTIFACTS_TOKEN"

setup-access.mjs is idempotent — it rewrites the applications and their policies to match the table above, so re-run it after any policy change.

Releasing the CLI to npm

The package is cli/, shared/, templates/ and the skill under .claude/skills/dcl-artifacts/ — the files list in package.json; nothing of the Worker ships. Releases go through .github/workflows/publish.yml using npm trusted publishing: npm trusts that workflow's GitHub OIDC identity, so there is no npm token in the repo, in Actions secrets, or anywhere else, and every release carries provenance.

# bump "version" in package.json, commit, then:
git tag v1.2.3 && git push origin v1.2.3

The trust is configured on npmjs.com under the package's Settings → Trusted Publisher (organisation dcl-regenesislabs, repository ia-generated-artifacts, workflow publish.yml). It keys on the workflow's filename, so renaming the file breaks publishing until the setting is updated. npm pack --dry-run shows exactly what a release would contain.

Retiring the shared token

UPLOAD_TOKEN still works alongside per-user tokens so nobody's publishing breaks mid-migration. Every API request logs its credential kind, so:

  1. Watch for "kind":"shared" in npm run tail. When two weeks pass with none — long enough to cover people who publish weekly — everybody has moved.
  2. wrangler secret put UPLOAD_TOKEN with a value nobody holds. That is a reversible kill switch; leave it a week.
  3. Delete the shared branch in src/auth.ts and the author fallback in src/api.ts.

Rotate it in any case. The old value is in this repo's git history, so removing it from HEAD did not remove it.

Caveats

  • Public URLs are genuinely public. Anyone with a /p/{alias} link can open it, with no login, until someone switches it off in the bar. Treat the switch as publishing — the bar asks for confirmation and records who did it.
  • All artifacts share one origin. A published page can read another artifact's localStorage, and — since it runs as the signed-in viewer — call another artifact's in-page API. Only people who can sign in with a work email can publish pages, so this is the same trust as the gate itself; still, do not publish pages that run untrusted third-party script.
  • Access does not encrypt anything at rest — it gates the front door. R2 objects are still readable by anyone with account access.
  • A revoked token can still work for up to a minute: KV reads are cached at the edge with a 60-second floor.
  • Windows has no file modes, so %APPDATA%\dcl-artifacts\credentials.json is protected by the default per-user ACL rather than by 600.
  • Limits: 25 MB per file, 2000 files per artifact, 1000 persisted checkboxes per artifact.