claudeers.
// Developer Tools

marketing-machine

Claim-provenance and brand-compliance harness for marketing content: scans your codebase for product truth, lints copy against it, renders on-brand cards and…

// Developer Tools[ cli ][ api ][ desktop ][ web ][ claude ]#claude#agent-skills#claude-code#claude-code-plugin#content-pipeline#cursor#linter#marketing#devtoolsMIT$open-sourceupdated 14 days ago
Actively maintained
98/100
last commit 12 days ago
last release none
releases 0
open issues 0

Install with your AI

Paste into Claude Code, Cursor, or any agent — it reads the repo and wires the tool into your project.

Install and set up marketing-machine (claude-plugin project) into my current project.
Found on https://claudeers.com/marketing-machine
Repo: https://github.com/cagatayuncu/marketing-machine
Homepage/docs: —
Detected install method: claude-plugin → /plugin install marketing-machine@cagatayuncu/marketing-machine
Category: devtools. Platforms: cli, api, desktop, web.
Read the repo's README for exact setup and env vars, then install it and wire it into my project.

Claudeers Health Verdict:
active; community-verified: false. Confirm the source before running anything.
// or install directly (claude-plugin)
/plugin marketplace add cagatayuncu/marketing-machine
/plugin install marketing-machine@cagatayuncu/marketing-machine
// or clone
git clone https://github.com/cagatayuncu/marketing-machine

// compatibility

Platformscli, api, desktop, web
Operating systems
AI compatibilityclaude
LicenseMIT
Pricingopen-source
LanguageJavaScript

marketing-machine

A claim-provenance and brand-compliance harness for marketing content. It reads your codebase to establish what the product actually does, enforces the rules mechanically, then renders cards (PNG) and video (MP4) from a single HTML contract.

The pitch: your marketing copy cannot outrun what your product actually does, and the build checks.

Built for teams that already work with a coding agent — Claude Code, Cursor, or anything that reads AGENTS.md — and want the marketing it produces to survive an audit.

Requirements: Node 20+ (record needs 22+ for the global WebSocket) and a Chrome/Edge/Chromium found at runtime. ffmpeg-static is an optional dependency, used by video only — linting, scanning and card rendering have zero dependencies.


Quick start

git clone https://github.com/cagatayuncu/marketing-machine ../marketing-machine
cd your-repo
node ../marketing-machine/scripts/init.mjs --agent claude   # or cursor, agents, all

Then, in your agent: setup — the skill completes its own configuration, installs the fonts the renderer needs, proposes the brand-role mapping, and asks you only for the things a codebase cannot answer (your one-liner, your stage, which claims you will defend).

From there you talk to it:

You sayIt does
setupcompletes config, fonts and brand roles; offers the CI gate
"write a post about inventory turnover"drafts to the anatomy, adds the mandatory verification table, lints
"make a card for post 12"picks a format (default: no visual), renders a PNG in your brand, writes alt text
"render the intro video"verifies a single frame first, then records the MP4
"review this draft"runs the linter, then the judgment passes a linter cannot do

The skill lands wherever your agent reads instructions — .claude/skills/ for Claude Code, .cursor/rules/ for Cursor, an AGENTS.md section for everything else. Per-agent detail: Installing it as an agent skill.

Other ways to get the toolchain:

  • Claude Code plugin — no clone, no paths: /plugin marketplace add cagatayuncu/marketing-machine, then /plugin install marketing-machine. The toolchain travels inside the plugin.
  • skills CLInpx skills add cagatayuncu/marketing-machine; the skill clones the toolchain itself on first use.
  • Vendor it — copy the directory into tools/marketing-machine/. Best for teams: the installed path stays relative.
  • Submodulegit submodule add https://github.com/cagatayuncu/marketing-machine tools/marketing-machine to pin a version and pull updates.
  • Fork it — add your own rule packs (rules/lang.<code>.json) and card templates on top.

Prefer driving it by hand? Every step is a plain command:

node <marketing-machine>/scripts/doctor.mjs    # environment and config
node <marketing-machine>/scripts/scan.mjs      # product-facts draft
node <marketing-machine>/scripts/lint.mjs      # enforce
node <marketing-machine>/scripts/render-card.mjs <name>
node <marketing-machine>/scripts/record.mjs <name>

npm link inside this directory gives you mkt as a shorthand for all of them.

What init derives from your codebase

ValueSource
Product namepackage.json name, or the .sln/.slnx name
Domainhomepage, or the first external link in the README
Brand colorsThe stylesheet declaring the most of them — CSS custom properties, SCSS/Less variables, or a Tailwind theme. Prefixed token conventions (--color-bg, --c-primary, --theme-text) are understood
Font familiesfont-family declarations, matched to the packages that ship them — including a Google Fonts @import, which names the equivalent @fontsource package
LanguagesThe localisation directory, with a key count per language
Legal routesRoutes like /privacy or /terms found in the code — these become the linter's draft-link blocklist
Source rootsDirectories that actually contain code

Anything it cannot find is left as [VERIFY: ...]. doctor lists those, and unmapped brand roles block rendering rather than falling back to someone else's palette.


Why

There is no shortage of tools that write marketing copy. What is missing is the layer underneath: something that proves the copy stayed inside what the product actually does. That layer is the whole of this package.

Four principles:

  1. Everything comes from the codebase it is installed into. Brand colors, font families, product name, domain, languages, legal-page routes, source roots — all detected from the host repo. No product's colors, name or fonts are baked into the package, and no value is inherited from the example config. When detection fails the field stays null with a [VERIFY] note and rendering is refused — publishing artwork in the wrong brand is worse than failing.
  2. Product truth is derived from code and tiered by evidence. WORKS / PARTIAL / ABSENT / LIMITS, every line carrying file:line.
  3. Nothing becomes claimable automatically. A scanner sees that a symbol exists; it cannot see that the feature works. The draft waits for a human.
  4. A rule is a build gate, not advice. Everything a machine can decide lives in the linter; everything requiring judgment lives in the skill. The two are never mixed.

Adoption walkthrough

Two commands and a conversation. The friction is concentrated in one place, and it is deliberate.

1 · Get the directory

git clone https://github.com/cagatayuncu/marketing-machine ../marketing-machine

It does not have to live inside your repo — init writes an absolute path into the installed skill, so the toolchain can sit anywhere. Vendoring it in (tools/marketing-machine/) also works if you would rather pin a version.

Working in a team? Vendor it. An absolute path baked into a committed AGENTS.md or .claude/ skill runs on exactly one machine; a vendored toolchain keeps the path relative and the repo self-contained. The alternative is each developer running init --refresh once after cloning — init defaults to that model when the toolchain sits outside the repo: the machine-specific skill copies go into .gitignore and stay out of everyone's commits.

2 · Install and hand over

cd your-repo
node ../marketing-machine/scripts/init.mjs --agent claude

init also maintains a marked block in your .gitignore covering everything the toolchain writes and can rewrite — rendered PNGs and MP4s, the scan draft, the _template-* starter files, and (when the toolchain path is absolute) the machine-specific skill copies. Content stays in git: config, context files, posts, card and scene HTML. That split is the collaboration model — a render is reproducible from the committed content, while the committed content is exactly what your team reviewed and your CI gate reads. Paths you already ignore in your own spelling are left alone, and --no-gitignore skips the whole step.

Then tell your agent: setup. From here the skill drives, and it will ask you for exactly four kinds of thing:

It asksWhy it cannot answer this itself
Which language the copy is written inIt can read your locale files, but a product's interface languages and its marketing language are different decisions. A UI in three languages may market in one.
Who the audience isTheir words for the problem, what they already tried, the first objection, and who this is explicitly not for. None of that is in the code.
Which channel this is forSets card and video geometry, and the post anatomy
Your one-line product descriptionPositioning is a judgment, not a fact in the code
Your stage (pre-launch / beta / GA)Same
A sanity check on the brand-role mappingRole guessing is name-based and can be confidently wrong
A .woff2 path, if your typeface is licensedNot resolvable from a public registry

The language answer is not a single field: the rule pack, the verification-table heading and the verify marker all move with it, which is why changing it goes through init --refresh --lang <code> rather than a hand edit. Anything you had already tuned survives that. Audience answers go into marketing/context/audience.md, because an audience description is prose, not configuration.

Everything else it handles: running doctor, installing the font package the renderer needs, writing the role mapping, detecting a logo file if the repo ships one (brand.logo — cards then use the image instead of the text wordmark), and offering to wire the CI gate.

3 · Establish product truth — the part that is not automated

Ask the skill to scan. It writes a draft where every line carries a file:line reference and nothing is claimable yet, then walks it with you section by section asking "does this work end to end, and what is the evidence?" It will not promote a line on its own.

Pay particular attention to the outbound-call inventory. If you intend to say anything about where data goes, every row there has to be reviewed — one unreviewed call falsifies an absolute claim.

The same conversation fills claims.md. Its UNPROVABLE rows get written as rules in marketing/rules/project.json, which is the moment they stop being advice and start failing builds.

4 · Produce

Ask for write, card, video, or review. Every output goes through the linter before you see it.

Everything written comes from your repository. Not from what products in the category usually claim, not from your README's own marketing language. The chain is: the scan found it in the code → a human confirmed it → it is WORKS in product-facts.md → it may appear in a sentence → the sentence carries its file:line. Anything that cannot be traced back through that chain either gets a verify marker or gets left out.

And the chain is re-checked, not trusted. Code keeps moving under approved copy, so every lint run re-resolves every file:line in the facts file and in post verification tables: a cited file that is gone fails the build, evidence that vanished from the file warns for re-verification, and evidence that merely moved gets a note with the new line number. A removed or renamed feature flags the copy that cites it — it does not sit stale forever.

Time to first artwork

Steps 1, 2 and 4 are minutes. Step 3 is the real investment, and it is deliberately not automatable: establishing what your product provably does, and which claims you will stand behind, is the thing that makes everything downstream trustworthy. A scanner can hand you the candidates; only you can confirm them.


Installing it as an agent skill

The instructions live in one place — skills/marketing-machine/SKILL.md — and init wraps that same body in whatever shape your agent expects. Rewriting the body per target would guarantee the copies drift, so the body is installed verbatim and only the wrapper differs.

node <marketing-machine>/scripts/init.mjs --agent claude    # default
node <marketing-machine>/scripts/init.mjs --agent cursor
node <marketing-machine>/scripts/init.mjs --agent agents
node <marketing-machine>/scripts/init.mjs --agent all
node <marketing-machine>/scripts/init.mjs --agent claude,cursor

Re-running init --refresh reinstalls every target with current paths — that is also the fix when a teammate clones a repo whose skill was installed from a different machine.

Claude Code — as a plugin (no clone)

/plugin marketplace add cagatayuncu/marketing-machine
/plugin install marketing-machine

The plugin carries the whole toolchain — scripts, rules, templates — inside the plugin directory, and the skill finds it through $CLAUDE_PLUGIN_ROOT. Nothing to clone, no path to manage: open your repo, say setup, and the skill creates marketing.config.json there. Updates arrive through the plugin marketplace rather than git pull.

npx skills CLI

npx skills add cagatayuncu/marketing-machine

The skills CLI copies the skill into whichever agents it manages — but only the skill, not the toolchain. The skill handles that itself: its first step on a machine without a toolchain is to clone one (the resolution ladder is written into the skill). If you would rather no agent cloned repos on your behalf, use any other route on this page.

Claude Code — via init

<your-repo>/.claude/skills/marketing-machine/
├── SKILL.md          name + description frontmatter, tool allowlist
├── TOOLCHAIN         where the scripts live
└── references/
    └── video-guide.md

Claude Code discovers project skills in .claude/skills/ automatically — no registration step, no settings change, and it works the same in the CLI, the desktop app and the IDE extensions. The whole skills/marketing-machine/ directory travels, so the video guide's relative link resolves locally.

Invoke it by name:

/marketing-machine setup
/marketing-machine write post about inventory turnover
/marketing-machine card 12

or just describe the task — routing matches on the description frontmatter, so "write a LinkedIn post about X", "make a card for this" or "review this copy" reach the skill without naming it.

One note on permissions: the frontmatter's allowed-tools (Read, Write, Edit, Glob, Grep, Bash) is a ceiling, not a grant. Claude Code still asks before running toolchain commands unless you allowlist them in your project settings.

Cursor

<your-repo>/.cursor/rules/marketing-machine.mdc

Installed as a project rule of the agent-requested kind — description in the frontmatter plus alwaysApply: false — so the agent pulls it in when the task looks like marketing work instead of carrying it in every request. Verify it landed: Cursor Settings → Rules lists it.

To use it, open the agent panel and describe the task, or reference the rule explicitly (@marketing-machine) when you want it loaded for certain. The rule tells the agent to run the toolchain scripts from the repo root; Cursor shows each command for approval as usual.

Recent Cursor versions also read AGENTS.md, so --agent agents reaches Cursor too. Pick one route, not both, or the same instructions land in context twice.

AGENTS.md (portable convention)

<your-repo>/AGENTS.md

The instructions are appended as a ## Marketing content (marketing-machine) section between <!-- marketing-machine:start --> / :end markers. Re-running init replaces what sits between the markers and leaves the rest of your AGENTS.md alone.

This is the widest net: OpenAI Codex, GitHub Copilot's coding agent, Google Jules, Amp, Zed and a growing list of tools read AGENTS.md as project instructions. The trade-off is the loading model. An AGENTS.md section sits in every prompt, while a Claude skill or a Cursor rule loads on demand — so if your AGENTS.md is already long, prefer the tool's own rules mechanism where one exists.

Everything else — manual paste

skills/marketing-machine/SKILL.md is plain Markdown with YAML frontmatter. Strip the frontmatter, replace <TOOLCHAIN> with the path to this directory, and paste the body wherever your tool reads project instructions — that substitution is the only templating in the file. As of this writing:

ToolWhere it reads project instructions
GitHub Copilot (VS Code / JetBrains chat).github/copilot-instructions.md
Windsurf.windsurf/rules/ — save the body as a rule with a description trigger
Cline / Roo Code.clinerules
Gemini CLIGEMINI.md
Anything with a custom-instructions fieldpaste the body directly

A first-class wrapper for a tool you use daily is a small, welcome PR — the cursor branch in scripts/init.mjs is the pattern to copy.

What the skill actually does

Six commands, in the order they get used:

AskThe skill does
setupInstalls itself, completes the config, installs the fonts the renderer needs, proposes the brand-role mapping, offers the CI gate — and asks you only for what code cannot answer
scanRuns the scanner, then walks the draft with you asking "does this actually work, and what is the evidence" — it will not promote a line on its own
write <channel> <topic>Loads the context files, writes to the anatomy, saves with a mandatory verification table, runs the linter, fixes violations
card <post>Applies the decision table (default: no visual), builds from the template, renders, writes alt text
video <scene>Checks a single frame first, then records; enforces the purity rules and the provenance declaration
review <file>Runs the linter, then adds the four passes a linter cannot do: claim verification, limitation quality, audience, CTA fit

Commands

CommandWhat it does
initDetect, write config and scaffolding, install the skill
scanScan the codebase, write a product-facts draft
lintCheck posts, cards and scenes against the rules
cardHTML → PNG
recordHTML scene → MP4 (or a single frame with --shot)
doctorBrowser, fonts, brand roles, ffmpeg, paths — plus freshness: is the installed skill copy in sync, is the toolchain behind upstream (--offline skips the network check)

Useful flags: init --refresh (re-derive brand/fonts and reinstall every agent target found on disk, keep the rest) · init --no-gitignore (leave the host .gitignore alone) · lint --only post|card|video|facts · lint --warnings-as-errors (for CI) · lint --json · card --channel <name> · record --shot 6.5 · record --scale 2. Note on card --loose-fonts: it only permits a source whose <!--FONTS--> marker is absent — it does not skip font resolution when the marker is present.


One HTML contract

Cards and video go through the same pipeline. The only difference is that a scene declares a timeline.

MarkerWhat it does
<!--FONTS-->Required. Fonts are inlined here as data URIs. Without it the page fetches fonts over the network and non-ASCII glyphs (ı ş ğ İ, ä ö ü ß, ą ę ł) break silently. The linter blocks its absence.
<!--TOKENS-->Injects brand colors and font families from config
{{product.name}}, {{brand.footerUrl}}, …Identity text from config. An unresolved placeholder fails the render.
body { width/height }Output geometry. Change the aspect ratio in the source and the renderer follows.

A scene adds four globals:

window.__duration = 15;                // total seconds
window.__seek = (t) => { ... };        // a PURE function of t
window.__ready = true;                 // once fonts and layout have settled
window.__provenance = 'simulation';    // real-recording | driven-app | simulation

__seek must be pure. The recorder does not play the page and capture the screen; for every frame it calls __seek(frame/fps) and screenshots. So: no CSS transition/animation, nothing driving visuals from requestAnimationFrame, no state accumulating between calls.

The cost is more code. What it buys: the output of --shot 6.5 is byte-for-byte frame 195 at 30fps, so you verify a frame instead of reviewing a video. Full contract: skills/marketing-machine/references/video-guide.md.


Why a browser render, not an image model

Every card and every video frame is a screenshot of a page you can read. That is a deliberate trade, and it buys four things a generative image or video model does not offer:

  • Type and identity survive. The text on a card is text: your fonts inlined as data URIs, your brand tokens injected from config. There is no diffusion pass to warp a glyph or melt a wordmark — which is where generated imagery still fails most visibly, and exactly what a brand asset cannot afford.
  • Output is deterministic. The same source renders the same pixels. A video frame at a given second is exactly the frame the encoder saw, so one still can be reviewed instead of a whole video re-watched, and an approval survives a re-render.
  • Revision is an edit, not a re-roll. No per-asset inference cost, no prompt roulette, no almost-right variant you settle for. Change a line, render again; nothing else moves.
  • The output does not read machine-made. On the visual side there are none of the artifacts that flag AI imagery. On the copy side the linter treats the tells of generated prose — the in-sentence em dash, the not-only-but-also formula, the rule-of-three tic, hype vocabulary — as violations, so what ships reads like it was written, because what survives the gate effectively was.

The cost of the trade is honest too: a browser render cannot invent a photograph, an illustration or a texture. When a post genuinely needs one, that asset comes from somewhere else — this pipeline covers cards, product scenes and motion design, not photography.

The brand role contract

Templates style against six canonical roles: bg, bgDeep, fg, accent, accentSoft, muted. config.brand.roles binds each one to a hex value or to $name referencing an entry in config.brand.colors.

Without that indirection a template has to hardcode one project's color names (--navy), and a project that calls the same color --surface falls silently through to the fallbacks: the artwork looks fine and is in the wrong brand.

So the templates contain no hex value and no font name, and no fallbacks. Font families arrive the same way (--mm-font-sans / --mm-font-mono) — a template naming a family literally would inline that font and then never use it, quietly falling back to a system font. init guesses the roles from the color names, leaves anything uncertain as null, and doctor reports it while rendering refuses (--allow-fallback to override deliberately).


Rules are data, not code

Edit a rule file and what the build enforces changes; no code changes. A rule is either a regex or a named builtin.

Three layers, general to specific:

LayerWhereWhat it holds
corepackaged (rules/core.json)Structural rules. Not one word of any human language: verification table, unsourced number, hashtag count, card word budget, provenance, leftover placeholder
lang.<code>packaged (rules/lang.en.json, lang.tr.json)That language's vocabulary bans and language-specific mechanics
project rulesyour repo (config.lint.projectRules)Claim bans specific to this product. init creates it empty

The third layer is why the package can ship at all: "do not claim compliance for feature X" is a sentence about one product, and baking it in would make every adopter inherit it. The UNPROVABLE rows of your claims.md get written here by hand — a claim there is advice until it appears here, at which point the build enforces it.

Language packs are not decoration. Turkish letters have no mathematical-bold counterpart, so a bolded Turkish word renders half-bold and broken; that is a purely mechanical check no other tool performs. For the same reason rule patterns avoid \w: it does not match ı ş ğ ç ö ü, so a \w-based pattern misses exactly the Turkish copy it was written for. Exclusion character classes ([^\s,.;:]+) are the portable form.

What the linter deliberately does not check

Out of scope on purpose: whether the limitation sentence is any good (its presence is checked, its quality is not), whether a claim maps to a WORKS fact, audience coherence, hook strength, archetype choice. Those need judgment and stay with the skill. Keep the split: putting a non-mechanical rule in the linter produces false positives, and a linter people stop trusting enforces nothing.


CI

One command is the gate:

node <marketing-machine>/scripts/lint.mjs --warnings-as-errors

Exit 1 on any violation (and on any warning in strict mode). Wire it to whatever runs on changes under your marketing/ paths. No workflow file ships with the package, because its shape depends on your CI.


File layout

lib/
  detect.mjs     everything derived from the host codebase — shared by init and scan so they cannot disagree
  config.mjs     config discovery, validation, path resolution, CLI args
  browser.mjs    the ONE Chromium locator and the ONE CDP client
  fonts.mjs      three-tier font resolution (explicit paths -> host node_modules -> this package's)
  html.mjs       the single HTML contract: markers, geometry, role tokens, {{placeholders}}
scripts/         init · scan · lint · render-card · record · doctor · cli
rules/           core.json · lang.en.json · lang.tr.json
templates/       card (four archetypes in one file) + scene — neither contains a hex or a font name
skills/marketing-machine/           the agent skill + the video guide

Font resolution has three tiers so nothing is pinned to one repo: a project that already ships its brand fonts points config.fonts.nodeModulesRoot at them and installs nothing here; a fresh project installs them here (npm i @fontsource-variable/<family>; save it, because npm prunes unsaved packages on the next install and a second font would silently delete the first). A missing font fails the build — shipping a card in a fallback font is worse than not shipping it, because the breakage is invisible until someone looks closely at published artwork.


Configuration reference

KeyNotes
product.name / oneLiner / stageName is detected; the other two are judgments a human writes
language.primaryInferred from the most complete locale dictionary
language.ruleSetsWhich packaged rule files load, e.g. ["core", "lang.en"]
paths.*Where context, posts, cards, scenes and output live
brand.colors / colorsSourceRaw inventory plus the file it came from
brand.rolesThe six canonical roles — what templates consume
brand.wordmark / footerUrlIdentity text for {{...}} placeholders
fonts.sans / mono / nodeModulesRoot / subsetsFamilies, packages, where to resolve them
channels.<name>.card / video / fpsOutput geometry per channel
scan.*Include/exclude globs and the patterns for flags, limits and outbound calls
lint.projectRulesPath to this product's own claim bans
lint.draftLegalPathsPolicy routes a post must not link to yet
lint.requireVerificationTable / verifyMarkerBoth appear inside content, so they follow the content language
provenance.required / allowedWhether every scene must declare how its footage was made

marketing.config.example.json is a filled-in example for a fictional product, there to show what populated fields look like. init never reads it and inherits nothing from it.


Verified state (2026-08-04)

Run end to end on a scratch repo with no relation to the tool's origin (nimbus: purple theme, Inter + JetBrains Mono, source under app/, en+de localisation):

StepResult
init --agent allDerived from the codebase: stack, product name nimbus, domain nimbus.example.co, 6 colors (app/ui/theme.css), Inter + JetBrains Mono, both font packages, en+de, source root app/**, legal routes /privacy /terms, content language en. All six brand roles mapped. Skill installed for Claude Code, Cursor and AGENTS.md
doctorAll green; fonts resolved from this package's own node_modules (the fresh-repo path)
scan4 files; 1 outbound call, 2 limits, 6 colors, 2 languages — everything under the verify marker
card1200×1200 PNG entirely in nimbus branding: purple background, purple accent, wordmark from package.json, footer from homepage, both fonts as detected from CSS
unmapped roleSetting accent to null refused the render, with the reason and the fix
lint (English copy)13 violations + 4 warnings; lang.en vocabulary rules fired and draft-legal-link triggered on the /privacy route detected from the code
lint (Turkish copy)16 violations + 2 warnings on a separate fixture, exit 1
provenanceDeclared → clean; declaration deleted → violation
record --shotThree distinct frames, 1080×1080
record450 frames → 15s @ 30fps MP4
language switchinit --refresh --lang tr moved the rule pack lang.enlang.tr, the heading to Doğrulama notları and the marker to [DOĞRULA, while preserving a customised hashtagCount and the hand-written one-liner. The Turkish pack then fired on Turkish copy.
third fixtureA React repo using Google Fonts and --color-* tokens with no font package: all six roles mapped, @fontsource/manrope inferred from the @import, card rendered in its own palette

Validated against a real production repo

The synthetic fixtures above prove the happy path. A .NET + React monorepo (891 files, three interface languages, an oklch() design-token system, existing hand-written marketing content) proved what the fixtures could not, and it found ten defects the fixtures never would have.

StepOutcome
initDerived stack, product name, 59 colours from the app's own token file, both font families and their packages, three languages, source roots, four legal routes. Correctly reported the domain as not found — the repo's SITE_URL falls back to localhost and the real host arrives from a build-time env var, so it genuinely is not a code fact
brand rolesFive of six mapped from --c-* tokens; one left null. A palette-based proposal for the sixth was accepted, and the card then rendered in the product's actual light theme
scan601 files → 51 outbound calls, 60 limits, 3 languages, all with file:line. After the fixes the egress inventory is real product code (OAuth clients, LLM provider, telemetry) rather than boilerplate
write + lintA post whose only factual claim was a constant the scan found (PeekRowLimit = 50) passed with 0 violations, one warning for missing hashtags, and one note holding publication open because a verify marker was unresolved — exactly the intended behaviour
existing contentproduct-facts.md, claims.md and brand-voice.md were already written by hand in that repo. Adoption left them untouched

Real defects found and fixed during that testing:

  1. --only <value> was being mistaken for a filename
  2. \w does not match Turkish letters — the rule-of-three check never fired at all
  3. %94. failed to match the verification table because of the trailing period
  4. A template's doc comment spelled out <!--FONTS-->, which closed the HTML comment early and printed the remaining doc text onto the card
  5. Windows holds locks on the Chrome profile; cleanup was crashing the render
  6. Templates hardcoded one project's color names, so another repo fell silently through to the fallbacks — this is why the role contract exists
  7. The source-root threshold (3 files) missed small repos and fell back to a src/** that did not exist
  8. Templates hardcoded font family names, so a detected font was inlined and then never used — fixed by making families role variables too
  9. The leftover-placeholder rule flagged config-bound placeholders as unfilled slots, so a correctly rendered card failed the lint — it now resolves against config exactly as the renderer does
  10. Role guessing did not understand prefixed token names (--color-bg, --c-primary), which is the most common convention in practice — three of six roles came back unmapped on a realistic repo, meaning cards could not render at all
  11. doctor truncated failures to their first line, throwing away the fix instruction underneath — the adopter saw "cannot resolve font package" without the npm i command that solves it

Found only by running against a real repo:

  1. --force destroyed hand-written context files. It replaced 567 lines of verified product facts, decided claims and a tuned voice with empty scaffolds, silently. Git had them, so nothing was lost — but the most expensive content in a repo was one mistyped flag away from gone. --force now covers config, templates and the skill only; replacing hand-written content needs --force-context, and every kept file is named in the report
  2. The locale search took the shallowest match, picking i18n over i18n/locales and finding zero languages in a repo that ships three
  3. Only hex colours were recognised. Against an oklch() token system that yielded five stray #ffffff values, mapped every role to white, and would have rendered a white-on-white card
  4. Colour ranking picked a marketing brochure stylesheet over the application's own theme, because it happened to declare more variables
  5. Source roots included test, tooling and design directories, so the outbound-call inventory — the section a data-locality claim depends on — was dominated by exported design-tool boilerplate
  6. Variable-font family names never matched their package: @fontsource-variable/* declares its CSS family as " Variable" while the package is named after the plain family
  7. An unresolved [VERIFY] package name was interpolated into an npm i command, producing an instruction that could not possibly work
  8. A [VERIFY] value rendered onto the card as literal text. It is a non-null string, so it counted as a value; a placeholder reaching published artwork is the exact failure this layer exists to prevent
  9. Domain detection ignored site-URL constants (SITE_URL, metadataBase), which is where a web product's canonical host actually lives
  10. Comment lines matched the egress and limit patterns, filling the draft with prose a reviewer has to reject line by line

Known gaps

Not doneWhy
No claims.md → rule automationClaim wording is product-specific and gets written into the project rules by hand. Next step.
Copy writing is not in codeWriting needs judgment; skills/marketing-machine/SKILL.md runs it, not a script
No Remotion / React-video pipelineThe package stops at the plain HTML contract; a project that wants Remotion adds it and reuses the same linter
The verification tier is not implementedExecuting what a scene shows is product-specific. The contract is in the video guide; the adapter is yours to write.
No CI workflow fileDepends on your CI. lint --warnings-as-errors is the only command needed.
The human-confirmation conversation is untestedscan produces the draft and the skill knows to walk it, but nobody has yet sat through the confirm-each-fact pass on a real product. Until then product-facts.md stays empty and no claim is publishable — which is the correct failure, just not a finished workflow.
Video untested on a real product UIFrames and a full MP4 render from the template scene. Reconstructing an actual product interface as a t-pure scene has not been attempted.
Only en and tr rule packs existA third language gets structural enforcement only until someone writes rules/lang.<code>.json. The skill says so rather than pretending otherwise.

Tests

npm test

node:test only, no dependencies, no browser needed. The suite covers the detection heuristics (locale layouts, role guessing from prefixed tokens, source-root ranking), the HTML contract (role/placeholder resolution and its refusal paths) and the CLI edges that would silently weaken the gate (--only typos, glob overmatch, malformed project rules) — each of the latter found by running against real repos first, then pinned by a test.

License

MIT.

// faq

What is marketing-machine?

Claim-provenance and brand-compliance harness for marketing content: scans your codebase for product truth, lints copy against it, renders on-brand cards and video. Installs as a Claude Code plugin/skill, Cursor rule, or AGENTS.md section.. It is open-source on GitHub.

Is marketing-machine free to use?

marketing-machine is open-source under the MIT license, so it is free to use.

What category does marketing-machine belong to?

marketing-machine is listed under devtools in the Claudeers registry of Claude-compatible tools.

1 views
12 stars
unclaimed
updated 14 days ago

// embed badge

marketing-machine on Claudeers
[![Claudeers](https://claudeers.com/api/badge/marketing-machine.svg)](https://claudeers.com/marketing-machine)

// retro hit counter

marketing-machine hit counter
[![Hits](https://claudeers.com/api/counter/marketing-machine.svg)](https://claudeers.com/marketing-machine)

// reviews

// guestbook

0/500

// related in Developer Tools

🔓

The agent harness performance optimization system. Skills, instincts, memory, security, and research-first development for Claude Code, Codex, Opencode, Curs…

// devtoolsaffaan-m/JavaScript240,305MIT[ claude ]
🔓

Use Garry Tan's exact Claude Code setup: 23 opinionated tools that serve as CEO, Designer, Eng Manager, Release Manager, Doc Engineer, and QA

// devtoolsgarrytan/TypeScript128,278MIT[ claude ]
🔓

AI coding assistant skill (Claude Code, Codex, OpenCode, Cursor, Gemini CLI, and more). Turn any folder of code, SQL schemas, R scripts, shell scripts, docs,…

// devtoolssafishamsi/Python109,277MIT[ claude ]
🔓

Makes your AI agent think like the laziest senior dev in the room. The best code is the code you never wrote.

// devtoolsDietrichGebert/JavaScript104,762MIT[ claude ]
→ see how marketing-machine connects across the ecosystem