CostGuard audits your repos and cloud providers for CI and infrastructure cost leaks. It is built for developers who run several projects across GitHub Actions, Vercel, Supabase, Railway, Netlify, Neon, Cloudflare, and more, and want a single command that surfaces what is being wasted and exactly how to fix it. On repositories that have never been optimized, the static audit alone typically reduces CI spend by 60–80%.
CostGuard installs three ways. The plugin paths are zero-build and zero-npm
(they ship a prebuilt, self-contained bundle); the npm package gives you the CLI
and the MCP server anywhere.
/plugin marketplace add mbanderas/costguard
/plugin install costguard@costguardAdds /costguard-audit, /costguard-fix, /costguard-live, and the full
costguard skill (scan, providers, registry, report, digest). No build step, no
npm install.
codex plugin marketplace add mbanderas/costguard
codex plugin add costguard@costguardAdds the bundled costguard skill to Codex CLI and Desktop.
Run the MCP server directly — no install:
npx -y @costguard/costguard-mcpInstall the MCP server and the costguard CLI globally (both bins):
npm i -g @costguard/costguard-mcpRun the CLI ad-hoc without installing:
npx -y -p @costguard/costguard-mcp costguardnpx -y @costguard/costguard-mcp launches the MCP server directly because the
package name matches its bin entry point — drop it straight into any MCP host's
server config.
Other CLIs / Desktop apps — one command drops a thin
/costguardadapter into Cursor, Gemini CLI, Cline, Windsurf, or a Codex project:npx -y -p @costguard/costguard-mcp costguard install --target <host|auto>
--target autodetects the host; the adapter is no-clobber and idempotent. Runcostguard install --helpfor the full target list.
Static half (zero credentials). Reads .github/workflows/*.yml and
application code to detect redundant CI triggers (push + pull_request on the
same branch), missing timeout-minutes, missing concurrency cancellation,
paths-ignore gaps that run CI on doc-only commits, and over-scheduled crons. No
API call, no token needed — always safe to run.
Billing half (read-only, opt-in). When a provider token is present, reconciles live billed resources against a declared allowlist and flags orphaned resources (billed but not listed) and over-provisioned resources (larger or more capable than declared), each with a best-effort estimated monthly cost. Covers thirteen providers: GitHub, Vercel, Supabase, Railway, Netlify, Neon, Cloudflare, Fly, Render, Sentry, Upstash, MongoDB Atlas, and Datadog.
Tested on production repositories. Patterns found and fixed across repositories
like my-app, web-app, and api-service:
- Redundant CI triggers. A workflow wired to both
pushandpull_requeston the same branch runs CI twice per commit. Dropping the redundant trigger halves runner-minute consumption for every push. - Missing timeouts. Jobs without
timeout-minutesburn minutes up to GitHub's 6-hour cap on hung runs. Adding a sane timeout is a one-line fix with no functional impact. - Missing concurrency cancellation. Without a
concurrencyblock, rapid pushes queue up and run sequentially instead of cancelling superseded runs. Addingcancel-in-progress: trueeliminates the queue. paths-ignoregaps. Workflows withoutpaths-ignorefor documentation paths run full CI on commit-message and README edits. Excluding**.mdanddocs/**removes a class of runs entirely.- Orphaned preview branches. A Supabase preview branch left running after a feature ships keeps accruing compute cost each billing cycle.
On repositories that have accumulated these patterns over time, fixing all of them has reduced observed CI spend by up to 80%. The typical range on repositories never previously optimized is 60–80%. Results vary by workflow structure and push cadence; these are observed, directional outcomes, not a guarantee.
Every host drives the same read-only engine — reach for it the native way. Each block below is self-contained; use the one for your host.
Three slash commands plus the full costguard skill (scan, providers, registry,
report, digest):
/costguard-audit— audit a workspace for CI/cron and cloud-spend waste./costguard-fix— preview or apply the safe in-repo CI fixes (dry-run first)./costguard-live— opt-in, consent-gated live billing read for a provider.
Invoke the bundled costguard skill by name, then say what you want:
- "Use the costguard skill to audit my-app for CI waste."
- "With costguard, show a dry-run fix for web-app's workflows."
- "Run a costguard provider billing check across api-service."
Install the /costguard adapter once for your host:
costguard install --target cursor
costguard install --target gemini
costguard install --target cline
costguard install --target windsurfThen drive it with any costguard arguments:
/costguard audit my-app
/costguard fix my-app --apply
(If costguard is not yet on your PATH, prefix the install command with
npx -y -p @costguard/costguard-mcp .)
Copy-paste CostGuard's MCP server into your host's config — no checkout:
{
"mcpServers": {
"costguard": {
"command": "npx",
"args": ["-y", "@costguard/costguard-mcp"],
"env": {
"GITHUB_TOKEN": "…",
"SUPABASE_ACCESS_TOKEN": "…",
"RAILWAY_TOKEN": "…",
"NETLIFY_AUTH_TOKEN": "…",
"NEON_API_KEY": "…"
}
}
}
}Every token is optional and read-only; a provider with no token is skipped. See Environment variables for the full list and aliases.
npx -y @costguard/costguard-mcp (no -p, no subcommand) starts the MCP
server for this config; npx -y -p @costguard/costguard-mcp costguard <subcommand> runs the CLI.
Provider tokens are read only from the process environment or a gitignored
.env in the CostGuard workspace. They are never printed, logged, or committed.
Each provider module runs only when one of its tokens is present; offline, the
modules are fully exercised by fixtures. All tokens are used read-only.
| Variable (any one of) | Provider | Used for |
|---|---|---|
GITHUB_TOKEN / GH_TOKEN |
github | Actions usage per repo (read-only billing PAT) |
SUPABASE_ACCESS_TOKEN / SUPABASE_TOKEN |
supabase | Projects, compute size, PITR, branches |
RAILWAY_TOKEN / RAILWAY_API_TOKEN |
railway | Services, deploys, usage (read-only GraphQL) |
NETLIFY_AUTH_TOKEN / NETLIFY_TOKEN |
netlify | Sites, build minutes, bandwidth |
NEON_API_KEY / NEON_API_TOKEN |
neon | Projects, branches, compute hours |
VERCEL_TOKEN / VERCEL_API_TOKEN |
vercel | Team deploying seats vs deploy activity |
SENTRY_AUTH_TOKEN / SENTRY_TOKEN |
sentry | Monthly error events vs plan quota |
UPSTASH_API_KEY / UPSTASH_TOKEN |
upstash | Redis DB commands + storage |
ATLAS_API_KEY / MONGODB_ATLAS_TOKEN |
atlas | Cluster tiers + data size |
CLOUDFLARE_API_TOKEN / CF_API_TOKEN |
cloudflare | R2 buckets, storage, class A/B ops |
FLY_API_TOKEN / FLY_ACCESS_TOKEN |
fly | Apps + dedicated IPv4 addresses |
RENDER_API_KEY / RENDER_TOKEN |
render | Service instance plans |
DD_API_KEY / DATADOG_API_KEY |
datadog | Enables the module (declaration-only; APM host counts read from config) |
COSTGUARD_DIGEST_WEBHOOK |
— | Optional digest --post destination (inert in this build) |
Use providers --check to confirm which tokens the environment exposes without
revealing any value.
CostGuard ships thirteen read-only, opt-in provider modules, and the roster
is actively expanding. Each reads live billed resources, reconciles them against
the registry active{} allowlist, and emits orphaned and over-provisioned
findings with a best-effort $/mo.
| Module | Reads | Flags |
|---|---|---|
| github | Actions usage per repo | top minute-burners; repos over budget |
| supabase | Projects, compute size, PITR/add-ons, branches | running preview branches; compute/PITR drift vs registry |
| railway | Services, deploys, usage (read-only GraphQL queries) | idle services; deploys never torn down |
| netlify | Sites, build minutes, bandwidth | build-minute spend; runaway bandwidth |
| neon | Projects, branches, compute hours | idle branches; orphaned (defunct but billed) projects |
| vercel | Team deploying seats vs deploy activity | idle paid deploying seats |
| sentry | Monthly error events | error-event overage vs plan quota |
| upstash | Redis DB commands + storage | pay-as-you-go cost above a fixed plan |
| atlas | Cluster tiers + data size | oversized cluster for its data |
| cloudflare | R2 buckets, storage, class A/B ops | operation-heavy R2 spend |
| fly | Apps + dedicated IPv4 addresses | dedicated IPv4 on non-critical apps |
| render | Service instance plans | oversized instance for its environment |
| datadog | APM host counts (declared in config) | excess provisioned APM hosts |
All live provider access is HTTP GET; the railway module uses GraphQL
queries only, guarded against mutations. The datadog module is
declaration-only — it reconciles operator-declared host counts offline and makes
no network call. No module ever issues a write or delete call. A module
activates only when its token (above) is present; otherwise it is skipped.
More providers are on the way — coverage is expanding as new billing surfaces are researched and sourced.
CostGuard is built to be safe to run anywhere, including on a schedule:
- Read-only provider access only. No write or mutating API call is ever issued. Provider tokens are read-only and are never printed, logged, or committed.
- Secrets stay out of the repo. Tokens are read from the environment or a gitignored
.env*only.providers --checkreports presence by variable name, never by value. - The static half needs no credentials.
audit(without--providers) andscanread only local files and are always safe to run. fixis in-repo and dry-run by default. It edits only.github/workflows/*files in the target workspace, never provider or cloud state, and writes nothing until--apply.- Outward actions are inert and gated.
fix --open-pranddigest --postrefuse to act without an explicit opt-in flag and the matching credential, and even then perform no git push or network post in this build.
workspaces.json is the registry of projects CostGuard tracks. registry --init
scans workspacesRoot (default: ~/Workspaces) and writes a fresh file with
auto-detected providers arrays (GitHub, Netlify, Supabase, etc.) and blank
active{} blocks. A starter workspaces.example.json
ships with this repo.
{
"root": "~/Workspaces",
"workspaces": {
"my-app": {
"providers": ["github", "netlify"],
"active": {}
}
}
}The active{} block is the allowlist used by the provider checks: any live
resource not listed there is flagged as orphaned, and any resource larger or
more capable than declared is flagged as over-provisioned. Leave it empty if
you only run the static half; the provider modules then have nothing to reconcile
against.
Create costguard.config.json in the project root to override defaults:
{
"workspacesRoot": "~/Workspaces",
"defaults": {
"cronThresholdMinutes": 15,
"ciMinuteRate": 0.008,
"assumedPushesPerDay": 10,
"assumedMinutesPerRun": 5
},
"perWorkspace": {
"my-app": {
"cronThresholdMinutes": 30
}
}
}| Key | Default | Description |
|---|---|---|
cronThresholdMinutes |
15 |
Crons running more often than this threshold are flagged |
ciMinuteRate |
0.008 |
USD per runner-minute (GitHub-hosted Linux) |
assumedPushesPerDay |
10 |
Estimated daily push cadence for cost projection |
assumedMinutesPerRun |
5 |
Assumed wasted minutes per redundant CI run |
Per-workspace overrides in perWorkspace merge on top of defaults.
A monthly digest can be wired to GitHub Actions via the documented, inert
scheduler template templates/costguard-digest.yml.
- It lives under
templates/— not.github/workflows/— so it never runs automatically and is not enabled by this project. - To activate: a human copies it into
.github/workflows/in the target repo and supplies the required secrets (e.g.COSTGUARD_DIGEST_WEBHOOK). - To roll back: delete the copy from
.github/workflows/.
Activating the template is a deliberate human action outside CostGuard's own runtime.
These flags apply to the costguard CLI, available via npm i -g @costguard/costguard-mcp or npx -y -p @costguard/costguard-mcp costguard <subcommand>.
All commands operate on the workspaces.json registry in the project root.
Workspace selection is by directory name; --all selects every registered
workspace. Examples use the global costguard command; from a checkout you can
equivalently run node dist/cli/index.js <command>.
Run the static CI/cron audit, optionally adding read-only provider billing checks, and print a report to stdout.
# Audit a single workspace (static checks only)
costguard audit my-app
# Audit everything at once
costguard audit --all
# CI-minutes check only
costguard audit my-app --ci-only
# Cron check only, JSON output
costguard audit my-app --crons-only --json
# Add provider billing checks for specific providers (only those whose token is present)
costguard audit --all --providers github,supabase
# Add provider checks for every provider whose token is present
costguard audit --all --providers all| Option | Effect |
|---|---|
--all |
Audit all registered workspaces |
--ci-only |
Run only the CI-minute checks |
--crons-only |
Run only the cron-frequency checks |
--site |
Also run read-only live-site checks for workspaces whose registry entry has a site URL (see site) |
--substitutions |
Add cross-tool <provider>/cheaper-alternative suggestions (e.g. a static Vercel/Netlify Pro site → Cloudflare Pages), each with a sourced saving, migration effort, and lock-in caveat |
--providers <list> |
Add read-only provider billing checks. Comma-separated ids (github,supabase,railway,netlify,neon) or all. A provider is only contacted when its token is present (see Environment variables); others are silently skipped. |
--json |
Emit JSON instead of Markdown |
Static-only audit across all workspaces. A convenience alias intended for a single catch-all CI step; it never touches provider credentials.
costguard scan
costguard scan --ci # CI minutes only
costguard scan --crons # Cron schedules onlyReport which provider tokens are present in the environment, by environment-variable name only. Secret values are never read into output, printed, or logged.
costguard providers --check--check is the default action.
Detect which providers a repo actually uses — from config files, package.json
dependencies, and environment-variable names (never values, never secrets).
Covers all 13 wired providers plus inngest, so you don't hand-edit the registry.
# List detected providers + the evidence for each (default dir: .)
costguard discover ./my-app
# JSON: { dir, providers, detections }
costguard discover ./my-app --json
# Union-merge detected providers into ./workspaces.json (non-destructive)
costguard discover ./my-app --write--write preserves any existing providers, the active{} block, and every other
workspace; it only adds newly detected providers for basename(dir).
Audit a live URL for cost-relevant waste — read-only and GET-only (no
browser, no form submit, no credential replay). It flags transfer weight,
oversized images, missing compression, weak cache headers, and render-blocking
scripts. The page's $/mo headline is the single site/transfer-weight line
(sourced when the host bills transfer — Vercel/Netlify — or an explicit $0
performance note when it doesn't, e.g. Cloudflare Pages static / unknown host;
never a fabricated number). Per-asset findings (oversized-image,
missing-compression) report their dollar share in the detail text and carry
estMonthlyUsd: 0 so the headline isn't double-counted. A $0 performance-only
page never raises a high finding, so it never fails CI on cost alone.
costguard site https://example.com
costguard site https://example.com --jsonUse audit --site to run the same checks for every workspace whose
workspaces.json entry declares a site URL.
Manage the workspace registry (workspaces.json).
# List all registered workspaces and detected providers
costguard registry --list
# Validate the registry against the filesystem
costguard registry --validate
# Scan ~/Workspaces and write a fresh workspaces.json
costguard registry --init--list is the default when no option is given.
Re-render the most recent saved audit run without re-scanning.
costguard report --last
costguard report --last --jsonDeterministically auto-fix the safe CI rules in-repo: paths-ignore,
concurrency, and timeout-minutes. It only edits .github/workflows/* files
inside the target workspace and never touches provider or cloud state. It
defaults to a dry run — it prints a unified diff and writes nothing until you
pass --apply.
# Dry run: print the unified diff for a workspace, write nothing (default)
costguard fix my-app
# Dry run across all workspaces
costguard fix --all
# Write the edits to disk (idempotent — safe to re-run)
costguard fix my-app --apply
# Write local PR artifacts (branch name, patch, PR body) under ~/.costguard/pr/
costguard fix my-app --pr| Option | Effect |
|---|---|
--all |
Fix all registered workspaces |
--apply |
Write the edits to disk. Idempotent. Omit for a dry-run preview. |
--pr |
Write local PR artifacts (branch.txt, fix.patch, pr-body.md) under ~/.costguard/pr/<workspace>/. No network or git action. |
--open-pr |
Gated and inert. Refuses unless both the --open-pr flag and a non-empty GITHUB_TOKEN are present, and even then performs no git branch, commit, or push — this build is dry-run only. |
Produce a concise monthly summary — total $/mo, a per-provider breakdown,
and the top findings — distinct from the full report. It defaults to printing
to stdout (a dry run). The digest deliberately omits per-finding detail/fix
text; run report --last for the full breakdown.
# Print the monthly digest to stdout (default)
costguard digest --all
# Render from the last saved run
costguard digest --last
# JSON output
costguard digest --all --json
# Write it to a local file instead of stdout
costguard digest --all --out digest-2026-05.md| Option | Effect |
|---|---|
--all |
Build the digest across all registered workspaces |
--last |
Render the digest from the last saved run instead of re-scanning |
--json |
Emit JSON instead of Markdown |
--out <file> |
Write the digest to a local file instead of stdout |
--post |
Gated and inert. Requires both the --post flag and a COSTGUARD_DIGEST_WEBHOOK env var; even then it performs no network post. It only reports the message it would post. |
| Code | Meaning |
|---|---|
0 |
All checks passed (or only INFO/WARN findings) |
1 |
At least one HIGH severity finding (CI gate signal) |
1 |
Error loading registry, config, or invalid arguments |
The plugin and npm installs are prebuilt — you only need a build when developing from a checkout:
pnpm install
pnpm build # emits dist/cli/index.js and dist/mcp/server.js
pnpm testGitHub Actions is metered on private repos, so CI is gated to run only when the repo is public or via manual dispatch. Run the exact same checks locally instead:
pnpm verify # typecheck + lint + test + check:dist (mirrors .github/workflows/ci.yml)Enable the pre-push hook to run pnpm verify automatically before every push:
git config core.hooksPath .githooks # once per clone
git push --no-verify # bypass for a single push (e.g. docs-only)To execute the actual ci.yml locally in a container — the closest thing to
GitHub's runner without spending minutes — use act
with Docker Desktop running:
gh extension install nektos/gh-act # once per machine
pnpm ci:docker # runs the ubuntu-latest leg via .actrcact runs Linux containers only, so it covers the ubuntu-latest matrix leg;
the windows-latest leg is covered by pnpm verify on the host. The first run
pulls the runner image (~1GB), then caches it.
- Maestro: Frontier multi-CLI fusion engine and orchestration discipline layer for AI coding agents. CostGuard finds the cost leaks; Maestro keeps the agents that fix them disciplined — verified done-claims, surgical scope, and a research-backed multi-agent gate.
- Govyn: Open-source AI agent governance proxy — your agents never hold real API keys, stay within budget, and follow policy. CostGuard audits the spend; Govyn enforces the guardrails at runtime.
- GitHub: https://github.com/mbanderas/costguard
- Issues: https://github.com/mbanderas/costguard/issues
- License: MIT
