mixpanel-headless
CLAUDE.md19 starsChanged 12 days agoMITView on GitHub
This file provides guidance to Claude Code when working with this repository. mixpanel_headless is a complete programmable interface to Mixpanel analytics—Python library and CLI for discovery, querying, streaming, and entity management. Discover your schema, run live analytics (segmentation, funnels, retention), and manage entities via the Mixpanel App API. Layered architecture with Workspace class as the primary facade: Capability areas: - Discovery: Explore schema (events, properties, funnels, cohorts, bookmarks, schema graph) - Live queries & streaming: Call Mixpanel API directly (segmentation,…
- Installs packages
We split this CLAUDE.md into the rules it gives an agent
13 rules. About 0 are common, 7 are distinctive, and 6 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈7
Distinctive · estimate
This author's own choices. Worth a look.
6
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 31
Live queries & streaming: Call Mixpanel API directly (segmentation, funnels, retention, user profiles), stream events and profiles
- Very specific: line 85
All code must be fully typed and pass mypy --strict.
- Very specific: line 87
Use Literal types for constrained string values
- Distinctive: line 88
All function signatures must have complete type annotations
- Distinctive: line 89
All return types must be explicitly declared
- Distinctive: line 103
Summary: Always. One line saying what it does.
- Very specific: line 104
Args: When it takes parameters (other than self / cls).
- Very specific: line 105
Returns: When it returns a non-None value.
- Very specific: line 111
Example format: Use markdown fenced code blocks with language hints, not doctest-style >>> operators:
- Distinctive: line 155
Use the same naming conventions and test organization
- Distinctive: line 173
Killed mutant: Test fails when mutation introduced (good - test catches bugs)
- Distinctive: line 174
Survived mutant: Test passes despite mutation (test gap - needs improvement)
- Very specific: line 190
Account → Project → Workspace hierarchy: every CLI verb and Python namespace maps to one of those three axes.
≈ “Common” is an estimate: at least 1 in 5 of the 25,680 CLAUDE.md files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
CLAUDE.md
This file provides guidance to Claude Code when working with this repository.
Project Overview
mixpanel_headless is a complete programmable interface to Mixpanel analytics—Python library and CLI for discovery, querying, streaming, and entity management. Discover your schema, run live analytics (segmentation, funnels, retention), and manage entities via the Mixpanel App API.
| Context | Name | Example |
| PyPI package | mixpanel_headless | pip install mixpanel_headless |
| Python import | mixpanel_headless | import mixpanel_headless as mp |
| CLI command | mp | mp query segmentation -e Login --from 2025-01-01 |
Architecture
Layered architecture with Workspace class as the primary facade:
CLI (Typer) → mp commands, output formatting
↓
Public API → Workspace, auth module, exceptions, types
↓
Services → DiscoveryService, LiveQueryService
↓
Infrastructure → ConfigManager, MixpanelAPIClientCapability areas:
- Discovery: Explore schema (events, properties, funnels, cohorts, bookmarks, schema graph)
- Live queries & streaming: Call Mixpanel API directly (segmentation, funnels, retention, user profiles), stream events and profiles
- Entity CRUD & Data Governance: Create, read, update, delete dashboards, reports (bookmarks), cohorts, feature flags, experiments, alerts, annotations, webhooks, Lexicon definitions, drop filters, custom properties, custom events, and lookup tables via App API; list, read, create, update, and delete saved metrics (behavior metrics, formulas, warehouse metrics) and saved behaviors
- Session replay: Discover, sign, fetch, and analyze rrweb session recordings (
Workspace.replays_for_user/fetch_replay,Replay/ReplayBundle,mp replays)
Package Structure
src/mixpanel_headless/
├── __init__.py # Public API exports
├── workspace.py # Workspace facade — `use(account=, project=, workspace=, target=)`
├── auth_types.py # Auth surface (Account union, Session, Region, OAuthTokens, …)
├── accounts.py # `mp.accounts` — add/list/use/login/test/...
├── session.py # `mp.session` — show/use the persisted [active] block
├── targets.py # `mp.targets` — saved (account, project, workspace?) cursors
├── exceptions.py # Exception hierarchy (incl. AccountInUseError, WorkspaceScopeError)
├── types.py # Result types (SegmentationResult, AccountSummary, …)
├── replay_labels.py # Public replay label helpers (default_label_fn, selector_label_fn, url_normalizer)
├── _internal/ # Private implementation (do not import directly)
│ ├── config.py # ConfigManager (TOML-backed)
│ ├── api_client.py # MixpanelAPIClient (Session-bound; per-request OAuth bearer)
│ ├── me.py # MeService + per-account MeCache (~/.mp/accounts/{name}/me.json)
│ ├── pagination.py # Cursor-based App API pagination
│ ├── auth/ # Auth subsystem
│ │ ├── account.py # Account discriminated union + TokenResolver protocol
│ │ ├── session.py # Session, Project, WorkspaceRef, ActiveSession
│ │ ├── resolver.py # resolve_session(...) — env > param > target > config
│ │ ├── token_resolver.py # OnDiskTokenResolver (refresh + per-account paths)
│ │ ├── token.py # OAuthTokens, OAuthClientInfo
│ │ ├── flow.py # OAuthFlow (PKCE + callback)
│ │ ├── storage.py # account_dir + ensure_account_dir + atomic writes
│ │ ├── pkce.py # PKCE challenge generation (RFC 7636)
│ │ ├── callback_server.py # Local HTTP callback server
│ │ └── client_registration.py # Dynamic Client Registration (RFC 7591)
│ ├── query/ # Query engine builders and validators
│ ├── replays/ # Session-replay analyzer + aggregators (vendored rrweb); public label helpers live in replay_labels.py
│ └── services/ # Discovery, LiveQuery, Replays services
└── cli/
├── main.py # Typer entry point + global flags (-a / -p / -w / -t)
├── commands/ # account / project / workspace / target / session
│ # + query, inspect, dashboards, reports, cohorts, flags,
│ # experiments, alerts, annotations, webhooks, lexicon,
│ # drop-filters, custom-properties, custom-events,
│ # lookup-tables, schemas, business-context, replays,
│ # metrics, behaviors
├── formatters.py # JSON, JSONL, Table, CSV, Plain output
└── utils.py # Error handling, console helpersCode Quality Standards (STRICT)
This project enforces strict standards. CI will reject code that doesn't meet them.
Type Safety (STRICT)
All code must be fully typed and pass mypy --strict. This is non-negotiable:
- No
Anytypes without explicit justification - Use
Literaltypes for constrained string values - All function signatures must have complete type annotations
- All return types must be explicitly declared
Formatting & Linting (STRICT)
Code must pass ruff format and ruff check. Run just check before committing:
- Zero tolerance for lint errors
- Consistent formatting enforced by pre-commit hooks
- CI will fail on any violation
Documentation (STRICT)
Every class, method, and function has a docstring — no exceptions. This covers public API, private/internal helpers (prefixed with _), module-level functions, nested functions, test fixtures, and test methods. Presence is enforced by interrogate in CI, just check, and the pre-commit hook: every definition in src/ and conformance/ (100%; [tool.interrogate] in pyproject.toml and the justfile), and 95% of tests/. Reviewers flag a new test or fixture that has no docstring, because the tests/ gate is an aggregate.
Which sections a docstring needs depends on what the function does (Google style):
- Summary: Always. One line saying what it does.
- Args: When it takes parameters (other than
self/cls). Describe each one; the type lives in the annotation, so don't repeat it. - Returns: When it returns a non-
Nonevalue. NoReturns: Nonesection for functions annotated-> None. - Raises: Exceptions the function raises deliberately, including ones it lets propagate from a call as part of its contract. Not every exception that could conceivably occur.
- Example: Where behavior isn't obvious from the signature and summary. Public API methods usually have one; simple helpers don't need one.
- Tests and fixtures: A one-line summary is enough when it states what the test proves or what the fixture provides. Match the convention already used in the test file.
- Vendored code (e.g.,
_internal/replays/rrweb_analyzer.py): Follow the module's existing conventions.
Example format: Use markdown fenced code blocks with language hints, not doctest-style >>> operators:
# CORRECT - markdown code fence with language hint
"""
Example:
```python
result = my_function("input")
# ["output"]
```
"""
# WRONG - doctest style (DO NOT USE)
"""
Example:
>>> my_function("input")
["output"]
"""A missing docstring will not pass code review. A missing section the rules above don't call for is not a defect.
Test-Driven Development (STRICT)
This project follows strict TDD. Tests are not optional or an afterthought.
The TDD Workflow
- Write tests FIRST — Before any implementation code
- Tests define behavior — The test is the specification
- Implement until tests pass — Only write code to make tests green
- Refactor with confidence — Tests protect against regressions
TDD Rules (Non-Negotiable)
- Never write implementation code without a failing test first
- Study existing test patterns — Before writing any new test, read existing tests for the same module to understand established conventions, fixtures, and mocking strategies
- Tests must pass in CI — Never assume local success means CI success; local environments often have configuration that CI lacks
- Coverage minimum: 90% — CI fails if coverage drops below this threshold
When Adding Tests
- Find and read the corresponding test file (e.g.,
test_workspace.pyforworkspace.py) - Copy fixture patterns and mocking approaches exactly
- Use the same naming conventions and test organization
Test Types
- Unit tests: Isolated, mocked dependencies
- Integration tests: Real component interaction
- Property-based tests: Invariants verified across random inputs (use Hypothesis)
Property-Based Testing
This project uses Hypothesis for property-based testing. PBT tests verify invariants across randomly generated inputs, catching edge cases that example-based tests miss. Name PBT test files with _pbt suffix (e.g., test_types_pbt.py). Hypothesis profiles control example counts:
default: 100 examples (local development)dev: 10 examples (fast iteration)ci: 200 examples, deterministic (CI/CD)
Mutation Testing
This project uses mutmut for mutation testing. Mutation testing evaluates test quality by introducing small code changes (mutations) and verifying tests detect them:
- Killed mutant: Test fails when mutation introduced (good - test catches bugs)
- Survived mutant: Test passes despite mutation (test gap - needs improvement)
- Mutation score: Percentage of mutants killed (target: 80%+)
Run mutation testing:
just mutate # Run on entire codebase (slow)
just mutate-results # View results summary
just mutate-show 1 # Inspect specific mutant
just mutate-apply 1 # Apply mutation to see the change
just mutate-apply 0 # Reset to original code
just mutate-check # Check score meets 80% thresholdKey Design Decisions
- Streaming data access: API returns iterators for memory-efficient processing of large datasets
- Account → Project → Workspace hierarchy: every CLI verb and Python namespace maps to one of those three axes;
Workspace.use(account=, project=, workspace=)is the single in-session switching method. - Three first-class account types:
service_account(Basic Auth),oauth_browser(PKCE, tokens auto-refreshed),oauth_token(static bearer for CI/agents). All managed through one unified surface. - Frictionless login:
mp login(CLI) andaccounts.login_unified()(Python) collapse the old two-stepmp account add+mp account loginflow into one call. Auth-type detection is env-driven:MP_USERNAME+MP_SECRET→service_account;MP_OAUTH_TOKEN→oauth_token; otherwiseoauth_browser. Region behavior is auth-type-specific: SA andoauth_tokenpaths probeus → eu → inwhen--regionis omitted;oauth_browserdefaults tous(PKCE commits to a region before the post-login/meprobe runs, so EU / India users must pass--region eu|inexplicitly). Project and account name derive from the post-login/meresponse; workspace stays lazy and resolves on first workspace-scoped call. - Single resolver:
resolve_session(...)consults env → param → target → config in priority order; no silent cross-axis fallback. - Connection-pool preservation:
ws.use(account=...)rebuilds the auth header but reuses the underlyinghttpx.Client(same Python instance — verified byid()equality intests/integration/test_cross_project_iteration.py). - Dependency injection: Services accept dependencies as constructor arguments for testing.
Environment Variables
| Variable | Purpose |
MP_USERNAME | Service account username (also triggers mp login SA detection when paired with MP_SECRET) |
MP_SECRET | Service account secret |
MP_OAUTH_TOKEN | Raw OAuth 2.0 bearer token (alternative to service account; requires MP_PROJECT_ID + MP_REGION; triggers mp login oauth_token detection when set; ignored only when the full service-account env-var set — MP_USERNAME + MP_SECRET + MP_PROJECT_ID + MP_REGION — is also present) |
MP_PROJECT_ID | Project ID |
MP_REGION | Data residency (us, eu, in); when unset, mp login probes us → eu → in for SA / oauth_token paths and defaults to us for the browser PKCE path |
MP_WORKSPACE_ID | Workspace ID for App API operations |
MP_CONFIG_PATH | Override config file location |
MP_API_BASE_URL | Route every API family at one alternate host (read per request; trailing slash tolerated). Bypasses the per-region ENDPOINTS table: query → {base}/api/query, export → {base}/api/2.0, engage → {base}/api/query/engage, app → {base}/api/app. Plain http:// bases are accepted (local / headless deployments only). mp login's region probe collapses to a single probe at the base (MP_REGION when valid, else us). MP_REGION stays required for non-URL uses. |
MP_APP_BASE_URL | Optional: re-home only the App API family at {app_base}/api/app (works alone or on top of MP_API_BASE_URL). Alone it does not collapse the mp login region probe — the persisted region still routes the live Query/Export/Engage families |
Recommended starter command: mp login (one-shot orchestrator covering region probe, /me-driven project pick, and account-name derivation; backed by mp.accounts.login_unified() in Python).
Config file: ~/.mp/config.toml
OAuth browser tokens: ~/.mp/accounts/{account_name}/tokens.json (per-account, atomic 0o600 writes)
OAuth client metadata: ~/.mp/oauth/client_{region}.json (DCR — one client per region)
Development
First-time setup after cloning: uv sync --all-extras then just install-hooks
to install the git pre-commit hook. Without the hook, ruff check / ruff format
violations slip through to CI.
This project uses just as a command runner.
just check is a strict superset of CI — if just check passes locally,
CI will pass. The only documented difference is that CI sets
HYPOTHESIS_PROFILE=ci (200 deterministic examples vs the local default 100),
which doesn't change pass/fail outcomes.
The conformance corpus is maintainer tooling. conformance/ holds the
recorded library behavior that the TypeScript port replays. It has its own
workflow (.github/workflows/conformance.yml) and recipe (just conformance),
and neither is part of just check or the CI workflow. The workflow runs on PRs
that change conformance/, after each merge to main, and on each release.
Library PRs never touch conformance/vectors/ or conformance/contract/.
Between releases the library drifts from the corpus; the workflow reports that
drift and fails on it only in a re-pin PR. The corpus is re-pinned once per
release (conformance/record/README.md, "When to re-pin").
| Command | Description |
just | List all available commands |
just install-hooks | One-time: install git pre-commit hook (block ruff/format failures) |
just check | Run all checks (lint + fmt-check + typecheck + test-cov + build) |
just conformance | Conformance corpus checks (maintainer tooling; not part of check) |
just test | Run tests (supports args: just test -k foo) |
just test-dev | Run tests with dev Hypothesis profile (fast, 10 examples) |
just test-ci | Run tests with CI Hypothesis profile (thorough, 200 examples) |
just test-pbt | Run property-based tests only |
just test-pbt-dev | Run PBT tests with dev profile |
just test-cov | Run tests with coverage (fails if below 90%) |
just mutate | Run mutation testing on entire codebase |
just mutate-results | Show mutation testing results |
just mutate-show ID | Show details for specific mutant |
just mutate-check | Check mutation score meets 80% threshold |
just hypo-codemod | Refactor deprecated Hypothesis code |
just hypo-write | Generate property-based tests for a module |
just lint | Lint code with ruff |
just fmt | Format code with ruff |
just typecheck | Type check with mypy |
just mp | Run the CLI (supports args: just mp --help) |
# Run all checks before committing
just check
# Run specific tests
just test -k test_name
# Fast iteration on property-based tests
just test-pbt-devCLI Debugging (IMPORTANT)
Never suppress stderr when running CLI commands. The mp CLI provides rich error messages, stack traces, and diagnostic information that are essential for debugging.
# WRONG - hides errors
uv run mp query segmentation -e login 2>/dev/null
# CORRECT - preserves error output
uv run mp query segmentation -e loginSuppressing stderr causes silent failures and makes it impossible to diagnose issues like rate limits, authentication errors, or malformed queries.
Technology Stack
- Python 3.10+ with full type hints (mypy --strict compliant)
- Typer (CLI) + Rich (output formatting)
- jq (JSON filtering via
--jqoption for CLI commands) - httpx (HTTP client), Pydantic (validation)
- Hypothesis (property-based testing), mutmut (mutation testing)
- uv (package manager), just (command runner)
mixpanel-headless Plugin
This project includes a Claude Code plugin in mixpanel-plugin/. The plugin's skills teach Mixpanel analysis judgment on top of the mixpanel_headless library; for API facts (signatures, types, allowed values) they point at the library's built-in reference (mp help / mp.help()) instead of copying it. The skills need mixpanel_headless 0.4.0 or later, and they run it from a plugin-owned venv: setup creates ${CLAUDE_PLUGIN_DATA}/venv (~/.claude/plugins/data/mixpanel-headless-<source>/venv; uv venv when uv exists, else python3 -m venv) and installs there only, never into the system or user Python. The skills call ${CLAUDE_PLUGIN_DATA}/venv/bin/python and .../bin/mp by full path, and the venv survives plugin updates. setup.sh takes the venv path as its required first argument (bash ${CLAUDE_SKILL_DIR}/scripts/setup.sh ${CLAUDE_PLUGIN_DATA}/venv); it checks mp help and <venv>/bin/mp, then prints the venv path. User-run ! commands use the full CLI path (${CLAUDE_PLUGIN_DATA}/venv/bin/mp login), because mp is often not on PATH.
Plugin Components
| Type | Name | Invocation |
| Skill | mixpanelyst | Auto-triggered on analytics questions |
| Skill | session-replay | Auto-triggered on session replay questions (web and mobile recordings) |
| Skill | dashboard-expert | Auto-triggered on dashboard analysis, creation, modification |
| Skill | auth | /mixpanel-headless:auth (also auto-triggered on credential questions) — manage credentials, accounts, OAuth |
| Skill | setup | /mixpanel-headless:setup — create or upgrade the plugin venv (0.4.0 floor), verify it and credentials (user-invoked only) |
| Script | auth_manager.py | ${CLAUDE_PLUGIN_DATA}/venv/bin/python ${CLAUDE_PLUGIN_ROOT}/skills/auth/scripts/auth_manager.py session — session status JSON (also account, project, workspace, target) |
Usage
# Setup
/mixpanel-headless:setup
# API lookup (no credentials, no network); the skills run the venv's mp by full path
mp help Workspace.query # method signature + docstring + referenced types
mp help Workspace.query_funnel.math # one parameter and its allowed values
mp help search cohort # fuzzy search across names, docstrings, enum members
mp help Filter # type fields + construction patterns + related methods
mp help Workspace --domain "feature flags" # every method in one domain
python3 -m mixpanel_headless help types # fallback when mp is not on PATHIn Python: mp.help("Workspace.query") prints the same text; mp.reference.search("cohort") returns structured results.
In this repository4 other files it gives its agents.
- .github/copilot-instructions.mdCopilot instructions
- mutation-testingSkill
- skill-creatorSkill
- code-reviewSkill
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
Reviews can't be read right now.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

