content-grade 1.0.5 → 1.0.7

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.
@@ -320,6 +320,8 @@ async function cmdAnalyze(filePath) {
320
320
  console.log(` ${D}· Wait until tomorrow (limit resets at midnight)${R}`);
321
321
  console.log(` ${D}· Unlock 100 checks/day: ${CY}content-grade activate${R}`);
322
322
  blank();
323
+ console.log(` ${MG}Upgrade to Pro ($9/mo) for unlimited analyses →${R} ${CY}https://content-grade.github.io/Content-Grade/${R}`);
324
+ blank();
323
325
  process.exit(1);
324
326
  }
325
327
 
@@ -464,7 +466,7 @@ async function cmdAnalyze(filePath) {
464
466
  // Upsell — show Pro path after user has seen value
465
467
  blank();
466
468
  if (isProUser()) {
467
- console.log(` ${D}Pro active · ${CY}contentgrade.dev${R}`);
469
+ console.log(` ${D}Pro active · ${CY}content-grade.github.io/Content-Grade${R}`);
468
470
  } else {
469
471
  const usage = getUsage();
470
472
  const remaining = Math.max(0, FREE_DAILY_LIMIT - usage.count);
@@ -472,7 +474,9 @@ async function cmdAnalyze(filePath) {
472
474
  console.log(` ${MG}${B}Unlock batch mode:${R} ${CY}content-grade activate${R}`);
473
475
  console.log(` ${D} · Analyze entire directories in one command${R}`);
474
476
  console.log(` ${D} · 100 checks/day (${remaining} remaining today on free tier)${R}`);
475
- console.log(` ${D} · Get a license at ${CY}contentgrade.dev${R}`);
477
+ console.log(` ${D} · Get a license at ${CY}content-grade.github.io/Content-Grade${R}`);
478
+ blank();
479
+ console.log(` ${MG}Upgrade to Pro ($9/mo) for unlimited analyses →${R} ${CY}https://content-grade.github.io/Content-Grade/${R}`);
476
480
  }
477
481
  blank();
478
482
  }
@@ -501,7 +505,10 @@ Return ONLY valid JSON:
501
505
  Use full range: generic = <35, good = 65-79, great = 80+.`;
502
506
 
503
507
  async function cmdHeadline(text) {
504
- if (!text || text.trim().length < 3) {
508
+ // Strip non-printable control characters (keep tab/newline/carriage-return)
509
+ if (text) text = text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '').trim();
510
+
511
+ if (!text || text.length < 3) {
505
512
  blank();
506
513
  fail(`No headline provided.`);
507
514
  blank();
@@ -516,6 +523,17 @@ async function cmdHeadline(text) {
516
523
  process.exit(2);
517
524
  }
518
525
 
526
+ // Guard: reject oversized input
527
+ const MAX_HEADLINE_BYTES = 2000;
528
+ if (text.length > MAX_HEADLINE_BYTES) {
529
+ blank();
530
+ fail(`Headline too long (${text.length} chars). Maximum is ${MAX_HEADLINE_BYTES} characters.`);
531
+ blank();
532
+ console.log(` ${YL}Tip:${R} Trim your headline to the core message and try again.`);
533
+ blank();
534
+ process.exit(2);
535
+ }
536
+
519
537
  // Free tier daily limit
520
538
  const limitCheck = checkDailyLimit();
521
539
  if (!limitCheck.ok) {
@@ -526,6 +544,8 @@ async function cmdHeadline(text) {
526
544
  console.log(` ${D}· Wait until tomorrow (limit resets at midnight)${R}`);
527
545
  console.log(` ${D}· Unlock 100 checks/day: ${CY}content-grade activate${R}`);
528
546
  blank();
547
+ console.log(` ${MG}Upgrade to Pro ($9/mo) for unlimited analyses →${R} ${CY}https://content-grade.github.io/Content-Grade/${R}`);
548
+ blank();
529
549
  process.exit(1);
530
550
  }
531
551
 
@@ -603,6 +623,10 @@ async function cmdHeadline(text) {
603
623
  hr();
604
624
  console.log(` ${D}Compare two headlines: ${CY}content-grade start${R} → HeadlineGrader compare${R}`);
605
625
  blank();
626
+ if (!isProUser()) {
627
+ console.log(` ${MG}Upgrade to Pro ($9/mo) for unlimited analyses →${R} ${CY}https://content-grade.github.io/Content-Grade/${R}`);
628
+ blank();
629
+ }
606
630
  }
607
631
 
608
632
  // ── Init command ──────────────────────────────────────────────────────────────
@@ -681,7 +705,7 @@ async function cmdInit() {
681
705
  console.log(` ${CY}content-grade start${R}`);
682
706
  blank();
683
707
  }
684
- console.log(` ${D}Pro tier ($9/mo): 100 analyses/day (vs 50 free) + competitor comparison + URL audits${R}`);
708
+ console.log(` ${D}Pro tier: 100 analyses/day + batch mode ${CY}content-grade.github.io/Content-Grade${R}`);
685
709
  blank();
686
710
  }
687
711
 
@@ -710,7 +734,7 @@ async function cmdActivate() {
710
734
  console.log(` ${D} · ${CY}content-grade batch <dir>${R}${D} — analyze entire directories${R}`);
711
735
  console.log(` ${D} · 100 checks/day (vs 50 free)${R}`);
712
736
  blank();
713
- console.log(` ${D}Get a license: ${CY}contentgrade.dev${R}${D} → Pricing → Team${R}`);
737
+ console.log(` ${D}Get a license: ${CY}content-grade.github.io/Content-Grade${R}${D} → Pricing${R}`);
714
738
  blank();
715
739
 
716
740
  let key;
@@ -817,7 +841,7 @@ async function cmdBatch(dirPath) {
817
841
  console.log(` ${B}Unlock batch mode:${R}`);
818
842
  console.log(` ${CY}content-grade activate${R} ${D}(enter your license key)${R}`);
819
843
  blank();
820
- console.log(` ${D}Get a license: ${CY}contentgrade.dev${R}`);
844
+ console.log(` ${D}Get a license: ${CY}content-grade.github.io/Content-Grade${R}`);
821
845
  blank();
822
846
  process.exit(1);
823
847
  }
@@ -885,6 +909,20 @@ async function cmdBatch(dirPath) {
885
909
  const rel = f.startsWith(process.cwd()) ? f.slice(process.cwd().length + 1) : f;
886
910
  process.stdout.write(` ${D}[${i + 1}/${files.length}]${R} ${rel}...`);
887
911
 
912
+ // Guard: check daily limit before each analysis
913
+ const batchLimitCheck = checkDailyLimit();
914
+ if (!batchLimitCheck.ok) {
915
+ process.stdout.write(` ${YL}limit reached${R}\n`);
916
+ blank();
917
+ warn(`Daily limit reached (${batchLimitCheck.count}/${batchLimitCheck.limit}). Remaining files skipped.`);
918
+ break;
919
+ }
920
+
921
+ // Guard: skip files over 500 KB to avoid OOM on large files
922
+ let fileStat;
923
+ try { fileStat = statSync(f); } catch { process.stdout.write(` ${RD}unreadable${R}\n`); continue; }
924
+ if (fileStat.size > 500 * 1024) { process.stdout.write(` ${YL}too large${R}\n`); continue; }
925
+
888
926
  let content;
889
927
  try { content = readFileSync(f, 'utf8'); } catch { process.stdout.write(` ${RD}unreadable${R}\n`); continue; }
890
928
  if (content.trim().length < 20 || content.includes('\x00')) { process.stdout.write(` ${YL}skipped${R}\n`); continue; }
@@ -1044,15 +1082,63 @@ function cmdStart() {
1044
1082
  });
1045
1083
  }
1046
1084
 
1085
+
1086
+ // ── Check-updates command ─────────────────────────────────────────────────────
1087
+
1088
+ async function cmdCheckUpdates() {
1089
+ blank();
1090
+ console.log(` ${B}Checking for updates...${R}`);
1091
+ blank();
1092
+ try {
1093
+ const res = await new Promise((resolve, reject) => {
1094
+ const req = httpsGet('https://registry.npmjs.org/content-grade/latest', {
1095
+ headers: { 'User-Agent': 'content-grade-cli' },
1096
+ timeout: 8000,
1097
+ }, (r) => {
1098
+ let data = '';
1099
+ r.on('data', chunk => { data += chunk; });
1100
+ r.on('end', () => resolve(data));
1101
+ });
1102
+ req.on('error', reject);
1103
+ req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
1104
+ });
1105
+ const { version: latest } = JSON.parse(res);
1106
+ if (latest === _version) {
1107
+ ok(`You're up to date — v${_version} is the latest.`);
1108
+ } else {
1109
+ warn(`Update available: ${RD}v${_version}${R} → ${GN}v${latest}${R}`);
1110
+ blank();
1111
+ console.log(` ${B}Update with:${R}`);
1112
+ console.log(` ${CY}npm install -g content-grade${R} ${D}(global install)${R}`);
1113
+ console.log(` ${CY}npm install content-grade@latest${R} ${D}(local install)${R}`);
1114
+ }
1115
+ } catch {
1116
+ warn('Could not reach npm registry. Check your internet connection.');
1117
+ blank();
1118
+ console.log(` ${D}Current version: v${_version}${R}`);
1119
+ console.log(` ${D}Check manually: ${CY}https://www.npmjs.com/package/content-grade${R}`);
1120
+ }
1121
+ blank();
1122
+ }
1123
+
1047
1124
  // ── Help ──────────────────────────────────────────────────────────────────────
1048
1125
 
1049
1126
  function cmdHelp() {
1050
1127
  banner();
1051
- console.log(` ${D}v${_version} · ${CY}https://github.com/StanislavBG/Content-Grade${R}`);
1128
+ console.log(` ${D}v${_version} · ${CY}content-grade.github.io/Content-Grade${R}`);
1129
+ blank();
1130
+ console.log(` ${B}QUICK START${R}`);
1052
1131
  blank();
1132
+ console.log(` ${CY}npx content-grade headline "Your title here"${R} ${D}# grade a headline (fastest, ~5s)${R}`);
1133
+ console.log(` ${CY}npx content-grade analyze ./post.md${R} ${D}# full AI content analysis (~20s)${R}`);
1134
+ console.log(` ${CY}npx content-grade demo${R} ${D}# live demo on sample content${R}`);
1135
+ console.log(` ${CY}npx content-grade start${R} ${D}# launch web dashboard (6 tools)${R}`);
1136
+ blank();
1137
+
1053
1138
  console.log(` ${B}USAGE${R}`);
1054
1139
  blank();
1055
- console.log(` ${CY}content-grade <command> [args]${R}`);
1140
+ console.log(` ${CY}npx content-grade <command> [args]${R} ${D}# zero install${R}`);
1141
+ console.log(` ${CY}content-grade <command> [args]${R} ${D}# after: npm install -g content-grade${R}`);
1056
1142
  blank();
1057
1143
 
1058
1144
  console.log(` ${B}COMMANDS${R}`);
@@ -1074,10 +1160,12 @@ function cmdHelp() {
1074
1160
  console.log(` ${CY}start${R} Launch the full web dashboard`);
1075
1161
  console.log(` ${D} 6 tools: headlines, pages, ads, threads, emails, audiences${R}`);
1076
1162
  blank();
1077
- console.log(` ${CY}init${R} First-run setup and diagnostics`);
1163
+ console.log(` ${CY}init${R} Run if Claude CLI isn't detected or tools aren't working`);
1078
1164
  blank();
1079
1165
  console.log(` ${CY}telemetry [on|off]${R} View or toggle anonymous usage tracking`);
1080
1166
  blank();
1167
+ console.log(` ${CY}check-updates${R} Check if a newer version is available on npm`);
1168
+ blank();
1081
1169
  console.log(` ${CY}help${R} Show this help`);
1082
1170
  blank();
1083
1171
  console.log(` ${B}FLAGS${R}`);
@@ -1119,7 +1207,7 @@ function cmdHelp() {
1119
1207
  console.log(` · Node.js 18+`);
1120
1208
  blank();
1121
1209
 
1122
- console.log(` ${B}PRO TIER${R} ${MG}$9/month${R}`);
1210
+ console.log(` ${B}PRO TIER${R} ${MG}$9/mo${R}`);
1123
1211
  blank();
1124
1212
  console.log(` · 100 analyses/day (vs 50 free)`);
1125
1213
  console.log(` · Competitor headline A/B comparison`);
@@ -1128,10 +1216,111 @@ function cmdHelp() {
1128
1216
  console.log(` · Email subject line optimizer`);
1129
1217
  console.log(` · Audience archetype decoder`);
1130
1218
  blank();
1131
- console.log(` ${CY}content-grade start${R} then click Upgrade`);
1219
+ console.log(` Purchase at: ${CY}content-grade.github.io/Content-Grade${R}`);
1220
+ console.log(` Then unlock: ${CY}content-grade activate <your-license-key>${R}`);
1132
1221
  blank();
1133
1222
  }
1134
1223
 
1224
+ // ── First-run detection ───────────────────────────────────────────────────────
1225
+
1226
+ function isFirstRun() {
1227
+ return !existsSync(CONFIG_FILE);
1228
+ }
1229
+
1230
+ // ── Quick Demo (instant, no Claude required) ──────────────────────────────────
1231
+
1232
+ function cmdQuickDemo() {
1233
+ blank();
1234
+ if (isFirstRun()) {
1235
+ console.log(`${B}${CY} Welcome to ContentGrade!${R}`);
1236
+ console.log(` ${D}Grade any blog post, landing page, or ad copy in seconds.${R}`);
1237
+ blank();
1238
+ console.log(` ${D}Here's a sample analysis — then try it on your own content.${R}`);
1239
+ } else {
1240
+ console.log(`${B}${CY} ContentGrade${R} ${D}— Sample Analysis (instant, no Claude needed)${R}`);
1241
+ console.log(` ${D}Run ${CY}npx content-grade analyze ./your-post.md${R}${D} for a real AI-powered result.${R}`);
1242
+ }
1243
+ blank();
1244
+
1245
+ // Pre-rendered analysis of DEMO_CONTENT — identical format to real cmdAnalyze output
1246
+ console.log(` ${D}Analyzing: "How to Write Better Blog Posts" (generic blog post)${R}`);
1247
+ blank();
1248
+ hr();
1249
+
1250
+ // Overall score
1251
+ console.log(` ${B}OVERALL SCORE${R} ${YL}${B}42/100${R} ${RD}${B}D${R} ${D}blog post${R}`);
1252
+ console.log(` ${scoreBar(42, 40)}`);
1253
+ blank();
1254
+
1255
+ // Headline
1256
+ console.log(` ${B}HEADLINE SCORE${R} ${scoreBar(35, 20)} 35/100`);
1257
+ console.log(` ${D}"How to Write Better Blog Posts"${R}`);
1258
+ console.log(` ${D}↳ Generic title with no specificity, number, or benefit — reads like every other guide.${R}`);
1259
+ blank();
1260
+
1261
+ hr();
1262
+ console.log(` ${B}DIMENSION BREAKDOWN${R}`);
1263
+ blank();
1264
+ const _demodims = [
1265
+ { label: 'Clarity', score: 55, note: 'Sentences are clear but vague — "good" and "better" appear 12× with no definition.' },
1266
+ { label: 'Engagement', score: 38, note: 'No hook, no tension. Opens with a passive statement any writing guide could claim.' },
1267
+ { label: 'Structure', score: 45, note: 'Numbered list helps scannability but every section carries equal weight — no pyramid.' },
1268
+ { label: 'Value Delivery', score: 30, note: '"Write good content" is not advice. Every tip is a truism with no specific technique.' },
1269
+ ];
1270
+ for (const d of _demodims) {
1271
+ console.log(` ${WH}${d.label.padEnd(16)}${R} ${scoreBar(d.score, 20)} ${d.score}/100`);
1272
+ console.log(` ${D} ↳ ${d.note}${R}`);
1273
+ blank();
1274
+ }
1275
+
1276
+ hr();
1277
+ console.log(` ${B}VERDICT${R}`);
1278
+ console.log(` ${YL}Technically correct but forgettable — every sentence could be cut in half and the advice doubled in specificity.${R}`);
1279
+ blank();
1280
+
1281
+ hr();
1282
+ console.log(` ${B}STRENGTHS${R}`);
1283
+ console.log(` ${GN}+${R} Logical four-step structure is easy to follow`);
1284
+ console.log(` ${GN}+${R} Short paragraphs with good white space`);
1285
+ blank();
1286
+
1287
+ hr();
1288
+ console.log(` ${B}TOP IMPROVEMENTS${R}`);
1289
+ blank();
1290
+ console.log(` ${RD}●${R} ${B}Headline has no specificity${R}`);
1291
+ console.log(` ${CY}Fix:${R} Add a number and outcome: "7 Blog Post Mistakes That Kill Readership (And the Fixes That Work)"`);
1292
+ blank();
1293
+ console.log(` ${RD}●${R} ${B}Tips are high-level truisms${R}`);
1294
+ console.log(` ${CY}Fix:${R} Replace "write good content" with a technique: "Open with the reader's frustration, not your advice."`);
1295
+ blank();
1296
+ console.log(` ${YL}●${R} ${B}No proof or examples${R}`);
1297
+ console.log(` ${CY}Fix:${R} Add a before/after example for one tip to make the advice concrete and credible`);
1298
+ blank();
1299
+
1300
+ hr();
1301
+ console.log(` ${B}HEADLINE REWRITES${R}`);
1302
+ blank();
1303
+ console.log(` ${D}1.${R} 7 Blog Post Mistakes That Kill Your Readership (And the Fixes That Work)`);
1304
+ console.log(` ${D}2.${R} How to Write Blog Posts That People Actually Finish Reading`);
1305
+ blank();
1306
+
1307
+ hr();
1308
+ console.log(` ${D}↑ This is a ${B}sample${R}${D} — your real content gets a live AI analysis in ~20 seconds.${R}`);
1309
+ blank();
1310
+ console.log(` ${B}${CY}Try it now:${R}`);
1311
+ blank();
1312
+ console.log(` ${CY}npx content-grade headline "Your headline here"${R} ${D}# fastest — grade any headline${R}`);
1313
+ console.log(` ${CY}npx content-grade analyze ./your-post.md${R} ${D}# full AI analysis of a file${R}`);
1314
+ console.log(` ${CY}npx content-grade demo${R} ${D}# live demo — takes ~20s with Claude${R}`);
1315
+ blank();
1316
+
1317
+ if (isFirstRun()) {
1318
+ console.log(` ${D}Requires Claude CLI (free): ${CY}claude.ai/code${R}${D} → install → ${CY}claude login${R}`);
1319
+ console.log(` ${D}Setup check: ${CY}npx content-grade init${R}`);
1320
+ blank();
1321
+ }
1322
+ }
1323
+
1135
1324
  // ── Demo command ──────────────────────────────────────────────────────────────
1136
1325
 
1137
1326
  const DEMO_CONTENT = `# How to Write Better Blog Posts
@@ -1167,8 +1356,8 @@ async function cmdDemo() {
1167
1356
  banner();
1168
1357
  console.log(` ${B}Demo Mode${R} ${D}— see ContentGrade in action${R}`);
1169
1358
  blank();
1170
- console.log(` ${D}Analyzing a sample blog post with generic writing.${R}`);
1171
- console.log(` ${D}Generic content scores lowthat's the point. Watch what ContentGrade finds.${R}`);
1359
+ console.log(` ${D}Analyzing a sample blog post. Watch ContentGrade identify exactly what's weak${R}`);
1360
+ console.log(` ${D}and how to fix it this is what you'll see on your own content.${R}`);
1172
1361
  blank();
1173
1362
 
1174
1363
  writeFileSync(tmpFile, DEMO_CONTENT, 'utf8');
@@ -1178,11 +1367,11 @@ async function cmdDemo() {
1178
1367
  // Post-demo CTA — shown AFTER user has seen the analysis output
1179
1368
  blank();
1180
1369
  hr();
1181
- console.log(` ${B}${CY}Now try it on your own content:${R}`);
1370
+ console.log(` ${B}${CY}Your turn — try it on real content:${R}`);
1182
1371
  blank();
1183
- console.log(` ${CY}content-grade analyze ./your-post.md${R} ${D}# any .md or .txt file${R}`);
1184
- console.log(` ${CY}content-grade https://your-blog.com/post${R} ${D}# analyze any URL${R}`);
1185
- console.log(` ${CY}content-grade headline "Your headline here"${R} ${D}# grade a headline${R}`);
1372
+ console.log(` ${CY}npx content-grade ./your-post.md${R} ${D}# score any .md or .txt file${R}`);
1373
+ console.log(` ${CY}npx content-grade https://example.com/blog/post${R} ${D}# fetch and score any URL${R}`);
1374
+ console.log(` ${CY}npx content-grade headline "Your title here"${R} ${D}# grade a single headline${R}`);
1186
1375
  blank();
1187
1376
 
1188
1377
  // Show telemetry notice after user has seen value (first run only)
@@ -1194,10 +1383,10 @@ async function cmdDemo() {
1194
1383
  // Interactive follow-up — strike while the iron is hot
1195
1384
  if (process.stdin.isTTY) {
1196
1385
  hr();
1197
- console.log(` ${B}${CY}Try it on YOUR content right now:${R}`);
1386
+ console.log(` ${B}${CY}Score your own content right now:${R}`);
1198
1387
  blank();
1199
- console.log(` ${D}Paste a URL, type a headline, or a file path — then press Enter.${R}`);
1200
- console.log(` ${D}(Press Enter to skip)${R}`);
1388
+ console.log(` ${D}Paste a URL (https://...), a headline, or a file path — press Enter to analyze.${R}`);
1389
+ console.log(` ${D}Or press Enter to skip.${R}`);
1201
1390
  blank();
1202
1391
  process.stdout.write(` ${CY}>${R} `);
1203
1392
 
@@ -1249,7 +1438,7 @@ async function cmdDemo() {
1249
1438
  function fetchUrl(url) {
1250
1439
  return new Promise((resolve, reject) => {
1251
1440
  const get = url.startsWith('https') ? httpsGet : httpGet;
1252
- const req = get(url, { headers: { 'User-Agent': 'ContentGrade/1.0 (+https://github.com/StanislavBG/Content-Grade)' }, timeout: 15000 }, (res) => {
1441
+ const req = get(url, { headers: { 'User-Agent': 'ContentGrade/1.0 (+https://github.com/content-grade/Content-Grade)' }, timeout: 15000 }, (res) => {
1253
1442
  // Follow one redirect
1254
1443
  if ((res.statusCode === 301 || res.statusCode === 302) && res.headers.location) {
1255
1444
  fetchUrl(res.headers.location).then(resolve).catch(reject);
@@ -1290,6 +1479,22 @@ function htmlToText(html) {
1290
1479
  }
1291
1480
 
1292
1481
  async function cmdAnalyzeUrl(url) {
1482
+ // Validate and sanitize URL input
1483
+ const sanitizedUrl = (url || '').replace(/[\x00-\x1F\x7F]/g, '').trim();
1484
+ if (!sanitizedUrl || !/^https?:\/\/.{3,}/i.test(sanitizedUrl)) {
1485
+ blank();
1486
+ fail(`Invalid URL: must start with http:// or https://`);
1487
+ blank();
1488
+ process.exit(2);
1489
+ }
1490
+ if (sanitizedUrl.length > 2048) {
1491
+ blank();
1492
+ fail(`URL too long (${sanitizedUrl.length} chars). Maximum is 2048.`);
1493
+ blank();
1494
+ process.exit(2);
1495
+ }
1496
+ url = sanitizedUrl;
1497
+
1293
1498
  banner();
1294
1499
  console.log(` ${B}Analyzing URL:${R} ${CY}${url}${R}`);
1295
1500
  blank();
@@ -1486,6 +1691,18 @@ switch (cmd) {
1486
1691
  cmdHelp();
1487
1692
  break;
1488
1693
 
1694
+ case 'check-updates':
1695
+ case 'update':
1696
+ case 'upgrade-check':
1697
+ recordEvent({ event: 'command', command: 'check-updates' });
1698
+ cmdCheckUpdates().catch(err => {
1699
+ blank();
1700
+ fail(`Update check error: ${err.message}`);
1701
+ blank();
1702
+ process.exit(1);
1703
+ });
1704
+ break;
1705
+
1489
1706
  case 'version':
1490
1707
  case '--version':
1491
1708
  case '-v':
@@ -1527,14 +1744,9 @@ switch (cmd) {
1527
1744
  }
1528
1745
 
1529
1746
  case undefined:
1530
- // No command — run demo immediately for instant value
1531
- recordEvent({ event: 'command', command: 'demo' });
1532
- cmdDemo().catch(err => {
1533
- blank();
1534
- fail(`Demo error: ${err.message}`);
1535
- blank();
1536
- process.exit(1);
1537
- });
1747
+ // No command — show instant static demo (no Claude required, zero wait)
1748
+ recordEvent({ event: 'command', command: 'quick_demo' });
1749
+ cmdQuickDemo();
1538
1750
  break;
1539
1751
 
1540
1752
  default:
package/bin/telemetry.js CHANGED
@@ -18,8 +18,9 @@ const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
18
18
  const EVENTS_FILE = join(CONFIG_DIR, 'events.jsonl');
19
19
 
20
20
  // Telemetry endpoint: CLI events are forwarded here when telemetry is enabled.
21
- // Override with CONTENT_GRADE_TELEMETRY_URL env var (e.g. for self-hosted or staging).
22
- const REMOTE_URL = process.env.CONTENT_GRADE_TELEMETRY_URL || 'https://contentgrade.ai/api/telemetry';
21
+ // Set CONTENT_GRADE_TELEMETRY_URL to enable remote aggregation (e.g. self-hosted endpoint).
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
24
 
24
25
  // ── Internal helpers ──────────────────────────────────────────────────────────
25
26