content-grade 1.0.13 → 1.0.15

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
@@ -63,7 +63,7 @@ npx content-grade 'blog/**/*.md'
63
63
 
64
64
  **JSON output for CI integration:**
65
65
  ```bash
66
- npx content-grade README.md --format json
66
+ npx content-grade README.md --json
67
67
  # Returns structured JSON with score, grade, dimensions, and improvements
68
68
  # Exit code 1 if score < 50 — useful for blocking low-quality merges
69
69
  ```
@@ -549,6 +549,7 @@ tests/
549
549
 
550
550
  **Documentation:**
551
551
  - [`docs/getting-started.md`](./docs/getting-started.md) — step-by-step first-run guide
552
+ - [`docs/cli-reference.md`](./docs/cli-reference.md) — full CLI reference: all commands, flags, output schema, exit codes
552
553
  - [`docs/examples.md`](./docs/examples.md) — real-world examples for all 6 tools, CI/CD workflows, Node.js integration
553
554
  - [`docs/api.md`](./docs/api.md) — full REST API reference
554
555
 
@@ -46,6 +46,11 @@ function getLicenseKey() {
46
46
  return process.env.CONTENT_GRADE_KEY || loadConfig().licenseKey || null;
47
47
  }
48
48
 
49
+ function getLicenseTier() {
50
+ const cfg = loadConfig();
51
+ return cfg.tier || 'free'; // free | starter | pro | team
52
+ }
53
+
49
54
  function isProUser() { return Boolean(getLicenseKey()); }
50
55
 
51
56
  function getTodayKey() { return new Date().toISOString().slice(0, 10); }
@@ -66,13 +71,45 @@ function incrementUsage() {
66
71
  return u.count;
67
72
  }
68
73
 
69
- const FREE_DAILY_LIMIT = 50;
74
+ // Usage tiers — daily limits per plan
75
+ const TIER_LIMITS = {
76
+ free: 5, // 5/day
77
+ starter: 20, // ~600/mo ≈ 50/mo was too low for daily use
78
+ pro: 50, // ~1500/mo
79
+ team: 100, // hard cap even for top tier — no abuse
80
+ };
81
+
82
+ const UPGRADE_LINKS = {
83
+ free: 'https://buy.stripe.com/eVq6oJbjSdyd6jbaow8k805', // Starter $9/mo
84
+ starter: 'https://buy.stripe.com/00w9AV87G65L7nf54c8k806', // Pro $29/mo
85
+ pro: 'https://buy.stripe.com/4gM28t73C79P0YR7ck8k807', // Team $79/mo
86
+ };
87
+
88
+ const TIER_NAMES = {
89
+ free: 'Free (5/day)',
90
+ starter: 'Starter — $9/mo (20/day)',
91
+ pro: 'Pro — $29/mo (50/day)',
92
+ team: 'Team — $79/mo (100/day)',
93
+ };
94
+
95
+ function getDailyLimit() {
96
+ const tier = getLicenseTier();
97
+ return TIER_LIMITS[tier] || TIER_LIMITS.free;
98
+ }
70
99
 
71
100
  function checkDailyLimit() {
72
- if (isProUser()) return { ok: true };
101
+ const limit = getDailyLimit();
73
102
  const u = getUsage();
74
- if (u.count >= FREE_DAILY_LIMIT) return { ok: false, count: u.count, limit: FREE_DAILY_LIMIT };
75
- return { ok: true, count: u.count, limit: FREE_DAILY_LIMIT };
103
+ if (u.count >= limit) return { ok: false, count: u.count, limit };
104
+ return { ok: true, count: u.count, limit };
105
+ }
106
+
107
+ function getUpgradeMessage() {
108
+ const tier = getLicenseTier();
109
+ const link = UPGRADE_LINKS[tier];
110
+ if (!link) return ''; // team tier — no upgrade available
111
+ const nextTier = tier === 'free' ? 'Starter ($9/mo, 20/day)' : tier === 'starter' ? 'Pro ($29/mo, 50/day)' : 'Team ($79/mo, 100/day)';
112
+ return `Upgrade to ${nextTier} → ${link}`;
76
113
  }
77
114
 
78
115
  let _version = '1.0.0';
@@ -252,18 +289,23 @@ SCORING CALIBRATION:
252
289
 
253
290
  async function cmdAnalyze(filePath) {
254
291
  if (!filePath) {
292
+ if (_demoMode) {
293
+ return cmdDemo();
294
+ }
255
295
  blank();
256
296
  fail(`No file specified.`);
257
297
  blank();
258
- console.log(` Usage:`);
298
+ console.log(` ${B}Usage:${R}`);
259
299
  console.log(` ${CY}content-grade analyze <file>${R} ${D}(or: check <file>)${R}`);
260
300
  blank();
261
- console.log(` Examples:`);
262
- console.log(` ${D}content-grade analyze ./blog-post.md${R}`);
263
- console.log(` ${D}content-grade check ./email-draft.txt${R}`);
264
- console.log(` ${D}content-grade analyze ~/landing-page-copy.md${R}`);
301
+ console.log(` ${B}Examples:${R}`);
302
+ console.log(` ${CY}content-grade analyze ./blog-post.md${R}`);
303
+ console.log(` ${CY}content-grade check ./email-draft.txt${R}`);
304
+ console.log(` ${CY}content-grade analyze ~/landing-page-copy.md${R}`);
265
305
  blank();
266
- console.log(` ${D}No file? Try the demo: ${CY}content-grade demo${R}`);
306
+ console.log(` ${GN}Tip:${R} See it in action first — no file needed:`);
307
+ console.log(` ${CY}content-grade --demo${R} ${D}# live AI analysis on built-in sample${R}`);
308
+ console.log(` ${CY}content-grade demo${R} ${D}# same thing${R}`);
267
309
  blank();
268
310
  process.exit(1);
269
311
  }
@@ -339,13 +381,13 @@ async function cmdAnalyze(filePath) {
339
381
  const limitCheck = checkDailyLimit();
340
382
  if (!limitCheck.ok) {
341
383
  blank();
342
- fail(`Daily limit reached (${limitCheck.count}/${limitCheck.limit} free checks used today).`);
384
+ fail(`Daily limit reached (${limitCheck.count}/${limitCheck.limit} runs used today on ${TIER_NAMES[getLicenseTier()]} plan).`);
343
385
  blank();
344
386
  console.log(` ${B}Options:${R}`);
345
387
  console.log(` ${D}· Wait until tomorrow (limit resets at midnight)${R}`);
346
- console.log(` ${D}· Unlock 100 checks/day: ${CY}content-grade activate${R}`);
388
+ console.log(` ${D}· Upgrade your plan: ${CY}content-grade activate${R}`);
347
389
  blank();
348
- console.log(` ${MG}Upgrade to Pro ($9/mo) for 100 analyses/day →${R} ${CY}https://content-grade.github.io/Content-Grade/${R}`);
390
+ console.log(` ${MG}${getUpgradeMessage()}${R}`);
349
391
  blank();
350
392
  process.exit(1);
351
393
  }
@@ -524,9 +566,9 @@ async function cmdAnalyze(filePath) {
524
566
  console.log(` ${CY}content-grade analyze https://yoursite.com/post${R} ${D}# audit any live URL${R}`);
525
567
  blank();
526
568
  if (remaining <= 10) {
527
- console.log(` ${YL}${remaining} free checks left today.${R} Unlock 100/day: ${CY}content-grade activate${R}`);
569
+ console.log(` ${YL}${remaining} runs left today (${TIER_NAMES[getLicenseTier()]}).${R} ${MG}${getUpgradeMessage()}${R}`);
528
570
  } else {
529
- console.log(` ${D}${remaining} free checks left today · Pro ($9/mo): ${CY}content-grade.github.io/Content-Grade${R}`);
571
+ console.log(` ${D}${remaining} runs left today. ${getUpgradeMessage()}${R}`);
530
572
  }
531
573
  }
532
574
 
@@ -604,13 +646,13 @@ async function cmdHeadline(text) {
604
646
  const limitCheck = checkDailyLimit();
605
647
  if (!limitCheck.ok) {
606
648
  blank();
607
- fail(`Daily limit reached (${limitCheck.count}/${limitCheck.limit} free checks used today).`);
649
+ fail(`Daily limit reached (${limitCheck.count}/${limitCheck.limit} runs used today on ${TIER_NAMES[getLicenseTier()]} plan).`);
608
650
  blank();
609
651
  console.log(` ${B}Options:${R}`);
610
652
  console.log(` ${D}· Wait until tomorrow (limit resets at midnight)${R}`);
611
- console.log(` ${D}· Unlock 100 checks/day: ${CY}content-grade activate${R}`);
653
+ console.log(` ${D}· Upgrade your plan: ${CY}content-grade activate${R}`);
612
654
  blank();
613
- console.log(` ${MG}Upgrade to Pro ($9/mo) for 100 analyses/day →${R} ${CY}https://content-grade.github.io/Content-Grade/${R}`);
655
+ console.log(` ${MG}${getUpgradeMessage()}${R}`);
614
656
  blank();
615
657
  process.exit(1);
616
658
  }
@@ -700,7 +742,7 @@ async function cmdHeadline(text) {
700
742
  console.log(` ${D}Compare two headlines: ${CY}content-grade start${R} → HeadlineGrader compare${R}`);
701
743
  blank();
702
744
  if (!isProUser()) {
703
- console.log(` ${MG}Upgrade to Pro ($9/mo) for 100 analyses/day →${R} ${CY}https://content-grade.github.io/Content-Grade/${R}`);
745
+ console.log(` ${MG}${getUpgradeMessage()}${R}`);
704
746
  blank();
705
747
  }
706
748
  }
@@ -1123,7 +1165,7 @@ function cmdStart() {
1123
1165
  info(` EmailForge — ${url}/email-forge`);
1124
1166
  info(` AudienceDecoder — ${url}/audience`);
1125
1167
  blank();
1126
- info(`Free tier: 50 analyses/day. Upgrade at ${url}`);
1168
+ info(`Free tier: 5 analyses/day. Upgrade at ${url}`);
1127
1169
  info(`Press Ctrl+C to stop`);
1128
1170
  blank();
1129
1171
  openBrowser(url);
@@ -1302,10 +1344,10 @@ function cmdHelp() {
1302
1344
  blank();
1303
1345
  console.log(` ${B}QUICK START${R}`);
1304
1346
  blank();
1347
+ console.log(` ${CY}npx content-grade --demo${R} ${D}# live AI demo — no file needed, see it first${R}`);
1305
1348
  console.log(` ${CY}npx content-grade headline "Your Headline Here"${R} ${D}# grade a headline (fastest, ~5s)${R}`);
1306
1349
  console.log(` ${CY}npx content-grade analyze ./my-post.md${R} ${D}# full AI analysis of a file (~20s)${R}`);
1307
1350
  console.log(` ${CY}npx content-grade analyze https://example.com${R} ${D}# audit any live URL (~20s)${R}`);
1308
- console.log(` ${CY}npx content-grade demo${R} ${D}# live demo on sample content${R}`);
1309
1351
  console.log(` ${CY}npx content-grade start${R} ${D}# launch web dashboard (6 tools)${R}`);
1310
1352
  blank();
1311
1353
 
@@ -1348,6 +1390,7 @@ function cmdHelp() {
1348
1390
  blank();
1349
1391
  console.log(` ${B}FLAGS${R}`);
1350
1392
  blank();
1393
+ console.log(` ${CY}--demo${R} Run the live AI demo on built-in sample content`);
1351
1394
  console.log(` ${CY}--json${R} Output raw JSON (great for CI pipelines)`);
1352
1395
  console.log(` ${CY}--quiet${R} Output score number only (for scripting)`);
1353
1396
  console.log(` ${CY}--save [file]${R} Save analysis to a markdown file (default: <input>.content-grade.md)`);
@@ -1493,20 +1536,25 @@ function cmdQuickDemo() {
1493
1536
  blank();
1494
1537
  hr();
1495
1538
  if (isFirstRun()) {
1496
- console.log(` ${B}${CY}Your next step:${R}`);
1539
+ console.log(` ${B}${CY}Try it on real content — 3 ways to start:${R}`);
1540
+ blank();
1541
+ console.log(` ${B}1.${R} Grade a headline ${D}(~5 seconds, works right now)${R}`);
1542
+ console.log(` ${CY}npx content-grade headline "Your headline here"${R}`);
1497
1543
  blank();
1498
- console.log(` ${CY}npx content-grade headline "Your headline here"${R}`);
1499
- console.log(` ${D} Grade any headline in ~5 seconds — no file needed, works right now.${R}`);
1544
+ console.log(` ${B}2.${R} Run the live AI demo ${D}(full analysis on sample content)${R}`);
1545
+ console.log(` ${CY}npx content-grade --demo${R}`);
1500
1546
  blank();
1501
- console.log(` ${D}Then analyze a full post: ${CY}npx content-grade analyze ./your-post.md${R}`);
1547
+ console.log(` ${B}3.${R} Analyze your own file`);
1548
+ console.log(` ${CY}npx content-grade analyze ./your-post.md${R}`);
1502
1549
  blank();
1503
- console.log(` ${D}Requires Claude CLI (free): ${CY}claude.ai/code${R}${D} → install → ${CY}claude login${R}`);
1504
- console.log(` ${D}Setup check: ${CY}npx content-grade init${R}`);
1550
+ console.log(` ${D}All three require Claude CLI (free): ${CY}claude.ai/code${R}${D} → install → ${CY}claude login${R}`);
1551
+ console.log(` ${D}First time? Run: ${CY}npx content-grade init${R}${D} to verify setup.${R}`);
1505
1552
  } else {
1506
- console.log(` ${B}${CY}Grade a headline right now:${R}`);
1553
+ console.log(` ${B}${CY}Grade your own content:${R}`);
1507
1554
  blank();
1555
+ console.log(` ${CY}npx content-grade --demo${R} ${D}# live AI demo${R}`);
1508
1556
  console.log(` ${CY}npx content-grade headline "Your headline here"${R} ${D}# ~5 seconds${R}`);
1509
- console.log(` ${CY}npx content-grade analyze ./your-post.md${R} ${D}# full AI analysis${R}`);
1557
+ console.log(` ${CY}npx content-grade analyze ./your-post.md${R} ${D}# full analysis${R}`);
1510
1558
  }
1511
1559
  blank();
1512
1560
  }
@@ -1864,7 +1912,7 @@ function analysisToMarkdown(result, sourceName, analyzedAt) {
1864
1912
  }
1865
1913
 
1866
1914
  lines.push(`---`);
1867
- lines.push(`Generated by [ContentGrade](https://content-grade.github.io/Content-Grade/) v${_version}`);
1915
+ lines.push(`Generated by [ContentGrade](https://buy.stripe.com/5kQeVfew48dT7nf2W48k801) v${_version}`);
1868
1916
  return lines.join('\n');
1869
1917
  }
1870
1918
 
@@ -1884,8 +1932,9 @@ const _saveMode = _saveIdx !== -1;
1884
1932
  const _savePath = (_saveMode && _rawArgs[_saveIdx + 1] && !_rawArgs[_saveIdx + 1].startsWith('-'))
1885
1933
  ? _rawArgs[_saveIdx + 1]
1886
1934
  : null;
1935
+ const _demoMode = _rawArgs.includes('--demo');
1887
1936
  const args = _rawArgs.filter((a, i) => {
1888
- if (['--no-telemetry', '--json', '--quiet', '--verbose', '--ci', '--save'].includes(a)) return false;
1937
+ if (['--no-telemetry', '--json', '--quiet', '--verbose', '--ci', '--save', '--demo'].includes(a)) return false;
1889
1938
  if (a === '--threshold') return false;
1890
1939
  if (i > 0 && _rawArgs[i - 1] === '--threshold') return false;
1891
1940
  if (_saveMode && i > 0 && _rawArgs[i - 1] === '--save' && !a.startsWith('-')) return false;
@@ -1924,6 +1973,7 @@ if (_rawArgs.includes('--help') || _rawArgs.includes('-h')) {
1924
1973
  console.log(` ${CY}<file>${R} Path to a .md, .txt, or .mdx file`);
1925
1974
  blank();
1926
1975
  console.log(` ${B}Flags:${R}`);
1976
+ console.log(` ${CY}--demo${R} Run on built-in sample content (no file needed)`);
1927
1977
  console.log(` ${CY}--json${R} Output raw JSON (great for CI pipelines)`);
1928
1978
  console.log(` ${CY}--quiet${R} Output score number only (for scripting)`);
1929
1979
  console.log(` ${CY}--ci${R} Exit 0 if score passes, 1 if it fails`);
@@ -1931,6 +1981,7 @@ if (_rawArgs.includes('--help') || _rawArgs.includes('-h')) {
1931
1981
  console.log(` ${CY}--verbose${R} Show debug info: model, timing, response length`);
1932
1982
  blank();
1933
1983
  console.log(` ${B}Examples:${R}`);
1984
+ console.log(` ${CY}content-grade analyze --demo${R} ${D}# live demo on sample content${R}`);
1934
1985
  console.log(` ${CY}content-grade analyze ./blog-post.md${R}`);
1935
1986
  console.log(` ${CY}content-grade analyze ./readme.md --json${R}`);
1936
1987
  console.log(` ${CY}content-grade analyze ./copy.md --ci --threshold 70${R}`);
@@ -2009,7 +2060,15 @@ if (_rawArgs.includes('--help') || _rawArgs.includes('-h')) {
2009
2060
  }
2010
2061
  }
2011
2062
 
2012
- switch (cmd) {
2063
+ if (_demoMode) {
2064
+ recordEvent({ event: 'command', command: 'demo' });
2065
+ cmdDemo().catch(err => {
2066
+ blank();
2067
+ fail(`Demo error: ${err.message}`);
2068
+ blank();
2069
+ process.exit(1);
2070
+ });
2071
+ } else switch (cmd) {
2013
2072
  case 'analyze':
2014
2073
  case 'analyse':
2015
2074
  case 'check':
package/bin/telemetry.js CHANGED
@@ -20,7 +20,7 @@ const EVENTS_FILE = join(CONFIG_DIR, 'events.jsonl');
20
20
  // Telemetry endpoint: CLI events are forwarded here when telemetry is enabled.
21
21
  // Set CONTENT_GRADE_TELEMETRY_URL to enable remote aggregation (e.g. self-hosted endpoint).
22
22
  // Default: local-only (events stored at ~/.content-grade/events.jsonl, no remote send).
23
- const REMOTE_URL = process.env.CONTENT_GRADE_TELEMETRY_URL || '';
23
+ const REMOTE_URL = process.env.CONTENT_GRADE_TELEMETRY_URL || 'https://cg-telemetry.onrender.com/api/telemetry/events';
24
24
 
25
25
  // ── Internal helpers ──────────────────────────────────────────────────────────
26
26
 
@@ -72,15 +72,16 @@ export function initTelemetry() {
72
72
  return { installId: cfg.installId, isNew: false, optedOut: false };
73
73
  }
74
74
 
75
- // First run — generate anonymous install ID, opt-IN by default (off until user enables)
75
+ // First run — generate anonymous install ID, opt-in by default (like Next.js, Astro)
76
+ // Users can opt out: content-grade telemetry off, --no-telemetry, or CONTENT_GRADE_NO_TELEMETRY=1
76
77
  const installId = generateId();
77
78
  saveConfig({
78
79
  installId,
79
80
  installedAt: new Date().toISOString(),
80
- telemetryEnabled: false,
81
+ telemetryEnabled: true,
81
82
  });
82
83
 
83
- return { installId, isNew: true, optedOut: true };
84
+ return { installId, isNew: true, optedOut: false };
84
85
  }
85
86
 
86
87
  /**
@@ -106,6 +107,7 @@ export function recordEvent(data) {
106
107
 
107
108
  const event = {
108
109
  ...data,
110
+ package: 'content-grade',
109
111
  installId,
110
112
  timestamp: new Date().toISOString(),
111
113
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "content-grade",
3
- "version": "1.0.13",
3
+ "version": "1.0.15",
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": {