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

bandstand

v0.2.2

Published

Unofficial TypeScript client + CLI for BAND (band.us) calendars — read and write schedules the official Developer API doesn't expose.

Readme

bandstand

Unofficial TypeScript client + CLI for BAND — reads and writes calendar events (get_schedules, create_schedule, update_schedule), which BAND's official Developer API does not expose.

The core is runtime-agnostic (Web fetch + Web Crypto), so BandClient runs on Node, Bun, Deno, and edge runtimes. The CLI and a file-backed cookie store are the Node-flavored layer on top. Zero runtime dependencies.

⚠️ Personal-use tool for your own BAND account, built because the official API doesn't cover events. Don't scrape, don't automate against bands you don't own. See Disclaimer.

How it works

Reverse-engineered from the web client:

  1. Static akey header (bbc59b0b…) identifies the web client. It's the same value for every user — a public identifier, not a secret.

  2. band_session cookie is an opaque session token from a real login at auth.band.us (captcha/2FA/SSO — not worth scripting). You paste it once. The server rotates it via Set-Cookie on API responses; the client persists the rotation automatically, so it stays fresh on its own.

  3. secretKey cookie is the HMAC key. Every request carries an md header:

    md = base64( HMAC_SHA256( secretKey, extractPath(url) ) )

    where extractPath(url) is the URL minus scheme://host. secretKey is HttpOnly, scoped to path /s/login/, so it is not visible to document.cookie — you must copy it from the DevTools cookies panel (see below).

Install

Not published to npm yet — build from source:

git clone <your-fork-url> bandstand && cd bandstand
npm install
npm run build
npm link        # installs two commands: `bandstand` and the short alias `bs`

As a library once published:

npm install bandstand

The CLI installs as both bandstand and bs — use whichever you prefer. Examples below use the full name.

First-time login

bandstand login

This opens https://www.band.us/. Then, in DevTools:

Application → Cookies → https://www.band.us, copy the values of two cookies:

  • band_session
  • secretKeyHttpOnly, so copy(document.cookie) in the Console silently misses it. Grab it from the cookies panel.

Paste them when prompted, in either form:

band_session=<value>; secretKey=<value>

Quotes around secretKey's value are fine — they're stripped. Cookies are saved to ~/.band_session.json (mode 600). If a command later hits auth, it re-prompts and retries once.

CLI

bandstand whoami     --band <band_no> [--save]
bandstand calendars  --band <band_no>
bandstand members    --band <band_no> [--group <member_group_id>] [--short]
bandstand groups     --band <band_no> [--short]
bandstand events     --band <band_no> [--start YYYYMMDD] [--end YYYYMMDD] [--calendar <id>] [--short]
bandstand rsvp         <schedule_id> --band <band_no> --going|--maybe|--no
bandstand rsvp-report  <schedule_id> --band <band_no> [--json]
bandstand search     <term> --band <band_no> [--start ...] [--end ...] [--days N] [--json]
bandstand week       --band <band_no> [--calendar <id>] [--days 7]
bandstand create     --band <band_no> --file event.json [--announce]
bandstand create     --band <band_no> --name "..." \
                     --start 2026-05-01T18:30:00-0700 --end 2026-05-01T20:30:00-0700 \
                     [--desc "..."] [--calendar <id>] [--tz America/Los_Angeles] \
                     [--secret] [--share USER_NO,...] [--group <id>] [--announce]
bandstand copy       <schedule_id> --band <band_no> [--name ...] [--start ...] \
                     [--group <id>] [--add-user ...] [--remove-user ...] [--dry-run]
bandstand delete     <schedule_id> --band <band_no> [--notify]
bandstand sync-group --band <band_no> --calendar <id> --group <id> [--days 120] [--apply] [--notify]

(bs is a drop-in alias for bandstand in every command above.)

Config file (optional defaults)

~/.band_config.json supplies defaults for --band, --calendar, and me, so you don't retype them. CLI flags still override. Override the path with BAND_CONFIG.

{
  "band": 12345678,
  "calendar": 11,
  "me": 123456789
}

me is your own user_no. BAND rejects the event owner as their own secret_sharer, so --group / sync-group filter this value out of any roster they expand. Get it (and optionally write it to config) with bandstand whoami --band <band_no> --save.

Finding band_no and calendar_id

  • band_no is in the URL: https://www.band.us/band/<band_no>/calendar.
  • bandstand calendars --band <band_no> lists calendars (each has calendar_id).
  • bandstand members --band <band_no> --short maps names to user_no.

Library usage

import { BandClient, FileCookieStore } from "bandstand";

// Loads / persists cookies at ~/.band_session.json (or $BAND_STATE).
const client = await BandClient.create({ store: new FileCookieStore() });

const cals = await client.getCalendars(12345678);
const { items } = await client.getSchedules(12345678, "20260401", "20260601");
await client.createSchedule(12345678, {
  name: "Canvass",
  start_at: "2026-05-01T18:30:00-0700",
  end_at: "2026-05-01T20:30:00-0700",
  // …see the Schedule type for the full shape
});

BandClient.create is async (it loads the jar and imports the HMAC key). Everything is injectable for testing or alternate runtimes:

const client = await BandClient.create({
  cookies: "band_session=…; secretKey=…", // seed instead of a store
  fetch: myImpersonatingFetch,            // swap the transport (see below)
  jitterMs: [500, 1500],                  // or null to disable pacing
  warmUp: false,
});

Browser / extension usage

The core BandClient is runtime-agnostic (Web fetch + Web Crypto), so it also runs in a browser extension or service worker. Import the browser-safe entry — bandstand/browser, which omits the Node FileCookieStore (and its node:fs import) — and use browser mode: the runtime owns the cookie jar, so you only supply secretKey (read it via chrome.cookies, since it's HttpOnly) and let fetch attach the session cookie.

import { BandClient } from "bandstand/browser";

const [{ value: secretKey }] = await chrome.cookies.getAll({ name: "secretKey" });
const client = await BandClient.create({
  cookies: { secretKey },
  sendCookieHeader: false, // `Cookie` is a forbidden header in browsers
  credentials: "include",  // let the browser attach band.us session cookies
});
await client.getCalendars(bandNo);

Browser-native traffic posture

To look like a normal browser to BAND, the client:

  • sends a browser User-Agent, Origin/Referer, and BAND's app headers;
  • paces calls with 300–900 ms jitter (configurable via jitterMs);
  • warms up before the first write with touch_band_access + get_calendars, the same sequence the calendar page loads;
  • persists the full cookie jar and any server-rotated cookies.

What it can't do: native fetch always presents Node's own TLS/JA3 fingerprint — it cannot forge a browser's the way curl-impersonate can. If you need that, pass your own fetch (e.g. one backed by curl-impersonate) via the fetch option; the client treats the transport as pluggable. For personal use at human cadence, plain fetch is fine.

Environment variables

| Var | Default | Purpose | | ------------- | ------------------------ | -------------------------------- | | BAND_STATE | ~/.band_session.json | Cookie-jar path (FileCookieStore) | | BAND_CONFIG | ~/.band_config.json | CLI defaults file |

Pacing, time zone, API base, and impersonation are BandClient options rather than env vars.

Endpoints in use

  • GET /v2.0.0/get_calendars
  • GET /v2.0.0/get_members_of_band_with_filter
  • GET /v2.1.0/get_member_groups
  • GET /v2.0.0/touch_band_access (warm-up)
  • GET /v1.6.0/get_schedule, /v1.6.0/get_schedules
  • POST /v2.0.0/create_schedule, /v2.0.3/update_schedule
  • GET /v1/schedule/delete_schedule
  • GET /v2.0.0/get_my_band_schedules (whoami)
  • POST /v2.0.0/set_schedule_rsvp_states (rsvp)

All under https://api-usw.band.us. Other region shards (api-ukw, api-jpw, api-krw) exist if your account is routed elsewhere — override with the apiBase option.

Development

npm run typecheck   # tsc --noEmit
npm test            # vitest
npm run build       # tsup -> dist/ (ESM + .d.ts)
npm run lint        # biome

Releasing

Publishing is automated by .github/workflows/publish.yml: cut a GitHub Release and CI publishes to npm via Trusted Publishing (OIDC) with provenance — no token in the repo. One-time setup: on npmjs.com, add this repo + publish.yml as a trusted publisher for the package.

npm version patch        # bumps package.json, commits, tags vX.Y.Z
git push --follow-tags
gh release create vX.Y.Z --generate-notes   # -> triggers the publish workflow

Limitations

  • get_schedules is paginated automatically (paging.next_params), capped at maxPages (default 50).
  • No rate-limit backoff beyond jitter.

Disclaimer

Built for managing your own BAND calendar where the official Developer API doesn't cover events. Use your own account. Don't scrape. This project is not affiliated with or endorsed by BAND / Naver.

License

MIT © Mitch Lillie