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 +42 -25
- package/bin/content-grade.js +185 -26
- 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/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
|
[](https://www.npmjs.com/package/content-grade)
|
|
6
|
+
[](https://www.npmjs.com/package/content-grade)
|
|
7
|
+
[](https://www.npmjs.com/package/content-grade)
|
|
6
8
|
[](https://opensource.org/licenses/MIT)
|
|
7
9
|
[](https://nodejs.org)
|
|
8
10
|
[](https://claude.ai/code)
|
|
9
|
-
[](https://github.com/
|
|
11
|
+
[](https://github.com/content-grade/Content-Grade/discussions)
|
|
10
12
|
[](EARLY_ADOPTERS.md)
|
|
11
13
|
|
|
12
14
|
---
|
|
@@ -14,30 +16,28 @@
|
|
|
14
16
|
## 60-second quickstart
|
|
15
17
|
|
|
16
18
|
```bash
|
|
17
|
-
# 1
|
|
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
|
-
#
|
|
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
|
-
#
|
|
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,
|
|
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
|
|
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/
|
|
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/
|
|
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/
|
|
752
|
-
| [Show & Tell](https://github.com/
|
|
753
|
-
| [Ideas](https://github.com/
|
|
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/
|
|
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/
|
|
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/
|
|
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
|
+
[](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
|
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}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}
|
|
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}
|
|
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
|
-
|
|
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
|
|
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}
|
|
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}
|
|
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}
|
|
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
|
|
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
|
|
1171
|
-
console.log(` ${D}
|
|
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}
|
|
1329
|
+
console.log(` ${B}${CY}Your turn — try it on real content:${R}`);
|
|
1182
1330
|
blank();
|
|
1183
|
-
console.log(` ${CY}content-grade
|
|
1184
|
-
console.log(` ${CY}content-grade https://
|
|
1185
|
-
console.log(` ${CY}content-grade headline "Your
|
|
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}
|
|
1345
|
+
console.log(` ${B}${CY}Score your own content right now:${R}`);
|
|
1198
1346
|
blank();
|
|
1199
|
-
console.log(` ${D}Paste a URL,
|
|
1200
|
-
console.log(` ${D}
|
|
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/
|
|
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 —
|
|
1531
|
-
recordEvent({ event: 'command', command: '
|
|
1532
|
-
|
|
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
|
-
//
|
|
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
|
|