Build MarketMaster
KINDAreplaces $29/mosaves $348/yrback to the verdict
A campaign generator whose hard problems are extracting a brief from one website without becoming a crawler, keeping every generated line traceable to an approved fact, and enforcing platform limits in code because the model cannot count.
Before step 1
Everything below is assumed from the first step. Tick each one when you actually have it, not when you plan to.
- installfree
Why Runs the server and the generation worker.
Get it nodejs.org, or nvm install 22. open ↗
Verify
node --version prints v22 or higher - API keycents per campaign at current small-model prices, but set the cap anyway
Why Generation needs exactly one provider, and it should carry a hard spending cap because a retry loop is the usual way a bill happens.
Get it Create a key with your chosen provider and set a monthly limit on the account.
Verify
A campaign generates and the estimated cost is recorded - have readyfree
Why These are the checks the whole build exists to run, and a limit you remembered from last year is the reason an upload gets rejected.
Get it Read each platform's own specification page and copy the numbers into configuration with the date and the URL. open ↗
Verify
Configuration holds every limit with its source URL and check date - have readyfree
Why Your own site or a client's, with permission, because the tool fetches real pages and stores their text on your machine.
Get it Use your own domain, or get written permission for a client's.
Verify
A brief extracts from it inside the page cap - decidefree
Why Both are enforced in code, and choosing them after an expensive night is choosing them too late.
Get it Write down the maximum estimated spend per day and the maximum retries per asset.
Verify
Both are in configuration and named in tests
Data model
Create these before the first phase that stores anything. Changing a table later is the expensive kind of change.
- sources(id, site_root, url, fetched_at, status, bytes, cache_path) · one row per page actually fetched - briefs(id, site_root, fields_json, approved_at, approved_by_note) · nothing generates until approved_at is set - facts(brief_id, key, value, evidence_url) · the only numbers and named customers allowed to appear - campaigns(id, brief_id, model_name, prompt_version, created_at, token_cost_estimate) - assets(id, campaign_id, channel, kind, text, fields_used_json, state) · state is draft, flagged or approved - findings(id, asset_id, rule, detail, severity) · one row per failed check, cleared only by an edit - An asset with an open finding of blocking severity cannot reach approved, enforced in the database write rather than in the template.
Environment variables
These go in a .env file the app reads at startup. The pack's .env.example is this table as a file · copy it, never commit the filled-in version.
| Variable | Needed | Example | Where the value comes from |
|---|---|---|---|
PORT | required | 4860 | Localhost only. The app is a tool, not a service. |
DATABASE_PATH | required | ./data/campaigns.db | Briefs, facts, campaigns, assets and findings. |
MODEL_PROVIDER | required | one provider name | Chosen once. Recorded on every campaign. |
MODEL_API_KEYsecret | required | sk-...redacted | Never logged, never written into an export. |
FETCH_PAGE_CAP | required | 25 | Maximum pages fetched per site, per run. |
FETCH_USER_AGENT | required | campaign-brief-tool/1.0 (contact: you@example.com) | Identifies you to the site you are fetching from. |
DAILY_SPEND_CEILING | required | 2.00 | Estimated. A run above it is refused before any API call. |
MAX_RETRIES_PER_ASSET | required | 3 | The other half of not spending a fortune overnight. |
The build, in order
One polite pass over one website
Fetching is bounded, identifiable, cached and stops at the domain you were given.
Two lines of code and the difference between a tool and a nuisance.
Files
src/fetch/crawl.jstest/crawl.test.jsSomebody is paying for the bandwidth you are using.
Files
src/fetch/client.jsYou will rerun this twenty times while tuning the prompt.
Files
src/fetch/cache.js
done when · tick each as it passeswatch out- Following every link on the page turns a brief extractor into an unannounced crawler that hits a small site a thousand times in a minute, and the first you hear of it is a blocked address. Cap the page count, sleep between requests, identify yourself, and cache so the second run is free.
A brief a person approved, field by field
Extraction proposes, a human disposes, and nothing generates before approval.
Approval without the evidence in front of you is a rubber stamp.
Files
src/extract/fields.jsChecked in the route, not in the interface.
Files
src/routes/campaign.jsThe next phase checks against this table and nothing else.
Files
src/db/facts.js
done when · tick each as it passeswatch out- Letting generation run straight from extraction means a marketing page's own puffery becomes your source of truth, and a phrase the site's designer wrote as filler is now the proof point in three advertisements. Make approval a required step with the source sentence beside each field, so keeping a claim is a decision somebody made.
Assets that say where each line came from
Every generated asset records the brief fields it used.
The reference is what makes the next phase's claim check possible.
Files
src/generate/assets.jsA half-parsed advertisement is worse than no advertisement.
Files
src/generate/validate.jstest/generate.test.jsIn three weeks you will not remember which prompt produced this.
Files
src/db/campaigns.js
done when · tick each as it passeswatch out- Accepting free prose and parsing headlines out of it with a regular expression works until the model returns four headlines under a heading you did not ask for, and now half your assets are fragments of a bullet list. Demand structured output, validate it, and treat a malformed response as a retry rather than as data.
Checks written in code, because the model cannot count
Length, claims and calls to action are validated deterministically against published limits.
The model's opinion about its own length is not evidence.
Files
src/checks/limits.jstest/limits.test.jsHighlighted in the asset, not summarised in a log.
Files
src/checks/claims.jsA rule enforced only in the template is a suggestion.
Files
src/db/assets.js
done when · tick each as it passeswatch out- Asking the model to keep headlines under thirty characters and trusting the answer produces a rejected upload every time, because a model predicting text is not counting it, and the limits differ per channel besides. Count in code against limits you read from the platform's documentation and stored with the date you read them.
Approval, export and a ceiling on what a run can costproduct builder
An approved package leaves the app as files, and no run can quietly spend a fortune.
An approved asset without its brief is just text.
Files
src/export/package.jsA retry loop plus a metered API is a bad night.
Files
src/generate/budget.jsThis is a tool on your machine, not a service.
Files
src/server.js
done when · tick each as it passeswatch out- A retry loop around a validation failure will happily regenerate a campaign forty times overnight against a metered API, and the first sign is the bill. Cap retries, estimate before you call, refuse above a daily ceiling, and show the running total on the page where the button is.
That is the whole plan for MarketMaster. What it deliberately does not cover is below · check the gaps before you call it a replacement.
- Image and video generation.
- Publishing and scheduling integrations.
- Multi-model review.
- Any predicted performance figure.
- multi-model campaign review
- generated image and video assets
- live social and email integrations
- scheduled publishing and delivery history
- maintained ad-pattern and platform-limit references
- A second channel's limits added as configuration rather than code, once the first set has been checked twice
- A diff view between two campaigns from the same brief, which makes prompt changes visible
- A brief that merges several approved sources, once single-source extraction has been used in anger
Need the files? The project pack on the verdict page hands your agent the whole brief · more seo & marketing.