A local-first Python CLI that screens candidate brand names before you commit to one — running each name through six local shallow filters and an optional deep-vet stage that checks trademarks, domains, and social handles against live external APIs, then emitting a scored, schema-versioned JSON report alongside a human-readable table.
Not legal advice. Output is a surface-level signal to triage obvious problems early — not a clearance opinion.
Status: all six build epics implemented, ~312 tests passing, CI green on Python 3.11 + 3.12. Alpha — deep vet is opt-in, and the X/Instagram handle probes are best-effort against anti-bot measures (see caveats).
Given names (via --name, --stdin, or --input), brand-vet runs a two-stage pipeline.
Shallow filters — local, no network, no keys, fast:
| Filter | Checks |
|---|---|
| dictionary | every token is a plain English word |
| common_name | collisions with common personal names |
| generic_descriptive | purely descriptive terms |
| profanity | whole-word match across 5 languages (en, es, zh-pinyin, ar, hi) |
| brand_collision | exact + fuzzy (Levenshtein ≤ 1) match against known brands |
| linguistic_sanity | length, mixed-script, IDN/punycode issues |
Names that fail a fail-threshold filter are marked shallow_fail and skip the expensive deep stage.
Deep vet — survivors are checked against external services, concurrently:
| Provider | Checks | API key |
|---|---|---|
| uspto_tsdr | USPTO trademark DB — closest marks, similarity, Nice classes | required |
| rdap | domain availability via RDAP (.com / .io / .ai / .co …) | none |
| handles (github / x / instagram) | username availability | github: none |
| domainr | fallback for RDAP-unsupported TLDs | required |
The deep-vet stage is the interesting part. Providers implement a common DeepVetProvider protocol, and an async orchestrator dispatches them concurrently behind per-provider asyncio semaphores, a circuit breaker, retry-with-backoff, and a per-provider SQLite cache (WAL mode, concurrent-process-safe, per-provider TTLs with error-TTL clamping). Adding a provider means implementing the protocol and registering it — no orchestrator changes.
Config resolves through a precedence chain: package defaults → user config → project config → --config → env vars → CLI flags. Secrets load only from BRAND_VET_* env vars or a chmod 600 ~/.config/brand-vet/secrets.toml, and never appear in logs, config hashes, or reports.
uv pip install -e . # or: pip install -e .
brand-vet --version
brand-vet check --name "Lumenflux" --name "Sparkle"
echo -e "Lumenflux\nSparkle" | brand-vet check --stdin
brand-vet check --input names.txt --output report.json --json
# cache + corpora management
brand-vet cache stats
brand-vet corpora list
Deep vet is off by default; enable it in config and supply keys (e.g. BRAND_VET_USPTO_API_KEY) for the providers that need them.
See src/brand_vet/exit_codes.py. Common ones:
| Code | Meaning |
|---|---|
| 0 | Run completed |
| 1 | Internal error |
| 2 | Invalid configuration |
| 64 | Usage error |
| 65 | Input parse error |
| 70 | Required shallow corpus unavailable |
| 130 | SIGINT |
pytest -q # ~312 tests: unit, integration, baseline-gate, chaos
pytest -m perf # performance tests
CI runs ruff + mypy + pytest on Python 3.11 and 3.12.
- The X and Instagram handle probes are unauthenticated HEAD requests and may be blocked by anti-bot measures in production.
domainr(RDAP fallback) reportsnot_configuredand is skipped without an API key.
- Report schema:
schemas/report.v1.json - Example config:
config/brand-vet.example.toml
MIT — see LICENSE.