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 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:** 50 analyses/day. No signup. `npx content-grade grade README.md` works immediately.
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: **50 analyses/day per tool**. Pro ($19/mo): **100 analyses/day** + all tools.
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
- ## Join the Discussion
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
- **[GitHub Discussions](https://github.com/Content-Grade/Content-Grade/discussions)** — ask questions, share setups, vote on what gets built next.
214
+ **[All discussions →](https://github.com/StanislavBG/Content-Grade/discussions)**
182
215
 
183
- Active threads:
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
 
@@ -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
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "content-grade",
3
- "version": "1.0.34",
3
+ "version": "1.0.35",
4
4
  "description": "AI-powered content analysis CLI. Score any blog post, landing page, or ad copy in under 30 seconds — runs on Claude CLI, no API key needed.",
5
5
  "type": "module",
6
6
  "bin": {