content-grade 1.0.5 → 1.0.6

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
@@ -3,10 +3,12 @@
3
3
  > AI-powered content quality scoring for developers and content teams. Score any blog post, landing page, ad copy, or email — in under 30 seconds. No API keys. No accounts. Runs on your local Claude CLI.
4
4
 
5
5
  [![npm](https://img.shields.io/npm/v/content-grade.svg)](https://www.npmjs.com/package/content-grade)
6
+ [![npm downloads/month](https://img.shields.io/npm/dm/content-grade.svg)](https://www.npmjs.com/package/content-grade)
7
+ [![npm downloads/week](https://img.shields.io/npm/dw/content-grade.svg)](https://www.npmjs.com/package/content-grade)
6
8
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
9
  [![Node.js 18+](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org)
8
10
  [![Requires Claude CLI](https://img.shields.io/badge/requires-Claude%20CLI-orange)](https://claude.ai/code)
9
- [![GitHub Discussions](https://img.shields.io/badge/GitHub-Discussions-blue?logo=github)](https://github.com/StanislavBG/Content-Grade/discussions)
11
+ [![GitHub Discussions](https://img.shields.io/badge/GitHub-Discussions-blue?logo=github)](https://github.com/content-grade/Content-Grade/discussions)
10
12
  [![Early Adopter seats open](https://img.shields.io/badge/Early%20Adopter-50%20seats%20open-success)](EARLY_ADOPTERS.md)
11
13
 
12
14
  ---
@@ -14,30 +16,28 @@
14
16
  ## 60-second quickstart
15
17
 
16
18
  ```bash
17
- # 1. Verify Claude CLI is installed and logged in
18
- claude -p "say hi"
19
-
20
- # 2. Run instant demo — no file needed, see results in ~30 seconds
19
+ # Step 1: See it in action instant demo, no file needed
21
20
  npx content-grade
22
21
 
23
- # 3. Analyze your own content (grade is an alias for analyze)
22
+ # Step 2: Score your own content
23
+ npx content-grade grade README.md
24
24
  npx content-grade grade ./my-post.md
25
- npx content-grade analyze ./my-post.md
26
25
 
27
- # 4. CI pipeline get raw JSON or score-only output
28
- npx content-grade analyze ./post.md --json
29
- npx content-grade analyze ./post.md --quiet # prints score number only
30
-
31
- # 5. Grade a headline
26
+ # Step 3: Grade a headline
32
27
  npx content-grade headline "Why Most Startups Fail at Month 18"
33
-
34
- # 6. Unlock Pro (batch mode, 100/day limit)
35
- npx content-grade activate
36
28
  ```
37
29
 
38
- **One requirement:** [Claude CLI](https://claude.ai/code) must be installed and logged in. No API keys, no accounts, no data leaves your machine.
30
+ **One requirement:** [Claude CLI](https://claude.ai/code) must be installed and logged in (`claude login`). No API keys, no accounts, your content never leaves your machine.
39
31
 
40
- **Free tier:** 50 analyses/day, no signup needed. `npx content-grade grade README.md` works immediately.
32
+ **Free tier:** 50 analyses/day. No signup. `npx content-grade grade README.md` works immediately.
33
+
34
+ ```bash
35
+ # More commands
36
+ npx content-grade https://yourblog.com/post # analyze any URL
37
+ npx content-grade analyze ./post.md --json # raw JSON for CI pipelines
38
+ npx content-grade analyze ./post.md --quiet # score number only (for scripts)
39
+ npx content-grade activate # unlock Pro: batch mode, 100/day
40
+ ```
41
41
 
42
42
  ---
43
43
 
@@ -319,7 +319,7 @@ Stripe variables are entirely optional — all tools work without them; upgrade
319
319
  ## Self-hosting / Development
320
320
 
321
321
  ```bash
322
- git clone https://github.com/StanislavBG/Content-Grade
322
+ git clone https://github.com/content-grade/Content-Grade
323
323
  cd Content-Grade
324
324
  npm install
325
325
  npm run dev # hot reload: server at :4000, client at :3000
@@ -350,6 +350,11 @@ tests/
350
350
 
351
351
  **REST API:** The web dashboard exposes a full REST API at `http://localhost:4000`. See [`docs/api.md`](./docs/api.md) for the complete reference — every endpoint, request shape, response schema, and cURL examples.
352
352
 
353
+ **Documentation:**
354
+ - [`docs/getting-started.md`](./docs/getting-started.md) — step-by-step first-run guide
355
+ - [`docs/examples.md`](./docs/examples.md) — real-world examples for all 6 tools, CI/CD workflows, Node.js integration
356
+ - [`docs/api.md`](./docs/api.md) — full REST API reference
357
+
353
358
  **Run tests:**
354
359
 
355
360
  ```bash
@@ -744,13 +749,13 @@ ContentGrade is built in public. The community is the roadmap.
744
749
 
745
750
  ### GitHub Discussions
746
751
 
747
- **[Join the conversation →](https://github.com/StanislavBG/Content-Grade/discussions)**
752
+ **[Join the conversation →](https://github.com/content-grade/Content-Grade/discussions)**
748
753
 
749
754
  | Category | What it's for |
750
755
  |----------|--------------|
751
- | [Q&A](https://github.com/StanislavBG/Content-Grade/discussions/new?category=q-a) | "Why is my score lower than expected?" — questions get answered here |
752
- | [Show & Tell](https://github.com/StanislavBG/Content-Grade/discussions/new?category=show-and-tell) | Share your workflow, integration, or results — early adopters post here |
753
- | [Ideas](https://github.com/StanislavBG/Content-Grade/discussions/new?category=ideas) | Feature requests and suggestions before they become issues |
756
+ | [Q&A](https://github.com/content-grade/Content-Grade/discussions/new?category=q-a) | "Why is my score lower than expected?" — questions get answered here |
757
+ | [Show & Tell](https://github.com/content-grade/Content-Grade/discussions/new?category=show-and-tell) | Share your workflow, integration, or results — early adopters post here |
758
+ | [Ideas](https://github.com/content-grade/Content-Grade/discussions/new?category=ideas) | Feature requests and suggestions before they become issues |
754
759
 
755
760
  ### Early Adopter Program — 50 seats
756
761
 
@@ -762,7 +767,7 @@ ContentGrade is built in public. The community is the roadmap.
762
767
  - Roadmap preview + design input before features ship
763
768
  - 30-minute onboarding call (optional)
764
769
 
765
- One action to claim: run ContentGrade, then post in [Show & Tell](https://github.com/StanislavBG/Content-Grade/discussions/new?category=show-and-tell) with `[Early Adopter]` in the title.
770
+ One action to claim: run ContentGrade, then post in [Show & Tell](https://github.com/content-grade/Content-Grade/discussions/new?category=show-and-tell) with `[Early Adopter]` in the title.
766
771
 
767
772
  ### Champions Program
768
773
 
@@ -774,16 +779,28 @@ See **[CONTRIBUTING.md](CONTRIBUTING.md)** for local dev setup, PR guidelines, a
774
779
 
775
780
  ### Roadmap
776
781
 
777
- **[ROADMAP.md](ROADMAP.md)** — what's built, what's next, what's planned, and what we're deliberately not doing. Open a [Discussion](https://github.com/StanislavBG/Content-Grade/discussions/new?category=ideas) to challenge or extend it.
782
+ **[ROADMAP.md](ROADMAP.md)** — what's built, what's next, what's planned, and what we're deliberately not doing. Open a [Discussion](https://github.com/content-grade/Content-Grade/discussions/new?category=ideas) to challenge or extend it.
778
783
 
779
784
  ### Feedback Loop
780
785
 
781
- **[FEEDBACK_LOOP.md](FEEDBACK_LOOP.md)** — how feedback flows into product decisions. Monthly structured check-ins, 48h triage SLA, and roadmap sync. Use the [monthly feedback template](https://github.com/StanislavBG/Content-Grade/issues/new?template=monthly_feedback.yml) to share your experience.
786
+ **[FEEDBACK_LOOP.md](FEEDBACK_LOOP.md)** — how feedback flows into product decisions. Monthly structured check-ins, 48h triage SLA, and roadmap sync. Use the [monthly feedback template](https://github.com/content-grade/Content-Grade/issues/new?template=monthly_feedback.yml) to share your experience.
782
787
 
783
788
  ### Contributors
784
789
 
785
790
  **[CONTRIBUTORS.md](CONTRIBUTORS.md)** — everyone who has improved ContentGrade. Code, bug reports, and real-world feedback all count. Early Adopters and Champions are listed here permanently.
786
791
 
792
+ ### Use ContentGrade in your project
793
+
794
+ Add a badge to show your content meets the bar:
795
+
796
+ ```markdown
797
+ [![content-grade](https://img.shields.io/badge/content--grade-passing-brightgreen)](https://www.npmjs.com/package/content-grade)
798
+ ```
799
+
800
+ Score-specific badge: `https://img.shields.io/badge/content--grade-{SCORE}%2F100-brightgreen`
801
+
802
+ See [docs/social-proof/built-with-badge.md](docs/social-proof/built-with-badge.md) for SVG assets and CI integration examples.
803
+
787
804
  ---
788
805
 
789
806
  ## License
@@ -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}Unlock unlimited analyses →${R} ${CY}content-grade.github.io/Content-Grade/#pricing${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,10 @@ 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}Unlock unlimited analyses →${R} ${CY}content-grade.github.io/Content-Grade/#pricing${R}`);
480
+ console.log(` ${D}⭐ Unlock deeper analysis →${R} ${CY}npm i content-grade && content-grade activate${R} ${D}| content-grade.github.io/Content-Grade/${R}`);
476
481
  }
477
482
  blank();
478
483
  }
@@ -501,7 +506,10 @@ Return ONLY valid JSON:
501
506
  Use full range: generic = <35, good = 65-79, great = 80+.`;
502
507
 
503
508
  async function cmdHeadline(text) {
504
- if (!text || text.trim().length < 3) {
509
+ // Strip non-printable control characters (keep tab/newline/carriage-return)
510
+ if (text) text = text.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '').trim();
511
+
512
+ if (!text || text.length < 3) {
505
513
  blank();
506
514
  fail(`No headline provided.`);
507
515
  blank();
@@ -516,6 +524,17 @@ async function cmdHeadline(text) {
516
524
  process.exit(2);
517
525
  }
518
526
 
527
+ // Guard: reject oversized input
528
+ const MAX_HEADLINE_BYTES = 2000;
529
+ if (text.length > MAX_HEADLINE_BYTES) {
530
+ blank();
531
+ fail(`Headline too long (${text.length} chars). Maximum is ${MAX_HEADLINE_BYTES} characters.`);
532
+ blank();
533
+ console.log(` ${YL}Tip:${R} Trim your headline to the core message and try again.`);
534
+ blank();
535
+ process.exit(2);
536
+ }
537
+
519
538
  // Free tier daily limit
520
539
  const limitCheck = checkDailyLimit();
521
540
  if (!limitCheck.ok) {
@@ -526,6 +545,8 @@ async function cmdHeadline(text) {
526
545
  console.log(` ${D}· Wait until tomorrow (limit resets at midnight)${R}`);
527
546
  console.log(` ${D}· Unlock 100 checks/day: ${CY}content-grade activate${R}`);
528
547
  blank();
548
+ console.log(` ${MG}Unlock unlimited analyses →${R} ${CY}content-grade.github.io/Content-Grade/#pricing${R}`);
549
+ blank();
529
550
  process.exit(1);
530
551
  }
531
552
 
@@ -603,6 +624,11 @@ async function cmdHeadline(text) {
603
624
  hr();
604
625
  console.log(` ${D}Compare two headlines: ${CY}content-grade start${R} → HeadlineGrader compare${R}`);
605
626
  blank();
627
+ if (!isProUser()) {
628
+ console.log(` ${MG}Unlock unlimited analyses →${R} ${CY}content-grade.github.io/Content-Grade/#pricing${R}`);
629
+ console.log(` ${D}⭐ Unlock deeper analysis →${R} ${CY}npm i content-grade && content-grade activate${R} ${D}| content-grade.github.io/Content-Grade/${R}`);
630
+ blank();
631
+ }
606
632
  }
607
633
 
608
634
  // ── Init command ──────────────────────────────────────────────────────────────
@@ -681,7 +707,7 @@ async function cmdInit() {
681
707
  console.log(` ${CY}content-grade start${R}`);
682
708
  blank();
683
709
  }
684
- console.log(` ${D}Pro tier ($9/mo): 100 analyses/day (vs 50 free) + competitor comparison + URL audits${R}`);
710
+ console.log(` ${D}Pro tier: 100 analyses/day + batch mode ${CY}content-grade.github.io/Content-Grade${R}`);
685
711
  blank();
686
712
  }
687
713
 
@@ -710,7 +736,7 @@ async function cmdActivate() {
710
736
  console.log(` ${D} · ${CY}content-grade batch <dir>${R}${D} — analyze entire directories${R}`);
711
737
  console.log(` ${D} · 100 checks/day (vs 50 free)${R}`);
712
738
  blank();
713
- console.log(` ${D}Get a license: ${CY}contentgrade.dev${R}${D} → Pricing → Team${R}`);
739
+ console.log(` ${D}Get a license: ${CY}content-grade.github.io/Content-Grade${R}${D} → Pricing${R}`);
714
740
  blank();
715
741
 
716
742
  let key;
@@ -817,7 +843,7 @@ async function cmdBatch(dirPath) {
817
843
  console.log(` ${B}Unlock batch mode:${R}`);
818
844
  console.log(` ${CY}content-grade activate${R} ${D}(enter your license key)${R}`);
819
845
  blank();
820
- console.log(` ${D}Get a license: ${CY}contentgrade.dev${R}`);
846
+ console.log(` ${D}Get a license: ${CY}content-grade.github.io/Content-Grade${R}`);
821
847
  blank();
822
848
  process.exit(1);
823
849
  }
@@ -885,6 +911,20 @@ async function cmdBatch(dirPath) {
885
911
  const rel = f.startsWith(process.cwd()) ? f.slice(process.cwd().length + 1) : f;
886
912
  process.stdout.write(` ${D}[${i + 1}/${files.length}]${R} ${rel}...`);
887
913
 
914
+ // Guard: check daily limit before each analysis
915
+ const batchLimitCheck = checkDailyLimit();
916
+ if (!batchLimitCheck.ok) {
917
+ process.stdout.write(` ${YL}limit reached${R}\n`);
918
+ blank();
919
+ warn(`Daily limit reached (${batchLimitCheck.count}/${batchLimitCheck.limit}). Remaining files skipped.`);
920
+ break;
921
+ }
922
+
923
+ // Guard: skip files over 500 KB to avoid OOM on large files
924
+ let fileStat;
925
+ try { fileStat = statSync(f); } catch { process.stdout.write(` ${RD}unreadable${R}\n`); continue; }
926
+ if (fileStat.size > 500 * 1024) { process.stdout.write(` ${YL}too large${R}\n`); continue; }
927
+
888
928
  let content;
889
929
  try { content = readFileSync(f, 'utf8'); } catch { process.stdout.write(` ${RD}unreadable${R}\n`); continue; }
890
930
  if (content.trim().length < 20 || content.includes('\x00')) { process.stdout.write(` ${YL}skipped${R}\n`); continue; }
@@ -1048,8 +1088,16 @@ function cmdStart() {
1048
1088
 
1049
1089
  function cmdHelp() {
1050
1090
  banner();
1051
- console.log(` ${D}v${_version} · ${CY}https://github.com/StanislavBG/Content-Grade${R}`);
1091
+ console.log(` ${D}v${_version} · ${CY}content-grade.github.io/Content-Grade${R}`);
1092
+ blank();
1093
+ console.log(` ${B}QUICK START${R}`);
1094
+ blank();
1095
+ console.log(` ${CY}npx content-grade headline "Your title here"${R} ${D}# grade a headline (fastest, ~5s)${R}`);
1096
+ console.log(` ${CY}npx content-grade analyze ./post.md${R} ${D}# full AI content analysis (~20s)${R}`);
1097
+ console.log(` ${CY}npx content-grade demo${R} ${D}# live demo on sample content${R}`);
1098
+ console.log(` ${CY}npx content-grade start${R} ${D}# launch web dashboard (6 tools)${R}`);
1052
1099
  blank();
1100
+
1053
1101
  console.log(` ${B}USAGE${R}`);
1054
1102
  blank();
1055
1103
  console.log(` ${CY}content-grade <command> [args]${R}`);
@@ -1128,8 +1176,108 @@ function cmdHelp() {
1128
1176
  console.log(` · Email subject line optimizer`);
1129
1177
  console.log(` · Audience archetype decoder`);
1130
1178
  blank();
1131
- console.log(` ${CY}content-grade start${R} then click Upgrade`);
1179
+ console.log(` ${CY}content-grade.github.io/Content-Grade${R} then: ${CY}content-grade activate${R}`);
1180
+ blank();
1181
+ }
1182
+
1183
+ // ── First-run detection ───────────────────────────────────────────────────────
1184
+
1185
+ function isFirstRun() {
1186
+ return !existsSync(CONFIG_FILE);
1187
+ }
1188
+
1189
+ // ── Quick Demo (instant, no Claude required) ──────────────────────────────────
1190
+
1191
+ function cmdQuickDemo() {
1192
+ blank();
1193
+ if (isFirstRun()) {
1194
+ console.log(`${B}${CY} Welcome to ContentGrade!${R}`);
1195
+ console.log(` ${D}Grade any blog post, landing page, or ad copy in seconds.${R}`);
1196
+ blank();
1197
+ console.log(` ${D}Here's a sample analysis — then try it on your own content.${R}`);
1198
+ } else {
1199
+ console.log(`${B}${CY} ContentGrade${R} ${D}— Sample Analysis (instant, no Claude needed)${R}`);
1200
+ console.log(` ${D}Run ${CY}npx content-grade analyze ./your-post.md${R}${D} for a real AI-powered result.${R}`);
1201
+ }
1202
+ blank();
1203
+
1204
+ // Pre-rendered analysis of DEMO_CONTENT — identical format to real cmdAnalyze output
1205
+ console.log(` ${D}Analyzing: "How to Write Better Blog Posts" (generic blog post)${R}`);
1206
+ blank();
1207
+ hr();
1208
+
1209
+ // Overall score
1210
+ console.log(` ${B}OVERALL SCORE${R} ${YL}${B}42/100${R} ${RD}${B}D${R} ${D}blog post${R}`);
1211
+ console.log(` ${scoreBar(42, 40)}`);
1212
+ blank();
1213
+
1214
+ // Headline
1215
+ console.log(` ${B}HEADLINE SCORE${R} ${scoreBar(35, 20)} 35/100`);
1216
+ console.log(` ${D}"How to Write Better Blog Posts"${R}`);
1217
+ console.log(` ${D}↳ Generic title with no specificity, number, or benefit — reads like every other guide.${R}`);
1218
+ blank();
1219
+
1220
+ hr();
1221
+ console.log(` ${B}DIMENSION BREAKDOWN${R}`);
1222
+ blank();
1223
+ const _demodims = [
1224
+ { label: 'Clarity', score: 55, note: 'Sentences are clear but vague — "good" and "better" appear 12× with no definition.' },
1225
+ { label: 'Engagement', score: 38, note: 'No hook, no tension. Opens with a passive statement any writing guide could claim.' },
1226
+ { label: 'Structure', score: 45, note: 'Numbered list helps scannability but every section carries equal weight — no pyramid.' },
1227
+ { label: 'Value Delivery', score: 30, note: '"Write good content" is not advice. Every tip is a truism with no specific technique.' },
1228
+ ];
1229
+ for (const d of _demodims) {
1230
+ console.log(` ${WH}${d.label.padEnd(16)}${R} ${scoreBar(d.score, 20)} ${d.score}/100`);
1231
+ console.log(` ${D} ↳ ${d.note}${R}`);
1232
+ blank();
1233
+ }
1234
+
1235
+ hr();
1236
+ console.log(` ${B}VERDICT${R}`);
1237
+ console.log(` ${YL}Technically correct but forgettable — every sentence could be cut in half and the advice doubled in specificity.${R}`);
1238
+ blank();
1239
+
1240
+ hr();
1241
+ console.log(` ${B}STRENGTHS${R}`);
1242
+ console.log(` ${GN}+${R} Logical four-step structure is easy to follow`);
1243
+ console.log(` ${GN}+${R} Short paragraphs with good white space`);
1244
+ blank();
1245
+
1246
+ hr();
1247
+ console.log(` ${B}TOP IMPROVEMENTS${R}`);
1248
+ blank();
1249
+ console.log(` ${RD}●${R} ${B}Headline has no specificity${R}`);
1250
+ console.log(` ${CY}Fix:${R} Add a number and outcome: "7 Blog Post Mistakes That Kill Readership (And the Fixes That Work)"`);
1251
+ blank();
1252
+ console.log(` ${RD}●${R} ${B}Tips are high-level truisms${R}`);
1253
+ console.log(` ${CY}Fix:${R} Replace "write good content" with a technique: "Open with the reader's frustration, not your advice."`);
1254
+ blank();
1255
+ console.log(` ${YL}●${R} ${B}No proof or examples${R}`);
1256
+ console.log(` ${CY}Fix:${R} Add a before/after example for one tip to make the advice concrete and credible`);
1257
+ blank();
1258
+
1259
+ hr();
1260
+ console.log(` ${B}HEADLINE REWRITES${R}`);
1261
+ blank();
1262
+ console.log(` ${D}1.${R} 7 Blog Post Mistakes That Kill Your Readership (And the Fixes That Work)`);
1263
+ console.log(` ${D}2.${R} How to Write Blog Posts That People Actually Finish Reading`);
1264
+ blank();
1265
+
1266
+ hr();
1267
+ console.log(` ${D}↑ This is a ${B}sample${R}${D} — your real content gets a live AI analysis in ~20 seconds.${R}`);
1268
+ blank();
1269
+ console.log(` ${B}${CY}Try it now:${R}`);
1270
+ blank();
1271
+ console.log(` ${CY}npx content-grade headline "Your headline here"${R} ${D}# fastest — grade any headline${R}`);
1272
+ console.log(` ${CY}npx content-grade analyze ./your-post.md${R} ${D}# full AI analysis of a file${R}`);
1273
+ console.log(` ${CY}npx content-grade demo${R} ${D}# live demo — takes ~20s with Claude${R}`);
1132
1274
  blank();
1275
+
1276
+ if (isFirstRun()) {
1277
+ console.log(` ${D}Requires Claude CLI (free): ${CY}claude.ai/code${R}${D} → install → ${CY}claude login${R}`);
1278
+ console.log(` ${D}Setup check: ${CY}npx content-grade init${R}`);
1279
+ blank();
1280
+ }
1133
1281
  }
1134
1282
 
1135
1283
  // ── Demo command ──────────────────────────────────────────────────────────────
@@ -1167,8 +1315,8 @@ async function cmdDemo() {
1167
1315
  banner();
1168
1316
  console.log(` ${B}Demo Mode${R} ${D}— see ContentGrade in action${R}`);
1169
1317
  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}`);
1318
+ console.log(` ${D}Analyzing a sample blog post. Watch ContentGrade identify exactly what's weak${R}`);
1319
+ console.log(` ${D}and how to fix it this is what you'll see on your own content.${R}`);
1172
1320
  blank();
1173
1321
 
1174
1322
  writeFileSync(tmpFile, DEMO_CONTENT, 'utf8');
@@ -1178,11 +1326,11 @@ async function cmdDemo() {
1178
1326
  // Post-demo CTA — shown AFTER user has seen the analysis output
1179
1327
  blank();
1180
1328
  hr();
1181
- console.log(` ${B}${CY}Now try it on your own content:${R}`);
1329
+ console.log(` ${B}${CY}Your turn — try it on real content:${R}`);
1182
1330
  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}`);
1331
+ console.log(` ${CY}npx content-grade ./your-post.md${R} ${D}# score any .md or .txt file${R}`);
1332
+ console.log(` ${CY}npx content-grade https://example.com/blog/post${R} ${D}# fetch and score any URL${R}`);
1333
+ console.log(` ${CY}npx content-grade headline "Your title here"${R} ${D}# grade a single headline${R}`);
1186
1334
  blank();
1187
1335
 
1188
1336
  // Show telemetry notice after user has seen value (first run only)
@@ -1194,10 +1342,10 @@ async function cmdDemo() {
1194
1342
  // Interactive follow-up — strike while the iron is hot
1195
1343
  if (process.stdin.isTTY) {
1196
1344
  hr();
1197
- console.log(` ${B}${CY}Try it on YOUR content right now:${R}`);
1345
+ console.log(` ${B}${CY}Score your own content right now:${R}`);
1198
1346
  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}`);
1347
+ console.log(` ${D}Paste a URL (https://...), a headline, or a file path — press Enter to analyze.${R}`);
1348
+ console.log(` ${D}Or press Enter to skip.${R}`);
1201
1349
  blank();
1202
1350
  process.stdout.write(` ${CY}>${R} `);
1203
1351
 
@@ -1249,7 +1397,7 @@ async function cmdDemo() {
1249
1397
  function fetchUrl(url) {
1250
1398
  return new Promise((resolve, reject) => {
1251
1399
  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) => {
1400
+ const req = get(url, { headers: { 'User-Agent': 'ContentGrade/1.0 (+https://github.com/content-grade/Content-Grade)' }, timeout: 15000 }, (res) => {
1253
1401
  // Follow one redirect
1254
1402
  if ((res.statusCode === 301 || res.statusCode === 302) && res.headers.location) {
1255
1403
  fetchUrl(res.headers.location).then(resolve).catch(reject);
@@ -1290,6 +1438,22 @@ function htmlToText(html) {
1290
1438
  }
1291
1439
 
1292
1440
  async function cmdAnalyzeUrl(url) {
1441
+ // Validate and sanitize URL input
1442
+ const sanitizedUrl = (url || '').replace(/[\x00-\x1F\x7F]/g, '').trim();
1443
+ if (!sanitizedUrl || !/^https?:\/\/.{3,}/i.test(sanitizedUrl)) {
1444
+ blank();
1445
+ fail(`Invalid URL: must start with http:// or https://`);
1446
+ blank();
1447
+ process.exit(2);
1448
+ }
1449
+ if (sanitizedUrl.length > 2048) {
1450
+ blank();
1451
+ fail(`URL too long (${sanitizedUrl.length} chars). Maximum is 2048.`);
1452
+ blank();
1453
+ process.exit(2);
1454
+ }
1455
+ url = sanitizedUrl;
1456
+
1293
1457
  banner();
1294
1458
  console.log(` ${B}Analyzing URL:${R} ${CY}${url}${R}`);
1295
1459
  blank();
@@ -1527,14 +1691,9 @@ switch (cmd) {
1527
1691
  }
1528
1692
 
1529
1693
  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
- });
1694
+ // No command — show instant static demo (no Claude required, zero wait)
1695
+ recordEvent({ event: 'command', command: 'quick_demo' });
1696
+ cmdQuickDemo();
1538
1697
  break;
1539
1698
 
1540
1699
  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