content-grade 1.0.46 → 1.0.47

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.
@@ -79,20 +79,21 @@ function getLicenseTier() {
79
79
  function isProUser() { return Boolean(getLicenseKey()); }
80
80
 
81
81
  function getUsage() {
82
+ const today = new Date().toISOString().slice(0, 10);
82
83
  try {
83
84
  const d = JSON.parse(readFileSync(USAGE_FILE, 'utf8'));
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 }; }
85
+ // Daily reset: if stored date differs from today, start fresh
86
+ if (d.date !== today) return { daily: 0, date: today };
87
+ return { daily: d.daily || 0, date: today };
88
+ } catch { return { daily: 0, date: today }; }
88
89
  }
89
90
 
90
91
  function incrementUsage() {
91
92
  mkdirSync(CONFIG_DIR, { recursive: true });
92
93
  const u = getUsage();
93
- u.lifetime = (u.lifetime || 0) + 1;
94
+ u.daily = (u.daily || 0) + 1;
94
95
  writeFileSync(USAGE_FILE, JSON.stringify(u, null, 2), 'utf8');
95
- return u.lifetime;
96
+ return u.daily;
96
97
  }
97
98
 
98
99
  // Upgrade links — Free → Pro → Business → Team
@@ -110,7 +111,7 @@ const TIER_NAMES = {
110
111
  };
111
112
 
112
113
  const TIER_LIMITS = {
113
- free: 15,
114
+ free: 3,
114
115
  pro: Infinity,
115
116
  business: Infinity,
116
117
  team: Infinity,
@@ -125,50 +126,29 @@ function getUpgradeMessage() {
125
126
  }
126
127
 
127
128
  // Show usage-aware upgrade CTA after each free run.
128
- // count = lifetime usage AFTER this run.
129
+ // count = daily usage AFTER this run.
129
130
  function showFreeTierCTA(count) {
130
- const limit = TIER_LIMITS.free; // 15
131
+ const limit = TIER_LIMITS.free; // 3
131
132
  const remaining = Math.max(0, limit - count);
132
133
 
133
134
  // Track that an upgrade prompt was shown after a run (post-run CTA, not a hard block)
134
- const ctaStrength = remaining === 0 ? 'strong' : remaining <= 2 ? 'warning' : 'nudge';
135
+ const ctaStrength = remaining === 0 ? 'strong' : remaining <= 1 ? 'warning' : 'nudge';
135
136
  recordEvent({ event: 'upgrade_prompt_shown', run_count: count, cta_strength: ctaStrength, cta_context: 'post_run' });
136
137
 
137
138
  blank();
138
139
  hr();
139
140
 
140
141
  if (remaining === 0) {
141
- // Limit reached that run just worked; now close the sale
142
- console.log(` ${RD}${B}That was your last free analysis (${limit}/${limit} used).${R}`);
142
+ // Last run of the day power user nudge
143
+ console.log(` ${YL}${B}${count}/${limit} — that's all your free runs for today.${R}`);
143
144
  blank();
144
- console.log(` ${WH}${B}Pro unlocks:${R}`);
145
- console.log(` ${WH} · Unlimited runs — no cap, no monthly resets${R}`);
146
- console.log(` ${WH} · Priority support — get answers within 24h${R}`);
147
- console.log(` ${WH} · Team sharing — share analyses across your team${R}`);
145
+ console.log(` ${WH} Pro: 50 analyses/day · Business: unlimited · $9/mo, cancel anytime${R}`);
146
+ console.log(` ${MG}${B}→ ${UPGRADE_LINKS.free}${R}`);
148
147
  blank();
149
- console.log(` ${MG}${B}→ Get Pro ($9/mo, cancel anytime): ${CY}${UPGRADE_LINKS.free}${R}`);
150
- blank();
151
- console.log(` ${D}After checkout — 2 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. Run: ${CY}content-grade activate <your-key>${R}`);
154
- console.log(` ${D} Already have a key? Run step 2 now.${R}`);
155
- } else if (remaining <= 2) {
156
- // 1-2 runs left — loss aversion + concrete Pro value
157
- console.log(` ${YL}${B}${remaining} free analysis${remaining === 1 ? '' : 'es'} remaining (${count}/${limit} used)${R}`);
158
- blank();
159
- console.log(` ${WH}${B}Pro: unlimited runs · priority support · team sharing${R}`);
160
- console.log(` ${WH} · No cap, no resets — grade every piece you publish${R}`);
161
- console.log(` ${MG}${B}→ Get Pro before you run out ($9/mo): ${CY}${UPGRADE_LINKS.free}${R}`);
148
+ console.log(` ${D}Or come back tomorrow for ${limit} more free runs.${R}`);
162
149
  } else {
163
- // Runs availablerotate benefit angle each run to avoid banner blindness
164
- const nudgeBenefits = [
165
- `Pro: unlimited runs · no cap, no resets`,
166
- `Pro: priority support · 24h response time`,
167
- `Pro: team sharing · analyze together, no limits`,
168
- ];
169
- console.log(` ${WH}${remaining} free analysis${remaining === 1 ? '' : 'es'} remaining (${count}/${limit} used)${R}`);
170
- console.log(` ${WH} ${nudgeBenefits[count % 3]}${R}`);
171
- console.log(` ${MG}→ Get Pro ($9/mo): ${CY}${UPGRADE_LINKS.free}${R}`);
150
+ // Runs remainingcompact single-line footer
151
+ console.log(` ${D}[ ${count}/${limit} free runs today · ${remaining} remaining · Pro: 50/day ($9/mo) → ${UPGRADE_LINKS.free} ]${R}`);
172
152
  }
173
153
  hr();
174
154
  maybeShowFeedbackCTA(count);
@@ -239,64 +219,53 @@ function showCommunityNudge() {
239
219
  }
240
220
 
241
221
  // Single-line usage counter appended after every command run.
242
- // count = total lifetime runs used (after this run).
222
+ // count = daily runs used (after this run).
243
223
  function showUsageFooter(count) {
244
224
  if (isProUser()) return;
245
225
  const limit = TIER_LIMITS.free;
246
226
  const remaining = Math.max(0, limit - count);
247
227
  blank();
248
228
  if (remaining === 0) {
249
- console.log(` ${RD}[ All ${limit} free analyses used · Pro removes the cap — $9/mo: ${UPGRADE_LINKS.free} ]${R}`);
250
- } else if (remaining <= 2) {
251
- console.log(` ${YL}[ ${remaining} free analysis${remaining === 1 ? '' : 'es'} remaining · Upgrade to Pro: ${UPGRADE_LINKS.free} ]${R}`);
229
+ console.log(` ${RD}[ ${limit}/${limit} free analyses used today · Pro: 50/day, Business: unlimited — $9/mo ${UPGRADE_LINKS.free} ]${R}`);
230
+ } else if (remaining <= 1) {
231
+ console.log(` ${YL}[ ${remaining} free analysis remaining today · Pro: 50/day, Business: unlimited $9/mo ${UPGRADE_LINKS.free} ]${R}`);
252
232
  } else {
253
- console.log(` ${D}[ ${count}/${limit} free analyses used · ${remaining} remaining · Unlimited with Pro ($9/mo): ${UPGRADE_LINKS.free} ]${R}`);
233
+ console.log(` ${D}[ ${count}/${limit} free analyses today · ${remaining} remaining · Pro: 50/day ($9/mo) ${UPGRADE_LINKS.free} ]${R}`);
254
234
  }
255
235
  maybeShowEarlyAdopterCTA(count);
256
236
  maybeShowFeedbackCTA(count);
257
237
  }
258
238
 
259
- // Returns { ok: boolean, count: number, limit: number } for tier-aware lifetime limit checks.
260
- // Used by batch mode to stop processing when the lifetime limit is reached.
239
+ // Returns { ok: boolean, count: number, limit: number } for tier-aware daily limit checks.
240
+ // Used by batch mode to stop processing when the daily limit is reached.
261
241
  // Pro tier has Infinity limit and always returns ok: true.
262
- function checkLifetimeLimit() {
242
+ function checkDailyLimit() {
263
243
  const tier = getLicenseTier();
264
244
  const limit = TIER_LIMITS[tier] ?? TIER_LIMITS.free;
265
245
  if (limit === Infinity) return { ok: true, count: 0, limit: Infinity };
266
246
  const usage = getUsage();
267
- const count = usage.lifetime || 0;
247
+ const count = usage.daily || 0;
268
248
  return { ok: count < limit, count, limit };
269
249
  }
270
250
 
271
- // Block a run before it starts if the free tier is exhausted.
251
+ // Block a run before it starts if the free daily limit is exhausted.
272
252
  // Returns true (blocked) and prints a visually distinct upgrade prompt.
273
253
  // Returns false if the user may proceed.
274
254
  function checkFreeTierLimit() {
275
255
  if (isProUser()) return false;
276
256
  const usage = getUsage();
277
257
  const limit = TIER_LIMITS.free;
278
- if (usage.lifetime < limit) return false;
258
+ if (usage.daily < limit) return false;
279
259
 
280
260
  // Track funnel event — user hit the limit and saw the upgrade prompt
281
261
  recordEvent({ event: 'free_limit_hit', version: _version });
282
- recordEvent({ event: 'upgrade_prompt_shown', run_count: usage.lifetime });
262
+ recordEvent({ event: 'upgrade_prompt_shown', run_count: usage.daily });
283
263
 
284
264
  blank();
285
- hr();
286
- console.log(` ${RD}${B}You've used all ${limit} free analyses.${R}`);
287
- blank();
288
- console.log(` ${WH}${B}Pro unlocks:${R}`);
289
- console.log(` ${WH} · Unlimited runs — no cap, no monthly resets${R}`);
290
- console.log(` ${WH} · Priority support — get answers within 24h${R}`);
291
- console.log(` ${WH} · Team sharing — share analyses across your team${R}`);
292
- blank();
293
- console.log(` ${MG}${B}→ Get Pro ($9/mo, cancel anytime): ${CY}${UPGRADE_LINKS.free}${R}`);
294
- blank();
295
- console.log(` ${D}After checkout — 2 steps to unlock:${R}`);
296
- console.log(` ${D} 1. Get your key: ${CY}https://content-grade.onrender.com/my-license${R}`);
297
- console.log(` ${D} 2. Run: ${CY}content-grade activate <your-key>${R}`);
298
- console.log(` ${D} Already have a key? Run step 2 now.${R}`);
299
- hr();
265
+ console.log(` ${RD}${B}${limit}/${limit} free analyses used today.${R}`);
266
+ console.log(` ${WH}Pro: 50/day · Business: unlimited — $9/mo, cancel anytime${R}`);
267
+ console.log(` ${MG}${B}→ ${UPGRADE_LINKS.free}${R}`);
268
+ console.log(` ${D}After purchase: content-grade activate <key>${R}`);
300
269
  blank();
301
270
  return true;
302
271
  }
@@ -1229,12 +1198,12 @@ async function cmdBatch(dirPath) {
1229
1198
  const rel = f.startsWith(process.cwd()) ? f.slice(process.cwd().length + 1) : f;
1230
1199
  process.stdout.write(` ${D}[${i + 1}/${files.length}]${R} ${rel}...`);
1231
1200
 
1232
- // Guard: check lifetime limit before each analysis
1233
- const batchLimitCheck = checkLifetimeLimit();
1201
+ // Guard: check daily limit before each analysis
1202
+ const batchLimitCheck = checkDailyLimit();
1234
1203
  if (!batchLimitCheck.ok) {
1235
1204
  process.stdout.write(` ${YL}limit reached${R}\n`);
1236
1205
  blank();
1237
- warn(`Free limit reached (${batchLimitCheck.count}/${batchLimitCheck.limit} lifetime analyses used). Remaining files skipped.`);
1206
+ warn(`Free limit reached (${batchLimitCheck.count}/${batchLimitCheck.limit} analyses used today). Remaining files skipped.`);
1238
1207
  break;
1239
1208
  }
1240
1209
 
@@ -1283,7 +1252,7 @@ async function cmdBatch(dirPath) {
1283
1252
 
1284
1253
  if (_jsonMode) process.stdout.write(JSON.stringify(results, null, 2) + '\n');
1285
1254
 
1286
- if (!isProUser() && results.length) showUsageFooter(getUsage().lifetime);
1255
+ if (!isProUser() && results.length) showUsageFooter(getUsage().daily);
1287
1256
  if (results.length) showCommunityNudge();
1288
1257
  }
1289
1258
 
package/dist/landing.html CHANGED
@@ -36,7 +36,7 @@
36
36
  "@type": "Offer",
37
37
  "price": "0",
38
38
  "priceCurrency": "USD",
39
- "description": "Free: 3 analyses/day. Pro: $9/mo, unlimited/day. Business: $29/mo, 100/day. Team: $79/mo, 500/day."
39
+ "description": "Free: 15 analyses total. Pro: $9/mo, unlimited. Business: $29/mo, unlimited. Team: $79/mo, unlimited + shared workspace."
40
40
  },
41
41
  "featureList": [
42
42
  "Headline analyzer with framework-based scoring",
@@ -631,13 +631,14 @@
631
631
 
632
632
  .pricing-grid {
633
633
  display: grid;
634
- grid-template-columns: repeat(3, 1fr);
635
- gap: 24px;
636
- max-width: 1060px;
634
+ grid-template-columns: repeat(4, 1fr);
635
+ gap: 20px;
636
+ max-width: 1160px;
637
637
  margin: 0 auto;
638
638
  }
639
639
 
640
- @media (max-width: 900px) { .pricing-grid { grid-template-columns: 1fr; max-width: 480px; } }
640
+ @media (max-width: 1100px) { .pricing-grid { grid-template-columns: repeat(2, 1fr); max-width: 720px; } }
641
+ @media (max-width: 600px) { .pricing-grid { grid-template-columns: 1fr; max-width: 480px; } }
641
642
 
642
643
  /* === SOCIAL PROOF === */
643
644
  .social-proof {
@@ -1016,7 +1017,7 @@
1016
1017
  </div>
1017
1018
 
1018
1019
  <div class="hero-meta">
1019
- <span>Free · 3 analyses/day per tool</span>
1020
+ <span>Free · 15 analyses to start</span>
1020
1021
  <span class="hero-meta-sep">·</span>
1021
1022
  <span>Claude CLI required · Zero data leaves your machine</span>
1022
1023
  <span class="hero-meta-sep">·</span>
@@ -1195,7 +1196,7 @@
1195
1196
  <div class="step">
1196
1197
  <div class="step-num">3</div>
1197
1198
  <h3>Launch the full dashboard</h3>
1198
- <p>Start the web interface to access all 6 tools — HeadlineGrader, PageRoast, AdScorer, ThreadGrader, EmailForge, and AudienceDecoder. Free tier: 3 analyses/day per tool.</p>
1199
+ <p>Start the web interface to access all 6 tools — HeadlineGrader, PageRoast, AdScorer, ThreadGrader, EmailForge, and AudienceDecoder. Free tier: 15 analyses total across all tools.</p>
1199
1200
  <div class="step-code">npx content-grade start</div>
1200
1201
  </div>
1201
1202
  </div>
@@ -1208,7 +1209,7 @@
1208
1209
  <div class="pricing-header">
1209
1210
  <div class="section-eyebrow">Pricing</div>
1210
1211
  <h2>Start free. Upgrade when it pays for itself.</h2>
1211
- <p class="pricing-lead">3 free analyses/day. No credit card, no signup, no API key required.</p>
1212
+ <p class="pricing-lead">15 free analyses to start. No credit card, no signup, no API key required.</p>
1212
1213
  </div>
1213
1214
 
1214
1215
  <div class="pricing-grid">
@@ -1218,7 +1219,7 @@
1218
1219
  <div class="plan-period">forever · no signup required</div>
1219
1220
  <a href="https://www.npmjs.com/package/content-grade" class="plan-cta">Install free →</a>
1220
1221
  <ul class="plan-features">
1221
- <li><span class="check">✓</span> <strong>3 analyses/day</strong></li>
1222
+ <li><span class="check">✓</span> <strong>15 analyses total</strong></li>
1222
1223
  <li><span class="check">✓</span> All 6 tools (CLI + dashboard)</li>
1223
1224
  <li><span class="check">✓</span> Full framework-based scoring</li>
1224
1225
  <li><span class="check">✓</span> AI rewrites with technique labels</li>
@@ -1246,10 +1247,10 @@
1246
1247
  <div class="plan">
1247
1248
  <div class="plan-name">BUSINESS</div>
1248
1249
  <div class="plan-price">$29<span>/mo</span></div>
1249
- <div class="plan-period">100 analyses/day · billed monthly</div>
1250
+ <div class="plan-period">Unlimited analyses · billed monthly</div>
1250
1251
  <a href="https://buy.stripe.com/bJefZjafO2Tz36Z2W48k80b" class="plan-cta" target="_blank" rel="noopener">Get Business →</a>
1251
1252
  <ul class="plan-features">
1252
- <li><span class="check">✓</span> <strong>100 analyses/day</strong></li>
1253
+ <li><span class="check">✓</span> <strong>Unlimited analyses</strong></li>
1253
1254
  <li><span class="check">✓</span> Everything in Pro</li>
1254
1255
  <li><span class="check">✓</span> A/B compare with conversion predictions</li>
1255
1256
  <li><span class="check">✓</span> Best value per analysis ($0.29/analysis)</li>
@@ -1259,16 +1260,24 @@
1259
1260
  <div class="plan">
1260
1261
  <div class="plan-name">TEAM</div>
1261
1262
  <div class="plan-price">$79<span>/mo</span></div>
1262
- <div class="plan-period">500 analyses/day · billed monthly</div>
1263
+ <div class="plan-period">Unlimited analyses · billed monthly</div>
1263
1264
  <a href="https://buy.stripe.com/cNiaEZfA8cu9bDv4088k80c" class="plan-cta" target="_blank" rel="noopener">Get Team →</a>
1264
1265
  <ul class="plan-features">
1265
- <li><span class="check">✓</span> <strong>500 analyses/day</strong></li>
1266
+ <li><span class="check">✓</span> <strong>Unlimited analyses</strong></li>
1266
1267
  <li><span class="check">✓</span> Everything in Business</li>
1267
1268
  <li><span class="check">✓</span> Shared team workspace</li>
1268
1269
  <li><span class="check">✓</span> Dedicated support + SLA</li>
1269
1270
  </ul>
1270
1271
  </div>
1271
1272
  </div>
1273
+
1274
+ <div style="margin-top:32px; padding:20px 24px; background:rgba(255,255,255,0.04); border:1px solid rgba(255,255,255,0.1); border-radius:10px; max-width:560px; margin-left:auto; margin-right:auto; text-align:left;">
1275
+ <div style="font-size:13px; color:rgba(255,255,255,0.5); text-transform:uppercase; letter-spacing:1px; margin-bottom:10px;">After checkout — 2 steps to unlock Pro</div>
1276
+ <ol style="padding-left:20px; color:rgba(255,255,255,0.8); font-size:15px; line-height:1.9; margin:0;">
1277
+ <li>Get your license key: <a href="https://content-grade.onrender.com/my-license" target="_blank" rel="noopener" style="color:#a78bfa;">content-grade.onrender.com/my-license</a></li>
1278
+ <li>Run: <code style="background:rgba(255,255,255,0.08); padding:2px 8px; border-radius:4px; font-size:13px;">content-grade activate &lt;your-key&gt;</code></li>
1279
+ </ol>
1280
+ </div>
1272
1281
  </div>
1273
1282
  </section>
1274
1283
 
@@ -1293,7 +1302,7 @@
1293
1302
  <details>
1294
1303
  <summary>Do I need an Anthropic API key or a paid Claude account?</summary>
1295
1304
  <div class="faq-answer">
1296
- No API key required. ContentGrade runs on <strong>Claude CLI</strong> — install it for free from claude.ai/code and log in with a Claude account. A free Claude account is sufficient for the free tier (3 analyses/day per tool).<br /><br />
1305
+ No API key required. ContentGrade runs on <strong>Claude CLI</strong> — install it for free from claude.ai/code and log in with a Claude account. A free Claude account is sufficient for the free tier (15 analyses total across all tools).<br /><br />
1297
1306
  There's no per-query Anthropic billing. You're not routing calls through the API console or accumulating usage charges. Claude CLI handles authentication locally, which is also why your content never leaves your machine.
1298
1307
  </div>
1299
1308
  </details>
@@ -1399,7 +1408,7 @@
1399
1408
  <section class="final-cta">
1400
1409
  <div class="container">
1401
1410
  <h2>Run your best headline.<br />See where it actually stands.</h2>
1402
- <p>Free · 3 analyses/day per tool · No API key · Claude CLI only</p>
1411
+ <p>Free · 15 analyses to start · No API key · Claude CLI only</p>
1403
1412
  <div style="display: flex; gap: 16px; justify-content: center; flex-wrap: wrap;">
1404
1413
  <a href="https://github.com/Content-Grade/Content-Grade" class="btn-primary">
1405
1414
  Install free — npx content-grade
@@ -75,6 +75,29 @@ export function registerAnalyticsRoutes(app) {
75
75
  }
76
76
  return { ok: true };
77
77
  });
78
+ // ── Preflight Suite telemetry/events — the endpoint all CLI tools point to ─
79
+ // stepproof, agent-comply, agent-gate, agent-trace all POST here.
80
+ // Accepts camelCase fields (installId, nodeVersion) as sent by the CLIs.
81
+ // Always returns 200 — fire-and-forget compatible.
82
+ app.post('/api/telemetry/events', async (req) => {
83
+ try {
84
+ const body = req.body;
85
+ const installId = body?.installId ?? body?.install_id;
86
+ if (!body || typeof installId !== 'string' || !installId) {
87
+ return { ok: true };
88
+ }
89
+ const db = getDb();
90
+ db.prepare(`
91
+ INSERT INTO cli_telemetry
92
+ (install_id, package, event, command, success, version, platform, node_version, exit_code, duration_ms)
93
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
94
+ `).run(String(installId).slice(0, 64), body.package ? String(body.package).slice(0, 64) : null, body.event ? String(body.event).slice(0, 64) : 'run', body.command ? String(body.command).slice(0, 64) : null, typeof body.success === 'boolean' ? (body.success ? 1 : 0) : null, body.version ? String(body.version).slice(0, 32) : null, body.platform ? String(body.platform).slice(0, 32) : null, (body.nodeVersion ?? body.node_version) ? String(body.nodeVersion ?? body.node_version).slice(0, 32) : null, typeof body.exit_code === 'number' ? body.exit_code : null, typeof body.duration_ms === 'number' ? Math.round(body.duration_ms) : null);
95
+ }
96
+ catch {
97
+ // never fail — telemetry is non-critical
98
+ }
99
+ return { ok: true };
100
+ });
78
101
  // ── CLI telemetry receiver ────────────────────────────────────────────────
79
102
  // Receives events from CLI users who have opted in to telemetry.
80
103
  // Always returns 200 — never interrupt a CLI session for analytics.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "content-grade",
3
- "version": "1.0.46",
3
+ "version": "1.0.47",
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": {