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

@solidgrove/claimbee_cli

v0.12.0

Published

CLI for ClaimBee automation billing workflows

Readme

@solidgrove/claimbee_cli

CLI for ClaimBee automation billing workflows.

Install

npm i -g @solidgrove/claimbee_cli

or run one-off:

npx @solidgrove/claimbee_cli@latest --help

Configure API URL

export CLAIMBEE_API_URL='https://us-central1-claimed-660e4.cloudfunctions.net/api'

Default if unset: https://us-central1-claimed-660e4.cloudfunctions.net/api.

Authenticate

claimbee auth login --api-key <api-key>
claimbee auth logout

Billing

claimbee billing lookup --email [email protected]
claimbee billing lookup --user-id QpJrWP3j9efP7n9IMgcLpI8MFuQ2
claimbee billing refund --payment pi_123
claimbee billing create-link --email [email protected]
claimbee billing cancel --email [email protected]
claimbee billing cancel --email [email protected] --subscription sub_123 --now
claimbee billing void-invoices --subscription sub_123
claimbee billing void-invoices --invoice in_123

Looking an account up

--email is case-insensitive. The CLI lowercases it and the backend queries Stripe with the normalized address, then falls back to a case-insensitive Stripe search, so [email protected], [email protected], and [email protected] all resolve the same customer.

An address is searched across all of its aliases. @icloud.com, @me.com and @mac.com are one Apple mailbox; @gmail.com and @googlemail.com are one Google mailbox. People pay with whichever alias their device filled in, so billing lookup --email [email protected] also queries [email protected] and [email protected], and [email protected] also queries [email protected]. The response's searchedEmails lists every address queried. Never conclude from one alias that a customer has no subscription — that conclusion is already made across the whole group.

What is not expanded: a shared local part on another provider ([email protected] vs [email protected]) is a different mailbox that may belong to a different person, and Gmail's dot-insensitivity does not apply because Stripe matches the address it stored literally. Both are real sources of misses — they just need an operator's judgement, not an automatic guess.

cancel and create-link still act on the address you pass. If the lookup found the subscription under a sibling alias, pass that alias — the entry's subscription.customerEmail shows which one it is.

--user-id takes the ClaimBee user id — the same value printed as Purchase ID in every email the app sends. Use it when you do not know which address the account was created under: it resolves the account first, then looks up billing across every email and Stripe customer that account owns.

claimbee user lookup --email [email protected]
claimbee user lookup --user-id QpJrWP3j9efP7n9IMgcLpI8MFuQ2

Which channel the subscription is on

channels[] says where the money actually is, in the same channel names the apps read from the backend — web, apple, google. activeChannels keeps the app's exact meaning (the channels active right now) and is built in the same order, so you can hold it against what the customer's app is showing them: if the two disagree, that is the bug, not a quirk.

{
  "activeChannels": ["apple"],
  "channels": [
    { "channel": "apple", "active": true,
      "evidence": ["apphud:claimbee.pro.monthly:regular"],
      "accessUntil": "2026-09-20T00:00:00.000Z", "cancelledAt": null,
      "test": false, "cancelIn": "app-store-settings" },
    { "channel": "web", "active": false,
      "evidence": ["funnelsgrove:resolved-inactive", "stripe:live:sub_1:canceled"],
      "accessUntil": "2026-08-24T00:00:00.000Z",
      "cancelledAt": "2026-08-24T09:12:00.000Z",
      "test": false, "cancelIn": "hosted-portal" }
  ]
}

channels[] also lists a channel that is over, because the people who write to support are usually exactly those. cancelIn is where that subscription is actually cancellable: hosted-portal for a funnel subscription, and the device's own App Store or Play settings for a store purchase — billing cancel cannot touch a store subscription, and telling a customer otherwise wastes both your time and theirs.

evidence names the sources that decided the channel. A marker like funnelsgrove:unavailable means that source was down when the answer was built: "active": false next to it is Stripe's word alone, not both sources agreeing.

What an empty result means

Nothing, unless you read sources[]. Every source is reported with what it was worth this time:

| status | meaning | |---|---| | answered | it looked and found something | | no-match | it looked and there is nothing | | not-asked | there was no identifier to ask it with | | failed | it broke; detail says how |

That distinction is the whole point. A customer whose account was deleted after a refund resolves no email and no Stripe customer id, so Stripe is never queried at all — that used to print an empty customers array indistinguishable from "Stripe has no such customer". It now says not-asked. Likewise a FunnelsGrove miss is no-match with the attempt count, never proof of no subscription: an email owning several funnel rows resolves to none of them.

unresolvedChannels is the short version — channels whose sources could not be reached (web, store). If it is non-empty, you do not know yet; say so instead of guessing.

The lookup no longer depends on the address the customer writes from. It also resolves them through the Stripe customer ids stored on the account and on the funnel record (searchedCustomerIds), through FunnelsGrove by funnel id and by every known email (funnel), and through Apphud by the Firebase uid (storeSubscriptions). That matters most for a wallet payment: Apple Pay and Google Pay file the charge under the address the wallet holds, so searching Stripe by the customer's own address finds nothing and the stored customer id is the only bridge to it.

A store purchase survives account deletion. Apphud keys its customer by the Firebase uid, and deleting an account removes our records but nothing of theirs, so billing lookup --user-id still finds the purchase and reports it — the user lookup response calls it orphanedStore.

Cancelling

cancel cancels at period end by default, so the customer keeps the access they already paid for. --now ends access immediately and Stripe refunds nothing — use it only when a refund is being issued for the same period.

Before an immediate cancel the CLI looks the subscription up and prints what the flag costs:

warning: --now forfeits 27 days of paid access (through 2026-09-01).
Omit --now to cancel at period end.

The warning goes to stderr and does not block the command. It is skipped when there is nothing to lose (a past_due subscription, or a period that already ended). Pass --subscription when the email has several subscriptions — otherwise the CLI cannot tell which one it is about to cancel and says so.

Old unpaid invoices

Cancelling a subscription does not close its open invoices in Stripe — they stay in the retry schedule, and a card the customer later updates triggers an immediate retry. That is how a subscription cancelled on the 24th takes money on the 26th, and the payment grants nothing, because access is derived from the subscription status. So cancel now voids the subscription's unpaid invoices from the last 60 days and lists them:

{
  "voidedInvoices": [
    { "invoiceId": "in_1", "amountDue": 1065, "currency": "usd", "created": "2026-05-28T00:00:00.000Z" }
  ]
}

The 60 days is a lookback window, not a staleness floor — the invoices that keep collecting are the ones from the run-up to the cancellation, and anything older than the window is left alone so a cancellation cannot reach back arbitrarily far. An invoice whose payment is still settling is skipped too. Anything the void could not close comes back in voidInvoiceErrors — read it, because those invoices can still collect.

Subscriptions cancelled before this shipped were not swept, so an old "you charged me after I cancelled" complaint can still be genuine.

Voiding invoices on a subscription that is already cancelled

cancel voids on its way through, which does nothing for a subscription that is already cancelled — Stripe refuses to update or cancel one, so the cancel fails before it can sweep. That is the exact shape of the complaint support gets ("I cancelled and it is still trying to charge me"), and closing it used to mean opening the Stripe dashboard by hand. void-invoices is that operation on its own:

claimbee billing void-invoices --subscription sub_123
claimbee billing void-invoices --invoice in_123 --dry-run

Give it either a subscription (all its open invoices in the window) or one named invoice (any age — an operator naming an invoice has decided its age is beside the point). --lookback-days widens the subscription window past the 60-day default; it does not apply to a named invoice. void-invoice is accepted as an alias.

Before the irreversible call the CLI runs the same operation as a dry run and prints what it is about to close:

voiding 2 invoices:
  in_1 10.65 USD from 2026-08-17
  in_2 49.99 USD from 2026-08-01
A voided invoice cannot be undone or reopened in Stripe.

--dry-run stops after that preview and prints the payload. When the preview finds nothing, the command says so and never makes the second call.

It refuses a subscription that still grants access (active, trialing or past_due and not already set to cancel): that invoice is a debt for access the customer still has, and cancel is the operation for it — which voids on its way through anyway. Being scheduled to cancel does not count as still earning, so the common case (cancelled today, today's invoice still retrying) is allowed. An invoice whose payment is settling, or one that is not open, is refused too.

Every cancel response reports the same numbers:

{
  "accessUntil": "2026-08-05T11:02:31.000Z",
  "accessUntilBefore": "2026-09-01T00:00:00.000Z",
  "paidDaysForfeited": 27
}

Publish

npm run publish:public