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

adops

v0.1.0

Published

The Google Ads CLI Google never shipped: browse and bulk-edit your account from the terminal, plus cross-platform reporting for Meta.

Readme

adops

The Google Ads CLI Google never shipped — with a cross-platform reporting layer.

Google gives you Google Ads Editor, a desktop GUI you can't script, cron, or pipe. adops is the missing command-line equivalent: browse your account, mine waste, and bulk-edit campaigns, keywords, budgets, and ads — every change staged as a reviewable plan and dry-run against Google's own API before it touches anything. On top of Google it adds one thing no single-platform tool can: Meta spend in the same table, so pacing and reporting are genuinely cross-platform.

$ adops spend
PLATFORM      SPEND      BUDGET   PACE   PROJECTED
google      $31,204     $50,000   103%     $51,633
meta        $44,890     $80,000    93%     $74,504
blended $76,094 / $130,000 · 1,966 conv · $38.70 CAC · day 19/31

Why Google-primary?

In April 2026 Meta shipped its own official Ads CLI + MCP connectors — a free, first-party way to create and edit Meta campaigns from a terminal. Competing with a platform's own tool on its own turf is a losing game, so adops doesn't: it goes deep where there's still no first-party CLI (Google), and keeps Meta for the one thing Meta's Meta-only tool can't do — showing both platforms side by side. For deep Meta writes, use Meta's CLI; for a unified view and a real Google editor, use this.

Status: v0.1 — 18 commands built, 58 tests green (mocked at the HTTP seam). Implemented: auth · accounts · report · spend · audit · watch · export · plan/apply/log · gads query · gads campaigns · gads keywords · gads ads · gads terms · gads negatives · gads budgets · gads urls check · meta insights · meta fatigue. Everything else in this README is spec (🔜) and errors honestly if you try it. Not yet validated against the live APIs — the field-name and mutate-payload assumptions are unverified until a Google developer token and a Meta system-user token go in (see the annoying parts).


Principles

  1. Text in, text out. Every command prints a table for humans and --json / --csv for machines. Everything composes with pipes.
  2. Read-only by default. Anything that writes prints a plan and stops. Nothing mutates without --apply, and every apply runs Google's native validateOnly dry-run first — the API itself checks the change before it executes.
  3. Built for cron. Quiet by default, meaningful exit codes: 0 fine, 1 error, 2 alert condition met. Schedulers and CI understand it.
  4. Your account belongs in git. adops export serializes account structure to files. Diff it, PR it, blame it.
  5. No magic. No AI bidding, no "optimization score," no telemetry. The tool does exactly what the command says.

Install

# not on npm yet — from a clone:
npm install && npm run build && npm i -g .   # Node ≥ 20

# later: npm i -g adops
# later still: curl -fsSL https://adops.sh/install | sh

Yes, the domain will be the install script.

Quickstart

adops auth google     # browser OAuth once; refresh token → OS keychain
adops auth meta       # system-user token recommended (doesn't expire in 60 days)
adops accounts        # list every account you can touch, both platforms

# first pull — raw GAQL, no boilerplate:
adops gads query "SELECT campaign.name, metrics.cost_micros, metrics.conversions
                  FROM campaign WHERE segments.date DURING LAST_7_DAYS
                  ORDER BY metrics.cost_micros DESC" --csv > last7.csv

# first cross-platform report:
adops report last-7d --by campaign

Configuration

~/.config/adops/config.toml, overridable per-invocation with --profile <name> (agencies: one profile per client).

[profiles.acme]
google_customer_id = "123-456-7890"
google_login_customer_id = "111-222-3333"   # MCC, if any
meta_ad_account = "act_1234567890"
currency = "USD"

[profiles.acme.budgets]                     # used by `adops spend`
google = 50_000
meta   = 80_000

Every credential also reads from env (CI-friendly): GOOGLE_ADS_DEVELOPER_TOKEN, GOOGLE_ADS_OAUTH_CLIENT_ID, GOOGLE_ADS_OAUTH_CLIENT_SECRET, GOOGLE_ADS_REFRESH_TOKEN, GOOGLE_ADS_LOGIN_CUSTOMER_ID, META_ACCESS_TOKEN, META_AD_ACCOUNT_ID, ADOPS_PROFILE.


Commands

Conventions: reads are plain commands; writes are verbs that produce a plan unless --apply is passed. - reads from stdin. --json, --csv, --table everywhere. NO_COLOR respected.

Cross-platform

| Command | What it does | |---|---| | adops auth <google\|meta> | OAuth / token setup; secrets stored in the OS keychain | | adops accounts | List accessible accounts and their IDs across both platforms | | adops report [preset] | Unified performance report (last-7d, mtd, custom --from --to), --by campaign\|adgroup\|ad\|day | | adops spend | Pacing vs. configured budgets: burn rate, month-end projection, blended CAC/ROAS | | adops audit | Run the QA checklist (below). --fix stages a plan for the fixable ones | | adops watch | Evaluate alert rules from watch.toml (spend spikes, conv drops to zero, paused-by-accident, disapprovals). Exit 2 + --notify <webhook> on trigger | | adops audiences push list.csv | Normalize + SHA-256 hash emails/phones, upload to Google Customer Match and Meta Custom Audiences in one shot | | adops export [dir] | Serialize full account structure (campaigns → ads, settings, targeting) to YAML/JSON files. --diff compares files against live state | | adops plan show <id> / adops apply <id> / adops rollback <id> | The mutation lifecycle — see Safety model | | adops log | Receipts of every applied mutation (what, when, by which command) |

adops audit checks: disapproved ads · final URLs returning non-200 · redirect chains · missing UTM/tracking templates · budget-capped campaigns · zero-impression entities (30d) · empty ad groups / ad sets · conflicting negatives blocking your own keywords · unlinked/silent pixels · naming-convention lint (regex per profile) · ads in learning limbo · orphaned assets.

Google Ads — adops gads … (the primary surface)

✅ built · 🔜 planned. The editor loop is list → stage → review → apply; every write goes through the safety model.

| Command | What it does | |---|---| | ✅ gads query "<GAQL>" | Raw GAQL passthrough via searchStream. The escape hatch — any report the API can produce, zero boilerplate | | ✅ gads campaigns list\|pause\|enable | Browse campaigns with MTD spend; bulk pause/enable by id (--id "111,222"), staged as a plan | | ✅ gads keywords list\|add\|pause\|enable\|bid | The keyword editor: list with bids/cost/conv, add positives to an ad group, bulk pause/enable, change max-CPC — all staged | | ✅ gads ads list\|pause\|enable | Ad-level status + cost; bulk pause/enable by id | | ✅ gads terms | Search terms report. Filters: --min-cost, --conv 0, --last 30d. The waste finder that feeds negatives | | ✅ gads negatives plan\|add | Campaign-level negative keywords. plan - reads terms JSON from stdin (pipe straight from terms --json) | | ✅ gads budgets list\|set | Per-campaign budgets and pacing; stage a daily-amount change with --budget <id> --daily <usd> | | ✅ gads urls check | Hit every enabled final URL: status codes, redirects, missing UTM. Broken-destination finder | | 🔜 gads negatives at ad-group / shared-set level; rm | Campaign-level ships today; the other scopes need more resource types | | 🔜 gads ideas "<seed>" | Keyword Planner ideas with volume/competition | | 🔜 gads clone <campaign> | Duplicate a campaign with --find/--replace overrides — the geo-expansion workhorse | | 🔜 gads rsa new\|update | Responsive search ads from a YAML spec (15 headlines + 4 descriptions in a file, not 19 UI boxes) | | 🔜 gads pmax, gads assets, gads perf, gads invoices | PMax asset groups, extensions, segmented/auction performance, invoice PDFs |

Meta Ads — adops meta … (reporting layer only)

Meta ships its own official Ads CLI for creating and editing Meta campaigns — adops doesn't duplicate it. What adops keeps is the read side, so Meta shows up in the same report/spend tables as Google. Writes are out of scope here by design; reach for Meta's CLI.

| Command | What it does | |---|---| | ✅ meta insights | Insights at any level (--level campaign\|adset\|ad) with breakdowns (--by age,gender); cursor paging handled | | ✅ meta fatigue | Frequency + CTR-decay report (7d vs. prior 7d) — surfaces burned-out ads to pause in Meta's own tools | | 🔜 meta leads pull | Pull lead-gen form submissions to CSV/JSON on a cron — a read Meta's CLI doesn't emphasize |

Safety model

$ adops gads terms --last 30d --min-cost 50 --conv 0 --json \
    | adops gads negatives plan - --campaign 456
plan-8f3a: add 14 exact negatives → campaign 456
nothing applied · review: adops plan show 8f3a · execute: adops apply 8f3a
  • Plan → apply. Write commands emit a plan file (~/.adops/plans/), print the operations, and exit. adops apply <id> executes it. Same idea as terraform plan. Ops are grouped by resource and batched into one mutate call each.
  • Native validation, always. Every apply runs the operations with Google's validateOnly: true flag first — the real API checks the real change without executing it — and only then runs them for real. adops apply --validate stops after the dry-run.
  • Receipts. Every real apply appends to an append-only JSONL log (~/.adops/log.jsonl): plan id, op count, operation descriptions, timestamp. Validate-only runs leave no receipt — nothing changed.
  • Rollback, honestly scoped. 🔜 adops rollback <receipt> will restore what's restorable (statuses, budgets, bids, added negatives). Deletions and served impressions can't be undone — it'll say so instead of pretending.

Automation recipes

Nightly guardrail — page yourself before the client does:

0 7 * * *  adops watch --profile acme --notify $SLACK_WEBHOOK

Weekly negatives, as a pull request (account structure lives in git via adops export):

# .github/workflows/negatives.yml
on: { schedule: [{ cron: "0 7 * * 1" }] }
jobs:
  mine:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm i -g adops
      - run: |
          adops gads terms --last 7d --min-cost 50 --conv 0 --json \
            | adops gads negatives plan - --out plans/
        env:
          GOOGLE_ADS_DEVELOPER_TOKEN: ${{ secrets.GADS_DEV_TOKEN }}
          GOOGLE_ADS_REFRESH_TOKEN:   ${{ secrets.GADS_REFRESH_TOKEN }}
      - uses: peter-evans/create-pull-request@v6
        with: { title: "negatives: weekly mine", branch: bot/negatives }

Review the plan in the PR, merge, and a second job runs adops apply. Negative keywords with code review.

The annoying parts (read before building anything on this)

Platform reality, not tool limitations:

  • Google Ads developer token. The API requires one per Google Cloud project, and Basic access involves an application Google reviews (days to weeks). Apply on day one; until approval you get test-account access only. This is the long pole for any Google Ads tooling.
  • Meta access tiers. Development tier works immediately on ad accounts you own, with lower rate limits; Standard access requires app review. Use a system user token from Business Manager — user tokens expire every 60 days and will break your cron jobs.
  • Rate limits. Google meters mutate operations; Meta throttles per ad account and reports usage in X-Business-Use-Case-Usage headers. adops reads those headers and backs off automatically — but a giant bulk job may deliberately take a while. It tells you.
  • API version churn. Google Ads ships ~3 versions/year and each lives about a year; Meta's Graph API versions sunset on a similar clock. adops pins versions and runs a daily CI canary against both APIs, so upgrades are a version bump here, not a fire drill in your scripts.

Non-goals

  • Meta writes. Meta shipped its own official Ads CLI in April 2026 — creating and editing Meta campaigns from the terminal is a solved, first-party problem. adops keeps Meta reads only, for the cross-platform view. Don't rebuild what the platform now gives you free.
  • Dashboards. You have a BI tool; adops feeds it (--json → BigQuery/Sheets/whatever).
  • Bid algorithms. The platforms' auto-bidding beats your spreadsheet. We move data and structure, not bids-by-vibes.
  • Attribution modeling. Out of scope, permanently.
  • Other networks. No TikTok/LinkedIn/Amazon until Google is deep and solid here.

Roadmap

Deepen the Google editor: ad-group & shared-set negatives, clone, rsa, pmax, perf, ideas. Then: Sheets/BigQuery sinks for report · rollback · standalone binaries (no Node) · a daily CI canary against the pinned Google API version · adops mcp (expose every command as an MCP server, so agents drive it with the same plan/apply guardrails you get).

Stack (for contributors)

TypeScript, Node ≥ 20, ESM. No SDKs on either platform — plain REST: Google Ads via googleAds:searchStream + :mutate endpoints (API version pinned in one constant in src/platforms/google.ts), Meta via the Graph API (pinned in src/platforms/meta.ts). Deps: commander, zod, picocolors, smol-toml — that's all. Retry/backoff and Meta usage-header handling live in one seam (src/core/http.ts), which is also where tests mock. Every command returns {columns, rows} internally and only the output layer prints — that's what makes --json/--csv free everywhere. No framework, no telemetry.

Run the tests: npm test (vitest, fixture-based, no network).

License

MIT.