content-grade 1.0.3 → 1.0.5

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
@@ -6,6 +6,8 @@
6
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
7
  [![Node.js 18+](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org)
8
8
  [![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)
10
+ [![Early Adopter seats open](https://img.shields.io/badge/Early%20Adopter-50%20seats%20open-success)](EARLY_ADOPTERS.md)
9
11
 
10
12
  ---
11
13
 
@@ -82,7 +84,7 @@ npx content-grade activate
82
84
  1. Why 90% of SaaS Startups Fail at Month 18 (and How to Be the 10%)
83
85
  2. The Month 18 Startup Trap: What Kills Growth-Stage Companies
84
86
 
85
- Unlock team features at contentgrade.dev
87
+ Unlock team features at content-grade.github.io/Content-Grade
86
88
  ```
87
89
 
88
90
  ---
@@ -139,7 +141,7 @@ Launch with `content-grade start` — opens at [http://localhost:4000](http://lo
139
141
  | **EmailForge** | `/email-forge` | Subject line + body copy for click-through optimization |
140
142
  | **AudienceDecoder** | `/audience` | Twitter handle → audience archetypes and content patterns |
141
143
 
142
- Free tier: **50 analyses/day per tool**. Pro ($9/mo): **100 analyses/day** + all tools.
144
+ Free tier: **50 analyses/day per tool**. Pro ($19/mo): **100 analyses/day** + all tools.
143
145
 
144
146
  ---
145
147
 
@@ -709,7 +711,7 @@ echo "$DATE,$HEADLINE,$SCORE" >> headline-scores.csv
709
711
 
710
712
  | | Free | Pro |
711
713
  |-|------|-----|
712
- | Price | Free forever | $9/month |
714
+ | Price | Free forever | $19/month |
713
715
  | Analyses/day (CLI) | Unlimited | Unlimited |
714
716
  | Analyses/day (web dashboard, per tool) | 3 | 100 |
715
717
  | HeadlineGrader (single grade) | ✓ | ✓ |
@@ -736,6 +738,54 @@ The web dashboard works the same way — every tool call goes through `claude -p
736
738
 
737
739
  ---
738
740
 
741
+ ## Community
742
+
743
+ ContentGrade is built in public. The community is the roadmap.
744
+
745
+ ### GitHub Discussions
746
+
747
+ **[Join the conversation →](https://github.com/StanislavBG/Content-Grade/discussions)**
748
+
749
+ | Category | What it's for |
750
+ |----------|--------------|
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 |
754
+
755
+ ### Early Adopter Program — 50 seats
756
+
757
+ **[EARLY_ADOPTERS.md](EARLY_ADOPTERS.md)** — The first 50 users who install and share feedback get:
758
+
759
+ - **Pro tier free for 12 months** ($19/mo value)
760
+ - Direct feedback channel with the maintainer
761
+ - Name in CONTRIBUTORS.md (permanent)
762
+ - Roadmap preview + design input before features ship
763
+ - 30-minute onboarding call (optional)
764
+
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.
766
+
767
+ ### Champions Program
768
+
769
+ Power users who go beyond early adoption get early access to `@beta` releases, a private Champions discussion thread, and a SVG badge for their profile. See **[CHAMPIONS.md](CHAMPIONS.md)** for selection criteria and current members.
770
+
771
+ ### Contributing
772
+
773
+ See **[CONTRIBUTING.md](CONTRIBUTING.md)** for local dev setup, PR guidelines, and how to write good bug reports. The short version: clone → `pnpm install` → `pnpm dev` → file a PR. All contributions get a review within 48 hours.
774
+
775
+ ### Roadmap
776
+
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.
778
+
779
+ ### Feedback Loop
780
+
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.
782
+
783
+ ### Contributors
784
+
785
+ **[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
+
787
+ ---
788
+
739
789
  ## License
740
790
 
741
791
  MIT
@@ -713,34 +713,42 @@ async function cmdActivate() {
713
713
  console.log(` ${D}Get a license: ${CY}contentgrade.dev${R}${D} → Pricing → Team${R}`);
714
714
  blank();
715
715
 
716
- process.stdout.write(` ${CY}License key:${R} `);
717
-
718
- const key = await new Promise(res => {
719
- let input = '';
720
- const isRaw = process.stdin.isTTY;
721
- if (isRaw) process.stdin.setRawMode(true);
722
- process.stdin.resume();
723
- process.stdin.setEncoding('utf8');
724
- process.stdin.on('data', function onData(chunk) {
725
- if (chunk === '\r' || chunk === '\n') {
726
- if (isRaw) process.stdin.setRawMode(false);
727
- process.stdin.pause();
728
- process.stdin.removeListener('data', onData);
729
- process.stdout.write('\n');
730
- res(input.trim());
731
- } else if (chunk === '\u0003') {
732
- if (isRaw) process.stdin.setRawMode(false);
733
- process.stdin.pause();
734
- process.stdout.write('\n');
735
- process.exit(0);
736
- } else if (chunk === '\u007f') {
737
- if (input.length > 0) { input = input.slice(0, -1); process.stdout.write('\b \b'); }
738
- } else {
739
- input += chunk;
740
- process.stdout.write('*');
741
- }
716
+ let key;
717
+ if (args[1] && args[1].length >= 8) {
718
+ key = args[1];
719
+ blank();
720
+ console.log(` ${D}Using provided key: ${args[1].slice(0, 8)}...${R}`);
721
+ blank();
722
+ } else {
723
+ process.stdout.write(` ${CY}License key:${R} `);
724
+
725
+ key = await new Promise(res => {
726
+ let input = '';
727
+ const isRaw = process.stdin.isTTY;
728
+ if (isRaw) process.stdin.setRawMode(true);
729
+ process.stdin.resume();
730
+ process.stdin.setEncoding('utf8');
731
+ process.stdin.on('data', function onData(chunk) {
732
+ if (chunk === '\r' || chunk === '\n') {
733
+ if (isRaw) process.stdin.setRawMode(false);
734
+ process.stdin.pause();
735
+ process.stdin.removeListener('data', onData);
736
+ process.stdout.write('\n');
737
+ res(input.trim());
738
+ } else if (chunk === '\u0003') {
739
+ if (isRaw) process.stdin.setRawMode(false);
740
+ process.stdin.pause();
741
+ process.stdout.write('\n');
742
+ process.exit(0);
743
+ } else if (chunk === '\u007f') {
744
+ if (input.length > 0) { input = input.slice(0, -1); process.stdout.write('\b \b'); }
745
+ } else {
746
+ input += chunk;
747
+ process.stdout.write('*');
748
+ }
749
+ });
742
750
  });
743
- });
751
+ }
744
752
 
745
753
  if (!key || key.length < 8) {
746
754
  blank();
@@ -749,13 +757,45 @@ async function cmdActivate() {
749
757
  process.exit(2);
750
758
  }
751
759
 
760
+ // Validate key against server before storing
761
+ const serverUrl = process.env.CONTENT_GRADE_SERVER_URL || 'https://contentgrade.ai';
762
+ blank();
763
+ process.stdout.write(` ${D}Validating key...${R}`);
764
+
765
+ let validated = false;
766
+ try {
767
+ const response = await fetch(`${serverUrl}/api/license/validate`, {
768
+ method: 'POST',
769
+ headers: { 'Content-Type': 'application/json' },
770
+ body: JSON.stringify({ key }),
771
+ });
772
+ const data = await response.json();
773
+ if (data.valid) {
774
+ validated = true;
775
+ } else {
776
+ process.stdout.write('\n');
777
+ blank();
778
+ fail(data.message || 'Invalid or expired license key. Visit contentgrade.ai to check your subscription.');
779
+ blank();
780
+ process.exit(2);
781
+ }
782
+ } catch {
783
+ // Server unreachable — allow offline activation with a warning
784
+ process.stdout.write('\n');
785
+ blank();
786
+ console.log(` ${D}⚠ Could not reach server — storing key locally without verification.${R}`);
787
+ validated = true;
788
+ }
789
+
790
+ process.stdout.write(' done\n');
791
+
752
792
  const config = loadConfig();
753
793
  config.licenseKey = key;
754
794
  config.activatedAt = new Date().toISOString();
755
795
  saveConfig(config);
756
796
 
757
797
  blank();
758
- ok(`License activated! Pro features unlocked.`);
798
+ ok(`Pro access activated! Your daily limit is now 100 analyses.`);
759
799
  blank();
760
800
  console.log(` ${B}Try it:${R}`);
761
801
  console.log(` ${CY} content-grade batch ./posts/${R} ${D}# analyze all files${R}`);
@@ -1094,19 +1134,19 @@ function cmdHelp() {
1094
1134
 
1095
1135
  // ── Demo command ──────────────────────────────────────────────────────────────
1096
1136
 
1097
- const DEMO_CONTENT = `# Why Your Morning Routine Is Secretly Destroying Your Productivity
1137
+ const DEMO_CONTENT = `# How to Write Better Blog Posts
1098
1138
 
1099
- Productivity is important. Many people struggle with it. In this post we will discuss some tips.
1139
+ Writing good blog posts is important for any content marketer. Many people struggle with this. In this post I will share some tips that can help you improve.
1100
1140
 
1101
- First, wake up early. Successful people wake up early. This gives you more time. Studies show that early risers are more productive.
1141
+ First, you need to have a good headline. The headline is the first thing people see. A good headline will make people want to read more. Try to make it interesting.
1102
1142
 
1103
- Second, make a to-do list. Write everything down. Then do the tasks one by one. This is a simple but effective strategy.
1143
+ Second, write good content. Your content should be useful and informative. People should learn something from reading your post. Make sure to cover the topic well.
1104
1144
 
1105
- Third, avoid distractions. Put your phone away. Social media is bad for focus. You should minimize interruptions.
1145
+ Third, use a good structure. Break your content into sections. Use headings and bullet points. This makes it easier to read. People can scan and find what they need.
1106
1146
 
1107
- Fourth, take breaks. Working too hard is not good. Make sure to rest sometimes. The Pomodoro technique can help with this.
1147
+ Finally, have a good conclusion. Summarize what you talked about. Tell people what to do next. A call to action is important.
1108
1148
 
1109
- In conclusion, these productivity tips are very helpful. Try them today and you will see a big difference in your output. Productivity is the key to success in today's world.
1149
+ In conclusion, following these tips will help you write better blog posts. Good content is important for success online. Start applying these tips today.
1110
1150
  `;
1111
1151
 
1112
1152
  async function cmdDemo() {
@@ -1125,22 +1165,80 @@ async function cmdDemo() {
1125
1165
  const tmpFile = `/tmp/content-grade-demo-${process.pid}-${Date.now()}.md`;
1126
1166
 
1127
1167
  banner();
1128
- console.log(` ${B}Demo Mode${R} ${D}— running analysis on sample content${R}`);
1168
+ console.log(` ${B}Demo Mode${R} ${D}— see ContentGrade in action${R}`);
1129
1169
  blank();
1130
- console.log(` ${D}This is what ContentGrade does. Try it on your own content:${R}`);
1131
- console.log(` ${CY} content-grade analyze ./your-post.md${R}`);
1132
- blank();
1133
- console.log(` ${D}Analyzing sample blog post...${R}`);
1170
+ console.log(` ${D}Analyzing a sample blog post with generic writing.${R}`);
1171
+ console.log(` ${D}Generic content scores low — that's the point. Watch what ContentGrade finds.${R}`);
1134
1172
  blank();
1135
1173
 
1136
1174
  writeFileSync(tmpFile, DEMO_CONTENT, 'utf8');
1137
1175
  try {
1138
1176
  await cmdAnalyze(tmpFile);
1177
+
1178
+ // Post-demo CTA — shown AFTER user has seen the analysis output
1179
+ blank();
1180
+ hr();
1181
+ console.log(` ${B}${CY}Now try it on your own content:${R}`);
1182
+ 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}`);
1186
+ blank();
1187
+
1139
1188
  // Show telemetry notice after user has seen value (first run only)
1140
1189
  if (_showTelemNotice) {
1141
1190
  console.log(` ${D}Anonymous usage data is collected by default. Opt out: ${CY}content-grade telemetry off${R}`);
1142
1191
  blank();
1143
1192
  }
1193
+
1194
+ // Interactive follow-up — strike while the iron is hot
1195
+ if (process.stdin.isTTY) {
1196
+ hr();
1197
+ console.log(` ${B}${CY}Try it on YOUR content right now:${R}`);
1198
+ 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}`);
1201
+ blank();
1202
+ process.stdout.write(` ${CY}>${R} `);
1203
+
1204
+ const userInput = await new Promise(res => {
1205
+ let line = '';
1206
+ process.stdin.resume();
1207
+ process.stdin.setEncoding('utf8');
1208
+ const onData = (chunk) => {
1209
+ const str = chunk.toString();
1210
+ if (str.includes('\n') || str.includes('\r')) {
1211
+ process.stdin.pause();
1212
+ process.stdin.removeListener('data', onData);
1213
+ process.stdout.write('\n');
1214
+ res(line.replace(/[\r\n]/g, '').trim());
1215
+ } else if (str === '\u0003') {
1216
+ process.stdin.pause();
1217
+ process.stdin.removeListener('data', onData);
1218
+ process.stdout.write('\n');
1219
+ res('');
1220
+ } else {
1221
+ line += str;
1222
+ process.stdout.write(str);
1223
+ }
1224
+ };
1225
+ process.stdin.on('data', onData);
1226
+ });
1227
+
1228
+ if (userInput) {
1229
+ blank();
1230
+ if (/^https?:\/\//i.test(userInput)) {
1231
+ await cmdAnalyzeUrl(userInput);
1232
+ } else if (looksLikePath(userInput)) {
1233
+ await cmdAnalyze(userInput);
1234
+ } else if (userInput.length >= 5) {
1235
+ await cmdHeadline(userInput);
1236
+ } else {
1237
+ warn(`Couldn't parse that. Try a URL like https://... or a headline like "Your title here".`);
1238
+ blank();
1239
+ }
1240
+ }
1241
+ }
1144
1242
  } finally {
1145
1243
  try { unlinkSync(tmpFile); } catch {}
1146
1244
  }
@@ -1483,15 +1581,30 @@ switch (cmd) {
1483
1581
  process.exit(1);
1484
1582
  });
1485
1583
  } else {
1486
- blank();
1487
- fail(`Unknown command: ${B}${raw}${R}`);
1488
- blank();
1489
- console.log(` Available commands:`);
1490
- console.log(` ${CY}analyze <file>${R} ${CY}headline "<text>"${R} ${CY}demo${R} ${CY}start${R} ${CY}init${R} ${CY}help${R}`);
1491
- blank();
1492
- console.log(` ${D}Pass a file/directory directly: ${CY}content-grade ./my-post.md${R}`);
1493
- console.log(` ${D}Run ${CY}content-grade help${R}${D} for full usage${R}`);
1494
- blank();
1495
- process.exit(1);
1584
+ // Smart fallback: treat unrecognised input as a headline to grade.
1585
+ // Covers: npx content-grade "Why most startups fail"
1586
+ // npx content-grade Why most startups fail
1587
+ const allText = args.join(' ').trim();
1588
+ if (allText && allText.length >= 5 && !allText.startsWith('-')) {
1589
+ recordEvent({ event: 'command', command: 'headline_smart' });
1590
+ cmdHeadline(allText).catch(err => {
1591
+ blank();
1592
+ fail(`Unexpected error: ${err.message}`);
1593
+ blank();
1594
+ process.exit(1);
1595
+ });
1596
+ } else {
1597
+ blank();
1598
+ fail(`Unknown command: ${B}${raw}${R}`);
1599
+ blank();
1600
+ console.log(` Available commands:`);
1601
+ console.log(` ${CY}analyze <file>${R} ${CY}headline "<text>"${R} ${CY}demo${R} ${CY}start${R} ${CY}init${R} ${CY}help${R}`);
1602
+ blank();
1603
+ console.log(` ${D}Pass a file/directory directly: ${CY}content-grade ./my-post.md${R}`);
1604
+ console.log(` ${D}Grade a headline: ${CY}content-grade "Your headline text"${R}`);
1605
+ console.log(` ${D}Run ${CY}content-grade help${R}${D} for full usage${R}`);
1606
+ blank();
1607
+ process.exit(1);
1608
+ }
1496
1609
  }
1497
1610
  }
package/bin/telemetry.js CHANGED
@@ -17,8 +17,9 @@ const CONFIG_DIR = join(homedir(), '.content-grade');
17
17
  const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
18
18
  const EVENTS_FILE = join(CONFIG_DIR, 'events.jsonl');
19
19
 
20
- // Telemetry endpoint: set CONTENT_GRADE_TELEMETRY_URL to enable remote reporting
21
- const REMOTE_URL = process.env.CONTENT_GRADE_TELEMETRY_URL || null;
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';
22
23
 
23
24
  // ── Internal helpers ──────────────────────────────────────────────────────────
24
25