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 +53 -3
- package/bin/content-grade.js +142 -38
- package/bin/telemetry.js +3 -2
- package/dist/assets/index-DWBbWitG.js +78 -0
- package/dist/index.html +1 -1
- package/dist-server/server/app.js +4 -0
- package/dist-server/server/db.js +46 -0
- package/dist-server/server/routes/analytics.js +283 -0
- package/dist-server/server/routes/demos.js +9 -1
- package/dist-server/server/routes/license.js +53 -0
- package/dist-server/server/routes/stripe.js +69 -0
- package/dist-server/server/services/license.js +38 -0
- package/package.json +1 -1
- package/dist/assets/index-BUN69TiT.js +0 -78
package/README.md
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
[](https://opensource.org/licenses/MIT)
|
|
7
7
|
[](https://nodejs.org)
|
|
8
8
|
[](https://claude.ai/code)
|
|
9
|
+
[](https://github.com/StanislavBG/Content-Grade/discussions)
|
|
10
|
+
[](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
|
|
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 ($
|
|
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 | $
|
|
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
|
package/bin/content-grade.js
CHANGED
|
@@ -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
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
process.
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
if (
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
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(`
|
|
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
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
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:
|
|
21
|
-
|
|
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
|
|