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

basalt-cli

v0.6.0

Published

CLI for Basalt (app.basalt.cofoundy.ai) — login + workspace CRUD + member management, plus pull / publish / list / status / whoami over HTTP.

Readme

basalt CLI (thin v1)

Thin-over-HTTP CLI for Basalt. The internal authoring loop for any harness with a working copy (Claude Code, Cursor, CI):

basalt pull <project> <slug>   →   edit locally   →   basalt publish <file>

The server compiles. This binary never compiles MDX and never enumerates a vault tree — it reads a file (and, for the space binding, the enclosing vault.yaml), computes content shas, and speaks the publisher API. (The old local-compile CLI drifted into a second render world and was deleted; a lint-invariant, cli-no-local-mdx, keeps this one honest.)

Addressing — frontmatter v2 (metadata only, #289)

Frontmatter is metadata, never infrastructure. The CLI derives the address from the filesystem:

  • slug = the file path, relative to the enclosing vault.yaml, slashes preserved and each segment normalized (decisions/Channel Stack.mddecisions/channel-stack). No vault.yaml above it → the file is a depth-0 loose doc and the slug is its basename.
  • project = the space binding: --project wins, else vault.yaml:name, else the vault-root folder name.

Allowed frontmatter keys: title (optional — defaults to the first body H1), tags, author, created, chrome. The infra keys slug: / project: / role: / version: are IGNORED — publishing one emits an infra_key_ignored warning and it is dropped. pull strips them so a pull → edit → publish round-trip never re-injects them. Migrate an existing vault with scripts/strip-infra-frontmatter.ts <dir> (in the basalt repo; dry-run by default, --apply to write) — a textual strip that never touches the body, so a republish is sha_match (basalt status stays unchanged).

Install / build

Standalone package (the repo is not a pnpm workspace):

cd cli
pnpm install
pnpm build      # → dist/, bin `basalt`

Auth

Set a Basalt publisher key (minted in Workspace settings → Publishers). The key is always Basalt-issued — never a raw IdP token.

export BASALT_API_KEY=ct_...
# optional: point at a non-prod host
export BASALT_BASE_URL=https://app.basalt.cofoundy.ai

~/.basalt/credentials (JSON {"api_key":"…"} or a raw token line) is read when BASALT_API_KEY is unset. It also seats the basalt login device-flow session (session_token), which the workspace commands use — publishing still needs a publisher key.

basalt login from an AI agent

login adapts to its caller. With a terminal it blocks until you approve in the browser. Without one (an agent harness, CI) it would be a deadlock — the harness could never surface the URL — so it does not block:

basalt login            # no TTY → prints URL + code, exits 0
# agent shows the URL to the human, then:
basalt login --wait     # run as a BACKGROUND job; exits the moment they approve

--wait resumes the in-flight request (~/.basalt/pending-login.json, 0600) — it never mints a second code, so the code the human is looking at stays the code the CLI polls for. Its exit is the approval notification; no cron, no polling loop in the agent. Where background jobs aren't available, basalt login --check polls once: exit 0 signed in, 3 still pending. Use one or the other — a concurrent --wait and --check race for the server's one-shot token mint.

--wait / --no-wait force either mode explicitly.

Commands

| Command | What it does | |---------|--------------| | basalt login [--wait\|--no-wait\|--check] | Sign in via browser (device flow). | | basalt whoami | Identify the current key (name, tenant, scope, expiry). | | basalt list [--project <p>] | List docs the key can see, with content sha. | | basalt status <files…> [--project <p>] | Show what publish would do — no write. | | basalt pull <project> <slug> [--out <file>] | Fetch canonical source MDX (- = stdout). | | basalt publish <files…> [--project <p>] [--force] [--dry-run] | Publish (server-compiled). |

Add --json to any command for machine-readable output.

Workspaces & members (require basalt login)

These speak the session-gated /api/tenant/* routes with the cf_docs_session JWT that basalt login stores — not the publisher key. Two credentials, two surfaces: a publisher key publishes docs, a login session administers workspaces.

| Command | What it does | |---------|--------------| | basalt workspace list | List your workspaces ( = active). | | basalt workspace create <name> [--slug <s>] | Create a workspace (you become owner). | | basalt workspace switch <slug> | Set the active workspace. | | basalt workspace edit [--name <n>] [--avatar <f>] | Rename / re-avatar a workspace. | | basalt workspace delete <slug> --yes | Delete a workspace (owner only). | | basalt workspace members [--tenant <slug>] | List a workspace's members. | | basalt workspace invite <email> [--role owner\|admin\|member] [--tenant <slug>] | Invite a member (owner/admin only; --role defaults to member). |

--tenant targets any workspace you belong to, so you can administer one workspace while another is active. Omit it to act on the active workspace. Re-inviting the same email at the same role is idempotent.

--tenant is verified before any write. Server-side, cf_active_tenant is a preference hint: for a caller who is not a member of the named workspace it is silently ignored and the request falls back to their personal/host workspace. So the CLI resolves --tenant against your live membership first and fails loudly (not a member of workspace "…") rather than let an invite land in the wrong workspace. A plain member inviting gets a clear message, not a raw 403.

Content-addressed skip. publish fetches the doc list once, then per file compares the local content sha (sha256 of the CRLF-normalized body — the exact digest the server stores) against the server's current_sha. An unchanged doc is a no-op; a frontmatter-only change publishes (cheap metadata upsert); --force always publishes.

Asset staging. Author a local image path — ![diagram](./assets/x.png) — and commit the image bytes next to the doc. On publish, each LOCAL image ref is content-hashed and uploaded to a shared, per-project namespace files/{project}/_assets/{sha16}-{name}, and the source POSTed to the server is rewritten to the resolved /files/… URL. The git .md is never mutated — its local path + committed bytes stay the source of truth; only what the server stores carries the resolved URLs.

  • Content-addressed → the same image referenced by many docs uploads once, and moving a doc to a new slug never breaks its images.
  • Bytes are uploaded verbatim (no transcoding); the sha16 prefix is a 64-bit content hash, collision-safe well past any vault's image count.
  • Re-publishing re-uses assets already in R2 (a HEAD probe skips the upload).
  • --dry-run validates that every local image ref resolves on disk and is a supported, in-size image — a missing/oversize/unsupported ref fails the run (exit 1), making it a real pre-merge check. Nothing is uploaded on --dry-run.

Supported: png · jpg/jpeg · gif · webp · svg, ≤ 10 MB each.

Exit codes

0 ok · 1 error · 2 usage · 22 brand violation.

Test

pnpm test            # vitest
pnpm test --coverage # ≥85% gate

The content-sha vectors in test/fixtures/sha-vectors.json are cross-asserted server-side (tests/sha-parity.test.ts) so CLI and server can never drift.