marketing-mindset 1.13.0 → 1.14.1

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/README.md CHANGED
@@ -17,7 +17,7 @@ npx marketing-mindset install # copy the skill into your agent's sk
17
17
 
18
18
  `npx marketing-mindset jd --level senior` prints the JD with the artifacts due by day 90,
19
19
  the sample-size floors, the kill rule and the screening questions. Web version:
20
- https://axelfreeman.github.io/marketing-mindset/tools/marketing-engineer-jd-generator.html
20
+ https://axelfreeman.github.io/marketing-mindset/tools/marketing-engineer-jd-generator.html?utm_source=npm&utm_medium=readme&utm_campaign=mindset
21
21
 
22
22
  ## Floors that do not move
23
23
 
@@ -30,11 +30,11 @@ https://axelfreeman.github.io/marketing-mindset/tools/marketing-engineer-jd-gene
30
30
 
31
31
  ## Links
32
32
 
33
- - Skill page: https://axelfreeman.github.io/marketing-mindset/
33
+ - Skill page: https://axelfreeman.github.io/marketing-mindset/?utm_source=npm&utm_medium=readme&utm_campaign=mindset
34
34
  - Source: https://github.com/axelfreeman/marketing-mindset
35
- - Free test-size calculator (browser, no signup): https://axelfreeman.github.io/marketing-mindset/tools/email-test-planner.html
35
+ - Free test-size calculator (browser, no signup): https://axelfreeman.github.io/marketing-mindset/tools/email-test-planner.html?utm_source=npm&utm_medium=readme&utm_campaign=mindset
36
36
  - MCP server, same method over stdio: https://www.npmjs.com/package/marketing-mindset-mcp
37
- - `llms.txt` for agents: https://axelfreeman.com/llms.txt
37
+ - `llms.txt` for agents: https://axelfreeman.com/llms.txt?utm_source=npm&utm_medium=readme&utm_campaign=mindset
38
38
 
39
39
  ## Need this done for you?
40
40
 
@@ -43,8 +43,8 @@ sending setup that passes a deliverability checklist, tests with a written kill
43
43
  measurement inside your own analytics property — is sold as fixed-scope packages, with the
44
44
  scope and the prices published before you get on a call:
45
45
 
46
- **https://axelfreeman.com/marketing-engineer.html** — Sprint $900 · Engine $1,900/month · Full build $2,900.
47
- Scope: https://axelfreeman.com/scope.html · Pricing: https://axelfreeman.com/pricing.html
46
+ **https://axelfreeman.com/marketing-engineer.html?utm_source=npm&utm_medium=readme&utm_campaign=mindset** — Sprint $900 · Engine $1,900/month · Full build $2,900.
47
+ Scope: https://axelfreeman.com/scope.html?utm_source=npm&utm_medium=readme&utm_campaign=mindset · Pricing: https://axelfreeman.com/pricing.html?utm_source=npm&utm_medium=readme&utm_campaign=mindset
48
48
 
49
49
  MIT © Axel Freeman
50
50
 
@@ -55,10 +55,30 @@ npx marketing-mindset aeo # 23 weighted checks, grouped, with the max s
55
55
  npx marketing-mindset aeo --json # same data for a script or an agent
56
56
  ```
57
57
 
58
+ ### Tag every outbound link before it ships
59
+
60
+ A channel you cannot attribute is a channel you cannot kill, so the rule is mechanical:
61
+ links that leave for your own property carry UTM, internal links never do.
62
+
63
+ ```
64
+ npx marketing-mindset utm https://axelfreeman.com/offer.html --source devto --medium article --campaign mindset
65
+ npx marketing-mindset utm --file article.md --source devto --medium article --campaign mindset
66
+ ```
67
+
68
+ `--file` rewrites the file in place and reports how many links it tagged; a second run tags nothing
69
+ (idempotent). Third-party links are left alone, existing query params are kept, and sentence
70
+ punctuation after a URL stays where it belongs. The convention it writes:
71
+
72
+ | parameter | what it holds | examples |
73
+ |---|---|---|
74
+ | `utm_source` | the channel that sent the click | `devto`, `github`, `npm`, `telegram`, `habr`, `reddit` |
75
+ | `utm_medium` | the placement | `article`, `readme`, `package`, `post`, `profile` |
76
+ | `utm_campaign` | the product or the cluster | `mindset`, `tapac`, `hire`, `lyzhi`, `engine` |
77
+
58
78
  The same checklist is a browser tool with a live score:
59
- <https://axelfreeman.github.io/marketing-mindset/tools/aeo-readiness-checklist.html>.
79
+ <https://axelfreeman.github.io/marketing-mindset/tools/aeo-readiness-checklist.html?utm_source=npm&utm_medium=readme&utm_campaign=mindset>.
60
80
  If you want the plumbing in that checklist built for you (offer page, query-intent pages, schema,
61
- `llms.txt`, IndexNow, the tracked event), packages are published at https://axelfreeman.com/marketing-engineer.html.
81
+ `llms.txt`, IndexNow, the tracked event), packages are published at https://axelfreeman.com/marketing-engineer.html?utm_source=npm&utm_medium=readme&utm_campaign=mindset.
62
82
 
63
83
  ### Sending ramp: `warmup`
64
84
 
@@ -66,7 +86,7 @@ If you want the plumbing in that checklist built for you (offer page, query-inte
66
86
  npx -y marketing-mindset@1.4.0 warmup --start 2026-09-21 --target 200 --days 21 --mailboxes 4
67
87
  ```
68
88
 
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
89
+ 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?utm_source=npm&utm_medium=readme&utm_campaign=mindset
70
90
 
71
91
  ### Test budget: `budget`
72
92
 
@@ -74,14 +94,14 @@ Day-by-day ramp for a new sending domain: starts at 20/day (or the target, if sm
74
94
  npx -y marketing-mindset@1.5.0 budget --base 0.03 --lift 0.2 --cpl 4 --daily 500
75
95
  ```
76
96
 
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
97
+ 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?utm_source=npm&utm_medium=readme&utm_campaign=mindset
78
98
  ### Dated plan: `plan`
79
99
 
80
100
  ```
81
101
  npx -y marketing-mindset plan --base 0.03 --lift 0.2 --daily 500 --weeks 4
82
102
  ```
83
103
 
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
104
+ 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?utm_source=npm&utm_medium=readme&utm_campaign=mindset
85
105
 
86
106
  ## `brief` — the plan as markdown you can send
87
107
 
@@ -93,7 +113,7 @@ One page: the floor in contacts per arm and in total, what reaching it costs at
93
113
  the dated read, a week-by-week table with the percentage of the floor covered, the three gates, and the
94
114
  kill rule. `--json` returns the same object for an agent or a script.
95
115
 
96
- Method, packages and the paid version of this work: https://axelfreeman.com/marketing-engineer.html
116
+ Method, packages and the paid version of this work: https://axelfreeman.com/marketing-engineer.html?utm_source=npm&utm_medium=readme&utm_campaign=mindset
97
117
 
98
118
  ## `mde` — what a volume you can afford can actually read
99
119
 
package/bin/cli.js CHANGED
@@ -66,6 +66,107 @@ function readSkill() {
66
66
  return fs.readFileSync(SKILL, "utf8");
67
67
  }
68
68
 
69
+ /* --- UTM tagging: every OUTBOUND link carries source, medium, campaign -------------------
70
+ * Rule: tag links that leave for our own domains; never touch internal site links,
71
+ * never tag a link twice, never drop existing query params.
72
+ */
73
+ const OUR_HOSTS = [
74
+ "axelfreeman.com", "axelfreeman.ru", "axelfreeman.github.io", "tapacapi.com", "tapac.ai",
75
+ "gymcamanalytics.com", "releazer.online", "dublicator.online", "spbilo.ru", "avtootkliki.ru",
76
+ ];
77
+
78
+ function utmTag(url, source, medium, campaign, content) {
79
+ if (!/^https?:\/\//i.test(url)) return url;
80
+ let host;
81
+ try {
82
+ host = new URL(url).hostname.replace(/^www\./, "");
83
+ } catch (e) {
84
+ return url;
85
+ }
86
+ if (OUR_HOSTS.indexOf(host) === -1) return url; // only our own destinations
87
+ if (/[?&]utm_source=/i.test(url)) return url; // idempotent
88
+ const parts = [];
89
+ if (source) parts.push("utm_source=" + encodeURIComponent(source));
90
+ if (medium) parts.push("utm_medium=" + encodeURIComponent(medium));
91
+ if (campaign) parts.push("utm_campaign=" + encodeURIComponent(campaign));
92
+ if (content) parts.push("utm_content=" + encodeURIComponent(content));
93
+ if (!parts.length) return url;
94
+ return url + (url.indexOf("?") === -1 ? "?" : "&") + parts.join("&");
95
+ }
96
+
97
+ function runUtm(opts) {
98
+ const source = opts.source ? String(opts.source) : null;
99
+ const medium = opts.medium ? String(opts.medium) : "referral";
100
+ const campaign = opts.campaign ? String(opts.campaign) : null;
101
+ const content = opts.content ? String(opts.content) : null;
102
+
103
+ // Tag links in prose, but never inside fenced blocks or `inline code` — those are examples.
104
+ function tagText(text) {
105
+ let n = 0;
106
+ let inFence = false;
107
+ const lines = text.split("\n").map((line) => {
108
+ if (/^\s*(```|~~~)/.test(line)) {
109
+ inFence = !inFence;
110
+ return line;
111
+ }
112
+ if (inFence) return line;
113
+ const parts = line.split("`");
114
+ return parts
115
+ .map((seg, i) => {
116
+ if (i % 2 === 1) return seg; // inline code — leave as is
117
+ return seg.replace(/https?:\/\/[^\s)>"'*`]+/g, (u) => {
118
+ const tailMatch = /[.,;:!?]+$/.exec(u); // sentence punctuation is not part of the url
119
+ const tail = tailMatch ? tailMatch[0] : "";
120
+ const core = tail ? u.slice(0, -tail.length) : u;
121
+ const t = utmTag(core, source, medium, campaign, content);
122
+ if (t !== core) n++;
123
+ return t + tail;
124
+ });
125
+ })
126
+ .join("`");
127
+ });
128
+ return { text: lines.join("\n"), count: n };
129
+ }
130
+
131
+ if (opts.file) {
132
+ const p = String(opts.file);
133
+ const src = fs.readFileSync(p, "utf8");
134
+ const res = tagText(src);
135
+ if (res.count) fs.writeFileSync(p, res.text);
136
+ console.log("tagged links: " + res.count + (res.count ? " in " + p : " (nothing to tag)"));
137
+ console.log("utm_source=" + (source || "-") + " utm_medium=" + medium + " utm_campaign=" + (campaign || "-"));
138
+ return 0;
139
+ }
140
+
141
+ const url = opts.url ? String(opts.url) : (opts._ ? String(opts._) : null);
142
+ if (!url) {
143
+ console.log(`utm — tag every outbound link, once, before it ships
144
+
145
+ Usage:
146
+ npx marketing-mindset utm <url> --source devto --medium article --campaign mindset
147
+ npx marketing-mindset utm --file article.md --source devto --medium article --campaign mindset
148
+
149
+ Convention:
150
+ utm_source the channel that sent the click devto | github | npm | telegram | habr | reddit | colony
151
+ utm_medium the placement article | readme | package | post | comment | profile | bio
152
+ utm_campaign the product or cluster mindset | tapac | hire | lyzhi | engine
153
+
154
+ Rules:
155
+ - internal links of your own site carry NO utm — they would restart the session
156
+ - tag only links that leave for our own domains; a third-party link stays clean
157
+ - never tag twice: a url that already has utm_source is returned untouched
158
+ - existing query params are kept`);
159
+ return 1;
160
+ }
161
+ const tagged = utmTag(url, source, medium, campaign, content);
162
+ console.log(tagged);
163
+ if (tagged === url) {
164
+ console.error("(not tagged: internal/no-own-domain/already tagged — see rules)");
165
+ return 1;
166
+ }
167
+ return 0;
168
+ }
169
+
69
170
 
70
171
  /* --- agent-readiness / AEO checklist (23 weighted checks) -------------------------------
71
172
  * The same checklist that runs in the browser tool, as data: agents can read it and score
@@ -1015,6 +1116,8 @@ if (cmd === "sample") {
1015
1116
  process.exit(1);
1016
1117
  }
1017
1118
  console.log("## " + hit.title + "\n" + hit.body.join("\n").trim());
1119
+ } else if (cmd === "utm") {
1120
+ process.exit(runUtm(flags(rest)));
1018
1121
  } else if (cmd === "install") {
1019
1122
  require(path.join(__dirname, "install.js"));
1020
1123
  } else {
@@ -1050,6 +1153,8 @@ Usage:
1050
1153
  --level senior --mode fulltime --surfaces seo,out,analytics
1051
1154
  npx marketing-mindset aeo agent-readiness / AEO checklist (23 weighted checks)
1052
1155
  --json machine-readable
1156
+ npx marketing-mindset utm tag every outbound link, once, before it ships
1157
+ --source devto --medium article --campaign mindset [--url <url> | --file article.md]
1053
1158
  npx marketing-mindset sections every section of the skill
1054
1159
  npx marketing-mindset read numbers print one section
1055
1160
  npx marketing-mindset install copy the skill into your agent's skills dir
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "marketing-mindset",
3
- "version": "1.13.0",
4
- "description": "A marketer's operating mindset as an installable agent skill and CLI: test-size floors, kill rules, honest verdicts on finished tests, which funnel step can fund a verdict, which channel can actually fund a verdict, the lift a volume can read, a queue of readable tests with read-out dates, the cost of one readable verdict. npx marketing-mindset sample|kill|verdict|unit|fit|queue|cost|mde|budget|brief|plan|warmup|aeo|jd|sections|read|install.",
3
+ "version": "1.14.1",
4
+ "description": "A marketer's operating mindset as an installable agent skill and CLI: test-size floors, kill rules, honest verdicts on finished tests, which funnel step can fund a verdict, which channel can actually fund a verdict, the lift a volume can read, a queue of readable tests with read-out dates, the cost of one readable verdict, and UTM tagging on every outbound link. npx marketing-mindset sample|kill|verdict|unit|fit|queue|cost|mde|budget|brief|plan|warmup|aeo|utm|jd|sections|read|install.",
5
5
  "keywords": [
6
6
  "agent-skill",
7
7
  "skill",
8
8
  "marketing",
9
9
  "growth",
10
10
  "aeo",
11
+ "utm",
12
+ "attribution",
13
+ "link-tagging",
11
14
  "claude-code",
12
15
  "codex",
13
16
  "llm",
package/skill/SKILL.md CHANGED
@@ -236,6 +236,25 @@ Sharp, honest, zero fluff. No corporate-speak, no generic templates, no hedging.
236
236
  - Using paid data sources when free ones exist
237
237
  - Working for free — zero-exchange tasks, "for the portfolio", or treating training time as free
238
238
 
239
+ ## Tagging: Every Outbound Link Carries UTM
240
+
241
+ A campaign you cannot attribute is a campaign you cannot kill. The rule is mechanical:
242
+ **every link that leaves for your own property carries UTM; internal links never do.**
243
+
244
+ - `utm_source` — the channel that sent the click: `devto`, `github`, `npm`, `telegram`, `habr`, `reddit`, `colony`.
245
+ - `utm_medium` — the placement: `article`, `readme`, `package`, `post`, `comment`, `profile`, `bio`.
246
+ - `utm_campaign` — the product or cluster: `mindset`, `tapac`, `hire`, `lyzhi`, `engine`.
247
+
248
+ Rules that keep the data clean:
249
+
250
+ - Tag only destinations you own. A third-party link stays clean.
251
+ - **Never tag internal site links.** An internal link with UTM restarts the session, so one visit looks like two and the page's own report becomes wrong.
252
+ - Never tag twice: a URL that already has `utm_source` is done. Tagging happens before the text ships, not after.
253
+ - Keep existing query params — append, never rebuild the URL.
254
+ - Run it: `npx marketing-mindset utm --file article.md --source devto --medium article --campaign mindset`
255
+
256
+ Why it pays: a channel with a tagged link can be read in the same report as everything else and killed on evidence. An untagged one only feels like it is working.
257
+
239
258
  ## Pitfalls
240
259
 
241
260
  - The temptation to answer from general marketing knowledge is strong — resist it; check fresh, recent context first.