content-grade 1.0.4 → 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}`);
@@ -1150,6 +1190,55 @@ async function cmdDemo() {
1150
1190
  console.log(` ${D}Anonymous usage data is collected by default. Opt out: ${CY}content-grade telemetry off${R}`);
1151
1191
  blank();
1152
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
+ }
1153
1242
  } finally {
1154
1243
  try { unlinkSync(tmpFile); } catch {}
1155
1244
  }
@@ -1492,15 +1581,30 @@ switch (cmd) {
1492
1581
  process.exit(1);
1493
1582
  });
1494
1583
  } else {
1495
- blank();
1496
- fail(`Unknown command: ${B}${raw}${R}`);
1497
- blank();
1498
- console.log(` Available commands:`);
1499
- console.log(` ${CY}analyze <file>${R} ${CY}headline "<text>"${R} ${CY}demo${R} ${CY}start${R} ${CY}init${R} ${CY}help${R}`);
1500
- blank();
1501
- console.log(` ${D}Pass a file/directory directly: ${CY}content-grade ./my-post.md${R}`);
1502
- console.log(` ${D}Run ${CY}content-grade help${R}${D} for full usage${R}`);
1503
- blank();
1504
- 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
+ }
1505
1609
  }
1506
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