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

budget-guard

v0.6.0

Published

A circuit breaker for your LLM API bill — hard budget caps + per-feature cost attribution for the OpenAI & Anthropic APIs.

Readme

budget-guard

CI CodeQL OpenSSF Scorecard Security Policy npm version npm downloads license

A circuit breaker for your LLM API bill. One wrap, set a hard daily cap — runaway retry loops get blocked before they bill you. Plus per-feature cost attribution so you know what actually costs what.

Built for indie devs shipping on the OpenAI / Anthropic APIs who've seen "a $40 bill from a $5 task." Drop-in: your calls still go straight to the provider — budget-guard just counts and caps.

Free & open source (MIT). A hosted dashboard with cross-project spend + alerts is planned — but the SDK is, and stays, free.

Install

npm i budget-guard

Use it (OpenAI)

import OpenAI from 'openai';
import { guard } from 'budget-guard';

const openai = new OpenAI();
const ai = guard(openai.chat.completions, { project: 'my-app', dailyCapUSD: 50 });

// use it exactly like before — just add an optional feature tag
const res = await ai.create(
  { model: 'gpt-4o', messages: [{ role: 'user', content: 'hi' }] },
  { feature: 'chat' },
);

If today's spend for my-app is already past $50, the next call throws BudgetExceededError before it bills. No more 3am surprise invoices.

Use it (Anthropic)

import Anthropic from '@anthropic-ai/sdk';
import { guard } from 'budget-guard';

const anthropic = new Anthropic();
const ai = guard(anthropic.messages, { project: 'my-app', dailyCapUSD: 50 });

await ai.create(
  { model: 'claude-opus-4', max_tokens: 1024, messages: [{ role: 'user', content: 'hi' }] },
  { feature: 'summarize' },
);

budget-guard auto-detects OpenAI (prompt_tokens/completion_tokens) and Anthropic (input_tokens/output_tokens) usage shapes. For anything else, pass your own extractor:

guard(client, opts, { usageOf: (res) => ({ input: res.in, output: res.out }) });

Know what costs what

import { spendReport } from 'budget-guard';

await spendReport('my-app'); // async
// → { chat: 2.41, summarize: 0.88 }   (today, in USD)

Shared caps across instances (Redis)

By default the ledger lives in memory (per process) — great for a single script, worker, or agent. Running multiple instances? Pass a shared store so they enforce one cap together and survive restarts:

import { createClient } from 'redis';
import { guard, redisStore } from 'budget-guard';

const redis = createClient();
await redis.connect();

const ai = guard(openai.chat.completions, {
  project: 'my-app',
  dailyCapUSD: 50,
  store: redisStore(redis), // node-redis v4; keys auto-expire (~2 days)
});

store accepts anything implementing the tiny SpendStore interface (add / get / entries), so you can back it with whatever you already run.

Block before the call (no overshoot)

By default the cap is enforced on the next call after you cross it, so one call can overshoot. Give it an estimator and it blocks the offending call itself:

import { encode } from 'gpt-tokenizer'; // or any tokenizer

const ai = guard(openai.chat.completions, {
  project: 'my-app',
  dailyCapUSD: 50,
  estimateUsage: (args) => ({
    input: args.messages.reduce((n, m) => n + encode(m.content).length, 0),
    output: args.max_tokens ?? 512,
  }),
});

LlamaIndex.TS

Wrap any LlamaIndex LLM — the cap applies before each call and non-streaming chat() is metered from the response:

import { guardLlamaIndex } from 'budget-guard';

const llm = guardLlamaIndex(openai({ model: 'gpt-4o' }), { project: 'my-app', dailyCapUSD: 50 });
Settings.llm = llm; // or call llm.chat(...) directly

Usage is read from response.raw (works across providers). Streaming chat() still enforces the cap; streaming metering is coming next. Zero new dependencies.

LangChain.js

Attach the callback handler to any LangChain model or chain — the cap is enforced before each call, and cost is metered from the response:

import { BudgetGuardHandler } from 'budget-guard/langchain';

const handler = new BudgetGuardHandler({ project: 'my-app', dailyCapUSD: 50, model: 'gpt-4o' });
await model.invoke(input, { callbacks: [handler] }); // over cap → throws before the call

Reads usage from usage_metadata (falling back to llmOutput.tokenUsage). Pass model for reliable pricing (it's also auto-detected from the response when present). Needs @langchain/core (an optional peer dependency).

Typed per-provider helpers

guardOpenAI / guardAnthropic / guardGemini are thin wrappers that set provider for you — so streaming is metered correctly without remembering the option:

import { guardAnthropic } from 'budget-guard';

const ai = guardAnthropic(anthropic.messages, { project: 'my-app', dailyCapUSD: 50 });
// streaming already knows it's Anthropic — no `provider` to forget

Streaming

Streaming calls are metered too — just pass stream: true as usual. budget-guard passes every chunk straight through to you and reads the usage from the final chunk, so the cost lands once, after the stream finishes:

const ai = guard(openai.chat.completions, { project: 'my-app', dailyCapUSD: 50 });

const stream = await ai.create({ model: 'gpt-4o', stream: true }, { feature: 'chat' });
for await (const chunk of stream) {
  process.stdout.write(chunk.choices[0]?.delta?.content ?? '');
}
// cost is recorded once the loop finishes

For OpenAI it injects stream_options: { include_usage: true } for you (OpenAI only sends usage on the final chunk when that flag is set). The cap is still enforced before the call.

For Anthropic streaming, set provider: 'anthropic' — it reads usage from the message_start / message_delta events and skips the OpenAI-only injection:

const ai = guard(anthropic.messages, {
  project: 'my-app',
  dailyCapUSD: 50,
  provider: 'anthropic',
});
const stream = await ai.create({ model: 'claude-sonnet-4-6', stream: true, max_tokens: 1024 });
for await (const event of stream) { /* ... */ }

For Gemini streaming, set provider: 'gemini' — usage comes from each chunk's usageMetadata (the last one carries the totals).

Vercel AI SDK

Using the AI SDK? Wrap any model with the middleware — no client to guard, the cap and per-feature metering apply automatically:

import { wrapLanguageModel, generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { budgetGuardMiddleware } from 'budget-guard';

const model = wrapLanguageModel({
  model: openai('gpt-4o'),
  middleware: budgetGuardMiddleware({ project: 'my-app', dailyCapUSD: 50, feature: 'chat' }),
});

await generateText({ model, prompt: 'hi' }); // over cap → throws before the model call

Meters both generateText and streamText (usage read from the stream's finish part). Over-cap streamText blocks before the model call; the error arrives on the SDK's standard error channel (onError, or await result.text) — textStream swallows stream errors by default.

Mastra

Mastra agents run on Vercel AI SDK models, so the middleware above already covers them — wrap the model before handing it to your agent, no Mastra-specific code:

import { wrapLanguageModel } from 'ai';
import { Agent } from '@mastra/core/agent';
import { budgetGuardMiddleware } from 'budget-guard';

const model = wrapLanguageModel({
  model: openai('gpt-4o'),
  middleware: budgetGuardMiddleware({ project: 'my-app', dailyCapUSD: 50 }),
});

const agent = new Agent({ id: 'support', model, instructions: '…' });
// (or withMastra(model, { … }) if you use @mastra/ai-sdk directly)

The cap and metering apply to every model call the agent makes.

See every call's cost (observability)

A hard cap stops the bleeding; onSpend lets you watch it. It fires on every successful call with a SpendEvent, so you can pipe per-call cost straight into your logs, traces, or a dashboard:

const ai = guard(openai.chat.completions, {
  project: 'my-app',
  dailyCapUSD: 50,
  onSpend: (e) => {
    // { project, feature, model, usd, dayTotalUsd }
    console.log(JSON.stringify({ evt: 'llm_spend', ...e }));
  },
  onExceeded: ({ project, spentUsd, capUsd }) => {
    metrics.increment('llm.cap_hit', { project }); // fires before block/warn
  },
});

Keep the callback light — it runs synchronously just before the response is returned. Push heavy work (network, disk) onto a queue.

Monthly caps & time zones

Cap per month instead of per day, and reset on your billing time zone's calendar (not UTC):

const ai = guard(openai.chat.completions, {
  project: 'my-app',
  dailyCapUSD: 500,          // the cap for the period
  period: 'monthly',         // 'daily' (default) | 'monthly'
  timezone: 'America/New_York', // optional IANA zone; default UTC
});

An invalid timezone throws at construction. With a redisStore, set ttlSeconds to cover a month (e.g. 60 * 60 * 24 * 40) so monthly counters don't expire early. To read the current period's total, use periodKey:

import { spentTotal, periodKey } from 'budget-guard';
await spentTotal('my-app', store, periodKey(new Date(), 'monthly', 'America/New_York'));

Options

guard(client, {
  project: 'my-app',     // groups spend & shares one cap
  dailyCapUSD: 50,       // hard cap per day
  onCap: 'block',        // 'block' (throw) | 'warn' (log only). default 'block'
  store: myStore,        // optional SpendStore (default: in-memory, per-process)
  estimateUsage: fn,     // optional: block before a call would exceed the cap
  onSpend: fn,           // optional: SpendEvent per successful call (logs/traces)
  onExceeded: fn,        // optional: fires when the cap is hit (before block/warn)
  provider: 'anthropic', // optional: 'openai' (default) | 'anthropic' | 'gemini' — for streaming
  onMissingUsage: 'zero',// optional: 'throw' (default) | 'zero' — when a response has no usage
});

Warn instead of block

const ai = guard(openai.chat.completions, { project: 'my-app', dailyCapUSD: 50, onCap: 'warn' });
// over cap → logs a warning, still calls. Good for easing in.

Examples

Runnable, no API key needed — see examples/:

Notes (v0.2)

  • Multi-instance + persistence via a pluggable SpendStore (in-memory default, Redis adapter included, or bring your own).
  • No-overshoot mode when you supply estimateUsage; otherwise the cap is enforced on the next call after you cross it.
  • spendReport() is async.
  • Prices live in PRICES (USD per 1K tokens) — PRs to keep them current are welcome.
  • Roadmap: streaming usage, more providers, a hosted dashboard. See ROADMAP.

Migrating from 0.1

spendReport() is now async — add await. Everything else is backward compatible (no store = same in-process behavior).

License

MIT