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

@axeptio/cmp-agents

v3.0.1

Published

Reverse-engineered interfaces able to interact with Consent Management Platforms online

Readme

CMP Agents

CI NPM

A collection of reverse-engineered JavaScript interfaces to interact with Consent Management Platforms (CMPs) online.

This library is intended to run in the browser. It detects and drives CMP UIs in the page context. When using NPM you must either (1) bundle your code and inject that script into the browser, or (2) inject the pre-built bundle (dist/cmp-agents.js) into the page and then run code that uses window.CMPAgents. It does not run in Node.js alone.

Looking at a live site

Every observation of a real site runs in the Docker harness, which pins the DNS, the browser build and the browser profile. No browser window opens on your workstation — it runs in the container on a virtual display — and everything it sees comes back to ./results in one format, so a person and a tool read the same evidence.

# What a page exposes, and which CMP wins
./test/docker/run.sh --cli detect <url> --settle 6000

# Add what you want to see
./test/docker/run.sh --cli detect <url> --settle 6000 \
  --dump-globals "Didomi,__tcfapi" --dump-selectors "#cc--main" --dump-resources

# What the page loaded that no integration claims — how a dead resource pattern is found
./test/docker/run.sh --cli detect <url> --dump-unmatched --unmatched-urls 40

# What the CMP does when it is driven
./test/docker/run.sh --cli detect <url> --act acceptAll --act-settle 6000

# Every URL an integration declares, as a JSON report
./test/docker/run.sh --cli check-urls <integration> --json --out /out/<label>.json

# A HAR of one URL, with a readable summary beside it
./test/docker/run.sh --cli detect <url> --settle 6000 --har --out /out/<label>.har
# → results/<label>.har  and  results/<label>.har.summary.txt

# A HAR of a failing test: the suite records one on the retry
./test/docker/run.sh --name <label> --mode complete -- --grep "<test title>"
# → results/<label>.json  and  results/playwright-report-<label>/data/network.har
npm run test:docker:compare -- results/<before>.json results/<after>.json

Always pass --name, or a run overwrites the previous report. Cite a capture, or it is a guess.

Overview

CMP Agents provides a unified API to detect, read, and interact with 40+ different Consent Management Platforms. Whether you're building a cookie scanner, consent automation tool, or privacy assistant, this library gives you the building blocks to work with CMPs programmatically.

Supported CMPs

| CMP | Detection | Accept/Reject | Granular Consent | |-----|-----------|---------------|------------------| | Axeptio | ✅ | ✅ | ✅ | | CookieBot | ✅ | ✅ | ✅ | | Didomi | ✅ | ✅ | ✅ | | OneTrust | ✅ | ✅ | ✅ | | UserCentrics | ✅ | ✅ | ✅ | | TrustArc | ✅ | ✅ | ✅ | | iubenda | ✅ | ✅ | ✅ | | CookieYes | ✅ | ✅ | ✅ | | Complianz | ✅ | ✅ | ✅ | | TarteAuCitron | ✅ | ✅ | ✅ | | ...and 30+ more | ✅ | ✅ | Varies |

See docs/integrations.md for the complete list.

Installation

NPM

npm install @axeptio/cmp-agents

Use the library in code that will run in the browser: bundle that code (e.g. with Webpack, Rollup) and inject the resulting script into the page, or inject the pre-built bundle (node_modules/@axeptio/cmp-agents/dist/cmp-agents.js) and call CMPAgents.create() from your own injected script.

CDN (Script tag)

<!-- Latest version -->
<script src="https://unpkg.com/@axeptio/cmp-agents/dist/cmp-agents.min.js"></script>

<!-- Or from jsDelivr -->
<script src="https://cdn.jsdelivr.net/npm/@axeptio/cmp-agents/dist/cmp-agents.min.js"></script>

<!-- Specific version -->
<script src="https://unpkg.com/@axeptio/[email protected]/dist/cmp-agents.min.js"></script>

Command line

Installing the package puts cmp-agents on your path:

npx cmp-agents --help

The commands that open a browser — detect, check-urls, debug, test, signals, cmp-list, add-url --validate and snapshot — need Playwright, which is an optional peer dependency. Install it when you want them:

npm install playwright && npx playwright install chromium

Every other command runs without it. One that needs it and cannot find it says so and exits non-zero.

Requirements

  • Browser: The library runs in the browser (script tag or injected bundle).
  • Node.js >= 22.17.0 < 23.0.0 and npm >= 10 for development, CLI, and building your own bundle.
  • Playwright (optional): required only by the browser-driving CLI commands listed above.

Quick Start

Browser Usage (CDN)

<script src="https://unpkg.com/@axeptio/cmp-agents/dist/cmp-agents.min.js"></script>
<script>
  const agent = CMPAgents.create();
  agent.on('cmp', async (cmp) => {
    console.log('Detected:', cmp.getName());
    const vendors = await cmp.getVendors();
    console.log('Vendors:', vendors);
  });
  agent.run();
</script>

NPM / Bundled script (browser)

Bundle this code and inject the bundle into the page so it runs in the browser:

import { CMPDetector, CookieBot, OneTrust, Didomi, CONSENT_INTENT } from '@axeptio/cmp-agents';

// Create a detector and register CMPs (runs in browser)
const cmpDetector = new CMPDetector();
cmpDetector.registerIntegrations(new CookieBot(), new OneTrust(), new Didomi());

// Listen for CMP detection
cmpDetector.on('cmp', async (cmp) => {
  console.log(`Detected: ${cmp.getName()}`);

  // Get vendors
  const vendors = await cmp.getVendors();
  console.log('Vendors:', vendors);

  // Accept all using the new API
  await cmp.applyPreferences({ intent: CONSENT_INTENT.ACCEPT_ALL });
});

// Start detection
cmpDetector.run();

Using Individual Integrations

import CookieBot from '@axeptio/cmp-agents/integrations/cookiebot';
import { CONSENT_INTENT } from '@axeptio/cmp-agents';

const cookiebot = new CookieBot();

// Check if CookieBot is present
if (window.Cookiebot) {
  cookiebot.trigger('detection');

  // Get current consent status
  await cookiebot.retrieveConsent();
  console.log('Consent:', cookiebot.consent);

  // Accept all vendors
  await cookiebot.applyPreferences({ intent: CONSENT_INTENT.ACCEPT_ALL });
}

Granular Vendor Consent

import { CONSENT_INTENT } from '@axeptio/cmp-agents';

const cmp = cmpDetector.getCMP();
const vendors = await cmp.getVendors();

// Build a map of vendor preferences
const vendorConsents = {};

// Allow Analytics vendor, deny Advertising
const analyticsVendor = vendors.find(v => v.name.includes('Analytics'));
const adsVendor = vendors.find(v => v.name.includes('Advertising'));

if (analyticsVendor) vendorConsents[analyticsVendor.name] = true;
if (adsVendor) vendorConsents[adsVendor.name] = false;

// Apply custom choices by passing vendors directly
await cmp.applyPreferences({
  intent: CONSENT_INTENT.APPLY_CUSTOM,
  vendors: vendorConsents
});

Architecture

┌─────────────────────────────────────────────────────────────┐
│                      CMPDetector                             │
│  - Registers CMP integrations                               │
│  - Manages detection lifecycle                              │
│  - Selects the operable CMP the evidence ranks first        │
└─────────────────────┬───────────────────────────────────────┘
                      │
          ┌───────────┴───────────┐
          │                       │
          ▼                       ▼
┌─────────────────┐     ┌─────────────────┐
│   CMP (Base)    │     │   TCF_CMP       │
│                 │     │   (extends CMP) │
│ - getName()     │     │                 │
│ - getVendors()  │     │ - TCF v2 API    │
│ - applyPrefs()  │     │ - getVendorList │
│ - acceptAll()   │     │ - getTCData()   │
│ - rejectAll()   │     │                 │
└────────┬────────┘     └────────┬────────┘
         │                       │
         └───────────┬───────────┘
                     │
    ┌────────────────┼────────────────┐
    │                │                │
    ▼                ▼                ▼
┌─────────┐    ┌─────────┐    ┌─────────┐
│CookieBot│    │ OneTrust│    │ Didomi  │
└─────────┘    └─────────┘    └─────────┘

API Reference

CMP Base Class

All CMP integrations extend the CMP base class:

class CMP {
  // Detection
  getName(): string
  getMatchingResources(): RegExp[]
  onTick(): void

  // Configuration
  getLayout(): string        // 'banner' | 'popup' | 'widget' | etc.
  getBehaviors(): string[]   // ['ACCEPTABLE', 'REJECTABLE', etc.]
  getGranularity(): string   // 'NONE' | 'GLOBAL' | 'VENDOR' | 'PURPOSE'

  // What TCF the page speaks, and who is serving it
  getCmpId(): number                               // this vendor's IAB registration, 0 when it holds none
  getTcfInfo(options): Promise<TcfInfo>            // measured, never assumed
  getTcfCmpIdentity(options): Promise<TcfCmpIdentity | null>  // the vendor the page's __tcfapi names

  // Data retrieval
  getVendors(): Promise<Vendor[]>
  getPurposes(): Promise<Purpose[]>
  retrieveConsent(): void
  hasExistingConsent(): boolean

  // Binary actions
  acceptAll(): Promise<void>
  rejectAll(): Promise<void>

  // Lifecycle methods
  applyPreferences(options): Promise<void>
  show(screenType): Promise<void>
  dismiss(): Promise<void>
}

// applyPreferences options:
// {
//   intent: CONSENT_INTENT,       // ACCEPT_ALL | REJECT_ALL | APPLY_CUSTOM
//   vendors: { [id]: boolean },   // Vendor consent map (for APPLY_CUSTOM)
//   purposes: { [id]: boolean },  // Purpose consent map (for APPLY_CUSTOM)
//   autoDismiss: boolean          // Auto-close CMP UI (default: true)
// }

Constants

import {
  BEHAVIORS,
  LAYOUTS,
  GRANULARITY,
  CONSENT_INTENT,
  SCREEN_TYPE
} from '@axeptio/cmp-agents';

// Behaviors
BEHAVIORS.ACCEPTABLE    // User can accept
BEHAVIORS.REJECTABLE    // User can reject
BEHAVIORS.CLOSABLE      // CMP can be closed
BEHAVIORS.OBSTRUCTIVE   // CMP blocks content

// Layouts
LAYOUTS.BANNER
LAYOUTS.POPUP
LAYOUTS.WIDGET
LAYOUTS.PAGE

// Granularity
GRANULARITY.NONE      // No choice possible
GRANULARITY.GLOBAL    // Accept/reject all only
GRANULARITY.VENDOR    // Per-vendor choice
GRANULARITY.PURPOSE   // Per-purpose choice

// Consent Intent (for applyPreferences)
CONSENT_INTENT.ACCEPT_ALL    // Accept all vendors
CONSENT_INTENT.REJECT_ALL    // Reject all vendors
CONSENT_INTENT.APPLY_CUSTOM  // Apply vendor-specific choices

// Screen Type (for show)
SCREEN_TYPE.DEFAULT   // Initial consent banner
SCREEN_TYPE.VENDORS   // Vendor list view
SCREEN_TYPE.PURPOSES  // Purpose/category view
SCREEN_TYPE.SETTINGS  // Settings panel

Browser identity (@axeptio/cmp-agents/browser-identity)

A subpath export for code that needs to present the same browser this package does — the user agent, the client-hint brands and high-entropy hints, the platform, the languages, the timezone, the viewport and the request headers derived from them. Import it rather than restating the values: two declarations of which browser you are is drift waiting to happen.

import {
  BROWSER_USER_AGENT,
  BROWSER_USER_AGENT_METADATA,
  BROWSER_REQUEST_HEADERS,
  BROWSER_LOCALE,
  BROWSER_TIMEZONE,
  CHROME_IDENTITY
} from '@axeptio/cmp-agents/browser-identity';

CHROME_IDENTITY;
// { source: 'committed-snapshot', version: '152.0.7977.64', greaseBrand: ..., greaseBrandVersion: ... }

Every value derives from one measurement of a real Google Chrome, committed as cli/lib/google-chrome.json and copied into dist/google-chrome.json by the build. Nothing resolves a browser at runtime, so the identity is fixed per release rather than per machine or per process: two runs of the same release present the same browser, and any run can be attributed to the build that froze it. The module reads that snapshot when it is imported and refuses to load if it is missing or malformed — there is no fallback identity.

Regenerate the snapshot from a real browser with the published CLI, which measures the Chrome installed on the machine and rewrites the file:

npx cmp-agents snapshot

The Playwright configuration this repository drives its own browser with is deliberately not exported: it is a harness, not an identity.

Adding a New Integration

Use the CLI to scaffold a new integration:

npm run cli:create

Or create manually in src/integrations/yourCmp/:

import { CMP } from '../../cmp.js';
import { GRANULARITY, SCREEN_TYPE } from '../../constants.js';

export default class YourCMP extends CMP {
  getName() {
    return 'YourCMP';
  }

  getGranularity() {
    return GRANULARITY.VENDOR;
  }

  getMatchingResources() {
    return [/yourcmp\.com\/sdk/];
  }

  onTick() {
    if (window.YourCMPSDK) {
      this.trigger('detection');
    }
  }

  async getVendors() {
    return window.YourCMPSDK.getVendors().map(v => ({
      name: v.name,
      iabId: v.id
    }));
  }

  async acceptAll() {
    window.YourCMPSDK.acceptAll();
  }

  async rejectAll() {
    window.YourCMPSDK.rejectAll();
  }

  retrieveConsent() {
    this.consent = window.YourCMPSDK.hasConsent();
    this.consentDate = window.YourCMPSDK.getConsentDate();
  }

  // Override for granular consent support
  async _applyCustomConsent(options = {}) {
    const { vendors = {}, purposes = {} } = options;
    Object.entries(vendors).forEach(([id, enabled]) => {
      window.YourCMPSDK.setVendorConsent(id, enabled);
    });
    Object.entries(purposes).forEach(([id, enabled]) => {
      window.YourCMPSDK.setPurposeConsent(id, enabled);
    });
    window.YourCMPSDK.save();
  }

  // Override for UI control
  async show(screenType = SCREEN_TYPE.DEFAULT) {
    window.YourCMPSDK.open();
  }

  async dismiss() {
    window.YourCMPSDK.close();
  }
}

See CONTRIBUTING.md for detailed guidelines.

Testing

# Run unit tests (fast, no external dependencies)
npm run test:unit

# Run unit tests in watch mode
npm run test:unit:watch

# Run unit tests with coverage
npm run test:unit:coverage

# Run E2E tests (requires build first)
npm test

# Run E2E tests for a specific CMP
npx playwright test --config=test/config.js -g "CookieBot"

# Run in complete mode (all test websites + extended flow tests)
TEST_MODE=complete npm test

An E2E run is two passes. The first runs in parallel with the configured retries. If anything is still failing, the second re-runs exactly those tests one at a time, once each, spaced five to ten seconds apart, and records the network activity of every attempt. A site that only ever failed under contention passes there and the run is green; one that fails again comes back with a HAR. The second pass writes results-final-pass.json and playwright-report-final-pass/ beside the first pass's report rather than over it, and ./test/docker/run.sh --name <label> labels all four.

Test Structure

test/
├── unit/                              # Unit tests (Vitest + jsdom)
│   ├── cmp.unit.test.js               # CMP base class tests
│   ├── tcf-cmp.unit.test.js           # TCF_CMP class tests
│   ├── vendor.unit.test.js            # Vendor class tests
│   ├── cmpDetector.unit.test.js       # CMPDetector + create() factory tests
│   ├── consent-signals.unit.test.js   # Consent signal detection tests
│   └── consentManager.unit.test.js    # ConsentManager integration tests
├── common.js                          # Shared E2E test helpers
└── config.js                          # Playwright configuration

See the Testing Guide for detailed instructions.

Documentation

Guides

Reference

Internal

  • CI/CD - Continuous integration and release process

Use Cases

1. Cookie Scanner (Shake)

Detect and analyze CMPs on websites during automated scans:

// In Playwright test
await page.addScriptTag({ path: 'dist/cmp-agents.js' });
await page.evaluate(() => {
  const agent = CMPAgents.create();
  window.cmpAgent = agent;
  agent.run();
});
const cmp = await page.evaluate(() => window.cmpAgent.getCMP());
const vendors = await page.evaluate(() => window.cmpAgent.getCMP().getVendors());

2. Privacy Assistant (Taste)

Auto-consent on behalf of users in a browser extension:

import { CONSENT_INTENT, BEHAVIORS } from '@axeptio/cmp-agents';

// Content script
const cmp = cmpDetector.getCMP();
if (cmp && cmp.getBehaviors().includes(BEHAVIORS.REJECTABLE)) {
  // Reject all vendors
  await cmp.applyPreferences({ intent: CONSENT_INTENT.REJECT_ALL });
}

3. Granular Consent Management

Apply specific vendor preferences:

import { CONSENT_INTENT } from '@axeptio/cmp-agents';

const cmp = cmpDetector.getCMP();
const vendors = await cmp.getVendors();

// Build vendor consent map
const vendorConsents = {};

// Find and configure specific vendors
const metaVendor = vendors.find(v => v.name.includes('Meta'));
const analyticsVendor = vendors.find(v => v.name.includes('Analytics'));

if (metaVendor) vendorConsents[metaVendor.name] = false;  // Deny Meta
if (analyticsVendor) vendorConsents[analyticsVendor.name] = true;  // Allow Analytics

// Apply custom choices by passing vendors directly
await cmp.applyPreferences({
  intent: CONSENT_INTENT.APPLY_CUSTOM,
  vendors: vendorConsents
});

4. UI Control for Testing

Open specific CMP views for manual testing or screenshots:

import { SCREEN_TYPE, CONSENT_INTENT } from '@axeptio/cmp-agents';

const cmp = cmpDetector.getCMP();

// Open vendor list for inspection
await cmp.show(SCREEN_TYPE.VENDORS);

// Apply preferences but keep UI open
await cmp.applyPreferences({
  intent: CONSENT_INTENT.APPLY_CUSTOM,
  autoDismiss: false
});

// Take screenshot, then close
await cmp.dismiss();

5. Research & Compliance

Analyze CMP configurations across websites:

const analysis = {
  name: cmp.getName(),
  layout: cmp.getLayout(),
  behaviors: cmp.getBehaviors(),
  granularity: cmp.getGranularity(),
  vendorCount: (await cmp.getVendors()).length,
  hasExistingConsent: cmp.hasExistingConsent()
};

License

This project is licensed under a custom license. See LICENSE for details.

TL;DR: Free for CMP industry peers to use, modify, and contribute. Redistribution as a competing product requires permission.

Credits

Developed and maintained by Axeptio.

Links