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 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.
180
206
 
181
- **[GitHub Discussions](https://github.com/Content-Grade/Content-Grade/discussions)** ask questions, share setups, vote on what gets built next.
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
- 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
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
  [![GitHub Stars](https://img.shields.io/github/stars/StanislavBG/Content-Grade?style=social)](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? Have a use case?** [Open an issue](https://github.com/StanislavBG/Content-Grade/issues/new/choose) or [start a discussion](https://github.com/StanislavBG/Content-Grade/discussions/new) — every report shapes what gets built next.
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
 
@@ -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 (_demoMode) {
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
 
@@ -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.33",
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": {