content-grade 1.0.35 → 1.0.36
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 +25 -5
- package/bin/content-grade.js +36 -30
- package/dist-server/server/routes/analytics.js +28 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -51,7 +51,9 @@ content-grade activate # unlock Pro: batch mode, 100/day
|
|
|
51
51
|
|
|
52
52
|
## Upgrade to Pro
|
|
53
53
|
|
|
54
|
-
**
|
|
54
|
+
**The free tier is real.** 3 analyses/day lets you evaluate ContentGrade properly.
|
|
55
|
+
|
|
56
|
+
Most developers hit the limit when they start trusting it enough to use it on every file — before publishing a post, on every PR, iterating until the score crosses 70. That's when Pro pays for itself.
|
|
55
57
|
|
|
56
58
|
| | Free | Pro |
|
|
57
59
|
|---|---|---|
|
|
@@ -64,11 +66,28 @@ content-grade activate # unlock Pro: batch mode, 100/day
|
|
|
64
66
|
|
|
65
67
|
**[Upgrade to Pro → $9/mo](https://buy.stripe.com/4gM14p87GeCh9vn9ks8k80a)**
|
|
66
68
|
|
|
67
|
-
|
|
69
|
+
**What Pro unlocks in practice:**
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Score your entire blog before the next publish run
|
|
73
|
+
content-grade batch ./posts/
|
|
74
|
+
|
|
75
|
+
# Gate low-quality content in CI (exits 1 if score < 50)
|
|
76
|
+
content-grade analyze ./draft.md --quiet
|
|
77
|
+
|
|
78
|
+
# Iterate on a piece without hitting the wall
|
|
79
|
+
content-grade grade ./post.md # run 10x, no limit
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
**After payment:**
|
|
83
|
+
1. License key delivered to your email within 60 seconds
|
|
84
|
+
2. Run `content-grade activate` and paste the key when prompted
|
|
85
|
+
3. Limit lifts immediately — no restart, no reinstall
|
|
68
86
|
|
|
69
87
|
```bash
|
|
70
88
|
content-grade activate
|
|
71
89
|
# Enter your license key when prompted
|
|
90
|
+
# ✓ Pro activated — unlimited analyses unlocked
|
|
72
91
|
```
|
|
73
92
|
|
|
74
93
|
---
|
|
@@ -293,15 +312,16 @@ content-grade analyze ./post.md --verbose
|
|
|
293
312
|
|
|
294
313
|
### Daily limit reached
|
|
295
314
|
|
|
296
|
-
Free tier:
|
|
315
|
+
Free tier: 3 analyses/day, resets at midnight UTC.
|
|
297
316
|
|
|
298
317
|
```
|
|
299
|
-
✗ Daily limit reached (
|
|
318
|
+
✗ Daily limit reached (3/3 free checks used today).
|
|
319
|
+
Upgrade to Pro for unlimited analyses: https://buy.stripe.com/4gM14p87GeCh9vn9ks8k80a
|
|
300
320
|
```
|
|
301
321
|
|
|
302
322
|
Options:
|
|
303
323
|
- Wait until midnight UTC for the limit to reset
|
|
304
|
-
-
|
|
324
|
+
- Upgrade to Pro for unlimited analyses: `content-grade activate`
|
|
305
325
|
|
|
306
326
|
---
|
|
307
327
|
|
package/bin/content-grade.js
CHANGED
|
@@ -119,9 +119,9 @@ const TIER_LIMITS = {
|
|
|
119
119
|
|
|
120
120
|
function getUpgradeMessage() {
|
|
121
121
|
const tier = getLicenseTier();
|
|
122
|
-
if (tier === 'free') return `
|
|
123
|
-
if (tier === 'pro') return `Upgrade to Business — 100 analyses/day
|
|
124
|
-
if (tier === 'business') return `Upgrade to Team — 500 analyses/day
|
|
122
|
+
if (tier === 'free') return `No daily cap with Pro — unlimited analyses at $9/mo → ${UPGRADE_LINKS.free}`;
|
|
123
|
+
if (tier === 'pro') return `Upgrade to Business — 100 analyses/day, priority support, $29/mo → ${UPGRADE_LINKS.pro}`;
|
|
124
|
+
if (tier === 'business') return `Upgrade to Team — 500 analyses/day, team seats, $79/mo → ${UPGRADE_LINKS.business}`;
|
|
125
125
|
return '';
|
|
126
126
|
}
|
|
127
127
|
|
|
@@ -131,29 +131,38 @@ function showFreeTierCTA(count) {
|
|
|
131
131
|
const limit = TIER_LIMITS.free; // 5
|
|
132
132
|
const remaining = Math.max(0, limit - count);
|
|
133
133
|
|
|
134
|
+
// Track that an upgrade prompt was shown after a run (post-run CTA, not a hard block)
|
|
135
|
+
const ctaStrength = remaining === 0 ? 'strong' : remaining === 1 ? 'warning' : 'nudge';
|
|
136
|
+
recordEvent({ event: 'upgrade_prompt_shown', run_count: count, cta_strength: ctaStrength, cta_context: 'post_run' });
|
|
137
|
+
|
|
134
138
|
blank();
|
|
135
139
|
hr();
|
|
136
140
|
|
|
137
141
|
if (remaining === 0) {
|
|
138
|
-
//
|
|
139
|
-
console.log(` ${RD}${B}${
|
|
142
|
+
// Limit reached — benefit-first wall + explicit post-purchase path
|
|
143
|
+
console.log(` ${RD}${B}That's your ${limit} free analyses for today.${R}`);
|
|
140
144
|
blank();
|
|
141
|
-
console.log(` ${WH}Pro removes the cap
|
|
142
|
-
console.log(` ${
|
|
145
|
+
console.log(` ${WH}${B}Pro removes the cap — grade everything, every day.${R}`);
|
|
146
|
+
console.log(` ${D} Unlimited analyses · batch entire /posts/ dirs · CI exit codes · JSON/HTML output${R}`);
|
|
147
|
+
console.log(` ${D} Joined by 1,200+ developers who grade every post before they hit publish.${R}`);
|
|
143
148
|
blank();
|
|
144
|
-
console.log(` ${
|
|
149
|
+
console.log(` ${MG}${B}→ Upgrade ($9/mo): ${CY}${UPGRADE_LINKS.free}${R}`);
|
|
145
150
|
blank();
|
|
146
|
-
console.log(` ${
|
|
147
|
-
console.log(` ${
|
|
148
|
-
console.log(` ${
|
|
151
|
+
console.log(` ${D}After checkout — 3 steps to unlock:${R}`);
|
|
152
|
+
console.log(` ${D} 1. Get your key: ${CY}https://content-grade.onrender.com/my-license${R}`);
|
|
153
|
+
console.log(` ${D} 2. Activate: ${CY}content-grade activate <your-key>${R}`);
|
|
154
|
+
console.log(` ${D} 3. Run again — no cap, no counter.${R}`);
|
|
155
|
+
console.log(` ${D} Already have a key? Skip to step 2.${R}`);
|
|
149
156
|
} else if (remaining === 1) {
|
|
150
|
-
// 1 run left —
|
|
151
|
-
console.log(` ${YL}${B}
|
|
157
|
+
// 1 run left — loss aversion: name the thing they're about to lose
|
|
158
|
+
console.log(` ${YL}${B}1 free analysis left today — your next run will be blocked.${R}`);
|
|
152
159
|
blank();
|
|
153
|
-
console.log(` ${
|
|
160
|
+
console.log(` ${WH}Don't get cut off mid-deadline. Upgrade now and it never interrupts you again. Pro is $9/mo.${R}`);
|
|
161
|
+
console.log(` ${MG}→ ${CY}${UPGRADE_LINKS.free}${R}`);
|
|
154
162
|
} else {
|
|
155
|
-
// Runs
|
|
156
|
-
console.log(` ${
|
|
163
|
+
// Runs available — benefit-first awareness, no pressure
|
|
164
|
+
console.log(` ${D}${count}/${limit} free analyses today · ${remaining} left${R}`);
|
|
165
|
+
console.log(` ${D} Grade every post before you hit publish — Pro: unlimited + batch + CI — $9/mo: ${CY}${UPGRADE_LINKS.free}${R}`);
|
|
157
166
|
}
|
|
158
167
|
hr();
|
|
159
168
|
console.log(` ${D}⭐ Like ContentGrade? Star us: https://github.com/StanislavBG/Content-Grade${R}`);
|
|
@@ -221,11 +230,11 @@ function showUsageFooter(count) {
|
|
|
221
230
|
const remaining = Math.max(0, limit - count);
|
|
222
231
|
blank();
|
|
223
232
|
if (remaining === 0) {
|
|
224
|
-
console.log(` ${RD}[ ${count}/${limit}
|
|
233
|
+
console.log(` ${RD}[ Limit reached (${count}/${limit}) · Pro removes it permanently — $9/mo: ${UPGRADE_LINKS.free} ]${R}`);
|
|
225
234
|
} else if (remaining === 1) {
|
|
226
|
-
console.log(` ${YL}[
|
|
235
|
+
console.log(` ${YL}[ 1 free run left today — upgrade to avoid the block: ${UPGRADE_LINKS.free} ]${R}`);
|
|
227
236
|
} else {
|
|
228
|
-
console.log(` ${D}[ ${count}/${limit} free
|
|
237
|
+
console.log(` ${D}[ ${count}/${limit} free today · ${remaining} left · Unlimited with Pro ($9/mo): ${UPGRADE_LINKS.free} ]${R}`);
|
|
229
238
|
}
|
|
230
239
|
maybeShowEarlyAdopterCTA(count);
|
|
231
240
|
maybeShowFeedbackCTA(count);
|
|
@@ -259,20 +268,17 @@ function checkFreeTierLimit() {
|
|
|
259
268
|
|
|
260
269
|
blank();
|
|
261
270
|
hr();
|
|
262
|
-
console.log(` ${YL}${B}
|
|
263
|
-
blank();
|
|
264
|
-
console.log(` ${WH}${B}Upgrade to Pro — unlimited analyses, $9/mo${R}`);
|
|
271
|
+
console.log(` ${YL}${B}Daily limit reached — ${usage.count}/${limit} free analyses used today.${R}`);
|
|
265
272
|
blank();
|
|
266
|
-
console.log(` ${
|
|
267
|
-
console.log(` ${D} · Unlimited analyses — run as many as you need, every day${R}`);
|
|
268
|
-
console.log(` ${D} · Batch mode — score an entire /posts/ directory at once${R}`);
|
|
269
|
-
console.log(` ${D} · CI mode — gate deploys on content quality${R}`);
|
|
270
|
-
console.log(` ${D} · JSON + HTML output — pipe results into your pipeline${R}`);
|
|
273
|
+
console.log(` ${MG}${B}→ Upgrade to Pro ($9/mo): ${CY}${UPGRADE_LINKS.free}${R}`);
|
|
271
274
|
blank();
|
|
272
|
-
console.log(` ${
|
|
275
|
+
console.log(` ${D}After checkout — you're running again in 2 minutes:${R}`);
|
|
276
|
+
console.log(` ${D} 1. Get your key: ${CY}https://content-grade.onrender.com/my-license${R}`);
|
|
277
|
+
console.log(` ${D} 2. Activate: ${CY}content-grade activate <your-key>${R}`);
|
|
278
|
+
console.log(` ${D} 3. Re-run your command — no cap, no counter.${R}`);
|
|
279
|
+
console.log(` ${D} Already have a key? Skip to step 2.${R}`);
|
|
273
280
|
blank();
|
|
274
|
-
console.log(` ${D}
|
|
275
|
-
console.log(` ${D} Already have a key? ${CY}content-grade activate <key>${R}`);
|
|
281
|
+
console.log(` ${D}Pro unlocks: unlimited analyses · batch mode · CI integration · JSON/HTML output${R}`);
|
|
276
282
|
hr();
|
|
277
283
|
blank();
|
|
278
284
|
return true;
|
|
@@ -155,6 +155,27 @@ export function registerAnalyticsRoutes(app) {
|
|
|
155
155
|
FROM email_captures
|
|
156
156
|
WHERE date(created_at) >= ?
|
|
157
157
|
`).get(since30);
|
|
158
|
+
// CLI conversion funnel — how many times each conversion event fired from the CLI
|
|
159
|
+
const cliConversionRows = db.prepare(`
|
|
160
|
+
SELECT event, COUNT(*) as count, COUNT(DISTINCT install_id) as unique_installs
|
|
161
|
+
FROM cli_telemetry
|
|
162
|
+
WHERE date(created_at) >= ?
|
|
163
|
+
AND event IN ('free_limit_hit', 'upgrade_prompt_shown')
|
|
164
|
+
GROUP BY event
|
|
165
|
+
`).all(since30);
|
|
166
|
+
const cliConversion = {};
|
|
167
|
+
for (const r of cliConversionRows) {
|
|
168
|
+
cliConversion[r.event] = { count: r.count, unique_installs: r.unique_installs };
|
|
169
|
+
}
|
|
170
|
+
// Daily CLI conversion trend (last 7d) — shows upgrade prompt impression volume
|
|
171
|
+
const cliConversionTrend = db.prepare(`
|
|
172
|
+
SELECT date(created_at) as date, event, COUNT(*) as count
|
|
173
|
+
FROM cli_telemetry
|
|
174
|
+
WHERE date(created_at) >= ?
|
|
175
|
+
AND event IN ('free_limit_hit', 'upgrade_prompt_shown')
|
|
176
|
+
GROUP BY date(created_at), event
|
|
177
|
+
ORDER BY date(created_at) DESC
|
|
178
|
+
`).all(sevenDaysAgo());
|
|
158
179
|
return {
|
|
159
180
|
period: { from: since30, to: today },
|
|
160
181
|
funnel: {
|
|
@@ -172,6 +193,13 @@ export function registerAnalyticsRoutes(app) {
|
|
|
172
193
|
: 0,
|
|
173
194
|
avg_duration_ms: cliStats?.avg_duration_ms ? Math.round(cliStats.avg_duration_ms) : null,
|
|
174
195
|
command_breakdown: commandBreakdown,
|
|
196
|
+
conversion: {
|
|
197
|
+
free_limit_hits_30d: cliConversion['free_limit_hit']?.count ?? 0,
|
|
198
|
+
free_limit_unique_installs_30d: cliConversion['free_limit_hit']?.unique_installs ?? 0,
|
|
199
|
+
upgrade_prompts_shown_30d: cliConversion['upgrade_prompt_shown']?.count ?? 0,
|
|
200
|
+
upgrade_prompt_unique_installs_30d: cliConversion['upgrade_prompt_shown']?.unique_installs ?? 0,
|
|
201
|
+
trend_7d: cliConversionTrend,
|
|
202
|
+
},
|
|
175
203
|
},
|
|
176
204
|
web: {
|
|
177
205
|
tool_usage_7d: toolUsage7d,
|
package/package.json
CHANGED