content-grade 1.0.10 → 1.0.12
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 +22 -12
- package/bin/content-grade.js +93 -7
- package/bin/telemetry.js +3 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
|
|
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
|
+
[](https://github.com/StanislavBG/Content-Grade/actions/workflows/ci.yml)
|
|
5
6
|
[](https://www.npmjs.com/package/content-grade)
|
|
6
7
|
[](https://www.npmjs.com/package/content-grade)
|
|
7
8
|
[](https://www.npmjs.com/package/content-grade)
|
|
8
9
|
[](https://opensource.org/licenses/MIT)
|
|
9
10
|
[](https://nodejs.org)
|
|
10
11
|
[](https://claude.ai/code)
|
|
11
|
-
[](https://github.com/
|
|
12
|
+
[](https://github.com/StanislavBG/Content-Grade/discussions)
|
|
12
13
|
[](EARLY_ADOPTERS.md)
|
|
13
14
|
|
|
14
15
|
---
|
|
@@ -171,7 +172,7 @@ Launch with `content-grade start` — opens at [http://localhost:4000](http://lo
|
|
|
171
172
|
| **EmailForge** | `/email-forge` | Subject line + body copy for click-through optimization |
|
|
172
173
|
| **AudienceDecoder** | `/audience` | Twitter handle → audience archetypes and content patterns |
|
|
173
174
|
|
|
174
|
-
Free tier: **50 analyses/day per tool**. Pro ($
|
|
175
|
+
Free tier: **50 analyses/day per tool**. Pro ($19/mo): **100 analyses/day** + all tools.
|
|
175
176
|
|
|
176
177
|
---
|
|
177
178
|
|
|
@@ -515,7 +516,7 @@ Stripe variables are entirely optional — all tools work without them; upgrade
|
|
|
515
516
|
## Self-hosting / Development
|
|
516
517
|
|
|
517
518
|
```bash
|
|
518
|
-
git clone https://github.com/
|
|
519
|
+
git clone https://github.com/StanislavBG/Content-Grade
|
|
519
520
|
cd Content-Grade
|
|
520
521
|
npm install
|
|
521
522
|
npm run dev # hot reload: server at :4000, client at :3000
|
|
@@ -912,7 +913,7 @@ echo "$DATE,$HEADLINE,$SCORE" >> headline-scores.csv
|
|
|
912
913
|
|
|
913
914
|
| | Free | Pro |
|
|
914
915
|
|-|------|-----|
|
|
915
|
-
| Price | Free forever | $
|
|
916
|
+
| Price | Free forever | $19/month |
|
|
916
917
|
| Analyses/day (CLI) | Unlimited | Unlimited |
|
|
917
918
|
| Analyses/day (web dashboard, per tool) | 3 | 100 |
|
|
918
919
|
| HeadlineGrader (single grade) | ✓ | ✓ |
|
|
@@ -945,25 +946,25 @@ ContentGrade is built in public. The community is the roadmap.
|
|
|
945
946
|
|
|
946
947
|
### GitHub Discussions
|
|
947
948
|
|
|
948
|
-
**[Join the conversation →](https://github.com/
|
|
949
|
+
**[Join the conversation →](https://github.com/StanislavBG/Content-Grade/discussions)**
|
|
949
950
|
|
|
950
951
|
| Category | What it's for |
|
|
951
952
|
|----------|--------------|
|
|
952
|
-
| [Q&A](https://github.com/
|
|
953
|
-
| [Show & Tell](https://github.com/
|
|
954
|
-
| [Ideas](https://github.com/
|
|
953
|
+
| [Q&A](https://github.com/StanislavBG/Content-Grade/discussions/new?category=q-a) | "Why is my score lower than expected?" — questions get answered here |
|
|
954
|
+
| [Show & Tell](https://github.com/StanislavBG/Content-Grade/discussions/new?category=show-and-tell) | Share your workflow, integration, or results — early adopters post here |
|
|
955
|
+
| [Ideas](https://github.com/StanislavBG/Content-Grade/discussions/new?category=ideas) | Feature requests and suggestions before they become issues |
|
|
955
956
|
|
|
956
957
|
### Early Adopter Program — 50 seats
|
|
957
958
|
|
|
958
959
|
**[EARLY_ADOPTERS.md](EARLY_ADOPTERS.md)** — The first 50 users who install and share feedback get:
|
|
959
960
|
|
|
960
|
-
- **Pro tier free for 12 months** ($
|
|
961
|
+
- **Pro tier free for 12 months** ($19/mo value)
|
|
961
962
|
- Direct feedback channel with the maintainer
|
|
962
963
|
- Name in CONTRIBUTORS.md (permanent)
|
|
963
964
|
- Roadmap preview + design input before features ship
|
|
964
965
|
- 30-minute onboarding call (optional)
|
|
965
966
|
|
|
966
|
-
One action to claim: run ContentGrade, then post in [Show & Tell](https://github.com/
|
|
967
|
+
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.
|
|
967
968
|
|
|
968
969
|
### Champions Program
|
|
969
970
|
|
|
@@ -975,16 +976,20 @@ See **[CONTRIBUTING.md](CONTRIBUTING.md)** for local dev setup, PR guidelines, a
|
|
|
975
976
|
|
|
976
977
|
### Roadmap
|
|
977
978
|
|
|
978
|
-
**[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/
|
|
979
|
+
**[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.
|
|
979
980
|
|
|
980
981
|
### Feedback Loop
|
|
981
982
|
|
|
982
|
-
**[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/
|
|
983
|
+
**[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.
|
|
983
984
|
|
|
984
985
|
### Contributors
|
|
985
986
|
|
|
986
987
|
**[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.
|
|
987
988
|
|
|
989
|
+
### How did you find ContentGrade?
|
|
990
|
+
|
|
991
|
+
If you found this tool useful, we'd love to know where you came across it — a Reddit thread, a blog post, a colleague's recommendation? Drop a note in [GitHub Discussions → Q&A](https://github.com/Content-Grade/Content-Grade/discussions/new?category=q-a) with the subject "How I found ContentGrade". This is 100% optional and takes 30 seconds. It helps us understand which communities to focus on and which content resonates.
|
|
992
|
+
|
|
988
993
|
### Use ContentGrade in your project
|
|
989
994
|
|
|
990
995
|
Add a badge to show your content meets the bar:
|
|
@@ -999,6 +1004,11 @@ See [docs/social-proof/built-with-badge.md](docs/social-proof/built-with-badge.m
|
|
|
999
1004
|
|
|
1000
1005
|
---
|
|
1001
1006
|
|
|
1007
|
+
## Legal
|
|
1008
|
+
|
|
1009
|
+
- [Privacy Policy](https://content-grade.github.io/Content-Grade/privacy.html)
|
|
1010
|
+
- [Terms of Service](https://content-grade.github.io/Content-Grade/terms.html)
|
|
1011
|
+
|
|
1002
1012
|
## License
|
|
1003
1013
|
|
|
1004
1014
|
MIT
|
package/bin/content-grade.js
CHANGED
|
@@ -388,8 +388,16 @@ async function cmdAnalyze(filePath) {
|
|
|
388
388
|
result = parseJSON(raw);
|
|
389
389
|
recordEvent({ event: 'analyze_result', score: result.total_score, content_type: result.content_type });
|
|
390
390
|
// Machine-readable output modes (exit cleanly, skip styled output)
|
|
391
|
-
if (_jsonMode) {
|
|
392
|
-
|
|
391
|
+
if (_jsonMode) {
|
|
392
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
393
|
+
if (_ciMode) process.exit(result.total_score >= _ciThreshold ? 0 : 1);
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
if (_quietMode) {
|
|
397
|
+
process.stdout.write(`${result.total_score}\n`);
|
|
398
|
+
if (_ciMode) process.exit(result.total_score >= _ciThreshold ? 0 : 1);
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
393
401
|
} catch (err) {
|
|
394
402
|
process.stdout.write(`\n`);
|
|
395
403
|
blank();
|
|
@@ -498,6 +506,21 @@ async function cmdAnalyze(filePath) {
|
|
|
498
506
|
blank();
|
|
499
507
|
console.log(` ${MG}Upgrade to Pro ($9/mo) for unlimited analyses →${R} ${CY}https://content-grade.github.io/Content-Grade/${R}`);
|
|
500
508
|
}
|
|
509
|
+
|
|
510
|
+
// CI exit code — shown after full output so user sees the score before exit
|
|
511
|
+
if (_ciMode) {
|
|
512
|
+
const passed = result.total_score >= _ciThreshold;
|
|
513
|
+
blank();
|
|
514
|
+
hr();
|
|
515
|
+
if (passed) {
|
|
516
|
+
ok(`CI PASS — score ${result.total_score} meets threshold ${_ciThreshold}`);
|
|
517
|
+
} else {
|
|
518
|
+
fail(`CI FAIL — score ${result.total_score} is below threshold ${_ciThreshold}`);
|
|
519
|
+
}
|
|
520
|
+
blank();
|
|
521
|
+
process.exit(passed ? 0 : 1);
|
|
522
|
+
}
|
|
523
|
+
|
|
501
524
|
blank();
|
|
502
525
|
}
|
|
503
526
|
|
|
@@ -1187,7 +1210,32 @@ async function cmdMetrics() {
|
|
|
1187
1210
|
}
|
|
1188
1211
|
blank();
|
|
1189
1212
|
|
|
1190
|
-
// 2.
|
|
1213
|
+
// 2. GitHub repository stats (public API, no auth)
|
|
1214
|
+
console.log(` ${B}GitHub${R}`);
|
|
1215
|
+
const ghData = await new Promise((resolve) => {
|
|
1216
|
+
const req = httpsGet(
|
|
1217
|
+
'https://api.github.com/repos/StanislavBG/Content-Grade',
|
|
1218
|
+
{ headers: { 'User-Agent': `content-grade-cli/${_version}` }, timeout: 5000 },
|
|
1219
|
+
(res) => {
|
|
1220
|
+
let data = '';
|
|
1221
|
+
res.on('data', (c) => data += c);
|
|
1222
|
+
res.on('end', () => { try { resolve(JSON.parse(data)); } catch { resolve(null); } });
|
|
1223
|
+
}
|
|
1224
|
+
);
|
|
1225
|
+
req.on('error', () => resolve(null));
|
|
1226
|
+
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
1227
|
+
});
|
|
1228
|
+
if (ghData?.stargazers_count != null) {
|
|
1229
|
+
console.log(` ${D}Stars: ${R}${B}${ghData.stargazers_count.toLocaleString()}${R}`);
|
|
1230
|
+
console.log(` ${D}Forks: ${R}${B}${ghData.forks_count.toLocaleString()}${R}`);
|
|
1231
|
+
console.log(` ${D}Open issues: ${R}${B}${ghData.open_issues_count.toLocaleString()}${R}`);
|
|
1232
|
+
console.log(` ${D}Watchers: ${R}${B}${ghData.watchers_count.toLocaleString()}${R}`);
|
|
1233
|
+
} else {
|
|
1234
|
+
console.log(` ${D}Unavailable (offline or rate limited)${R}`);
|
|
1235
|
+
}
|
|
1236
|
+
blank();
|
|
1237
|
+
|
|
1238
|
+
// 3. Local telemetry summary
|
|
1191
1239
|
console.log(` ${B}Local Usage${R}`);
|
|
1192
1240
|
const eventsFile = join(homedir(), '.content-grade', 'events.jsonl');
|
|
1193
1241
|
try {
|
|
@@ -1245,6 +1293,8 @@ function cmdHelp() {
|
|
|
1245
1293
|
blank();
|
|
1246
1294
|
console.log(` ${CY}batch <directory>${R} ${MG}[Pro]${R} Analyze all .md/.txt files in a directory`);
|
|
1247
1295
|
blank();
|
|
1296
|
+
console.log(` ${CY}seo-audit <url>${R} Fetch a live URL and analyze its content (SEO, readability, structure)`);
|
|
1297
|
+
blank();
|
|
1248
1298
|
console.log(` ${CY}headline "<text>"${R} Grade a single headline (4 copywriting frameworks)`);
|
|
1249
1299
|
blank();
|
|
1250
1300
|
console.log(` ${CY}activate${R} Enter license key to unlock Pro features`);
|
|
@@ -1266,6 +1316,8 @@ function cmdHelp() {
|
|
|
1266
1316
|
blank();
|
|
1267
1317
|
console.log(` ${CY}--json${R} Output raw JSON (great for CI pipelines)`);
|
|
1268
1318
|
console.log(` ${CY}--quiet${R} Output score number only (for scripting)`);
|
|
1319
|
+
console.log(` ${CY}--ci${R} Exit 0 if score passes threshold, exit 1 if it fails`);
|
|
1320
|
+
console.log(` ${CY}--threshold <n>${R} Score threshold for --ci mode (default: 60)`);
|
|
1269
1321
|
console.log(` ${CY}--verbose${R} Show debug info: model, timing, raw response length`);
|
|
1270
1322
|
console.log(` ${CY}--version${R} Print version and exit`);
|
|
1271
1323
|
console.log(` ${CY}--no-telemetry${R} Skip usage tracking for this invocation`);
|
|
@@ -1279,8 +1331,11 @@ function cmdHelp() {
|
|
|
1279
1331
|
console.log(` ${D}# Grade README.md (alias)${R}`);
|
|
1280
1332
|
console.log(` ${CY}content-grade grade README.md${R}`);
|
|
1281
1333
|
blank();
|
|
1282
|
-
console.log(` ${D}#
|
|
1283
|
-
console.log(` ${CY}content-grade
|
|
1334
|
+
console.log(` ${D}# Audit a live URL${R}`);
|
|
1335
|
+
console.log(` ${CY}content-grade seo-audit https://yoursite.com/blog-post${R}`);
|
|
1336
|
+
blank();
|
|
1337
|
+
console.log(` ${D}# CI pipeline — fail build if score is below 70${R}`);
|
|
1338
|
+
console.log(` ${CY}content-grade analyze ./blog.md --ci --threshold 70${R}`);
|
|
1284
1339
|
blank();
|
|
1285
1340
|
console.log(` ${D}# Get score number only (for scripts)${R}`);
|
|
1286
1341
|
console.log(` ${CY}content-grade analyze ./post.md --quiet${R}`);
|
|
@@ -1470,7 +1525,8 @@ async function cmdDemo() {
|
|
|
1470
1525
|
|
|
1471
1526
|
// Show telemetry notice after user has seen value (first run only)
|
|
1472
1527
|
if (_showTelemNotice) {
|
|
1473
|
-
console.log(` ${D}
|
|
1528
|
+
console.log(` ${D}Help improve ContentGrade — enable anonymous usage analytics: ${CY}content-grade telemetry on${R}`);
|
|
1529
|
+
console.log(` ${D}No PII, no file contents. View what's collected: ${CY}content-grade telemetry${R}`);
|
|
1474
1530
|
blank();
|
|
1475
1531
|
}
|
|
1476
1532
|
|
|
@@ -1696,7 +1752,17 @@ const _rawArgs = process.argv.slice(2);
|
|
|
1696
1752
|
const _jsonMode = _rawArgs.includes('--json');
|
|
1697
1753
|
const _quietMode = _rawArgs.includes('--quiet');
|
|
1698
1754
|
const _verboseMode = _rawArgs.includes('--verbose') || _rawArgs.includes('-v') && !_rawArgs.includes('--version');
|
|
1699
|
-
const
|
|
1755
|
+
const _ciMode = _rawArgs.includes('--ci');
|
|
1756
|
+
const _threshIdx = _rawArgs.indexOf('--threshold');
|
|
1757
|
+
const _ciThreshold = (_threshIdx !== -1 && _rawArgs[_threshIdx + 1])
|
|
1758
|
+
? (parseInt(_rawArgs[_threshIdx + 1], 10) || 60)
|
|
1759
|
+
: 60;
|
|
1760
|
+
const args = _rawArgs.filter((a, i) => {
|
|
1761
|
+
if (['--no-telemetry', '--json', '--quiet', '--verbose', '--ci'].includes(a)) return false;
|
|
1762
|
+
if (a === '--threshold') return false;
|
|
1763
|
+
if (i > 0 && _rawArgs[i - 1] === '--threshold') return false;
|
|
1764
|
+
return true;
|
|
1765
|
+
});
|
|
1700
1766
|
const raw = args[0];
|
|
1701
1767
|
const cmd = raw?.toLowerCase();
|
|
1702
1768
|
|
|
@@ -1775,6 +1841,26 @@ switch (cmd) {
|
|
|
1775
1841
|
});
|
|
1776
1842
|
break;
|
|
1777
1843
|
|
|
1844
|
+
case 'seo-audit':
|
|
1845
|
+
case 'seoaudit':
|
|
1846
|
+
recordEvent({ event: 'command', command: 'seo-audit' });
|
|
1847
|
+
if (!args[1]) {
|
|
1848
|
+
blank();
|
|
1849
|
+
fail(`No URL specified.`);
|
|
1850
|
+
blank();
|
|
1851
|
+
console.log(` ${B}Usage:${R} ${CY}content-grade seo-audit <url>${R}`);
|
|
1852
|
+
console.log(` ${B}Example:${R} ${CY}content-grade seo-audit https://yoursite.com/blog-post${R}`);
|
|
1853
|
+
blank();
|
|
1854
|
+
process.exit(2);
|
|
1855
|
+
}
|
|
1856
|
+
cmdAnalyzeUrl(args[1]).catch(err => {
|
|
1857
|
+
blank();
|
|
1858
|
+
fail(`Unexpected error: ${err.message}`);
|
|
1859
|
+
blank();
|
|
1860
|
+
process.exit(1);
|
|
1861
|
+
});
|
|
1862
|
+
break;
|
|
1863
|
+
|
|
1778
1864
|
case 'batch':
|
|
1779
1865
|
recordEvent({ event: 'command', command: 'batch' });
|
|
1780
1866
|
cmdBatch(args[1]).catch(err => {
|
package/bin/telemetry.js
CHANGED
|
@@ -72,18 +72,15 @@ export function initTelemetry() {
|
|
|
72
72
|
return { installId: cfg.installId, isNew: false, optedOut: false };
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
// First run — generate anonymous install ID
|
|
75
|
+
// First run — generate anonymous install ID, opt-IN by default (off until user enables)
|
|
76
76
|
const installId = generateId();
|
|
77
77
|
saveConfig({
|
|
78
78
|
installId,
|
|
79
79
|
installedAt: new Date().toISOString(),
|
|
80
|
-
telemetryEnabled:
|
|
80
|
+
telemetryEnabled: false,
|
|
81
81
|
});
|
|
82
82
|
|
|
83
|
-
|
|
84
|
-
_write({ event: 'install', installId, platform: process.platform, nodeVersion: process.version });
|
|
85
|
-
|
|
86
|
-
return { installId, isNew: true, optedOut: false };
|
|
83
|
+
return { installId, isNew: true, optedOut: true };
|
|
87
84
|
}
|
|
88
85
|
|
|
89
86
|
/**
|
package/package.json
CHANGED