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 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:** 3 analyses/day. No signup. `npx content-grade grade README.md` works immediately.
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.** 3 analyses/day lets you evaluate ContentGrade properly.
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 | 3 | Unlimited |
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: **3 analyses/day**. [Pro ($9/mo)](https://buy.stripe.com/4gM14p87GeCh9vn9ks8k80a): **unlimited analyses** + batch mode.
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: 3 analyses/day, resets at midnight UTC.
328
+ Free tier: 5 analyses/day, resets at midnight UTC.
316
329
 
317
330
  ```
318
- ✗ Daily limit reached (3/3 free checks used today).
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
 
@@ -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
- if (d.date !== getTodayKey()) return { date: getTodayKey(), count: 0 };
87
- return d;
88
- } catch { return { date: getTodayKey(), count: 0 }; }
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.count = (u.count || 0) + 1;
93
+ u.lifetime = (u.lifetime || 0) + 1;
95
94
  writeFileSync(USAGE_FILE, JSON.stringify(u, null, 2), 'utf8');
96
- return u.count;
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: 5,
113
+ free: 15,
115
114
  pro: Infinity,
116
- business: 100,
117
- team: 500,
115
+ business: Infinity,
116
+ team: Infinity,
118
117
  };
119
118
 
120
119
  function getUpgradeMessage() {
121
120
  const tier = getLicenseTier();
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}`;
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 = today's usage AFTER this run (1 = first run, 3 = last free run).
128
+ // count = lifetime usage AFTER this run.
130
129
  function showFreeTierCTA(count) {
131
- const limit = TIER_LIMITS.free; // 5
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 === 1 ? 'warning' : 'nudge';
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}That's your ${limit} free analyses for today.${R}`);
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 — 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}`);
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 === 1) {
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}`);
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}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}`);
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 — 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}`);
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 today
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 today (after this run).
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}[ Limit reached (${count}/${limit}) · Pro removes it permanently — $9/mo: ${UPGRADE_LINKS.free} ]${R}`);
234
- } else if (remaining === 1) {
235
- console.log(` ${YL}[ 1 free run left today upgrade to avoid the block: ${UPGRADE_LINKS.free} ]${R}`);
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 today · ${remaining} left · Unlimited with Pro ($9/mo): ${UPGRADE_LINKS.free} ]${R}`);
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 daily limit checks.
245
- // Used by batch mode to stop processing when the daily limit is reached.
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 checkDailyLimit() {
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.count || 0;
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.count < limit) return false;
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.count });
275
+ recordEvent({ event: 'upgrade_prompt_shown', run_count: usage.lifetime });
268
276
 
269
277
  blank();
270
278
  hr();
271
- console.log(` ${YL}${B}Daily limit reached ${usage.count}/${limit} free analyses used today.${R}`);
279
+ console.log(` ${WH}${B}You've used all ${limit} free runs Pro gives you unlimited.${R}`);
272
280
  blank();
273
- console.log(` ${MG}${B}→ Upgrade to Pro ($9/mo): ${CY}${UPGRADE_LINKS.free}${R}`);
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(` ${D}After checkout you're running again in 2 minutes:${R}`);
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 your command — no cap, no counter.${R}`);
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/day (vs 5 free)${R}`);
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 runs/day + batch mode for $9/mo:${R}`);
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! Your daily limit is now ${tierLimitDisplay} analyses.`);
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 also unlocks unlimited daily runs.`);
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 daily limit before each analysis
1205
- const batchLimitCheck = checkDailyLimit();
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(`Daily limit reached (${batchLimitCheck.count}/${batchLimitCheck.limit}). Remaining files skipped.`);
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().count);
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: 5 analyses/day. Unlimited with Pro ($9/mo) → ${UPGRADE_LINKS.free}`);
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 5/day $0`);
1623
+ console.log(` Free 15 total $0`);
1610
1624
  console.log(` Pro Unlimited $9/mo`);
1611
- console.log(` Business 100/day $29/mo`);
1612
- console.log(` Team 500/day $79/mo`);
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 FREE_DAILY_LIMIT = 3;
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 daily limit for a given command type.
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 checkDailyLimit(type = 'analyze') {
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 today = new Date().toISOString().slice(0, 10);
255
- const used = (cfg?.dailyUsage?.date === today ? cfg.dailyUsage[type] ?? 0 : 0);
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 daily usage counter for a command type after a successful run.
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 incrementDailyUsage(type = 'analyze') {
265
+ export function incrementUsage(type = 'analyze') {
265
266
  const cfg = loadConfig() || {};
266
- const today = new Date().toISOString().slice(0, 10);
267
- if (!cfg.dailyUsage || cfg.dailyUsage.date !== today) {
268
- cfg.dailyUsage = { date: today, analyze: 0, headline: 0 };
267
+ if (!cfg.lifetimeUsage) {
268
+ // Migrate from legacy dailyUsage if present
269
+ cfg.lifetimeUsage = { total: 0, analyze: 0, headline: 0 };
269
270
  }
270
- cfg.dailyUsage[type] = (cfg.dailyUsage[type] ?? 0) + 1;
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.dailyUsage[type];
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 daily limit reached (${FREE_TIER_LIMIT}/day). Upgrade to Pro for unlimited: ${UPGRADE_URL}`;
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 `Daily limit reached (${limit}/day). Upgrade for more at content-grade.github.io/Content-Grade/#pricing. Resets at midnight UTC.`;
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "content-grade",
3
- "version": "1.0.36",
3
+ "version": "1.0.38",
4
4
  "description": "AI-powered content analysis CLI. Score any blog post, landing page, or ad copy in under 30 seconds — runs on Claude CLI, no API key needed.",
5
5
  "type": "module",
6
6
  "bin": {