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

claude-multiacc

v1.0.4

Published

Multi-account addon for Claude Code: every claude / claude -p runs under a randomly-picked subscription account with the most usage headroom. Mirrors to a deploy server. No API keys.

Readme

claude-multiacc

Multi-account addon for Claude Code: every claude / claude -p invocation runs under a randomly picked subscription account with limit headroom (no API keys, ever). The account set mirrors automatically from the Mac (source of truth) to the deploy server.

Installs as a PATH shim + helper CLI from this repo. Never touches the Claude Code app: no binary edits, no install-dir writes, survives claude update — the real binary is resolved dynamically at exec time.

Tested on: macOS (bash 3.2, zsh, Claude Code 2.1.207) and Ubuntu 24.04 (bash 5.2, Claude Code 2.1.201). Test suite: tests/run-tests.sh (35 sandboxed tests, no network / no quota — passes on both platforms).


How it works

MAC (source of truth)                      SERVER (mirror)
~/.claude-accounts/                        /root/.claude-accounts/
  accounts.json  <- manifest                 accounts.json          (synced)
  acct-01 -> ~/.claude   ("adopted")         acct-01/server.token   (synced, 600, once minted)
  acct-02/               (registered)        acct-02 -> /root/.claude ("adopted")
    server.token / .credentials.json
  selection.log  sync.log  health.log      /usr/local/bin/claude -> repo shim
repo bin/ first on PATH (rc-file block)    /root/claude-multiacc/  (addon repo)

Shim selection order (identical file on both machines, bin/claude):

  1. CLAUDE_CONFIG_DIR or CLAUDE_CODE_OAUTH_TOKEN already set → pass straight through.
  2. CLAUDE_ACCOUNT=acct-NN → use that account (even if limit-marked — explicit wins).
  3. Among accounts valid on this machine (.credentials.json or server.token present) and not excluded, pick the one with the most remaining headroom (see below). Ties break randomly to spread load. CLAUDE_SHIM_SELECT=random restores plain uniform-random selection.
  4. If every account is excluded → the highest-headroom valid account anyway + a warning in selection.log (degraded beats down: the 100% rule).

Which account has "the most headroom"? A Max subscription has three limits that a Fable request consumes at once: the 5-hour session bucket, the weekly all-models bucket, and the weekly Fable bucket. Anthropic's docs confirm these reset on very different horizons — the session bucket refills every ~5 hours, but weekly buckets only refill on the account's fixed weekly reset (days away). So the picker ranks primarily on weekly headroom (the peak of the durable buckets) and uses the self-healing session bucket only as a tiebreaker. An account sitting at 85% session / 10% weekly outranks one at 20% session / 70% weekly: the first only needs a few hours to recover, the second is burning headroom that's gone until next week. (score = weekly% × 1000 + session%, lowest wins.) Exclusion still fires on any bucket ≥ 90% — a full session bucket really does block right now — but that marker expires when the session resets, not days later.

The shim prints nothing, logs timestamp account cwd (never prompt text) to selection.log, and execs the real binary — stdin/stdout/exit codes pass through byte-identically. If anything is missing (no manifest, no accounts, unreadable state, even an unset HOME) it fails open into plain passthrough.

Limit-aware marking. claude-accounts limits (every 60s via launchd on the Mac, cron on the server, plus an opportunistic non-blocking kick from the shim when data is

3 min stale) reads each account's own OAuth usage endpoint — the same data /usage shows. Every bucket the endpoint returns is tracked separately: session, weekly_all, and per-model scoped buckets — weekly_scoped:Fable is its own bucket. Any bucket ≥ 90% ⇒ the account gets a .limited marker (with the bucket's reset time) and leaves the random pool until that bucket resets or a refresh shows it back under threshold.

Bucket parsing is shape-agnostic: buckets are derived from whatever the endpoint returns (kind, plus scope.model when present), so if Anthropic renames a bucket, reshapes the payload, or drops the per-model Fable separation entirely, the remaining buckets keep being tracked and the ≥90% rule keeps working. Unparseable entries are skipped, an all-legacy payload falls back to five_hour/seven_day, and a payload the code cannot read at all degrades that one account (fail open), never the run.

Telemetry failures never block work: no fresh data ⇒ account treated as available. The endpoint rate-limits per account, so the refresher skips accounts fetched in the last 45s and backs off exponentially (honoring Retry-After) on a 429 — limits --force overrides both. If an account's OAuth access token has expired (quiet machine, nothing ran claude for hours), the refresher first runs a zero-cost claude auth status under that account to let the app refresh its own credentials, then fetches; if that fails it logs and fails open.

Auto-retry (-p/--print only, default on, CLAUDE_SHIM_RETRY=0 disables): on an auth/rate-limit-looking failure the shim marks the account with a 10-minute error cooldown and retries once on a different account. Output is buffered so a retried call never double-emits. Only engages when stdin is finite (tty / regular file / /dev/null) and ≥2 accounts are eligible; service-spawned pipes take the plain exec path untouched.

Install / update / uninstall

npm (recommended)

npm install -g claude-multiacc     # postinstall wires up the shim, CLI, and scheduled jobs
# or run it once without installing globally:
npx claude-multiacc

claude-multiacc then wraps everything:

claude-multiacc                    # install or update the addon (idempotent)
claude-multiacc self-update        # npm i -g @latest + re-install
claude-multiacc uninstall [--purge-data]
claude-multiacc list | status | add <email> | ...   # passthrough to claude-accounts

Automatic updates: a globally-installed package re-runs install.sh from its own postinstall, so npm i -g claude-multiacc@latest fully updates the shim, CLI, and jobs. A weekly job (launchd on macOS, cron on Linux) runs claude-accounts self-update to pull the latest npm release on its own; set CLAUDE_MULTIACC_AUTOUPDATE=0 before install to opt out. update-notifier also prints a one-line nudge when a newer version is on npm. (Note: newer npm versions gate install scripts; if postinstall is blocked, just run claude-multiacc install once.)

New versions publish to npm automatically from main via GitHub Actions (.github/workflows/publish.yml, OIDC Trusted Publishing — no long-lived token after the first bootstrap publish; scripts/auto-version.mjs bumps the patch above the last release).

From a git checkout

git clone <repo> && cd claude-multiacc && ./install.sh    # macOS or Linux, idempotent
./install.sh --server [email protected]                        # override sync target
git pull && ./install.sh                                  # update (data untouched)
./install.sh --uninstall                # restores stock behavior, keeps account data
./install.sh --uninstall --purge-data   # also deletes ~/.claude-accounts

self-update auto-detects which of these you used (npm global vs git checkout).

What install does (all reversible, nothing else):

  • macOS: marked PATH block at the END of ~/.zshenv, ~/.zprofile, ~/.zshrc (+ bash rc files if present) — end-of-file placement matters because those files re-prepend ~/.local/bin; launchd agents com.claude-multiacc.limits (60s) and .health (weekly Mon 09:17). Warns if this Mac stores credentials in the Keychain (file-based credentials are required for per-dir logins).
  • Linux (root): PATH block in ~/.bashrc + /etc/profile.d/claude-multiacc.sh, shim symlink at /usr/local/bin/claude (shadows via PATH order — on the systemd default PATH too; the original binary is untouched), cron entries for limits/health.
  • Both: ~/.claude-accounts/ skeleton + accounts.json manifest.

Accounts

claude-accounts list      # pool at a glance
claude-accounts status    # per-account auth health, per-bucket limits incl. Fable, markers, last pick

Every account in the pool is authenticated — add cannot register one otherwise.

Adopt this machine's existing login (no browser needed)

claude-accounts import [email protected] --id acct-01 --home mac
claude-accounts adopt acct-01

adopt makes the account dir a symlink to ~/.claude — the machine's existing login joins the pool with a single credential file (no copy of the OAuth grant exists anywhere else, so refreshes can never race). This is how the two current accounts run: acct-01 = the Mac's login, acct-02 = the server's login (adopted server-side).

Add a fresh account (~1 min, one browser sign-in)

claude-accounts add               # no email needed — read back from the sign-in
# or name it explicitly (fast-fails on a duplicate, and pre-fills the login page):
claude-accounts add [email protected]

This runs the normal full Claude Code login — the exact same claude auth login flow the app itself uses (claude.com/cai/oauth/authorize, full Claude Code scopes). A browser opens (or a sign-in link is printed); sign in to the account you're adding, approve, and if a code is shown, paste it back. It draws on the subscription — never API keys.

Using the full login on purpose: it does not hit the "Sign in again to continue" step-up wall that the narrow long-lived-token grant triggers, and it stores an auto-refreshing .credentials.json. That credential is machine-local (never synced), which is what keeps two machines from invalidating each other's refresh token — so an account you add on the Mac runs on the Mac, and you add it on the server (over SSH) if you want it there too. The account is registered only after the signed-in email is read back; an aborted login leaves nothing behind, and duplicate emails are refused.

claude-accounts login acct-NN         # complete/refresh an existing account (full login)
claude-accounts add [email protected] --token   # portable setup-token instead: works on BOTH
                                              # machines and syncs to the server, but needs a
                                              # recent sign-in (may show the step-up screen)
claude-accounts login acct-NN --token         # same, for an existing account

Use --token when you specifically want one account usable on the server via the Mac's sync (the ~1-year inference-only token that mirrors over). For everyday adds, the default full login is smoother. status warns when a token nears end of life; re-run login.

Both accounts currently in the pool are live and verified: acct-01 ([email protected], Mac) and acct-02 ([email protected], server) — each adopted from its machine's existing login, so each runs on the machine that holds its credential.

Remove

claude-accounts remove acct-NN          # deletes locally, propagates to the server

Server sync

Mac → server, one-way (Mac = source of truth), fired automatically by add/import/remove/mint and manually via:

claude-accounts sync

Pushes the manifest + per-account server.token (0600) + one-time seeds; never syncs .credentials.json in either direction (two machines refreshing one OAuth grant invalidate each other). Removals propagate; an empty/corrupt manifest refuses to sync (guard against wiping the server pool). After every push the server re-seeds dirs and re-runs its quick verification matrix (post-sync hook). Everything logs to sync.log; failures are loud and non-zero.

Pinning & env switches

| Env | Effect | | --- | --- | | CLAUDE_ACCOUNT=acct-03 | pin this invocation to one account (wins over markers) | | CLAUDE_CONFIG_DIR=... | shim passes straight through (scripts can pin the old way) | | CLAUDE_MULTIACC_DISABLE=1 | bypass selection entirely | | CLAUDE_SHIM_RETRY=0 | disable the -p auto-retry | | CLAUDE_ACCOUNTS_DIR=... | relocate the pool (used by the test suite) |

Verification

tests/run-tests.sh            # 35 sandboxed tests, no quota
claude-accounts verify        # real matrix: `claude -p "reply OK"` per authed account
claude-accounts verify --quick# auth presence/expiry only, no inference
claude-accounts limits        # live per-bucket usage incl. the Fable bucket
claude-accounts health        # limits + full verify, logs to health.log, notifies on failure (Mac)

Verified end-to-end on both machines (2026-07-13): full matrix PASS, 20-invocation shim spread clean, real project -p through the shim OK on Mac (~/pipeline) and server (/opt/inapp-onboarding), uninstall→stock→reinstall roundtrip byte-identical, three server systemd services (vpn-server-management, inapp-onboarding, vpn-seo-panel) repointed via CLAUDE_BIN=/usr/local/bin/claude and restarted healthy.

Troubleshooting

  • claude resolves to the real binary, not the shim — open a new shell, or check that the marked block is the LAST PATH manipulation in your rc file (grep -A2 'claude-multiacc >>>' ~/.zshrc).
  • An account never gets pickedclaude-accounts status: no auth on this machine, or an active .limited marker (shows bucket + minutes to reset).
  • Everything marked limited — the shim still runs (least-utilized fallback); check selection.log for all-limited lines.
  • Sync failstail ~/.claude-accounts/sync.log; it's ssh/rsync to the manifest's server (BatchMode — needs key auth).
  • A service bypasses the shim — it spawns an absolute path. Point its env (CLAUDE_BIN etc.) at /usr/local/bin/claude. Docker-internal claude installs (e.g. openclaw live-test containers) are out of scope for a host shim.
  • add sign-in says "Sign in again to continue" — that's Claude's step-up authentication, not a tool error: granting a long-lived token needs a recent sign-in. Open a fresh incognito window, sign in to the target account at claude.ai first, then paste the link and approve. The approval scope is user:inference ("Contribute to your Claude subscription usage") — that's correct and minimal.
  • Limits look stale — launchd: launchctl list | grep claude-multiacc; cron: crontab -l | grep claude-multiacc. The shim also kicks a refresh when data is >3 min old.

Security notes & known risks

  • Tokens and credentials live as 0600 files under ~/.claude-accounts (and /root/.claude-accounts on the server). Server compromise = account access.
  • selection.log records timestamps/account/cwd only — never prompt text.
  • Rotating multiple subscriptions to spread usage may be flagged by anti-abuse systems; accounts can be banned for limit circumvention. Known and accepted by the operator.
  • The usage endpoint is the internal one /usage consumes; if it changes shape, limit tracking fails open (accounts stay available, error-retry is the backstop).