@ads-repo/meta-creative-buckets 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,14 @@
1
+ Copyright (c) 2026 Tomas Kliment / adsrepo.com
2
+
3
+ All rights reserved.
4
+
5
+ This tool and its contents are the exclusive property of adsrepo.com.
6
+ You may use this tool solely for your own internal business purposes.
7
+
8
+ You may NOT:
9
+ - Redistribute, resell, sublicense, or share this tool or any part of it
10
+ - Make the source code or generated outputs publicly available
11
+ - Use this tool to build competing products or services
12
+ - Share download links or access credentials with third parties
13
+
14
+ For licensing inquiries: tomas@adsrepo.com
package/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # Meta Creative Buckets
2
+
3
+ A Claude Code skill that reads your Meta ad account and plots every creative on a
4
+ spend vs efficiency grid, so each ad lands in one of four buckets:
5
+
6
+ ```
7
+ CRR bad (high) │ Wait │ Kill
8
+ ├───────────┼──────────
9
+ CRR good (low) │ NotData │ Scale
10
+ └──────────────────────
11
+ low spend enough spend
12
+ ```
13
+
14
+ The output is a single self-contained HTML file that opens in your browser.
15
+
16
+ ## Install
17
+
18
+ ```bash
19
+ npm install -g @ads-repo/meta-creative-buckets
20
+ ```
21
+
22
+ That copies the skill into `~/.claude/skills/meta-creative-buckets`. Restart Claude
23
+ Code and it will pick it up.
24
+
25
+ ## Setup
26
+
27
+ You need two values from Meta.
28
+
29
+ **1. An access token** with the `ads_read` permission. The quickest route is the
30
+ [Graph API Explorer](https://developers.facebook.com/tools/explorer/): pick your app,
31
+ add the `ads_read` scope, generate the token. That one expires in about an hour - for
32
+ repeat use, exchange it for a long-lived token or create a System User token in
33
+ Business Manager.
34
+
35
+ **2. Your ad account id** - the `act_…` number in the Ads Manager URL.
36
+
37
+ Put both in a `.env` file in your project root:
38
+
39
+ ```bash
40
+ META_ACCESS_TOKEN=your-token-here
41
+ META_AD_ACCOUNT_ID=act_1234567890
42
+ ```
43
+
44
+ Reading several accounts from one project? Suffix each pair with a client slug
45
+ (`META_ACCESS_TOKEN_ACME_CZ`) and pass `--client acme-cz`.
46
+
47
+ ## Use it
48
+
49
+ In Claude Code, say:
50
+
51
+ > build the creative buckets dashboard
52
+
53
+ It pulls 30 days of ad-level spend and purchase revenue, works out CRR per creative,
54
+ and opens the dashboard in your browser.
55
+
56
+ ## The metric
57
+
58
+ **CRR = spend ÷ purchase revenue**, on the 7-day-click window. It is ROAS upside down:
59
+ CRR 40% means 40 € of ads per 100 € of sales, so **lower is better** (CRR 40% = ROAS 2.5).
60
+
61
+ Both thresholds are computed from your own account at build time - the target starts at
62
+ your blended CRR, the spend gate at the 75th percentile of per-ad spend - and both are
63
+ live sliders you can move.
64
+
65
+ ## Requires
66
+
67
+ - Node.js 18+
68
+ - Claude Code
69
+ - A Meta ad account that tracks purchases (the grid is built on revenue)
70
+
71
+ No npm dependencies: the scripts use only Node's standard library.
72
+
73
+ ## Docs
74
+
75
+ Full walkthrough, bucket rules and the two Meta API details worth knowing:
76
+ <https://adsrepo.com/products/meta-creative-buckets>
package/SKILL.md ADDED
@@ -0,0 +1,231 @@
1
+ ---
2
+ name: meta-creative-buckets
3
+ description: >
4
+ Build a Spend × CRR creative-bucket matrix dashboard from any Meta ad account — every ad
5
+ plotted as a bubble in a Not-enough-data / Wait / Scale / Kill quadrant grid, with a time
6
+ slider that replays the window day by day. Answers "which creatives deserve more budget
7
+ and which are burning it" for ecommerce accounts, using CRR (spend ÷ revenue) as the
8
+ efficiency metric.
9
+
10
+ Auto-activates when you mention:
11
+ - "creative buckets", "buckets dashboard", "creative matrix"
12
+ - "which creatives should I scale", "scale kill wait"
13
+ - "crr dashboard", "spend vs crr matrix", "roas by creative"
14
+ ---
15
+
16
+ # Meta Creative Buckets
17
+
18
+ Sorts every creative into four buckets on a Spend × CRR grid, so you can see at a glance
19
+ which ads deserve more budget and which are burning it.
20
+
21
+ ```
22
+ CRR bad (high) │ Wait │ Kill
23
+ ├───────────┼──────────
24
+ CRR good (low) │ NotData │ Scale
25
+ └──────────────────────
26
+ low spend enough spend
27
+ ```
28
+
29
+ Works with any Meta ad account that tracks purchases. One dependency: a Meta access token.
30
+
31
+ ## Setup (once)
32
+
33
+ **1. Get a Meta access token** with `ads_read` permission for the account you want to read.
34
+ The quickest route is [Graph API Explorer](https://developers.facebook.com/tools/explorer/):
35
+ pick your app, add the `ads_read` scope, generate the token. Short-lived tokens expire in an
36
+ hour — for repeated use, exchange it for a long-lived one or create a System User token in
37
+ Business Manager.
38
+
39
+ **2. Find your ad account id** — it is the `act_…` number in the Ads Manager URL.
40
+
41
+ **3. Put both in `.env`** in your project root (see `env.example` in this folder):
42
+
43
+ ```bash
44
+ META_ACCESS_TOKEN=your-token-here
45
+ META_AD_ACCOUNT_ID=act_1234567890
46
+ ```
47
+
48
+ Managing several accounts? Suffix each pair with the client slug, upper-cased:
49
+ `META_ACCESS_TOKEN_ACME_CZ` / `META_AD_ACCOUNT_ID_ACME_CZ`, then pass `--client acme-cz`.
50
+
51
+ **4. Add the npm scripts** to your `package.json`:
52
+
53
+ ```json
54
+ "meta:buckets": "node .claude/skills/meta-creative-buckets/scripts/fetch-buckets-data.cjs 30 && node .claude/skills/meta-creative-buckets/scripts/build-buckets-matrix.cjs",
55
+ "meta:buckets-chart": "node .claude/skills/meta-creative-buckets/scripts/build-buckets-matrix.cjs",
56
+ "meta:buckets-creatives": "node .claude/skills/meta-creative-buckets/scripts/fetch-creative-meta.cjs",
57
+ "meta:buckets-lock": "node .claude/skills/meta-creative-buckets/scripts/encrypt-dashboard.cjs"
58
+ ```
59
+
60
+ No install step — the scripts use only Node's standard library. They are `.cjs` so they run
61
+ in both ESM and CommonJS projects.
62
+
63
+ **5. Optional — `buckets.config.json`** in your project root, for a nicer header:
64
+
65
+ ```json
66
+ {
67
+ "client": "acme-cz",
68
+ "accountName": "Acme s.r.o.",
69
+ "logo": "public/acme-logo.png",
70
+ "agencyLogo": "public/my-agency-logo.png"
71
+ }
72
+ ```
73
+
74
+ Every field is optional. With no config the client slug is `default`, the title comes from
75
+ the account name Meta reports, and no logo is drawn.
76
+
77
+ ## Running it
78
+
79
+ ```bash
80
+ npm run meta:buckets # fetch 30 days + build (the usual command)
81
+ npm run meta:buckets-chart # rebuild the HTML from data already on disk
82
+ ```
83
+
84
+ The dashboard opens in your browser automatically. Everything lands in
85
+ `data/meta-ads/<client>/`:
86
+
87
+ - `buckets-latest.json` — per-ad summary + daily series
88
+ - `history/YYYYMMDD-buckets.json` — dated snapshot, drives the day-over-day ▲▼ arrows
89
+ - `buckets-matrix.html` — the dashboard
90
+
91
+ Run it a second day and the table gains movement arrows, comparing against the last snapshot.
92
+
93
+ Custom window: `node …/fetch-buckets-data.cjs 60 2026-09-07` fetches 60 days ending on that
94
+ date. Passing yesterday avoids a partial final day.
95
+
96
+ ## The metric
97
+
98
+ **CRR = spend ÷ purchase revenue**, shown as a percentage, on the 7-day-click window.
99
+
100
+ It is **ROAS upside down**: CRR 40% means you spend 40 € of ads per 100 € of sales, so
101
+ **lower is better** (CRR 40% = ROAS 2.5).
102
+
103
+ ## Bucket rules
104
+
105
+ With **target CRR** `T` and **spend gate** `S` (both live sliders):
106
+
107
+ | Bucket | Rule |
108
+ |---|---|
109
+ | **Not enough data** | `spend < S` — under the gate, never judged |
110
+ | **Scale** | `spend ≥ S` and `CRR ≤ T` — earning well, give it more |
111
+ | **Wait** | `spend ≥ S` and `T < CRR < 3T` — mediocre, watch it |
112
+ | **Kill** | `spend ≥ S` and `CRR ≥ 3T` — burning money |
113
+
114
+ An ad with **no revenue at all** stays in Not-enough-data until it burns `3 × S`, then goes
115
+ to Kill. It is never "Wait" — there is nothing to wait on without a single purchase. Those
116
+ ads have no CRR, so they sit in a separate strip below the plot, placed by spend only.
117
+
118
+ ## Defaults come from the data
119
+
120
+ Both defaults are computed at build time from your account, not hardcoded:
121
+
122
+ - **Target CRR** = the account's **blended CRR** over the window. Starting the line at the
123
+ account average splits creatives into "better than my current average" vs "worse", which
124
+ is the honest opening question.
125
+ - **Spend gate** = 75th percentile of per-ad spend, floored at **3 × AOV**, rounded to 10.
126
+ An ad that has not spent a few average order values cannot be judged on revenue; the
127
+ median would wave through far too many.
128
+
129
+ Both are sliders and click-to-edit — these are only the opening position.
130
+
131
+ ## The dashboard
132
+
133
+ A standalone HTML file, no server needed. Open it off disk, email it, put it anywhere.
134
+
135
+ - **Quadrant scatter** — bubble per ad, area ∝ spend, X = spend (log), Y = CRR %. Click a
136
+ zone to filter the table; click a bubble (or tick a row) to select across both views.
137
+ - **Time slider** — replays the window day by day; every bubble animates to its cumulative
138
+ position as of that day. Day / Week / Month granularity, plus a date-range calendar with
139
+ presets. Picking a range restarts accumulation at the range start. On a first visit the
140
+ thumb pulses with a hint, dismissed for good after the first drag.
141
+ - **Aggregate tiles** — Spend, Revenue and CRR (with ROAS) for whatever is in focus:
142
+ selection > clicked bucket > everything shown. CRR is spend ÷ revenue weighted, never an
143
+ average of per-ad CRRs.
144
+ - **Spend distribution bar** — what share of budget sits in each bucket. Usually the fastest
145
+ read on the page.
146
+ - **Filters** — campaign / ad set / ad (cascading), status, and an Active-only toggle
147
+ (= spent money on the selected day).
148
+ - **Table** — thumbnail + name, spend, revenue, purchases, CRR (with ▲▼ vs the previous
149
+ snapshot), ROAS, last-3-day spend and CRR, CTR, active days. Sortable; ads with no CRR
150
+ always sort last. Click a thumbnail for a lightbox; `←` `→` step through the current sort.
151
+
152
+ ## Sharing it with a password
153
+
154
+ ```bash
155
+ npm run meta:buckets-lock -- "your-password"
156
+ ```
157
+
158
+ Writes `buckets-matrix-locked.html` next to the original: the page is AES-encrypted and asks
159
+ for the password before rendering. Good enough to stop a passer-by following a shared link —
160
+ the ciphertext is public, so do not treat it as confidential.
161
+
162
+ ## Two API details worth knowing
163
+
164
+ **Currency is read from the API, never assumed.** An account named "… CZ" may well bill in
165
+ EUR. Inferring a currency from the account name was once wrong by a factor of ~25.
166
+
167
+ **Revenue uses the named `7d_click` sub-field** of `action_values`, not Meta's plain `value`.
168
+ `value` is the account's default attribution (click + view) and can overstate revenue by
169
+ roughly a third, which would silently understate CRR.
170
+
171
+ ## Rate limiting
172
+
173
+ Meta counts API calls **per account over a rolling window**, so the size of the account
174
+ matters more than the size of the window you asked for. Three defences are built in:
175
+
176
+ 1. **Creative metadata is fetched by id, only for the ads in the window.** An account
177
+ accumulates every ad ever created — 8,000+ is ordinary — while a 30-day window usually has
178
+ a couple of hundred with spend. Paging the whole `/ads` edge to reach those few hundred
179
+ spends the limit on ads that will never appear in the output, and then the creative call
180
+ that carries the thumbnails gets refused. So the fetch reads the insights first, collects
181
+ the ids that actually spent, and looks those up in batches of 40.
182
+ 2. **The fetch degrades instead of failing.** A refused creative pass leaves
183
+ `media_type: 'unknown'` and no preview, rather than discarding the expensive daily
184
+ insights. If the bulk insights range is refused, it falls back to fetching day by day.
185
+ 3. **`npm run meta:buckets-creatives` is a repair pass** — re-run it later to patch media
186
+ types and previews into the existing JSON without re-fetching insights. Rarely needed now
187
+ that the main fetch is narrow, but useful if you were already near the limit.
188
+
189
+ Raise `META_API_RATE_LIMIT_DELAY` in `.env` (default 400 ms) if you keep hitting it, and give
190
+ the account a quiet 15-30 minutes before retrying — consecutive runs keep the window full.
191
+ The bucket maths never depended on creative metadata, so a run without it is still correct.
192
+
193
+ ## Per-ad revenue does not sum to the account total (expected)
194
+
195
+ Summing revenue across ads gives a **higher** number than the same period read at account
196
+ level — typically **+8–12%**. This is Meta's **cross-ad deduplication**, not a bug: one
197
+ purchase touched by two ads is credited to both in ad-level rows, but counted once at
198
+ account level. Any per-ad report has this property.
199
+
200
+ In practice:
201
+
202
+ - **Per-ad CRR is sound for ranking creatives against each other** — that is what this
203
+ dashboard is for.
204
+ - The **aggregate CRR tile is slightly optimistic** versus Ads Manager's account view. Do
205
+ not quote it as the account's true blended CRR; read that at account level.
206
+ - The gap grows with retargeting overlap.
207
+
208
+ ## Optional extras
209
+
210
+ `scripts/optional/` holds two scripts that add promo labels and creative thumbnails from a
211
+ Meta Ad Library scrape. They need a scraping and classification pipeline this skill does not
212
+ include — see the README in that folder for the file format if you want to wire up your own.
213
+ The dashboard works fine without them.
214
+
215
+ ## When explaining results to the user
216
+
217
+ Use MEDIUM-SIMPLE language, in the user's language.
218
+
219
+ Explain **CRR** plainly: how much you spend on ads to earn 100 € of sales. CRR 38% means 38 €
220
+ of ads per 100 € of revenue, so **lower is better**. It is ROAS upside down (CRR 38% = ROAS 2.6).
221
+
222
+ Say plainly how many ads sit in **Not enough data**. On most accounts that is the majority,
223
+ because spend concentrates in a handful of creatives. That is a finding, not a gap: the
224
+ question "should I spread budget wider?" follows directly from it.
225
+
226
+ Quote **CRR and spend together**. A creative can look excellent on a small budget; the
227
+ interesting question is what happens to CRR as spend rises, which the matrix answers by
228
+ position, not the table by sorting.
229
+
230
+ Do not present the aggregate CRR tile as the account's blended CRR — per-ad revenue
231
+ double-counts (see above). Read the real one at account level.
@@ -0,0 +1,8 @@
1
+ {
2
+ "_comment": "Copy to buckets.config.json in your PROJECT ROOT (not this folder). Every field is optional — with no config file at all the dashboard uses the client slug 'default', takes its title from the account name Meta reports, and renders no logo.",
3
+
4
+ "client": "acme-cz",
5
+ "accountName": "Acme s.r.o.",
6
+ "logo": "public/acme-logo.png",
7
+ "agencyLogo": "public/my-agency-logo.png"
8
+ }
package/env.example ADDED
@@ -0,0 +1,29 @@
1
+ # Meta Creative Buckets — credentials
2
+ #
3
+ # Copy the lines you need into your project's .env (or .env.local) in the project root.
4
+ # Never commit the real values — .env belongs in .gitignore.
5
+
6
+ # --- Single account -----------------------------------------------------------
7
+ # The simplest setup. Works with the default client slug, no config file needed.
8
+
9
+ META_ACCESS_TOKEN=your-access-token-here
10
+ META_AD_ACCOUNT_ID=act_1234567890
11
+
12
+ # --- Several accounts ---------------------------------------------------------
13
+ # Suffix each pair with the client slug, upper-cased and non-alphanumerics turned
14
+ # into underscores: --client acme-cz -> _ACME_CZ
15
+ #
16
+ # META_ACCESS_TOKEN_ACME_CZ=...
17
+ # META_AD_ACCOUNT_ID_ACME_CZ=act_1234567890
18
+ # META_ACCESS_TOKEN_OTHER_CLIENT=...
19
+ # META_AD_ACCOUNT_ID_OTHER_CLIENT=act_9876543210
20
+
21
+ # --- Optional tuning ----------------------------------------------------------
22
+ # Raise the delay if the account keeps tripping Meta's rate limit (error 80004).
23
+
24
+ # META_API_VERSION=v25.0
25
+ # META_API_PAGE_SIZE=500
26
+ # META_API_RATE_LIMIT_DELAY=400
27
+
28
+ # Default client when no --client flag is passed.
29
+ # BUCKETS_CLIENT=acme-cz
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const os = require('os');
5
+
6
+ const SKILL_NAME = 'meta-creative-buckets';
7
+ const target = path.join(os.homedir(), '.claude', 'skills', SKILL_NAME);
8
+ const source = __dirname;
9
+
10
+ try {
11
+ fs.mkdirSync(target, { recursive: true });
12
+
13
+ // env.example and the config sample travel with the skill: the first thing a new
14
+ // user needs is to know which two values go where, and they are easier to find
15
+ // next to SKILL.md than inside a node_modules folder.
16
+ ['SKILL.md', 'README.md', 'env.example', 'buckets.config.example.json'].forEach(f => {
17
+ const src = path.join(source, f);
18
+ if (fs.existsSync(src)) fs.copyFileSync(src, path.join(target, f));
19
+ });
20
+
21
+ ['scripts'].forEach(d => {
22
+ const src = path.join(source, d);
23
+ if (fs.existsSync(src)) fs.cpSync(src, path.join(target, d), { recursive: true });
24
+ });
25
+
26
+ console.log(`\n✅ Skill installed to ${target}`);
27
+ console.log(` Restart Claude Code to pick it up.`);
28
+ console.log(` Next: add META_ACCESS_TOKEN and META_AD_ACCOUNT_ID to a .env file`);
29
+ console.log(` in your project (see env.example in the folder above).`);
30
+ console.log(` Updates: npm update -g @ads-repo/meta-creative-buckets`);
31
+ console.log(` More skills: https://adsrepo.com\n`);
32
+ } catch (err) {
33
+ console.error(`\n❌ Install failed: ${err.message}`);
34
+ console.error(` Try running manually: install-meta-creative-buckets\n`);
35
+ process.exit(0);
36
+ }
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@ads-repo/meta-creative-buckets",
3
+ "version": "1.0.0",
4
+ "description": "Sort your Meta creatives into Scale, Wait and Kill on a spend vs CRR grid — drop-in Claude Code skill",
5
+ "keywords": [
6
+ "claude-code",
7
+ "skill",
8
+ "meta-ads",
9
+ "creative-testing",
10
+ "crr",
11
+ "roas",
12
+ "adsrepo"
13
+ ],
14
+ "homepage": "https://adsrepo.com/products/meta-creative-buckets",
15
+ "license": "SEE LICENSE IN LICENSE",
16
+ "author": "adsrepo",
17
+ "files": [
18
+ "LICENSE",
19
+ "SKILL.md",
20
+ "README.md",
21
+ "env.example",
22
+ "buckets.config.example.json",
23
+ "scripts/",
24
+ "install-to-claude.js"
25
+ ],
26
+ "bin": {
27
+ "install-meta-creative-buckets": "install-to-claude.js"
28
+ },
29
+ "scripts": {
30
+ "postinstall": "node ./install-to-claude.js",
31
+ "buckets": "node scripts/fetch-buckets-data.cjs 30 && node scripts/build-buckets-matrix.cjs",
32
+ "buckets-chart": "node scripts/build-buckets-matrix.cjs"
33
+ },
34
+ "engines": {
35
+ "node": ">=18"
36
+ },
37
+ "publishConfig": {
38
+ "access": "public"
39
+ }
40
+ }