content-grade 1.0.36 → 1.0.38
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 +19 -6
- package/bin/content-grade.js +83 -62
- package/bin/telemetry.js +24 -19
- package/dist-server/server/routes/demos.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ npx content-grade headline "Why Most Startups Fail at Month 18"
|
|
|
30
30
|
|
|
31
31
|
**One requirement:** [Claude CLI](https://claude.ai/code) must be installed and logged in (`claude login`). No API keys, no accounts, your content never leaves your machine.
|
|
32
32
|
|
|
33
|
-
**Free tier:**
|
|
33
|
+
**Free tier:** 5 analyses/day. No signup. `npx content-grade grade README.md` works immediately.
|
|
34
34
|
|
|
35
35
|
**Install globally** (recommended — skips the `npx` download on every run):
|
|
36
36
|
|
|
@@ -51,13 +51,13 @@ content-grade activate # unlock Pro: batch mode, 100/day
|
|
|
51
51
|
|
|
52
52
|
## Upgrade to Pro
|
|
53
53
|
|
|
54
|
-
**The free tier is real.**
|
|
54
|
+
**The free tier is real.** 5 analyses/day lets you evaluate ContentGrade properly.
|
|
55
55
|
|
|
56
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.
|
|
57
57
|
|
|
58
58
|
| | Free | Pro |
|
|
59
59
|
|---|---|---|
|
|
60
|
-
| Analyses/day |
|
|
60
|
+
| Analyses/day | 5 | Unlimited |
|
|
61
61
|
| All 6 CLI commands | ✓ | ✓ |
|
|
62
62
|
| Web dashboard (6 tools) | ✓ | ✓ |
|
|
63
63
|
| Batch mode (`batch <dir>`) | — | ✓ |
|
|
@@ -215,7 +215,7 @@ Launch with `content-grade start` — opens at [http://localhost:4000](http://lo
|
|
|
215
215
|
| **EmailForge** | `/email-forge` | Subject line + body copy for click-through optimization |
|
|
216
216
|
| **AudienceDecoder** | `/audience` | Twitter handle → audience archetypes and content patterns |
|
|
217
217
|
|
|
218
|
-
Free tier: **
|
|
218
|
+
Free tier: **5 analyses/day**. [Pro ($9/mo)](https://buy.stripe.com/4gM14p87GeCh9vn9ks8k80a): **unlimited analyses** + batch mode.
|
|
219
219
|
|
|
220
220
|
---
|
|
221
221
|
|
|
@@ -229,6 +229,7 @@ Free tier: **3 analyses/day**. [Pro ($9/mo)](https://buy.stripe.com/4gM14p87GeCh
|
|
|
229
229
|
| [Show & Tell](https://github.com/StanislavBG/Content-Grade/discussions/new?category=show-and-tell) | Workflows, CI integrations, results — claim Early Adopter here |
|
|
230
230
|
| [Ideas](https://github.com/StanislavBG/Content-Grade/discussions/new?category=ideas) | Feature requests before they become issues |
|
|
231
231
|
| [Bug reports](https://github.com/StanislavBG/Content-Grade/issues/new?template=bug_report.yml) | Something broken? File it here |
|
|
232
|
+
| [Feedback](https://github.com/StanislavBG/Content-Grade/issues/new?title=Feedback%3A+&labels=feedback) | General feedback — what's working, what isn't |
|
|
232
233
|
|
|
233
234
|
**[All discussions →](https://github.com/StanislavBG/Content-Grade/discussions)**
|
|
234
235
|
|
|
@@ -236,6 +237,18 @@ Free tier: **3 analyses/day**. [Pro ($9/mo)](https://buy.stripe.com/4gM14p87GeCh
|
|
|
236
237
|
|
|
237
238
|
---
|
|
238
239
|
|
|
240
|
+
## Built with Content-Grade?
|
|
241
|
+
|
|
242
|
+
Using ContentGrade in a real workflow? [Open an issue](https://github.com/StanislavBG/Content-Grade/issues/new?title=Built+with+Content-Grade%3A+&labels=use-case) or post in [Show & Tell](https://github.com/StanislavBG/Content-Grade/discussions/new?category=show-and-tell) and tell us:
|
|
243
|
+
|
|
244
|
+
- What are you grading? (blog posts, landing pages, ad copy, emails?)
|
|
245
|
+
- Are you running it in CI? What score threshold do you use?
|
|
246
|
+
- Any workflow, config, or integration worth sharing?
|
|
247
|
+
|
|
248
|
+
Use cases that get shared here get featured in this README. Posting also qualifies you for the [Early Adopter program](EARLY_ADOPTERS.md) — 12 months Pro free.
|
|
249
|
+
|
|
250
|
+
---
|
|
251
|
+
|
|
239
252
|
## Requirements
|
|
240
253
|
|
|
241
254
|
- **Node.js 18+**
|
|
@@ -312,10 +325,10 @@ content-grade analyze ./post.md --verbose
|
|
|
312
325
|
|
|
313
326
|
### Daily limit reached
|
|
314
327
|
|
|
315
|
-
Free tier:
|
|
328
|
+
Free tier: 5 analyses/day, resets at midnight UTC.
|
|
316
329
|
|
|
317
330
|
```
|
|
318
|
-
✗ Daily limit reached (
|
|
331
|
+
✗ Daily limit reached (5/5 free checks used today).
|
|
319
332
|
Upgrade to Pro for unlimited analyses: https://buy.stripe.com/4gM14p87GeCh9vn9ks8k80a
|
|
320
333
|
```
|
|
321
334
|
|
package/bin/content-grade.js
CHANGED
|
@@ -78,22 +78,21 @@ function getLicenseTier() {
|
|
|
78
78
|
|
|
79
79
|
function isProUser() { return Boolean(getLicenseKey()); }
|
|
80
80
|
|
|
81
|
-
function getTodayKey() { return new Date().toISOString().slice(0, 10); }
|
|
82
|
-
|
|
83
81
|
function getUsage() {
|
|
84
82
|
try {
|
|
85
83
|
const d = JSON.parse(readFileSync(USAGE_FILE, 'utf8'));
|
|
86
|
-
|
|
87
|
-
return d;
|
|
88
|
-
|
|
84
|
+
// Migrate legacy daily-reset format: if file has a date key, treat existing count as lifetime
|
|
85
|
+
if (d.date) return { lifetime: d.count || 0 };
|
|
86
|
+
return { lifetime: d.lifetime || 0 };
|
|
87
|
+
} catch { return { lifetime: 0 }; }
|
|
89
88
|
}
|
|
90
89
|
|
|
91
90
|
function incrementUsage() {
|
|
92
91
|
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
93
92
|
const u = getUsage();
|
|
94
|
-
u.
|
|
93
|
+
u.lifetime = (u.lifetime || 0) + 1;
|
|
95
94
|
writeFileSync(USAGE_FILE, JSON.stringify(u, null, 2), 'utf8');
|
|
96
|
-
return u.
|
|
95
|
+
return u.lifetime;
|
|
97
96
|
}
|
|
98
97
|
|
|
99
98
|
// Upgrade links — Free → Pro → Business → Team
|
|
@@ -111,28 +110,28 @@ const TIER_NAMES = {
|
|
|
111
110
|
};
|
|
112
111
|
|
|
113
112
|
const TIER_LIMITS = {
|
|
114
|
-
free:
|
|
113
|
+
free: 15,
|
|
115
114
|
pro: Infinity,
|
|
116
|
-
business:
|
|
117
|
-
team:
|
|
115
|
+
business: Infinity,
|
|
116
|
+
team: Infinity,
|
|
118
117
|
};
|
|
119
118
|
|
|
120
119
|
function getUpgradeMessage() {
|
|
121
120
|
const tier = getLicenseTier();
|
|
122
|
-
if (tier === 'free') return `
|
|
123
|
-
if (tier === 'pro') return `Upgrade to Business —
|
|
124
|
-
if (tier === 'business') return `Upgrade to Team —
|
|
121
|
+
if (tier === 'free') return `Unlimited analyses with Pro — $9/mo → ${UPGRADE_LINKS.free}`;
|
|
122
|
+
if (tier === 'pro') return `Upgrade to Business — priority support, $29/mo → ${UPGRADE_LINKS.pro}`;
|
|
123
|
+
if (tier === 'business') return `Upgrade to Team — team seats, $79/mo → ${UPGRADE_LINKS.business}`;
|
|
125
124
|
return '';
|
|
126
125
|
}
|
|
127
126
|
|
|
128
127
|
// Show usage-aware upgrade CTA after each free run.
|
|
129
|
-
// count =
|
|
128
|
+
// count = lifetime usage AFTER this run.
|
|
130
129
|
function showFreeTierCTA(count) {
|
|
131
|
-
const limit = TIER_LIMITS.free; //
|
|
130
|
+
const limit = TIER_LIMITS.free; // 15
|
|
132
131
|
const remaining = Math.max(0, limit - count);
|
|
133
132
|
|
|
134
133
|
// Track that an upgrade prompt was shown after a run (post-run CTA, not a hard block)
|
|
135
|
-
const ctaStrength = remaining === 0 ? 'strong' : remaining
|
|
134
|
+
const ctaStrength = remaining === 0 ? 'strong' : remaining <= 2 ? 'warning' : 'nudge';
|
|
136
135
|
recordEvent({ event: 'upgrade_prompt_shown', run_count: count, cta_strength: ctaStrength, cta_context: 'post_run' });
|
|
137
136
|
|
|
138
137
|
blank();
|
|
@@ -140,11 +139,10 @@ function showFreeTierCTA(count) {
|
|
|
140
139
|
|
|
141
140
|
if (remaining === 0) {
|
|
142
141
|
// Limit reached — benefit-first wall + explicit post-purchase path
|
|
143
|
-
console.log(` ${RD}${B}
|
|
142
|
+
console.log(` ${RD}${B}You've used all ${limit} free analyses.${R}`);
|
|
144
143
|
blank();
|
|
145
|
-
console.log(` ${WH}${B}Pro removes the cap —
|
|
146
|
-
console.log(` ${D} Unlimited
|
|
147
|
-
console.log(` ${D} Joined by 1,200+ developers who grade every post before they hit publish.${R}`);
|
|
144
|
+
console.log(` ${WH}${B}Pro removes the cap — unlimited analyses, forever.${R}`);
|
|
145
|
+
console.log(` ${D} Unlimited runs · batch entire /posts/ dirs · priority support · CI exit codes${R}`);
|
|
148
146
|
blank();
|
|
149
147
|
console.log(` ${MG}${B}→ Upgrade ($9/mo): ${CY}${UPGRADE_LINKS.free}${R}`);
|
|
150
148
|
blank();
|
|
@@ -153,19 +151,19 @@ function showFreeTierCTA(count) {
|
|
|
153
151
|
console.log(` ${D} 2. Activate: ${CY}content-grade activate <your-key>${R}`);
|
|
154
152
|
console.log(` ${D} 3. Run again — no cap, no counter.${R}`);
|
|
155
153
|
console.log(` ${D} Already have a key? Skip to step 2.${R}`);
|
|
156
|
-
} else if (remaining
|
|
157
|
-
// 1
|
|
158
|
-
console.log(` ${YL}${B}
|
|
154
|
+
} else if (remaining <= 2) {
|
|
155
|
+
// 1-2 runs left — loss aversion + clear Pro value
|
|
156
|
+
console.log(` ${YL}${B}${count}/${limit} runs used · ${remaining} free analysis${remaining === 1 ? '' : 'es'} remaining${R}`);
|
|
159
157
|
blank();
|
|
160
|
-
console.log(` ${WH}
|
|
161
|
-
console.log(` ${MG}→ ${CY}${UPGRADE_LINKS.free}${R}`);
|
|
158
|
+
console.log(` ${WH}${B}Pro: unlimited runs · batch analysis · priority support${R}`);
|
|
159
|
+
console.log(` ${MG}${B}→ Get Pro ($9/mo): ${CY}${UPGRADE_LINKS.free}${R}`);
|
|
162
160
|
} else {
|
|
163
|
-
// Runs available —
|
|
164
|
-
console.log(` ${
|
|
165
|
-
console.log(` ${D}
|
|
161
|
+
// Runs available — always visible count + clear Pro benefits + Stripe link
|
|
162
|
+
console.log(` ${WH}${count}/${limit} runs used · ${remaining} remaining${R}`);
|
|
163
|
+
console.log(` ${D} Pro: unlimited runs · batch analysis · priority support${R}`);
|
|
164
|
+
console.log(` ${MG}→ Upgrade ($9/mo): ${CY}${UPGRADE_LINKS.free}${R}`);
|
|
166
165
|
}
|
|
167
166
|
hr();
|
|
168
|
-
console.log(` ${D}⭐ Like ContentGrade? Star us: https://github.com/StanislavBG/Content-Grade${R}`);
|
|
169
167
|
maybeShowFeedbackCTA(count);
|
|
170
168
|
}
|
|
171
169
|
|
|
@@ -186,7 +184,7 @@ function markEarlyAdopterCTAShown() {
|
|
|
186
184
|
// Show once, after the first successful run, to non-Pro users who haven't seen it.
|
|
187
185
|
function maybeShowEarlyAdopterCTA(count) {
|
|
188
186
|
if (isProUser()) return;
|
|
189
|
-
if (count !== 1) return; // only after the very first run
|
|
187
|
+
if (count !== 1) return; // only after the very first run
|
|
190
188
|
if (hasShownEarlyAdopterCTA()) return; // only once per install
|
|
191
189
|
markEarlyAdopterCTAShown();
|
|
192
190
|
blank();
|
|
@@ -222,34 +220,44 @@ function maybeShowFeedbackCTA(count) {
|
|
|
222
220
|
console.log(` ${D}Found a bug or have a suggestion? Open an issue: ${CY}https://github.com/StanislavBG/Content-Grade/issues/new${R}`);
|
|
223
221
|
}
|
|
224
222
|
|
|
223
|
+
// Community nudge — shown after every run, ungated (free AND Pro).
|
|
224
|
+
// One line, low noise. Converts CLI runs into visible community members.
|
|
225
|
+
const GITHUB_URL = 'https://github.com/StanislavBG/Content-Grade';
|
|
226
|
+
const DISCUSSIONS_URL = 'https://github.com/StanislavBG/Content-Grade/discussions';
|
|
227
|
+
|
|
228
|
+
function showCommunityNudge() {
|
|
229
|
+
blank();
|
|
230
|
+
console.log(` ${D}⭐ Found this useful? Star us on GitHub: ${CY}${GITHUB_URL}${R}`);
|
|
231
|
+
console.log(` ${D}💬 Questions or feedback? Discussions: ${CY}${DISCUSSIONS_URL}${R}`);
|
|
232
|
+
}
|
|
233
|
+
|
|
225
234
|
// Single-line usage counter appended after every command run.
|
|
226
|
-
// count = total runs used
|
|
235
|
+
// count = total lifetime runs used (after this run).
|
|
227
236
|
function showUsageFooter(count) {
|
|
228
237
|
if (isProUser()) return;
|
|
229
238
|
const limit = TIER_LIMITS.free;
|
|
230
239
|
const remaining = Math.max(0, limit - count);
|
|
231
240
|
blank();
|
|
232
241
|
if (remaining === 0) {
|
|
233
|
-
console.log(` ${RD}[
|
|
234
|
-
} else if (remaining
|
|
235
|
-
console.log(` ${YL}[
|
|
242
|
+
console.log(` ${RD}[ All ${limit} free analyses used · Pro removes the cap — $9/mo: ${UPGRADE_LINKS.free} ]${R}`);
|
|
243
|
+
} else if (remaining <= 2) {
|
|
244
|
+
console.log(` ${YL}[ ${remaining} free analysis${remaining === 1 ? '' : 'es'} remaining · Upgrade to Pro: ${UPGRADE_LINKS.free} ]${R}`);
|
|
236
245
|
} else {
|
|
237
|
-
console.log(` ${D}[ ${count}/${limit} free
|
|
246
|
+
console.log(` ${D}[ ${count}/${limit} free analyses used · ${remaining} remaining · Unlimited with Pro ($9/mo): ${UPGRADE_LINKS.free} ]${R}`);
|
|
238
247
|
}
|
|
239
248
|
maybeShowEarlyAdopterCTA(count);
|
|
240
249
|
maybeShowFeedbackCTA(count);
|
|
241
|
-
console.log(` ${D}⭐ Like ContentGrade? Star us: https://github.com/StanislavBG/Content-Grade${R}`);
|
|
242
250
|
}
|
|
243
251
|
|
|
244
|
-
// Returns { ok: boolean, count: number, limit: number } for tier-aware
|
|
245
|
-
// Used by batch mode to stop processing when the
|
|
252
|
+
// Returns { ok: boolean, count: number, limit: number } for tier-aware lifetime limit checks.
|
|
253
|
+
// Used by batch mode to stop processing when the lifetime limit is reached.
|
|
246
254
|
// Pro tier has Infinity limit and always returns ok: true.
|
|
247
|
-
function
|
|
255
|
+
function checkLifetimeLimit() {
|
|
248
256
|
const tier = getLicenseTier();
|
|
249
257
|
const limit = TIER_LIMITS[tier] ?? TIER_LIMITS.free;
|
|
250
258
|
if (limit === Infinity) return { ok: true, count: 0, limit: Infinity };
|
|
251
259
|
const usage = getUsage();
|
|
252
|
-
const count = usage.
|
|
260
|
+
const count = usage.lifetime || 0;
|
|
253
261
|
return { ok: count < limit, count, limit };
|
|
254
262
|
}
|
|
255
263
|
|
|
@@ -260,25 +268,28 @@ function checkFreeTierLimit() {
|
|
|
260
268
|
if (isProUser()) return false;
|
|
261
269
|
const usage = getUsage();
|
|
262
270
|
const limit = TIER_LIMITS.free;
|
|
263
|
-
if (usage.
|
|
271
|
+
if (usage.lifetime < limit) return false;
|
|
264
272
|
|
|
265
273
|
// Track funnel event — user hit the limit and saw the upgrade prompt
|
|
266
274
|
recordEvent({ event: 'free_limit_hit', version: _version });
|
|
267
|
-
recordEvent({ event: 'upgrade_prompt_shown', run_count: usage.
|
|
275
|
+
recordEvent({ event: 'upgrade_prompt_shown', run_count: usage.lifetime });
|
|
268
276
|
|
|
269
277
|
blank();
|
|
270
278
|
hr();
|
|
271
|
-
console.log(` ${
|
|
279
|
+
console.log(` ${WH}${B}You've used all ${limit} free runs — Pro gives you unlimited.${R}`);
|
|
272
280
|
blank();
|
|
273
|
-
console.log(` ${
|
|
281
|
+
console.log(` ${WH}Pro unlocks:${R}`);
|
|
282
|
+
console.log(` ${D} · Unlimited runs — no daily cap, no lifetime cap${R}`);
|
|
283
|
+
console.log(` ${D} · Batch analysis — grade entire /posts/ directories at once${R}`);
|
|
284
|
+
console.log(` ${D} · Priority support — direct response within 24h${R}`);
|
|
285
|
+
console.log(` ${D} · CI integration — exit codes, JSON/HTML output${R}`);
|
|
274
286
|
blank();
|
|
275
|
-
console.log(` ${
|
|
287
|
+
console.log(` ${MG}${B}→ Get Pro ($9/mo): ${CY}${UPGRADE_LINKS.free}${R}`);
|
|
288
|
+
blank();
|
|
289
|
+
console.log(` ${D}After checkout — running again in 2 minutes:${R}`);
|
|
276
290
|
console.log(` ${D} 1. Get your key: ${CY}https://content-grade.onrender.com/my-license${R}`);
|
|
277
291
|
console.log(` ${D} 2. Activate: ${CY}content-grade activate <your-key>${R}`);
|
|
278
|
-
console.log(` ${D} 3. Re-run
|
|
279
|
-
console.log(` ${D} Already have a key? Skip to step 2.${R}`);
|
|
280
|
-
blank();
|
|
281
|
-
console.log(` ${D}Pro unlocks: unlimited analyses · batch mode · CI integration · JSON/HTML output${R}`);
|
|
292
|
+
console.log(` ${D} 3. Re-run — no cap, no counter.${R}`);
|
|
282
293
|
hr();
|
|
283
294
|
blank();
|
|
284
295
|
return true;
|
|
@@ -718,6 +729,7 @@ async function cmdAnalyze(filePath) {
|
|
|
718
729
|
} else {
|
|
719
730
|
showFreeTierCTA(usageCount);
|
|
720
731
|
}
|
|
732
|
+
if (!_ciMode) showCommunityNudge();
|
|
721
733
|
|
|
722
734
|
// CI exit code — shown after full output so user sees the score before exit
|
|
723
735
|
if (_ciMode) {
|
|
@@ -879,6 +891,7 @@ async function cmdHeadline(text) {
|
|
|
879
891
|
const usageCount = incrementUsage();
|
|
880
892
|
showFreeTierCTA(usageCount);
|
|
881
893
|
}
|
|
894
|
+
showCommunityNudge();
|
|
882
895
|
}
|
|
883
896
|
|
|
884
897
|
// ── Init command ──────────────────────────────────────────────────────────────
|
|
@@ -977,12 +990,12 @@ async function cmdActivate() {
|
|
|
977
990
|
blank();
|
|
978
991
|
console.log(` ${B}Pro features:${R}`);
|
|
979
992
|
console.log(` ${D} content-grade batch ./posts/ ${R}${D}# analyze all files in a directory${R}`);
|
|
980
|
-
console.log(` ${D} Unlimited analyses
|
|
993
|
+
console.log(` ${D} Unlimited analyses (vs 15 free total)${R}`);
|
|
981
994
|
blank();
|
|
982
995
|
return;
|
|
983
996
|
}
|
|
984
997
|
|
|
985
|
-
console.log(` ${D}Pro unlocks unlimited
|
|
998
|
+
console.log(` ${D}Pro unlocks unlimited analyses + batch mode for $9/mo:${R}`);
|
|
986
999
|
blank();
|
|
987
1000
|
console.log(` ${MG}${B}→ Get Pro: ${CY}${UPGRADE_LINKS.free}${R}`);
|
|
988
1001
|
blank();
|
|
@@ -1104,7 +1117,7 @@ async function cmdActivate() {
|
|
|
1104
1117
|
const tierLimit = TIER_LIMITS[activatedTier] || TIER_LIMITS.free;
|
|
1105
1118
|
const tierLimitDisplay = tierLimit === Infinity ? 'unlimited' : `${tierLimit}`;
|
|
1106
1119
|
blank();
|
|
1107
|
-
ok(`${(TIER_NAMES[activatedTier] || activatedTier).split(' —')[0]} activated!
|
|
1120
|
+
ok(`${(TIER_NAMES[activatedTier] || activatedTier).split(' —')[0]} activated! You now have ${tierLimitDisplay} analyses.`);
|
|
1108
1121
|
blank();
|
|
1109
1122
|
console.log(` ${B}Try it:${R}`);
|
|
1110
1123
|
console.log(` ${CY} content-grade batch ./posts/${R} ${D}# analyze all files${R}`);
|
|
@@ -1120,7 +1133,7 @@ async function cmdBatch(dirPath) {
|
|
|
1120
1133
|
blank();
|
|
1121
1134
|
console.log(` ${D}Batch mode grades every file in a directory in one shot — Pro only.${R}`);
|
|
1122
1135
|
blank();
|
|
1123
|
-
console.log(` Pro is $9/mo and
|
|
1136
|
+
console.log(` Pro is $9/mo and unlocks unlimited analyses.`);
|
|
1124
1137
|
blank();
|
|
1125
1138
|
console.log(` ${MG}${B}→ Get Pro: ${CY}${UPGRADE_LINKS.free}${R}`);
|
|
1126
1139
|
blank();
|
|
@@ -1201,12 +1214,12 @@ async function cmdBatch(dirPath) {
|
|
|
1201
1214
|
const rel = f.startsWith(process.cwd()) ? f.slice(process.cwd().length + 1) : f;
|
|
1202
1215
|
process.stdout.write(` ${D}[${i + 1}/${files.length}]${R} ${rel}...`);
|
|
1203
1216
|
|
|
1204
|
-
// Guard: check
|
|
1205
|
-
const batchLimitCheck =
|
|
1217
|
+
// Guard: check lifetime limit before each analysis
|
|
1218
|
+
const batchLimitCheck = checkLifetimeLimit();
|
|
1206
1219
|
if (!batchLimitCheck.ok) {
|
|
1207
1220
|
process.stdout.write(` ${YL}limit reached${R}\n`);
|
|
1208
1221
|
blank();
|
|
1209
|
-
warn(`
|
|
1222
|
+
warn(`Free limit reached (${batchLimitCheck.count}/${batchLimitCheck.limit} lifetime analyses used). Remaining files skipped.`);
|
|
1210
1223
|
break;
|
|
1211
1224
|
}
|
|
1212
1225
|
|
|
@@ -1255,7 +1268,8 @@ async function cmdBatch(dirPath) {
|
|
|
1255
1268
|
|
|
1256
1269
|
if (_jsonMode) process.stdout.write(JSON.stringify(results, null, 2) + '\n');
|
|
1257
1270
|
|
|
1258
|
-
if (!isProUser() && results.length) showUsageFooter(getUsage().
|
|
1271
|
+
if (!isProUser() && results.length) showUsageFooter(getUsage().lifetime);
|
|
1272
|
+
if (results.length) showCommunityNudge();
|
|
1259
1273
|
}
|
|
1260
1274
|
|
|
1261
1275
|
// ── Start command ─────────────────────────────────────────────────────────────
|
|
@@ -1332,7 +1346,7 @@ function cmdStart() {
|
|
|
1332
1346
|
info(` EmailForge — ${url}/email-forge`);
|
|
1333
1347
|
info(` AudienceDecoder — ${url}/audience`);
|
|
1334
1348
|
blank();
|
|
1335
|
-
info(`Free tier:
|
|
1349
|
+
info(`Free tier: 15 analyses total. Unlimited with Pro ($9/mo) → ${UPGRADE_LINKS.free}`);
|
|
1336
1350
|
info(`Press Ctrl+C to stop`);
|
|
1337
1351
|
blank();
|
|
1338
1352
|
openBrowser(url);
|
|
@@ -1507,7 +1521,7 @@ async function cmdMetrics() {
|
|
|
1507
1521
|
function cmdHelp() {
|
|
1508
1522
|
banner();
|
|
1509
1523
|
console.log(` ${D}AI-powered content quality scoring — readability, SEO, structure analysis${R}`);
|
|
1510
|
-
console.log(` ${D}v${_version} · ${CY}npmjs.com/package/content-grade${R}`);
|
|
1524
|
+
console.log(` ${D}v${_version} · ${CY}npmjs.com/package/content-grade${R} · ${CY}${GITHUB_URL}${R}`);
|
|
1511
1525
|
blank();
|
|
1512
1526
|
console.log(` ${B}QUICK START${R}`);
|
|
1513
1527
|
blank();
|
|
@@ -1606,14 +1620,21 @@ function cmdHelp() {
|
|
|
1606
1620
|
|
|
1607
1621
|
console.log(` ${B}PRICING${R}`);
|
|
1608
1622
|
blank();
|
|
1609
|
-
console.log(` Free
|
|
1623
|
+
console.log(` Free 15 total $0`);
|
|
1610
1624
|
console.log(` Pro Unlimited $9/mo`);
|
|
1611
|
-
console.log(` Business
|
|
1612
|
-
console.log(` Team
|
|
1625
|
+
console.log(` Business Unlimited $29/mo`);
|
|
1626
|
+
console.log(` Team Unlimited $79/mo`);
|
|
1613
1627
|
blank();
|
|
1614
1628
|
console.log(` Get Pro: ${CY}${UPGRADE_LINKS.free}${R} ${D}# direct checkout${R}`);
|
|
1615
1629
|
console.log(` Activate: ${CY}content-grade activate <your-license-key>${R}`);
|
|
1616
1630
|
blank();
|
|
1631
|
+
|
|
1632
|
+
console.log(` ${B}COMMUNITY${R}`);
|
|
1633
|
+
blank();
|
|
1634
|
+
console.log(` ⭐ GitHub: ${CY}${GITHUB_URL}${R}`);
|
|
1635
|
+
console.log(` 💬 Discussions: ${CY}${DISCUSSIONS_URL}${R}`);
|
|
1636
|
+
console.log(` 🐛 Issues: ${CY}${FEEDBACK_URL}${R}`);
|
|
1637
|
+
blank();
|
|
1617
1638
|
}
|
|
1618
1639
|
|
|
1619
1640
|
// ── Feedback command ─────────────────────────────────────────────────────────
|
package/bin/telemetry.js
CHANGED
|
@@ -234,44 +234,49 @@ export function telemetryStatus() {
|
|
|
234
234
|
|
|
235
235
|
// ── Rate limiting ─────────────────────────────────────────────────────────────
|
|
236
236
|
|
|
237
|
-
const
|
|
238
|
-
const PRO_DAILY_LIMIT = 20;
|
|
239
|
-
const BUSINESS_DAILY_LIMIT = 100;
|
|
240
|
-
const TEAM_DAILY_LIMIT = 500;
|
|
237
|
+
const FREE_LIFETIME_LIMIT = 15;
|
|
241
238
|
|
|
242
239
|
/**
|
|
243
|
-
* Check whether the user is within their
|
|
240
|
+
* Check whether the user is within their lifetime limit for a given command type.
|
|
244
241
|
* Type: 'analyze' | 'headline'
|
|
242
|
+
* Pro/paid users always pass (unlimited).
|
|
243
|
+
* Free users get 15 lifetime total runs across all command types.
|
|
245
244
|
* Returns { ok: boolean, used: number, remaining: number, limit: number, isPro: boolean }
|
|
246
245
|
*/
|
|
247
|
-
export function
|
|
246
|
+
export function checkUsageLimit(type = 'analyze') {
|
|
248
247
|
const cfg = loadConfig();
|
|
249
|
-
const tier = cfg?.tier || 'free';
|
|
250
|
-
const LIMITS = { free: FREE_DAILY_LIMIT, pro: PRO_DAILY_LIMIT, business: BUSINESS_DAILY_LIMIT, team: TEAM_DAILY_LIMIT };
|
|
251
|
-
const limit = cfg?.licenseKey ? (LIMITS[tier] || PRO_DAILY_LIMIT) : FREE_DAILY_LIMIT;
|
|
252
248
|
const isPro = !!cfg?.licenseKey;
|
|
249
|
+
if (isPro) return { ok: true, used: 0, remaining: Infinity, limit: Infinity, isPro: true };
|
|
253
250
|
|
|
254
|
-
const
|
|
255
|
-
|
|
251
|
+
const limit = FREE_LIFETIME_LIMIT;
|
|
252
|
+
// Lifetime usage stored in cfg.lifetimeUsage (total across all types)
|
|
253
|
+
const used = cfg?.lifetimeUsage?.total ?? 0;
|
|
256
254
|
const remaining = limit - used;
|
|
257
255
|
return { ok: remaining > 0, used, remaining: Math.max(0, remaining), limit, isPro };
|
|
258
256
|
}
|
|
259
257
|
|
|
258
|
+
/** @deprecated Use checkUsageLimit instead */
|
|
259
|
+
export const checkDailyLimit = checkUsageLimit;
|
|
260
|
+
|
|
260
261
|
/**
|
|
261
|
-
* Increment the
|
|
262
|
-
* Returns the new usage count.
|
|
262
|
+
* Increment the lifetime usage counter for a command type after a successful run.
|
|
263
|
+
* Returns the new total usage count.
|
|
263
264
|
*/
|
|
264
|
-
export function
|
|
265
|
+
export function incrementUsage(type = 'analyze') {
|
|
265
266
|
const cfg = loadConfig() || {};
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
cfg.
|
|
267
|
+
if (!cfg.lifetimeUsage) {
|
|
268
|
+
// Migrate from legacy dailyUsage if present
|
|
269
|
+
cfg.lifetimeUsage = { total: 0, analyze: 0, headline: 0 };
|
|
269
270
|
}
|
|
270
|
-
cfg.
|
|
271
|
+
cfg.lifetimeUsage[type] = (cfg.lifetimeUsage[type] ?? 0) + 1;
|
|
272
|
+
cfg.lifetimeUsage.total = (cfg.lifetimeUsage.total ?? 0) + 1;
|
|
271
273
|
saveConfig(cfg);
|
|
272
|
-
return cfg.
|
|
274
|
+
return cfg.lifetimeUsage.total;
|
|
273
275
|
}
|
|
274
276
|
|
|
277
|
+
/** @deprecated Use incrementUsage instead */
|
|
278
|
+
export const incrementDailyUsage = incrementUsage;
|
|
279
|
+
|
|
275
280
|
// ── Internal ──────────────────────────────────────────────────────────────────
|
|
276
281
|
|
|
277
282
|
function _write(event) {
|
|
@@ -20,10 +20,10 @@ const TIER_LIMITS = {
|
|
|
20
20
|
team: TEAM_TIER_LIMIT,
|
|
21
21
|
};
|
|
22
22
|
function freeGateMsg(_what) {
|
|
23
|
-
return `Free
|
|
23
|
+
return `Free limit reached (${FREE_TIER_LIMIT} per session). Upgrade to Pro for unlimited: ${UPGRADE_URL}`;
|
|
24
24
|
}
|
|
25
25
|
function paidGateMsg(limit) {
|
|
26
|
-
return `
|
|
26
|
+
return `Rate limit reached (${limit}/day). Upgrade for more at content-grade.github.io/Content-Grade/#pricing. Resets at midnight UTC.`;
|
|
27
27
|
}
|
|
28
28
|
function hashIp(ip) {
|
|
29
29
|
return createHash('sha256').update(ip).digest('hex');
|
package/package.json
CHANGED