Brand Extractor
A CLI that turns a website URL into a frozen-schema brand profile through exactly one Claude call, then generates on-voice content packs whose every claim is code-verified…
- 126
- offline test cases across crawler, parsers, extraction, generation, and schema validation
- 4
- runtime dependencies, frozen by design
- 7
- day TTL on the raw HTML cache before a re-crawl is forced
- 01Application
- Python 3.12
- 02Intelligence
- Claude Code CLI (keyless)
- 03Media
- Pillow
- 04Operations
- pytest
- 05Supporting
- uv
- requests
- BeautifulSoup4
- jsonschema
- ruff
Brand Extractor holds live data, so this shows the verified technology stack by layer rather than a screenshot. Layers, not connections — which service calls which is not something a dependency list can prove. Hosts, ports and topology are deliberately absent.
Problem
Building a brand voice profile by hand — reading a client's site, writing down its tone, colors, and audience — doesn't scale past a handful of clients, and asking a language model to write like a brand from a single prompt produces generic copy or, worse, invented facts: a testimonial that was never on the site, a brand color the model guessed instead of observed, a slogan that sounds plausible but appears nowhere in the source material. That is a real liability once the output touches a licensed industry such as real estate, finance, or health, where required disclosures and prohibited claims are not optional. A second, quieter problem is cost and contract stability: an extraction tool re-run every week for every client either burns metered API credits per call, or drifts its own output shape every time someone tweaks a prompt, breaking whatever downstream tool consumes it.
What was built
The tool is a three-command CLI. Extract crawls a site sitemap-first, same-domain, robots-respected, capped at a handful of pages, runs deterministic parsers over the HTML for colors, fonts, voice signals, and social links, then makes exactly one Claude call to produce a brand profile validated against a frozen JSON schema. Review opens that profile beside a sparse overrides file where a human pins corrections that survive every future re-extraction. Generate makes one more Claude call, merging the reviewed brand profile with verbatim voice excerpts and a running history of past hooks, to produce a dated content pack, a structured file plus a rendered weekly document, in that brand's voice for a chosen set of platforms. Everything from a constraints field for licensed-industry disclosures to per-brand workspace paths is designed so the same engine works unmodified across many client brands, each isolated under its own domain-keyed workspace directory. A companion documentation wiki and a backup and restore drill were built alongside the engine itself, before it was registered as a tool in the wider Agentic tools catalog.
Technical approach
Extraction and generation each cost exactly one Claude call, two per brand-week total, enforced by code rather than convention: a single call site per pass, wrapped in one retry on schema-validation failure before hard-failing and persisting the raw response for debugging. The Claude transport shells out to the local Claude Code CLI in headless mode with credentials for the metered API stripped from the child process's environment, so every call bills the machine's flat-rate subscription rather than per-token API credits, a decision isolated to one swappable module specifically so a future multi-tenant web wrapper can drop in per-user billing without touching extraction or generation logic. Anti-fabrication is enforced after the model responds, not trusted to the prompt: signature phrases are kept only if they appear verbatim, whitespace- and case-normalized, in the crawled corpus; palette hexes are kept only if a parser actually observed them in CSS or a dominant-color pass over the site's own image assets; the model's JSON response is filtered to an explicit key allowlist so nothing outside the schema's model-writable fields survives; and a do-not-repeat hook history is checked on every generation run. The brand schema itself is embedded inline inside the extraction prompt rather than referenced by name, so the schema and the prompt cannot drift independently — a schema change shows up as a change to the prompt's own recorded hash, stored alongside every output. The crawler treats scraped HTML as adversarial input: an explicit security-contract paragraph precedes the corpus in the prompt, telling the model to treat embedded instructions as suspicious website copy rather than commands, and sitemap XML carrying a DTD declaration is rejected outright by string inspection before it ever reaches the standard library's XML parser, a hand-rolled guard chosen specifically to close an XML entity-expansion vulnerability without adding a dependency to a deliberately frozen four-package dependency list. A Tailwind-color fallback, parsing a site's social-share image or favicon with Pillow, only activates when CSS analysis yields no usable hex at all, which is common on utility-class-only or newer color-function-only sites.
Creative approach
Craft
The social-proof guard is the clearest design decision in the system: rather than blocking generation whenever a testimonial or metric cannot be verified against the corpus, unverifiable claims are rewritten into bracketed fill-in frameworks with a flag marking them as needing human input, so the tool still produces a usable, structurally complete content pack on a thin site instead of either fabricating a number or refusing to run. The overrides file is deliberately sparse rather than a full copy of the schema: a human only ever writes the fields they are correcting, and the merge logic guarantees those pinned fields survive every future re-extraction, which makes a one-time correction permanent instead of something that has to be reapplied by hand after every re-run.
Reframe
The non-obvious move is treating cost architecture and output-contract stability as the same design problem rather than two separate concerns bolted on later. Isolating every Claude call behind one small transport module that shells out to a locally authenticated CLI keeps this internal tool at zero marginal AI cost per client-week, and that same module is explicitly where a future multi-tenant product swaps in metered per-user billing; nothing about extraction or generation needs to know which transport sits underneath. Freezing the output schema is the other half of that same idea: rather than let the profile shape evolve informally as new fields seem useful, additive changes are required to go into an entirely new schema file, so anything already consuming the brand profile, whether a review step, a generation pass, or eventually a web wrapper in a separate repository, can trust the contract will never silently change shape underneath it.
Process and what failed
Two real defects surfaced only through live use, not design review. The XML sitemap parser is a rejected 'obvious' fix: the standard defense against XML entity-expansion attacks is a dedicated safe-parsing library, but adding one would have broken the deliberately frozen four-dependency list this project holds as a hard rule, so the guard instead is a plain string check for a DTD declaration before the corpus reaches the standard library's parser, narrower than a real XML-security library would provide, accepted because it closes the concrete attack this tool is exposed to without adding a dependency. Separately, the very first live extraction exposed a parser bug that had passed every existing test: URL normalization stripped every query string, which turned a Facebook profile link into a bare, meaningless path with no identifying information. The fix special-cases that one path shape to keep its identifying parameter and skips id-less profile links entirely rather than emitting a broken one; the corrected URL then became the first real value ever pinned in a brand's override file, exercising the human-override path for the first time under a real bug instead of a synthetic test.
Outcome
The engine shipped end to end in one day of stage-by-stage test-driven work, tagged as its first release, with 126 offline tests passing and the anti-fabrication guards exercised against two real sites: one production Evolve site, which returned high confidence, its actual accent color, real licensed-industry disclosure constraints, and hooks that stayed unique across three separate generation runs; and a second, thinner site tested deliberately with pasted voice samples to confirm low-confidence handling and the fill-in-framework path both worked as designed. It is registered internally as one of several tools in the wider Evolve Agentic tools catalog, reached through a separate wrapper repository that is covered elsewhere in this portfolio rather than duplicated here.
Related work
- Prototype2026Evolve AI StudioA campaign-production pipeline that turns one brief into a validated multi-shot creative spec and assembled multi-format exports, built end to end as a walking skeleton with…
- Internal2026FUB MCPA self-hosted MCP server wrapping the Follow Up Boss CRM API with 21 tools that encode the CRM's own quirks — event-based lead dedupe, an isCompleted task field — directly into…
- Internal2026RepurposerA standalone content-repurposing microservice that turns one source transcript into voice-compliant, publish-ready drafts across eight channel formats, gated by a deterministic…