content-grade 1.0.37 → 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 +13 -0
- package/bin/content-grade.js +61 -59
- package/bin/telemetry.js +24 -19
- package/dist-server/server/routes/demos.js +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -229,6 +229,7 @@ Free tier: **5 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: **5 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+**
|
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,16 +151,17 @@ 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
167
|
maybeShowFeedbackCTA(count);
|
|
@@ -185,7 +184,7 @@ function markEarlyAdopterCTAShown() {
|
|
|
185
184
|
// Show once, after the first successful run, to non-Pro users who haven't seen it.
|
|
186
185
|
function maybeShowEarlyAdopterCTA(count) {
|
|
187
186
|
if (isProUser()) return;
|
|
188
|
-
if (count !== 1) return; // only after the very first run
|
|
187
|
+
if (count !== 1) return; // only after the very first run
|
|
189
188
|
if (hasShownEarlyAdopterCTA()) return; // only once per install
|
|
190
189
|
markEarlyAdopterCTAShown();
|
|
191
190
|
blank();
|
|
@@ -233,32 +232,32 @@ function showCommunityNudge() {
|
|
|
233
232
|
}
|
|
234
233
|
|
|
235
234
|
// Single-line usage counter appended after every command run.
|
|
236
|
-
// count = total runs used
|
|
235
|
+
// count = total lifetime runs used (after this run).
|
|
237
236
|
function showUsageFooter(count) {
|
|
238
237
|
if (isProUser()) return;
|
|
239
238
|
const limit = TIER_LIMITS.free;
|
|
240
239
|
const remaining = Math.max(0, limit - count);
|
|
241
240
|
blank();
|
|
242
241
|
if (remaining === 0) {
|
|
243
|
-
console.log(` ${RD}[
|
|
244
|
-
} else if (remaining
|
|
245
|
-
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}`);
|
|
246
245
|
} else {
|
|
247
|
-
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}`);
|
|
248
247
|
}
|
|
249
248
|
maybeShowEarlyAdopterCTA(count);
|
|
250
249
|
maybeShowFeedbackCTA(count);
|
|
251
250
|
}
|
|
252
251
|
|
|
253
|
-
// Returns { ok: boolean, count: number, limit: number } for tier-aware
|
|
254
|
-
// 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.
|
|
255
254
|
// Pro tier has Infinity limit and always returns ok: true.
|
|
256
|
-
function
|
|
255
|
+
function checkLifetimeLimit() {
|
|
257
256
|
const tier = getLicenseTier();
|
|
258
257
|
const limit = TIER_LIMITS[tier] ?? TIER_LIMITS.free;
|
|
259
258
|
if (limit === Infinity) return { ok: true, count: 0, limit: Infinity };
|
|
260
259
|
const usage = getUsage();
|
|
261
|
-
const count = usage.
|
|
260
|
+
const count = usage.lifetime || 0;
|
|
262
261
|
return { ok: count < limit, count, limit };
|
|
263
262
|
}
|
|
264
263
|
|
|
@@ -269,25 +268,28 @@ function checkFreeTierLimit() {
|
|
|
269
268
|
if (isProUser()) return false;
|
|
270
269
|
const usage = getUsage();
|
|
271
270
|
const limit = TIER_LIMITS.free;
|
|
272
|
-
if (usage.
|
|
271
|
+
if (usage.lifetime < limit) return false;
|
|
273
272
|
|
|
274
273
|
// Track funnel event — user hit the limit and saw the upgrade prompt
|
|
275
274
|
recordEvent({ event: 'free_limit_hit', version: _version });
|
|
276
|
-
recordEvent({ event: 'upgrade_prompt_shown', run_count: usage.
|
|
275
|
+
recordEvent({ event: 'upgrade_prompt_shown', run_count: usage.lifetime });
|
|
277
276
|
|
|
278
277
|
blank();
|
|
279
278
|
hr();
|
|
280
|
-
console.log(` ${
|
|
279
|
+
console.log(` ${WH}${B}You've used all ${limit} free runs — Pro gives you unlimited.${R}`);
|
|
280
|
+
blank();
|
|
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}`);
|
|
281
286
|
blank();
|
|
282
|
-
console.log(` ${MG}${B}→
|
|
287
|
+
console.log(` ${MG}${B}→ Get Pro ($9/mo): ${CY}${UPGRADE_LINKS.free}${R}`);
|
|
283
288
|
blank();
|
|
284
|
-
console.log(` ${D}After checkout —
|
|
289
|
+
console.log(` ${D}After checkout — running again in 2 minutes:${R}`);
|
|
285
290
|
console.log(` ${D} 1. Get your key: ${CY}https://content-grade.onrender.com/my-license${R}`);
|
|
286
291
|
console.log(` ${D} 2. Activate: ${CY}content-grade activate <your-key>${R}`);
|
|
287
|
-
console.log(` ${D} 3. Re-run
|
|
288
|
-
console.log(` ${D} Already have a key? Skip to step 2.${R}`);
|
|
289
|
-
blank();
|
|
290
|
-
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}`);
|
|
291
293
|
hr();
|
|
292
294
|
blank();
|
|
293
295
|
return true;
|
|
@@ -988,12 +990,12 @@ async function cmdActivate() {
|
|
|
988
990
|
blank();
|
|
989
991
|
console.log(` ${B}Pro features:${R}`);
|
|
990
992
|
console.log(` ${D} content-grade batch ./posts/ ${R}${D}# analyze all files in a directory${R}`);
|
|
991
|
-
console.log(` ${D} Unlimited analyses
|
|
993
|
+
console.log(` ${D} Unlimited analyses (vs 15 free total)${R}`);
|
|
992
994
|
blank();
|
|
993
995
|
return;
|
|
994
996
|
}
|
|
995
997
|
|
|
996
|
-
console.log(` ${D}Pro unlocks unlimited
|
|
998
|
+
console.log(` ${D}Pro unlocks unlimited analyses + batch mode for $9/mo:${R}`);
|
|
997
999
|
blank();
|
|
998
1000
|
console.log(` ${MG}${B}→ Get Pro: ${CY}${UPGRADE_LINKS.free}${R}`);
|
|
999
1001
|
blank();
|
|
@@ -1115,7 +1117,7 @@ async function cmdActivate() {
|
|
|
1115
1117
|
const tierLimit = TIER_LIMITS[activatedTier] || TIER_LIMITS.free;
|
|
1116
1118
|
const tierLimitDisplay = tierLimit === Infinity ? 'unlimited' : `${tierLimit}`;
|
|
1117
1119
|
blank();
|
|
1118
|
-
ok(`${(TIER_NAMES[activatedTier] || activatedTier).split(' —')[0]} activated!
|
|
1120
|
+
ok(`${(TIER_NAMES[activatedTier] || activatedTier).split(' —')[0]} activated! You now have ${tierLimitDisplay} analyses.`);
|
|
1119
1121
|
blank();
|
|
1120
1122
|
console.log(` ${B}Try it:${R}`);
|
|
1121
1123
|
console.log(` ${CY} content-grade batch ./posts/${R} ${D}# analyze all files${R}`);
|
|
@@ -1131,7 +1133,7 @@ async function cmdBatch(dirPath) {
|
|
|
1131
1133
|
blank();
|
|
1132
1134
|
console.log(` ${D}Batch mode grades every file in a directory in one shot — Pro only.${R}`);
|
|
1133
1135
|
blank();
|
|
1134
|
-
console.log(` Pro is $9/mo and
|
|
1136
|
+
console.log(` Pro is $9/mo and unlocks unlimited analyses.`);
|
|
1135
1137
|
blank();
|
|
1136
1138
|
console.log(` ${MG}${B}→ Get Pro: ${CY}${UPGRADE_LINKS.free}${R}`);
|
|
1137
1139
|
blank();
|
|
@@ -1212,12 +1214,12 @@ async function cmdBatch(dirPath) {
|
|
|
1212
1214
|
const rel = f.startsWith(process.cwd()) ? f.slice(process.cwd().length + 1) : f;
|
|
1213
1215
|
process.stdout.write(` ${D}[${i + 1}/${files.length}]${R} ${rel}...`);
|
|
1214
1216
|
|
|
1215
|
-
// Guard: check
|
|
1216
|
-
const batchLimitCheck =
|
|
1217
|
+
// Guard: check lifetime limit before each analysis
|
|
1218
|
+
const batchLimitCheck = checkLifetimeLimit();
|
|
1217
1219
|
if (!batchLimitCheck.ok) {
|
|
1218
1220
|
process.stdout.write(` ${YL}limit reached${R}\n`);
|
|
1219
1221
|
blank();
|
|
1220
|
-
warn(`
|
|
1222
|
+
warn(`Free limit reached (${batchLimitCheck.count}/${batchLimitCheck.limit} lifetime analyses used). Remaining files skipped.`);
|
|
1221
1223
|
break;
|
|
1222
1224
|
}
|
|
1223
1225
|
|
|
@@ -1266,7 +1268,7 @@ async function cmdBatch(dirPath) {
|
|
|
1266
1268
|
|
|
1267
1269
|
if (_jsonMode) process.stdout.write(JSON.stringify(results, null, 2) + '\n');
|
|
1268
1270
|
|
|
1269
|
-
if (!isProUser() && results.length) showUsageFooter(getUsage().
|
|
1271
|
+
if (!isProUser() && results.length) showUsageFooter(getUsage().lifetime);
|
|
1270
1272
|
if (results.length) showCommunityNudge();
|
|
1271
1273
|
}
|
|
1272
1274
|
|
|
@@ -1344,7 +1346,7 @@ function cmdStart() {
|
|
|
1344
1346
|
info(` EmailForge — ${url}/email-forge`);
|
|
1345
1347
|
info(` AudienceDecoder — ${url}/audience`);
|
|
1346
1348
|
blank();
|
|
1347
|
-
info(`Free tier:
|
|
1349
|
+
info(`Free tier: 15 analyses total. Unlimited with Pro ($9/mo) → ${UPGRADE_LINKS.free}`);
|
|
1348
1350
|
info(`Press Ctrl+C to stop`);
|
|
1349
1351
|
blank();
|
|
1350
1352
|
openBrowser(url);
|
|
@@ -1618,10 +1620,10 @@ function cmdHelp() {
|
|
|
1618
1620
|
|
|
1619
1621
|
console.log(` ${B}PRICING${R}`);
|
|
1620
1622
|
blank();
|
|
1621
|
-
console.log(` Free
|
|
1623
|
+
console.log(` Free 15 total $0`);
|
|
1622
1624
|
console.log(` Pro Unlimited $9/mo`);
|
|
1623
|
-
console.log(` Business
|
|
1624
|
-
console.log(` Team
|
|
1625
|
+
console.log(` Business Unlimited $29/mo`);
|
|
1626
|
+
console.log(` Team Unlimited $79/mo`);
|
|
1625
1627
|
blank();
|
|
1626
1628
|
console.log(` Get Pro: ${CY}${UPGRADE_LINKS.free}${R} ${D}# direct checkout${R}`);
|
|
1627
1629
|
console.log(` Activate: ${CY}content-grade activate <your-license-key>${R}`);
|
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