marketing-mindset 1.6.2 → 1.8.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 +32 -0
  2. package/bin/cli.js +164 -2
  3. package/package.json +3 -2
package/README.md CHANGED
@@ -82,3 +82,35 @@ npx -y marketing-mindset plan --base 0.03 --lift 0.2 --daily 500 --weeks 4
82
82
  ```
83
83
 
84
84
  The floor from `sample`/`budget` laid out on a calendar: contacts per week, cumulative volume against the floor, the first date the test can honestly be read, the mechanics-only first week, and an honest verdict when the volume cannot reach the floor inside the plan. `--json` for agents. Work with me: https://axelfreeman.com/marketing-engineer.html
85
+
86
+ ## `brief` — the plan as markdown you can send
87
+
88
+ ```
89
+ npx marketing-mindset brief --base 0.03 --lift 0.2 --cpl 4 --daily 500 --start 2026-09-21
90
+ ```
91
+
92
+ One page: the floor in contacts per arm and in total, what reaching it costs at your price per contact,
93
+ the dated read, a week-by-week table with the percentage of the floor covered, the three gates, and the
94
+ kill rule. `--json` returns the same object for an agent or a script.
95
+
96
+ Method, packages and the paid version of this work: https://axelfreeman.com/marketing-engineer.html
97
+
98
+ ## `mde` — what a volume you can afford can actually read
99
+
100
+ The other direction of the same arithmetic: instead of the volume a lift requires, the lift a volume can
101
+ read. Useful when the channel caps the volume (a small list, a niche audience, one store's traffic) and the
102
+ honest question is not "how much do we need" but "what is the smallest win this test could ever show".
103
+
104
+ ```bash
105
+ npx marketing-mindset mde --base 0.03 --per-arm 5000
106
+ # base rate 3.00% · per arm 5,000 · power 0.8 · alpha 0.05
107
+ # smallest readable relative lift: 33.4% (1pp absolute)
108
+ # verdict: at this volume only a difference of 33.4% or more is readable ...
109
+
110
+ npx marketing-mindset mde --base 0.03 --per-arm 5000 --json
111
+ ```
112
+
113
+ `--per-arm` takes the volume you can actually buy per variant; `--power` (0.8 or 0.9) and `--alpha`
114
+ (0.05, 0.01, 0.1) are the same knobs as `sample`. When no lift is readable at that volume the command
115
+ says so instead of printing a number, and names the three ways out: more volume, a higher base rate, or a
116
+ cheaper measurable unit (for cold email the reply rate, floor ~1,500–2,000 sends per variant).
package/bin/cli.js CHANGED
@@ -3,6 +3,7 @@
3
3
  * marketing-mindset help + the test-size floors
4
4
  * marketing-mindset sample required per-arm sample size for a two-proportion test
5
5
  * marketing-mindset kill the kill rule for a running test
6
+ * marketing-mindset mde smallest relative lift a fixed per-arm volume can read
6
7
  * marketing-mindset sections list every section of SKILL.md
7
8
  * marketing-mindset read <text> print one section by fuzzy title match
8
9
  * marketing-mindset install copy the skill into your agent's skills directory
@@ -380,6 +381,161 @@ function runPlan(opts) {
380
381
  return 0;
381
382
  }
382
383
 
384
+
385
+ /* brief — the whole plan as one markdown page a client can keep: the floor, what reaching it costs,
386
+ * the dates, the gate for each week and the kill rule. --json returns the same object for an agent. */
387
+ function mdeLift(rate, perArm, power, alpha) {
388
+ if (sampleSize(rate, 5, power, alpha) > perArm) { return null; }
389
+ let lo = 1e-6, hi = 5;
390
+ for (let i = 0; i < 100; i++) {
391
+ const mid = (lo + hi) / 2;
392
+ if (sampleSize(rate, mid, power, alpha) <= perArm) { hi = mid; } else { lo = mid; }
393
+ }
394
+ return hi;
395
+ }
396
+
397
+ function runMde(opts) {
398
+ const rate = Number(opts.base !== undefined ? opts.base : 0.03);
399
+ const perArm = Number(opts["per-arm"] !== undefined ? opts["per-arm"] : (opts.n !== undefined ? opts.n : 5000));
400
+ const power = Number(opts.power !== undefined ? opts.power : 0.8);
401
+ const alpha = Number(opts.alpha !== undefined ? opts.alpha : 0.05);
402
+ const problems = [];
403
+ if (!(rate > 0 && rate < 1)) { problems.push("base must be between 0 and 1 (0.03 = 3%)"); }
404
+ if (!(perArm > 0)) { problems.push("per-arm must be a positive volume (contacts, sends or visitors)"); }
405
+ if (!ZB[String(power)]) { problems.push("power must be 0.8 or 0.9 - this CLI carries exact quantiles for those two"); }
406
+ if (!ZA[String(alpha)]) { problems.push("alpha must be 0.05, 0.01 or 0.1"); }
407
+ if (problems.length) { console.error("mde: " + problems.join("; ")); return 1; }
408
+ const lift = mdeLift(rate, perArm, power, alpha);
409
+ if (lift === null) {
410
+ const out = {
411
+ base_rate: rate, per_arm: perArm, power, alpha,
412
+ min_relative_lift: null, min_absolute_pp: null, readable: false,
413
+ verdict: "no lift is readable at this volume: even a five-fold difference needs more than " + fmtNum(perArm) + " per arm at a " + (rate * 100).toFixed(2) + "% base rate. Raise the volume, raise the base rate (tighter list or sharper promise), or measure a cheaper unit (for cold email the readable unit is the reply rate, floor ~1,500-2,000 sends per variant)."
414
+ };
415
+ if (opts.json) { console.log(JSON.stringify(out, null, 2)); } else {
416
+ console.log("base rate " + (rate * 100).toFixed(2) + "% · per arm " + fmtNum(perArm) + " · power " + power + " · alpha " + alpha);
417
+ console.log("smallest readable relative lift: none");
418
+ console.log("verdict: " + out.verdict);
419
+ }
420
+ return 0;
421
+ }
422
+ const pct = Math.round(lift * 1000) / 10;
423
+ const pp = Math.round(rate * lift * 100 * 100) / 100;
424
+ const verdict = pct >= 20
425
+ ? "at this volume only a difference of " + pct + "% or more is readable - a smaller win will not separate from noise, so the honest plan is a bigger volume before the first read"
426
+ : "at this volume a difference of " + pct + "% or more is readable - a plan that promises less than that is promising a number this test cannot produce";
427
+ const out = {
428
+ base_rate: rate, per_arm: perArm, power, alpha,
429
+ min_relative_lift: Math.round(lift * 1e9) / 1e9,
430
+ min_absolute_pp: pp, readable: true, verdict
431
+ };
432
+ if (opts.json) { console.log(JSON.stringify(out, null, 2)); } else {
433
+ console.log("base rate " + (rate * 100).toFixed(2) + "% · per arm " + fmtNum(perArm) + " · power " + power + " · alpha " + alpha);
434
+ console.log("smallest readable relative lift: " + pct + "% (" + pp + "pp absolute)");
435
+ console.log("contacts needed at that lift: " + fmtNum(sampleSize(rate, lift, power, alpha)) + " per arm");
436
+ console.log("verdict: " + verdict);
437
+ }
438
+ return 0;
439
+ }
440
+
441
+ function fmtNum(n) {
442
+ return String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ",");
443
+ }
444
+ function addDays(iso, n) {
445
+ const d = new Date(iso + "T00:00:00Z");
446
+ d.setUTCDate(d.getUTCDate() + n);
447
+ return d.toISOString().slice(0, 10);
448
+ }
449
+ function runBrief(opts) {
450
+ const base = Number(opts.base === undefined ? 0 : opts.base);
451
+ const lift = Number(opts.lift === undefined ? 0 : opts.lift);
452
+ if (!isFinite(base) || base <= 0 || base >= 1) {
453
+ console.error("brief: --base must be a rate between 0 and 1 (got: " + JSON.stringify(opts.base) + ")");
454
+ return 1;
455
+ }
456
+ if (!isFinite(lift) || lift <= 0) {
457
+ console.error("brief: --lift must be a positive relative lift, e.g. 0.2 for +20%");
458
+ return 1;
459
+ }
460
+ const cpl = Number(opts.cpl === undefined ? 0 : opts.cpl);
461
+ if (!isFinite(cpl) || cpl <= 0) {
462
+ console.error("brief: --cpl must be a positive cost per contact (got: " + JSON.stringify(opts.cpl) + ")");
463
+ return 1;
464
+ }
465
+ const daily = Number(opts.daily === undefined ? 0 : opts.daily);
466
+ if (opts.daily !== undefined && (!isFinite(daily) || daily <= 0)) {
467
+ console.error("brief: --daily must be a positive number of contacts per day");
468
+ return 1;
469
+ }
470
+ const weeks = opts.weeks === undefined ? 4 : Number(opts.weeks);
471
+ if (!isFinite(weeks) || weeks < 1 || weeks > 52 || Math.floor(weeks) !== weeks) {
472
+ console.error("brief: --weeks must be a whole number between 1 and 52");
473
+ return 1;
474
+ }
475
+ const power = opts.power === undefined ? 0.8 : opts.power;
476
+ const alpha = opts.alpha === undefined ? 0.05 : opts.alpha;
477
+ const start = typeof opts.start === "string" && /^\d{4}-\d{2}-\d{2}$/.test(opts.start)
478
+ ? opts.start : new Date().toISOString().slice(0, 10);
479
+
480
+ const perArm = sampleSize(base, lift, power, alpha);
481
+ const need = perArm * 2;
482
+ const cost = Math.round(need * cpl * 100) / 100;
483
+ const days = daily > 0 ? Math.ceil(need / daily) : null;
484
+ const readDate = days ? addDays(start, days - 1) : null;
485
+ const rows = [];
486
+ for (let w = 1; w <= weeks; w++) {
487
+ const planned = daily > 0 ? daily * 7 : 0;
488
+ const cumulative = planned * w;
489
+ rows.push({
490
+ week: w, start: addDays(start, (w - 1) * 7), end: addDays(start, w * 7 - 1),
491
+ planned_contacts: planned, cumulative,
492
+ against_floor_pct: Math.round(Math.min(100, (cumulative / need) * 100) * 10) / 10
493
+ });
494
+ }
495
+ const gates = [
496
+ "gate 1 (week 1): the tracked event fires on a real lead - if it does not, stop and fix the event, the channel stays closed",
497
+ "gate 2 (50% of floor): counts per arm look like the plan; if one arm is 30% behind, the split is broken, not the offer",
498
+ "gate 3 (read date): read the result at the declared floor or later, never earlier - below the floor the difference is noise"
499
+ ];
500
+ const killRule = "kill if conversions are at or under the floor on the read date; double only when the target rate is hit at or above the floor";
501
+ const result = {
502
+ base_rate: base, target_rate: Math.round(base * (1 + lift) * 10000) / 10000, relative_lift: lift,
503
+ power, alpha, cost_per_contact: cpl, per_arm: perArm, contacts_needed: need,
504
+ cost_to_read_the_test: cost, daily_contacts: daily > 0 ? daily : null,
505
+ days_to_read: days, first_honest_read: readDate, start_date: start, weeks: rows,
506
+ gates, kill_rule: killRule,
507
+ offer: "https://axelfreeman.com/marketing-engineer.html",
508
+ method: "two-proportion floor; same formula as `marketing-mindset sample`"
509
+ };
510
+ if (opts.json) {
511
+ console.log(JSON.stringify(result, null, 2));
512
+ return 0;
513
+ }
514
+ const pct = (x) => (x * 100).toFixed(2);
515
+ console.log("# Campaign brief - the honest test plan\n");
516
+ console.log("Base rate " + pct(base) + "% -> target " + pct(base * (1 + lift)) + "% (relative lift +"
517
+ + (lift * 100).toFixed(0) + "%), power " + power + ", alpha " + alpha);
518
+ console.log("Floor: " + fmtNum(perArm) + " contacts per arm | " + fmtNum(need) + " in total");
519
+ console.log("At $" + cpl + " per contact: $" + fmtNum(cost) + " to read the test");
520
+ if (days) {
521
+ console.log("At " + fmtNum(daily) + " contacts/day: " + days + " days -> first honest read " + readDate);
522
+ } else {
523
+ console.log("Daily volume not given: add --daily to date the read");
524
+ }
525
+ console.log("\n## Weeks");
526
+ console.log("| Week | Dates | Contacts | Cumulative | % of floor |");
527
+ console.log("| --- | --- | --- | --- | --- |");
528
+ rows.forEach((r) => console.log("| " + r.week + " | " + r.start + " .. " + r.end + " | " + fmtNum(r.planned_contacts)
529
+ + " | " + fmtNum(r.cumulative) + " | " + r.against_floor_pct.toFixed(1) + "% |"));
530
+ console.log("\n## Gates");
531
+ gates.forEach((g) => console.log("- " + g));
532
+ console.log("\n## Kill rule");
533
+ console.log("- " + killRule);
534
+ console.log("\nMethod and packages: " + result.offer);
535
+ console.log("Tools: https://axelfreeman.github.io/marketing-mindset/tools/email-test-planner.html");
536
+ return 0;
537
+ }
538
+
383
539
  const cmd = (process.argv[2] || "").toLowerCase();
384
540
  const rest = process.argv.slice(3);
385
541
 
@@ -414,8 +570,12 @@ if (cmd === "sample") {
414
570
  }, null, 2));
415
571
  } else if (cmd === "aeo") {
416
572
  process.exit(runAeo(flags(rest)));
573
+ } else if (cmd === "brief") {
574
+ process.exit(runBrief(flags(rest)));
417
575
  } else if (cmd === "plan") {
418
576
  process.exit(runPlan(flags(rest)));
577
+ } else if (cmd === "mde") {
578
+ process.exit(runMde(flags(rest)));
419
579
  } else if (cmd === "budget") {
420
580
  process.exit(runBudget(flags(rest)));
421
581
  } else if (cmd === "warmup") {
@@ -444,12 +604,14 @@ Usage:
444
604
  --base 0.03 --lift 0.2 --power 0.8 --alpha 0.05
445
605
  npx marketing-mindset kill kill rule for a running test
446
606
  --base 0.03 --n 1500 --lift 0.2
607
+ npx marketing-mindset brief the whole plan as markdown you can send to a client
608
+ --base 0.03 --lift 0.2 --cpl 4 --daily 500 [--weeks 4] [--start 2026-09-21] [--json]
447
609
  npx marketing-mindset budget what an honest test costs before you can read it
448
610
  --base 0.03 --lift 0.2 --cpl 4 [--daily 500]
449
611
  npx marketing-mindset plan dated plan: weekly volume, gates, first honest read
450
612
  --base 0.03 --lift 0.2 --daily 500 [--weeks 4] [--start 2026-09-21]
451
- npx marketing-mindset plan dated plan: weekly volume, gates, first honest read
452
- --base 0.03 --lift 0.2 --daily 500 [--weeks 4] [--start 2026-09-21]
613
+ npx marketing-mindset mde what a volume you can afford can actually read
614
+ --base 0.03 --per-arm 5000 [--power 0.8] [--alpha 0.05] [--json]
453
615
  npx marketing-mindset warmup day-by-day sending ramp with stop rules
454
616
  --start 2026-09-21 --target 200 --days 21 --mailboxes 4
455
617
  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.6.2",
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.",
3
+ "version": "1.8.0",
4
+ "description": "A marketer's operating mindset as an installable agent skill and CLI: test-size floors, kill rules, the lift a volume can actually read, agent-readiness checklist, marketing-engineer job description. npx marketing-mindset sample|kill|mde|budget|brief|plan|warmup|aeo|jd|sections|read|install.",
5
5
  "keywords": [
6
6
  "agent-skill",
7
7
  "skill",
@@ -13,6 +13,7 @@
13
13
  "llm",
14
14
  "prompt",
15
15
  "sample-size",
16
+ "minimum-detectable-effect",
16
17
  "job-description",
17
18
  "cold-email",
18
19
  "deliverability",