content-grade 1.0.33 → 1.0.35
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 +44 -12
- package/bin/content-grade.js +68 -2
- package/bin/telemetry.js +14 -1
- package/dist-server/server/db.js +8 -0
- package/dist-server/server/routes/analytics.js +3 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -30,7 +30,7 @@ npx content-grade headline "Why Most Startups Fail at Month 18"
|
|
|
30
30
|
|
|
31
31
|
**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.
|
|
32
32
|
|
|
33
|
-
**Free tier:**
|
|
33
|
+
**Free tier:** 3 analyses/day. No signup. `npx content-grade grade README.md` works immediately.
|
|
34
34
|
|
|
35
35
|
**Install globally** (recommended — skips the `npx` download on every run):
|
|
36
36
|
|
|
@@ -49,6 +49,30 @@ content-grade activate # unlock Pro: batch mode, 100/day
|
|
|
49
49
|
|
|
50
50
|
---
|
|
51
51
|
|
|
52
|
+
## Upgrade to Pro
|
|
53
|
+
|
|
54
|
+
**Free tier:** 3 analyses/day — enough to try it, not enough to rely on it.
|
|
55
|
+
|
|
56
|
+
| | Free | Pro |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| Analyses/day | 3 | Unlimited |
|
|
59
|
+
| All 6 CLI commands | ✓ | ✓ |
|
|
60
|
+
| Web dashboard (6 tools) | ✓ | ✓ |
|
|
61
|
+
| Batch mode (`batch <dir>`) | — | ✓ |
|
|
62
|
+
| CI integration (`--json` exit codes) | ✓ | ✓ |
|
|
63
|
+
| **Price** | **$0** | **$9/mo** |
|
|
64
|
+
|
|
65
|
+
**[Upgrade to Pro → $9/mo](https://buy.stripe.com/4gM14p87GeCh9vn9ks8k80a)**
|
|
66
|
+
|
|
67
|
+
After payment you'll receive a license key by email. Activate it in one command:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
content-grade activate
|
|
71
|
+
# Enter your license key when prompted
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
52
76
|
## Usage Examples
|
|
53
77
|
|
|
54
78
|
**Grade a single file:**
|
|
@@ -172,20 +196,24 @@ Launch with `content-grade start` — opens at [http://localhost:4000](http://lo
|
|
|
172
196
|
| **EmailForge** | `/email-forge` | Subject line + body copy for click-through optimization |
|
|
173
197
|
| **AudienceDecoder** | `/audience` | Twitter handle → audience archetypes and content patterns |
|
|
174
198
|
|
|
175
|
-
Free tier: **
|
|
199
|
+
Free tier: **3 analyses/day**. [Pro ($9/mo)](https://buy.stripe.com/4gM14p87GeCh9vn9ks8k80a): **unlimited analyses** + batch mode.
|
|
176
200
|
|
|
177
201
|
---
|
|
178
202
|
|
|
179
|
-
##
|
|
203
|
+
## Community
|
|
204
|
+
|
|
205
|
+
**[COMMUNITY.md](COMMUNITY.md)** — how to report bugs, request features, join the Early Adopter program, and share your work.
|
|
180
206
|
|
|
181
|
-
|
|
207
|
+
| Channel | Best for |
|
|
208
|
+
|---------|----------|
|
|
209
|
+
| [Q&A](https://github.com/StanislavBG/Content-Grade/discussions/new?category=q-a) | Questions, troubleshooting, "why is my score X?" |
|
|
210
|
+
| [Show & Tell](https://github.com/StanislavBG/Content-Grade/discussions/new?category=show-and-tell) | Workflows, CI integrations, results — claim Early Adopter here |
|
|
211
|
+
| [Ideas](https://github.com/StanislavBG/Content-Grade/discussions/new?category=ideas) | Feature requests before they become issues |
|
|
212
|
+
| [Bug reports](https://github.com/StanislavBG/Content-Grade/issues/new?template=bug_report.yml) | Something broken? File it here |
|
|
182
213
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
- [Feature requests & roadmap input](https://github.com/Content-Grade/Content-Grade/discussions/3) — Ideas
|
|
187
|
-
- [Content scoring in CI — who is actually doing this?](https://github.com/Content-Grade/Content-Grade/discussions/4) — General
|
|
188
|
-
- [What integrations would make ContentGrade essential to your workflow?](https://github.com/Content-Grade/Content-Grade/discussions/5) — Ideas
|
|
214
|
+
**[All discussions →](https://github.com/StanislavBG/Content-Grade/discussions)**
|
|
215
|
+
|
|
216
|
+
**Early Adopter program:** 50 seats, 0 claimed. Install ContentGrade, post in Show & Tell with `[Early Adopter]`, get 12 months Pro free. Details: [EARLY_ADOPTERS.md](EARLY_ADOPTERS.md)
|
|
189
217
|
|
|
190
218
|
---
|
|
191
219
|
|
|
@@ -945,6 +973,8 @@ The web dashboard works the same way — every tool call goes through `claude -p
|
|
|
945
973
|
|
|
946
974
|
ContentGrade is built in public. The community is the roadmap.
|
|
947
975
|
|
|
976
|
+
**→ [COMMUNITY.md](COMMUNITY.md)** — how to report bugs, request features, and claim an Early Adopter seat.
|
|
977
|
+
|
|
948
978
|
### GitHub Discussions
|
|
949
979
|
|
|
950
980
|
**[Join the conversation →](https://github.com/StanislavBG/Content-Grade/discussions)**
|
|
@@ -1007,7 +1037,7 @@ See [docs/social-proof/built-with-badge.md](docs/social-proof/built-with-badge.m
|
|
|
1007
1037
|
|
|
1008
1038
|
---
|
|
1009
1039
|
|
|
1010
|
-
## Community
|
|
1040
|
+
## Community & Feedback
|
|
1011
1041
|
|
|
1012
1042
|
[](https://github.com/StanislavBG/Content-Grade)
|
|
1013
1043
|
|
|
@@ -1017,7 +1047,9 @@ If Content-Grade saves you time, a ⭐ goes a long way. It helps more developers
|
|
|
1017
1047
|
|
|
1018
1048
|
**Early adopter program:** The first 50 seats are still open — early adopters get permanent free Pro tier. [Claim your seat →](https://content-grade.github.io/Content-Grade/#early-adopter)
|
|
1019
1049
|
|
|
1020
|
-
**Found a bug
|
|
1050
|
+
**Found a bug?** [Open an issue →](https://github.com/StanislavBG/Content-Grade/issues/new/choose) — every report shapes what gets built next.
|
|
1051
|
+
|
|
1052
|
+
**[GitHub Discussions →](https://github.com/StanislavBG/Content-Grade/discussions)** — questions, ideas, and show & tell. Built something with content-grade? Open a Discussion — we want to see it.
|
|
1021
1053
|
|
|
1022
1054
|
---
|
|
1023
1055
|
|
package/bin/content-grade.js
CHANGED
|
@@ -156,6 +156,8 @@ function showFreeTierCTA(count) {
|
|
|
156
156
|
console.log(` ${WH}${count}/${limit} free analyses · ${remaining} left · Unlimited with Pro ($9/mo): ${CY}${UPGRADE_LINKS.free}${R}`);
|
|
157
157
|
}
|
|
158
158
|
hr();
|
|
159
|
+
console.log(` ${D}⭐ Like ContentGrade? Star us: https://github.com/StanislavBG/Content-Grade${R}`);
|
|
160
|
+
maybeShowFeedbackCTA(count);
|
|
159
161
|
}
|
|
160
162
|
|
|
161
163
|
// ── Early Adopter program enrollment CTA ─────────────────────────────────────
|
|
@@ -190,6 +192,27 @@ function maybeShowEarlyAdopterCTA(count) {
|
|
|
190
192
|
hr();
|
|
191
193
|
}
|
|
192
194
|
|
|
195
|
+
// ── Feedback CTA ──────────────────────────────────────────────────────────────
|
|
196
|
+
|
|
197
|
+
function hasFeedbackCTAShown() {
|
|
198
|
+
return Boolean(loadConfig().feedbackCTAShown);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function markFeedbackCTAShown() {
|
|
202
|
+
const cfg = loadConfig();
|
|
203
|
+
cfg.feedbackCTAShown = true;
|
|
204
|
+
saveConfig(cfg);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Show once, after the 3rd run, to seed the GitHub Issues feedback loop.
|
|
208
|
+
function maybeShowFeedbackCTA(count) {
|
|
209
|
+
if (count !== 3) return; // only after exactly the 3rd run
|
|
210
|
+
if (hasFeedbackCTAShown()) return; // only once per install
|
|
211
|
+
markFeedbackCTAShown();
|
|
212
|
+
blank();
|
|
213
|
+
console.log(` ${D}Found a bug or have a suggestion? Open an issue: ${CY}https://github.com/StanislavBG/Content-Grade/issues/new${R}`);
|
|
214
|
+
}
|
|
215
|
+
|
|
193
216
|
// Single-line usage counter appended after every command run.
|
|
194
217
|
// count = total runs used today (after this run).
|
|
195
218
|
function showUsageFooter(count) {
|
|
@@ -205,6 +228,8 @@ function showUsageFooter(count) {
|
|
|
205
228
|
console.log(` ${D}[ ${count}/${limit} free runs used · ${remaining} left. Unlimited: ${UPGRADE_LINKS.free} ]${R}`);
|
|
206
229
|
}
|
|
207
230
|
maybeShowEarlyAdopterCTA(count);
|
|
231
|
+
maybeShowFeedbackCTA(count);
|
|
232
|
+
console.log(` ${D}⭐ Like ContentGrade? Star us: https://github.com/StanislavBG/Content-Grade${R}`);
|
|
208
233
|
}
|
|
209
234
|
|
|
210
235
|
// Returns { ok: boolean, count: number, limit: number } for tier-aware daily limit checks.
|
|
@@ -230,6 +255,7 @@ function checkFreeTierLimit() {
|
|
|
230
255
|
|
|
231
256
|
// Track funnel event — user hit the limit and saw the upgrade prompt
|
|
232
257
|
recordEvent({ event: 'free_limit_hit', version: _version });
|
|
258
|
+
recordEvent({ event: 'upgrade_prompt_shown', run_count: usage.count });
|
|
233
259
|
|
|
234
260
|
blank();
|
|
235
261
|
hr();
|
|
@@ -1059,6 +1085,8 @@ async function cmdActivate() {
|
|
|
1059
1085
|
config.activatedAt = new Date().toISOString();
|
|
1060
1086
|
saveConfig(config);
|
|
1061
1087
|
|
|
1088
|
+
recordEvent({ event: 'license_activated', tier: activatedTier });
|
|
1089
|
+
|
|
1062
1090
|
// Also write canonical license file to ~/.content-grade/license.json
|
|
1063
1091
|
mkdirSync(LICENSE_DIR, { recursive: true });
|
|
1064
1092
|
writeFileSync(LICENSE_FILE, JSON.stringify({
|
|
@@ -1519,6 +1547,8 @@ function cmdHelp() {
|
|
|
1519
1547
|
blank();
|
|
1520
1548
|
console.log(` ${CY}check-updates${R} Check if a newer version is available on npm`);
|
|
1521
1549
|
blank();
|
|
1550
|
+
console.log(` ${CY}feedback${R} Open GitHub Issues to report a bug or request a feature`);
|
|
1551
|
+
blank();
|
|
1522
1552
|
console.log(` ${CY}help${R} Show this help`);
|
|
1523
1553
|
blank();
|
|
1524
1554
|
console.log(` ${B}FLAGS${R}`);
|
|
@@ -1532,6 +1562,7 @@ function cmdHelp() {
|
|
|
1532
1562
|
console.log(` ${CY}--verbose${R} Show debug info: model, timing, raw response length`);
|
|
1533
1563
|
console.log(` ${CY}--version${R} Print version and exit`);
|
|
1534
1564
|
console.log(` ${CY}--no-telemetry${R} Skip usage tracking for this invocation`);
|
|
1565
|
+
console.log(` ${CY}--feedback${R} Open GitHub Issues in your browser`);
|
|
1535
1566
|
blank();
|
|
1536
1567
|
|
|
1537
1568
|
console.log(` ${B}EXAMPLES${R}`);
|
|
@@ -1579,6 +1610,27 @@ function cmdHelp() {
|
|
|
1579
1610
|
blank();
|
|
1580
1611
|
}
|
|
1581
1612
|
|
|
1613
|
+
// ── Feedback command ─────────────────────────────────────────────────────────
|
|
1614
|
+
|
|
1615
|
+
const FEEDBACK_URL = 'https://github.com/StanislavBG/Content-Grade/issues/new';
|
|
1616
|
+
|
|
1617
|
+
function cmdFeedback() {
|
|
1618
|
+
blank();
|
|
1619
|
+
console.log(` ${B}Report an issue or request a feature${R}`);
|
|
1620
|
+
blank();
|
|
1621
|
+
console.log(` ${CY}${FEEDBACK_URL}${R}`);
|
|
1622
|
+
blank();
|
|
1623
|
+
const { platform } = process;
|
|
1624
|
+
const opener = platform === 'darwin' ? 'open' : platform === 'win32' ? 'start' : 'xdg-open';
|
|
1625
|
+
try {
|
|
1626
|
+
spawn(opener, [FEEDBACK_URL], { stdio: 'ignore', shell: platform === 'win32', detached: true }).unref();
|
|
1627
|
+
console.log(` ${GN}✓${R} Opening in your browser…`);
|
|
1628
|
+
} catch {
|
|
1629
|
+
console.log(` ${D}(Could not open browser — copy the URL above)${R}`);
|
|
1630
|
+
}
|
|
1631
|
+
blank();
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1582
1634
|
// ── First-run detection ───────────────────────────────────────────────────────
|
|
1583
1635
|
|
|
1584
1636
|
function isFirstRun() {
|
|
@@ -2063,8 +2115,9 @@ const _savePath = (_saveMode && _rawArgs[_saveIdx + 1] && !_rawArgs[_saveIdx + 1
|
|
|
2063
2115
|
? _rawArgs[_saveIdx + 1]
|
|
2064
2116
|
: null;
|
|
2065
2117
|
const _demoMode = _rawArgs.includes('--demo');
|
|
2118
|
+
const _feedbackMode = _rawArgs.includes('--feedback');
|
|
2066
2119
|
const args = _rawArgs.filter((a, i) => {
|
|
2067
|
-
if (['--no-telemetry', '--json', '--quiet', '--verbose', '--ci', '--save', '--demo'].includes(a)) return false;
|
|
2120
|
+
if (['--no-telemetry', '--json', '--quiet', '--verbose', '--ci', '--save', '--demo', '--feedback'].includes(a)) return false;
|
|
2068
2121
|
if (a === '--threshold') return false;
|
|
2069
2122
|
if (i > 0 && _rawArgs[i - 1] === '--threshold') return false;
|
|
2070
2123
|
if (_saveMode && i > 0 && _rawArgs[i - 1] === '--save' && !a.startsWith('-')) return false;
|
|
@@ -2078,6 +2131,11 @@ const _telem = initTelemetry();
|
|
|
2078
2131
|
// Defer first-run telemetry notice so users see value first
|
|
2079
2132
|
const _showTelemNotice = _telem.isNew;
|
|
2080
2133
|
|
|
2134
|
+
// Fire install event exactly once — on the very first run
|
|
2135
|
+
if (_telem.isNew) {
|
|
2136
|
+
recordEvent({ event: 'install' });
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2081
2139
|
// ── License key startup validation ───────────────────────────────────────────
|
|
2082
2140
|
// Background check: re-validate stored license key against server every 7 days.
|
|
2083
2141
|
// Non-blocking — never delays the CLI. Clears invalid/revoked keys silently.
|
|
@@ -2235,7 +2293,9 @@ if (_rawArgs.includes('--help') || _rawArgs.includes('-h')) {
|
|
|
2235
2293
|
}
|
|
2236
2294
|
}
|
|
2237
2295
|
|
|
2238
|
-
if (
|
|
2296
|
+
if (_feedbackMode) {
|
|
2297
|
+
cmdFeedback();
|
|
2298
|
+
} else if (_demoMode) {
|
|
2239
2299
|
recordEvent({ event: 'grade_run', is_pro: isProUser(), command:'demo' });
|
|
2240
2300
|
cmdDemo().catch(err => {
|
|
2241
2301
|
blank();
|
|
@@ -2335,6 +2395,12 @@ if (_demoMode) {
|
|
|
2335
2395
|
});
|
|
2336
2396
|
break;
|
|
2337
2397
|
|
|
2398
|
+
case 'feedback':
|
|
2399
|
+
case 'issue':
|
|
2400
|
+
case 'report':
|
|
2401
|
+
cmdFeedback();
|
|
2402
|
+
break;
|
|
2403
|
+
|
|
2338
2404
|
case 'help':
|
|
2339
2405
|
case '--help':
|
|
2340
2406
|
case '-h':
|
package/bin/telemetry.js
CHANGED
|
@@ -10,8 +10,16 @@
|
|
|
10
10
|
|
|
11
11
|
import { existsSync, readFileSync, writeFileSync, appendFileSync, mkdirSync } from 'fs';
|
|
12
12
|
import { homedir } from 'os';
|
|
13
|
-
import { join } from 'path';
|
|
13
|
+
import { join, resolve, dirname } from 'path';
|
|
14
14
|
import { createHash } from 'crypto';
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
16
|
+
|
|
17
|
+
// Package version — loaded once at module init
|
|
18
|
+
const __telemetryDir = dirname(fileURLToPath(import.meta.url));
|
|
19
|
+
let _pkgVersion = '1.0.0';
|
|
20
|
+
try {
|
|
21
|
+
_pkgVersion = JSON.parse(readFileSync(resolve(__telemetryDir, '../package.json'), 'utf8')).version ?? '1.0.0';
|
|
22
|
+
} catch {}
|
|
15
23
|
|
|
16
24
|
const CONFIG_DIR = join(homedir(), '.content-grade');
|
|
17
25
|
const CONFIG_FILE = join(CONFIG_DIR, 'config.json');
|
|
@@ -126,6 +134,11 @@ export function recordEvent(data) {
|
|
|
126
134
|
package: 'content-grade',
|
|
127
135
|
install_id: installId,
|
|
128
136
|
run_count_bucket: data.run_count_bucket ?? getRunCountBucket(totalRuns),
|
|
137
|
+
// Auto-enrich with environment context if caller didn't provide
|
|
138
|
+
is_pro: data.is_pro ?? Boolean(cfg?.licenseKey),
|
|
139
|
+
version: data.version ?? _pkgVersion,
|
|
140
|
+
platform: data.platform ?? process.platform,
|
|
141
|
+
node_version: data.node_version ?? process.version,
|
|
129
142
|
timestamp: new Date().toISOString(),
|
|
130
143
|
};
|
|
131
144
|
|
package/dist-server/server/db.js
CHANGED
|
@@ -134,4 +134,12 @@ function migrate(db) {
|
|
|
134
134
|
db.exec(`ALTER TABLE cli_telemetry ADD COLUMN run_count_bucket TEXT`);
|
|
135
135
|
}
|
|
136
136
|
catch { }
|
|
137
|
+
try {
|
|
138
|
+
db.exec(`ALTER TABLE cli_telemetry ADD COLUMN tier TEXT`);
|
|
139
|
+
}
|
|
140
|
+
catch { }
|
|
141
|
+
try {
|
|
142
|
+
db.exec(`ALTER TABLE cli_telemetry ADD COLUMN run_count INTEGER`);
|
|
143
|
+
}
|
|
144
|
+
catch { }
|
|
137
145
|
}
|
|
@@ -43,9 +43,9 @@ export function registerAnalyticsRoutes(app) {
|
|
|
43
43
|
const db = getDb();
|
|
44
44
|
db.prepare(`
|
|
45
45
|
INSERT INTO cli_telemetry
|
|
46
|
-
(install_id, event, command, is_pro, duration_ms, success, exit_code, score, content_type, version, platform, node_version, run_count_bucket)
|
|
47
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
48
|
-
`).run(String(body.install_id).slice(0, 64), String(body.event ?? 'unknown').slice(0, 64), body.command ? String(body.command).slice(0, 64) : null, typeof body.is_pro === 'boolean' ? (body.is_pro ? 1 : 0) : null, typeof body.duration_ms === 'number' ? Math.round(body.duration_ms) : null, typeof body.success === 'boolean' ? (body.success ? 1 : 0) : null, typeof body.exit_code === 'number' ? body.exit_code : null, typeof body.score === 'number' ? body.score : null, body.content_type ? String(body.content_type).slice(0, 64) : null, body.version ? String(body.version).slice(0, 32) : null, body.platform ? String(body.platform).slice(0, 32) : null, body.nodeVersion ? String(body.nodeVersion).slice(0, 32) : null, body.run_count_bucket ? String(body.run_count_bucket).slice(0, 10) : null);
|
|
46
|
+
(install_id, event, command, is_pro, duration_ms, success, exit_code, score, content_type, version, platform, node_version, run_count_bucket, tier, run_count)
|
|
47
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
48
|
+
`).run(String(body.install_id).slice(0, 64), String(body.event ?? 'unknown').slice(0, 64), body.command ? String(body.command).slice(0, 64) : null, typeof body.is_pro === 'boolean' ? (body.is_pro ? 1 : 0) : null, typeof body.duration_ms === 'number' ? Math.round(body.duration_ms) : null, typeof body.success === 'boolean' ? (body.success ? 1 : 0) : null, typeof body.exit_code === 'number' ? body.exit_code : null, typeof body.score === 'number' ? body.score : null, body.content_type ? String(body.content_type).slice(0, 64) : null, body.version ? String(body.version).slice(0, 32) : null, body.platform ? String(body.platform).slice(0, 32) : null, body.nodeVersion ? String(body.nodeVersion).slice(0, 32) : null, body.run_count_bucket ? String(body.run_count_bucket).slice(0, 10) : null, body.tier ? String(body.tier).slice(0, 32) : null, typeof body.run_count === 'number' ? body.run_count : null);
|
|
49
49
|
}
|
|
50
50
|
catch {
|
|
51
51
|
// never fail — telemetry is non-critical
|
package/package.json
CHANGED