content-grade 1.0.34 → 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 +38 -10
- package/bin/content-grade.js +8 -0
- 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.
|
|
206
|
+
|
|
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 |
|
|
180
213
|
|
|
181
|
-
**[
|
|
214
|
+
**[All discussions →](https://github.com/StanislavBG/Content-Grade/discussions)**
|
|
182
215
|
|
|
183
|
-
|
|
184
|
-
- [What content quality checks matter most to you?](https://github.com/Content-Grade/Content-Grade/discussions/1) — General
|
|
185
|
-
- [Show your ContentGrade setup — configs, workflows, CI integrations](https://github.com/Content-Grade/Content-Grade/discussions/2) — Show & Tell
|
|
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
|
|
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
|
|
package/bin/content-grade.js
CHANGED
|
@@ -255,6 +255,7 @@ function checkFreeTierLimit() {
|
|
|
255
255
|
|
|
256
256
|
// Track funnel event — user hit the limit and saw the upgrade prompt
|
|
257
257
|
recordEvent({ event: 'free_limit_hit', version: _version });
|
|
258
|
+
recordEvent({ event: 'upgrade_prompt_shown', run_count: usage.count });
|
|
258
259
|
|
|
259
260
|
blank();
|
|
260
261
|
hr();
|
|
@@ -1084,6 +1085,8 @@ async function cmdActivate() {
|
|
|
1084
1085
|
config.activatedAt = new Date().toISOString();
|
|
1085
1086
|
saveConfig(config);
|
|
1086
1087
|
|
|
1088
|
+
recordEvent({ event: 'license_activated', tier: activatedTier });
|
|
1089
|
+
|
|
1087
1090
|
// Also write canonical license file to ~/.content-grade/license.json
|
|
1088
1091
|
mkdirSync(LICENSE_DIR, { recursive: true });
|
|
1089
1092
|
writeFileSync(LICENSE_FILE, JSON.stringify({
|
|
@@ -2128,6 +2131,11 @@ const _telem = initTelemetry();
|
|
|
2128
2131
|
// Defer first-run telemetry notice so users see value first
|
|
2129
2132
|
const _showTelemNotice = _telem.isNew;
|
|
2130
2133
|
|
|
2134
|
+
// Fire install event exactly once — on the very first run
|
|
2135
|
+
if (_telem.isNew) {
|
|
2136
|
+
recordEvent({ event: 'install' });
|
|
2137
|
+
}
|
|
2138
|
+
|
|
2131
2139
|
// ── License key startup validation ───────────────────────────────────────────
|
|
2132
2140
|
// Background check: re-validate stored license key against server every 7 days.
|
|
2133
2141
|
// Non-blocking — never delays the CLI. Clears invalid/revoked keys silently.
|
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