marketing-mindset 1.4.0 → 1.5.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.
Files changed (3) hide show
  1. package/README.md +8 -0
  2. package/bin/cli.js +61 -0
  3. package/package.json +6 -3
package/README.md CHANGED
@@ -67,3 +67,11 @@ npx -y marketing-mindset@1.4.0 warmup --start 2026-09-21 --target 200 --days 21
67
67
  ```
68
68
 
69
69
  Day-by-day ramp for a new sending domain: starts at 20/day (or the target, if smaller), grows by half a day, never above the target, and prints the four stop rules (bounce >2%/day, complaints >0.1%, any reply ends that sequence, opens halving with flat sends -> drop back two ramp days). `--json` gives the machine-readable schedule. Work with me: https://axelfreeman.com/marketing-engineer.html
70
+
71
+ ### Test budget: `budget`
72
+
73
+ ```
74
+ npx -y marketing-mindset@1.5.0 budget --base 0.03 --lift 0.2 --cpl 4 --daily 500
75
+ ```
76
+
77
+ The money side of the same floor `sample` computes: contacts needed in both arms, cost per arm and cost to read the test at your cost per contact, days to the floor at a given daily volume, and the spend guard (below the floor the difference is noise, not a result). `--json` for agents. Work with me: https://axelfreeman.com/marketing-engineer.html
package/bin/cli.js CHANGED
@@ -233,6 +233,63 @@ function runWarmup(opts) {
233
233
  return 0;
234
234
  }
235
235
 
236
+ /* budget — the money it takes to read a test, from the same sample-size floor as `sample`. */
237
+ function runBudget(opts) {
238
+ const base = Number(opts.base === undefined ? 0 : opts.base);
239
+ const lift = Number(opts.lift === undefined ? 0 : opts.lift);
240
+ if (!isFinite(base) || base <= 0 || base >= 1) {
241
+ console.error("budget: --base must be a rate between 0 and 1 (got: " + JSON.stringify(opts.base) + ")");
242
+ return 1;
243
+ }
244
+ if (!isFinite(lift) || lift <= 0) {
245
+ console.error("budget: --lift must be a positive relative lift, e.g. 0.2 for +20% (got: " + JSON.stringify(opts.lift) + ")");
246
+ return 1;
247
+ }
248
+ const cpl = Number(opts.cpl === undefined ? 0 : opts.cpl);
249
+ if (!isFinite(cpl) || cpl <= 0) {
250
+ console.error("budget: --cpl must be a positive cost per contact (got: " + JSON.stringify(opts.cpl) + ")");
251
+ return 1;
252
+ }
253
+ const power = opts.power === undefined ? 0.8 : opts.power;
254
+ const alpha = opts.alpha === undefined ? 0.05 : opts.alpha;
255
+ const daily = opts.daily === undefined ? 0 : Number(opts.daily);
256
+ if (opts.daily !== undefined && (!isFinite(daily) || daily <= 0)) {
257
+ console.error("budget: --daily must be a positive number of contacts per day");
258
+ return 1;
259
+ }
260
+ const perArm = sampleSize(base, lift, power, alpha);
261
+ const bothArms = perArm * 2;
262
+ const perArmCost = Math.round(perArm * cpl * 100) / 100;
263
+ const totalCost = Math.round(bothArms * cpl * 100) / 100;
264
+ const days = daily > 0 ? Math.ceil(bothArms / daily) : null;
265
+ const p2 = base * (1 + lift);
266
+ const expectedLift = Math.round((p2 - base) * 10000) / 10000;
267
+ if (opts.json) {
268
+ console.log(JSON.stringify({
269
+ base_rate: base, target_rate: Math.round(p2 * 10000) / 10000, relative_lift: lift, power: power, alpha: alpha,
270
+ per_arm: perArm, contacts_needed: bothArms, cost_per_contact: cpl,
271
+ cost_per_arm: perArmCost, cost_to_read_the_test: totalCost,
272
+ daily_contacts: daily > 0 ? daily : null, days_to_read: days,
273
+ spend_guard: [
274
+ "below the per-arm floor the difference is noise, not a result - that spend is a donation",
275
+ "decide the kill rule before the first send: what result ends the test and what result doubles it",
276
+ "if cost_to_read_the_test is more than the deal is worth, change the base rate or the channel, not the sample"
277
+ ],
278
+ method: "two-proportion sample size; same formula as `marketing-mindset sample`"
279
+ }, null, 2));
280
+ return 0;
281
+ }
282
+ console.log("Honest test budget: base " + (base * 100).toFixed(2) + "% -> target " + (p2 * 100).toFixed(2)
283
+ + "% (relative lift " + lift + "), power " + power + ", alpha " + alpha);
284
+ console.log("Per arm: " + perArm + " contacts | both arms: " + bothArms + " contacts");
285
+ console.log("At $" + cpl + " per contact: $" + perArmCost + " per arm, $" + totalCost + " to read the test");
286
+ if (days) console.log("At " + daily + " contacts/day: " + days + " days to reach the floor");
287
+ console.log("Spend guard: under the floor the difference is noise, not a result - that spend is a donation.");
288
+ console.log("Kill rule first: what result ends the test, what result doubles it.");
289
+ console.log("Method and packages: https://axelfreeman.com/marketing-engineer.html");
290
+ return 0;
291
+ }
292
+
236
293
  const cmd = (process.argv[2] || "").toLowerCase();
237
294
  const rest = process.argv.slice(3);
238
295
 
@@ -267,6 +324,8 @@ if (cmd === "sample") {
267
324
  }, null, 2));
268
325
  } else if (cmd === "aeo") {
269
326
  process.exit(runAeo(flags(rest)));
327
+ } else if (cmd === "budget") {
328
+ process.exit(runBudget(flags(rest)));
270
329
  } else if (cmd === "warmup") {
271
330
  process.exit(runWarmup(flags(rest)));
272
331
  } else if (cmd === "jd") {
@@ -293,6 +352,8 @@ Usage:
293
352
  --base 0.03 --lift 0.2 --power 0.8 --alpha 0.05
294
353
  npx marketing-mindset kill kill rule for a running test
295
354
  --base 0.03 --n 1500 --lift 0.2
355
+ npx marketing-mindset budget what an honest test costs before you can read it
356
+ --base 0.03 --lift 0.2 --cpl 4 [--daily 500]
296
357
  npx marketing-mindset warmup day-by-day sending ramp with stop rules
297
358
  --start 2026-09-21 --target 200 --days 21 --mailboxes 4
298
359
  npx marketing-mindset jd marketing engineer job description
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "marketing-mindset",
3
- "version": "1.4.0",
4
- "description": "A marketer's operating mindset as an installable agent skill and CLI: test-size floors, kill rules, agent-readiness checklist, marketing-engineer job description. npx marketing-mindset sample|kill|warmup|aeo|jd|sections|read|install.",
3
+ "version": "1.5.0",
4
+ "description": "A marketer's operating mindset as an installable agent skill and CLI: test-size floors, kill rules, agent-readiness checklist, marketing-engineer job description. npx marketing-mindset sample|kill|budget|warmup|aeo|jd|sections|read|install.",
5
5
  "keywords": [
6
6
  "agent-skill",
7
7
  "skill",
@@ -16,7 +16,10 @@
16
16
  "job-description",
17
17
  "cold-email",
18
18
  "deliverability",
19
- "warmup"
19
+ "warmup",
20
+ "budget",
21
+ "cac",
22
+ "unit-economics"
20
23
  ],
21
24
  "license": "MIT",
22
25
  "author": "Axel Freeman",