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.
- package/README.md +240 -27
- package/bin/content-grade.js +241 -29
- package/bin/telemetry.js +3 -2
- package/dist/assets/index-Bc3ZrBgH.js +78 -0
- package/dist/index.html +1 -1
- package/package.json +7 -7
- package/dist/assets/index-DWBbWitG.js +0 -78
package/bin/content-grade.js
CHANGED
|
@@ -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}
|
|
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}
|
|
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
|
-
|
|
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
|
|
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}
|
|
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}
|
|
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}
|
|
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}
|
|
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/
|
|
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
|
|
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
|
|
1171
|
-
console.log(` ${D}
|
|
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}
|
|
1370
|
+
console.log(` ${B}${CY}Your turn — try it on real content:${R}`);
|
|
1182
1371
|
blank();
|
|
1183
|
-
console.log(` ${CY}content-grade
|
|
1184
|
-
console.log(` ${CY}content-grade https://
|
|
1185
|
-
console.log(` ${CY}content-grade headline "Your
|
|
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}
|
|
1386
|
+
console.log(` ${B}${CY}Score your own content right now:${R}`);
|
|
1198
1387
|
blank();
|
|
1199
|
-
console.log(` ${D}Paste a URL,
|
|
1200
|
-
console.log(` ${D}
|
|
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/
|
|
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 —
|
|
1531
|
-
recordEvent({ event: 'command', command: '
|
|
1532
|
-
|
|
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
|
-
//
|
|
22
|
-
|
|
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
|
|