content-grade 1.0.6 → 1.0.8
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 +203 -7
- package/bin/content-grade.js +100 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,12 +31,40 @@ npx content-grade headline "Why Most Startups Fail at Month 18"
|
|
|
31
31
|
|
|
32
32
|
**Free tier:** 50 analyses/day. No signup. `npx content-grade grade README.md` works immediately.
|
|
33
33
|
|
|
34
|
+
**Install globally** (recommended — skips the `npx` download on every run):
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install -g content-grade
|
|
38
|
+
content-grade --help
|
|
39
|
+
```
|
|
40
|
+
|
|
34
41
|
```bash
|
|
35
42
|
# More commands
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
43
|
+
content-grade https://yourblog.com/post # analyze any URL
|
|
44
|
+
content-grade analyze ./post.md --json # raw JSON for CI pipelines
|
|
45
|
+
content-grade analyze ./post.md --quiet # score number only (for scripts)
|
|
46
|
+
content-grade activate # unlock Pro: batch mode, 100/day
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
---
|
|
50
|
+
|
|
51
|
+
## Usage Examples
|
|
52
|
+
|
|
53
|
+
**Grade a single file:**
|
|
54
|
+
```bash
|
|
55
|
+
npx content-grade article.md
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Grade multiple files with a glob pattern:**
|
|
59
|
+
```bash
|
|
60
|
+
npx content-grade 'blog/**/*.md'
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
**JSON output for CI integration:**
|
|
64
|
+
```bash
|
|
65
|
+
npx content-grade README.md --format json
|
|
66
|
+
# Returns structured JSON with score, grade, dimensions, and improvements
|
|
67
|
+
# Exit code 1 if score < 50 — useful for blocking low-quality merges
|
|
40
68
|
```
|
|
41
69
|
|
|
42
70
|
---
|
|
@@ -107,6 +135,7 @@ npx content-grade activate # unlock Pro: batch mode, 100/day
|
|
|
107
135
|
| `init` | First-run setup: verify Claude CLI, run smoke test |
|
|
108
136
|
| `start` | Launch the full web dashboard (6 tools) |
|
|
109
137
|
| `telemetry [on\|off]` | View or toggle anonymous usage tracking |
|
|
138
|
+
| `check-updates` | Check if a newer version is available on npm |
|
|
110
139
|
| `help` | Full usage and examples |
|
|
111
140
|
|
|
112
141
|
**Flags:**
|
|
@@ -116,6 +145,7 @@ npx content-grade activate # unlock Pro: batch mode, 100/day
|
|
|
116
145
|
| `--json` | Raw JSON output — pipe to `jq` for CI integration |
|
|
117
146
|
| `--quiet` | Score number only — for shell scripts |
|
|
118
147
|
| `--version` | Print version |
|
|
148
|
+
| `--verbose` | Show debug info (model, timing, Claude response length) |
|
|
119
149
|
| `--no-telemetry` | Skip usage tracking for this run |
|
|
120
150
|
|
|
121
151
|
**Global flags:**
|
|
@@ -141,7 +171,20 @@ Launch with `content-grade start` — opens at [http://localhost:4000](http://lo
|
|
|
141
171
|
| **EmailForge** | `/email-forge` | Subject line + body copy for click-through optimization |
|
|
142
172
|
| **AudienceDecoder** | `/audience` | Twitter handle → audience archetypes and content patterns |
|
|
143
173
|
|
|
144
|
-
Free tier: **50 analyses/day per tool**. Pro ($
|
|
174
|
+
Free tier: **50 analyses/day per tool**. Pro ($9/mo): **100 analyses/day** + all tools.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Join the Discussion
|
|
179
|
+
|
|
180
|
+
**[GitHub Discussions](https://github.com/Content-Grade/Content-Grade/discussions)** — ask questions, share setups, vote on what gets built next.
|
|
181
|
+
|
|
182
|
+
Active threads:
|
|
183
|
+
- [What content quality checks matter most to you?](https://github.com/Content-Grade/Content-Grade/discussions/1) — General
|
|
184
|
+
- [Show your ContentGrade setup — configs, workflows, CI integrations](https://github.com/Content-Grade/Content-Grade/discussions/2) — Show & Tell
|
|
185
|
+
- [Feature requests & roadmap input](https://github.com/Content-Grade/Content-Grade/discussions/3) — Ideas
|
|
186
|
+
- [Content scoring in CI — who is actually doing this?](https://github.com/Content-Grade/Content-Grade/discussions/4) — General
|
|
187
|
+
- [What integrations would make ContentGrade essential to your workflow?](https://github.com/Content-Grade/Content-Grade/discussions/5) — Ideas
|
|
145
188
|
|
|
146
189
|
---
|
|
147
190
|
|
|
@@ -160,6 +203,112 @@ If this fails, run `npx content-grade init` for step-by-step diagnostics.
|
|
|
160
203
|
|
|
161
204
|
---
|
|
162
205
|
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Troubleshooting
|
|
209
|
+
|
|
210
|
+
### `Error: Claude CLI not found`
|
|
211
|
+
|
|
212
|
+
ContentGrade requires Claude CLI. Install it:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
npm install -g @anthropic-ai/claude-code
|
|
216
|
+
claude login
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Then verify it works:
|
|
220
|
+
|
|
221
|
+
```bash
|
|
222
|
+
claude -p "say hi"
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
If Claude is installed but not on your PATH, set `CLAUDE_PATH`:
|
|
226
|
+
|
|
227
|
+
```bash
|
|
228
|
+
CLAUDE_PATH=/path/to/claude content-grade analyze ./post.md
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
Or add it permanently to your shell profile:
|
|
232
|
+
|
|
233
|
+
```bash
|
|
234
|
+
export CLAUDE_PATH=/path/to/claude
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
### Analysis times out or hangs
|
|
240
|
+
|
|
241
|
+
Claude CLI calls can take 10–60 seconds on first run. If it hangs beyond 2 minutes:
|
|
242
|
+
|
|
243
|
+
1. Test Claude directly: `claude -p "say hi"` — if this hangs too, Claude CLI is the issue
|
|
244
|
+
2. Check your internet connection (Claude CLI phones home on first use)
|
|
245
|
+
3. Run `content-grade init` for a guided diagnostic
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
### `Could not parse response as JSON`
|
|
250
|
+
|
|
251
|
+
This means Claude returned a non-JSON response. Common causes:
|
|
252
|
+
|
|
253
|
+
- Claude CLI rate-limited your account (try again in a few minutes)
|
|
254
|
+
- The file content was too short or unrecognizable as text content
|
|
255
|
+
- Claude returned an error message instead of scoring output
|
|
256
|
+
|
|
257
|
+
Run with `--verbose` to see the raw response:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
content-grade analyze ./post.md --verbose
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
### Daily limit reached
|
|
266
|
+
|
|
267
|
+
Free tier: 50 CLI analyses/day, resets at midnight UTC.
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
✗ Daily limit reached (50/50 free checks used today).
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
Options:
|
|
274
|
+
- Wait until midnight UTC for the limit to reset
|
|
275
|
+
- Unlock 100/day with Pro: `content-grade activate`
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
### `content-grade: command not found` after global install
|
|
280
|
+
|
|
281
|
+
If `npm install -g content-grade` succeeded but the command isn't found:
|
|
282
|
+
|
|
283
|
+
```bash
|
|
284
|
+
# Find where npm puts global binaries
|
|
285
|
+
npm bin -g
|
|
286
|
+
|
|
287
|
+
# Add to PATH — for bash/zsh, add this to ~/.bashrc or ~/.zshrc:
|
|
288
|
+
export PATH="$(npm bin -g):$PATH"
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
---
|
|
292
|
+
|
|
293
|
+
### `activate` says "Could not reach server"
|
|
294
|
+
|
|
295
|
+
The activation server may be temporarily unreachable. The key is stored locally without online verification — this is normal and your Pro access will still work for CLI tools. Re-run `content-grade activate` to re-verify once the server is back.
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
### Something else is broken
|
|
300
|
+
|
|
301
|
+
Run the built-in diagnostic:
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
content-grade init
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
This checks: Claude CLI on PATH, live smoke test, production build status. It will tell you exactly what's wrong.
|
|
308
|
+
|
|
309
|
+
Still stuck? Open an issue: [github.com/Content-Grade/Content-Grade/issues](https://github.com/Content-Grade/Content-Grade/issues)
|
|
310
|
+
|
|
311
|
+
|
|
163
312
|
## CLI reference
|
|
164
313
|
|
|
165
314
|
### `analyze` / `check`
|
|
@@ -291,6 +440,53 @@ Scans up to 2 directory levels deep for `.md`, `.txt`, and `.mdx` files. Picks t
|
|
|
291
440
|
|
|
292
441
|
---
|
|
293
442
|
|
|
443
|
+
---
|
|
444
|
+
|
|
445
|
+
### `check-updates`
|
|
446
|
+
|
|
447
|
+
```bash
|
|
448
|
+
content-grade check-updates
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
Checks npm for the latest published version of `content-grade` and compares it against the installed version. If an update is available, prints the upgrade command.
|
|
452
|
+
|
|
453
|
+
```bash
|
|
454
|
+
⚠ Update available: v1.0.4 → v1.0.5
|
|
455
|
+
Update with:
|
|
456
|
+
npm install -g content-grade (global install)
|
|
457
|
+
npm install content-grade@latest (local install)
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
---
|
|
461
|
+
|
|
462
|
+
### `activate`
|
|
463
|
+
|
|
464
|
+
```bash
|
|
465
|
+
content-grade activate
|
|
466
|
+
content-grade activate <your-license-key>
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
Unlocks Pro features by storing your license key locally. Can be run interactively (prompts for the key) or non-interactively by passing the key as an argument — useful for automated setups:
|
|
470
|
+
|
|
471
|
+
```bash
|
|
472
|
+
# Interactive
|
|
473
|
+
content-grade activate
|
|
474
|
+
|
|
475
|
+
# Non-interactive (CI/CD or scripted setup)
|
|
476
|
+
CONTENT_GRADE_KEY=your-key-here content-grade analyze ./post.md
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
The key is validated against the server and saved to `~/.config/content-grade/config.json`. If the server is unreachable (corporate proxy, offline), the key is stored locally anyway with a warning.
|
|
480
|
+
|
|
481
|
+
**Pro unlocks:**
|
|
482
|
+
- 100 analyses/day (vs 50 free)
|
|
483
|
+
- `content-grade batch <dir>` — analyze entire directories at once
|
|
484
|
+
- A/B headline comparison (web dashboard)
|
|
485
|
+
- Landing page URL audit, ad copy scoring, email optimizer
|
|
486
|
+
|
|
487
|
+
Get a license: [content-grade.github.io/Content-Grade/](https://content-grade.github.io/Content-Grade/)
|
|
488
|
+
|
|
489
|
+
|
|
294
490
|
## Configuration
|
|
295
491
|
|
|
296
492
|
All configuration is through environment variables — no config file needed.
|
|
@@ -716,7 +912,7 @@ echo "$DATE,$HEADLINE,$SCORE" >> headline-scores.csv
|
|
|
716
912
|
|
|
717
913
|
| | Free | Pro |
|
|
718
914
|
|-|------|-----|
|
|
719
|
-
| Price | Free forever | $
|
|
915
|
+
| Price | Free forever | $9/month |
|
|
720
916
|
| Analyses/day (CLI) | Unlimited | Unlimited |
|
|
721
917
|
| Analyses/day (web dashboard, per tool) | 3 | 100 |
|
|
722
918
|
| HeadlineGrader (single grade) | ✓ | ✓ |
|
|
@@ -761,7 +957,7 @@ ContentGrade is built in public. The community is the roadmap.
|
|
|
761
957
|
|
|
762
958
|
**[EARLY_ADOPTERS.md](EARLY_ADOPTERS.md)** — The first 50 users who install and share feedback get:
|
|
763
959
|
|
|
764
|
-
- **Pro tier free for 12 months** ($
|
|
960
|
+
- **Pro tier free for 12 months** ($9/mo value)
|
|
765
961
|
- Direct feedback channel with the maintainer
|
|
766
962
|
- Name in CONTRIBUTORS.md (permanent)
|
|
767
963
|
- Roadmap preview + design input before features ship
|
package/bin/content-grade.js
CHANGED
|
@@ -90,6 +90,17 @@ const MG = '\x1b[35m';
|
|
|
90
90
|
const BL = '\x1b[34m';
|
|
91
91
|
const WH = '\x1b[97m';
|
|
92
92
|
|
|
93
|
+
// ── Input sanitization ────────────────────────────────────────────────────────
|
|
94
|
+
|
|
95
|
+
// Reject null bytes (path traversal vector) and trim surrounding whitespace.
|
|
96
|
+
// Returns sanitized string, or null if the input is invalid.
|
|
97
|
+
function sanitizeFilePath(p) {
|
|
98
|
+
if (typeof p !== 'string') return null;
|
|
99
|
+
if (p.includes('\0')) return null;
|
|
100
|
+
const trimmed = p.trim();
|
|
101
|
+
return trimmed || null;
|
|
102
|
+
}
|
|
103
|
+
|
|
93
104
|
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
94
105
|
|
|
95
106
|
function ok(msg) { console.log(` ${GN}✓${R} ${msg}`); }
|
|
@@ -257,6 +268,15 @@ async function cmdAnalyze(filePath) {
|
|
|
257
268
|
process.exit(2);
|
|
258
269
|
}
|
|
259
270
|
|
|
271
|
+
const safeFilePath = sanitizeFilePath(filePath);
|
|
272
|
+
if (!safeFilePath) {
|
|
273
|
+
blank();
|
|
274
|
+
fail(`Invalid file path.`);
|
|
275
|
+
blank();
|
|
276
|
+
process.exit(2);
|
|
277
|
+
}
|
|
278
|
+
filePath = safeFilePath;
|
|
279
|
+
|
|
260
280
|
const absPath = resolve(process.cwd(), filePath);
|
|
261
281
|
if (!existsSync(absPath)) {
|
|
262
282
|
blank();
|
|
@@ -320,7 +340,7 @@ async function cmdAnalyze(filePath) {
|
|
|
320
340
|
console.log(` ${D}· Wait until tomorrow (limit resets at midnight)${R}`);
|
|
321
341
|
console.log(` ${D}· Unlock 100 checks/day: ${CY}content-grade activate${R}`);
|
|
322
342
|
blank();
|
|
323
|
-
console.log(` ${MG}
|
|
343
|
+
console.log(` ${MG}Upgrade to Pro ($9/mo) for unlimited analyses →${R} ${CY}https://content-grade.github.io/Content-Grade/${R}`);
|
|
324
344
|
blank();
|
|
325
345
|
process.exit(1);
|
|
326
346
|
}
|
|
@@ -476,8 +496,7 @@ async function cmdAnalyze(filePath) {
|
|
|
476
496
|
console.log(` ${D} · 100 checks/day (${remaining} remaining today on free tier)${R}`);
|
|
477
497
|
console.log(` ${D} · Get a license at ${CY}content-grade.github.io/Content-Grade${R}`);
|
|
478
498
|
blank();
|
|
479
|
-
console.log(` ${MG}
|
|
480
|
-
console.log(` ${D}⭐ Unlock deeper analysis →${R} ${CY}npm i content-grade && content-grade activate${R} ${D}| content-grade.github.io/Content-Grade/${R}`);
|
|
499
|
+
console.log(` ${MG}Upgrade to Pro ($9/mo) for unlimited analyses →${R} ${CY}https://content-grade.github.io/Content-Grade/${R}`);
|
|
481
500
|
}
|
|
482
501
|
blank();
|
|
483
502
|
}
|
|
@@ -545,7 +564,7 @@ async function cmdHeadline(text) {
|
|
|
545
564
|
console.log(` ${D}· Wait until tomorrow (limit resets at midnight)${R}`);
|
|
546
565
|
console.log(` ${D}· Unlock 100 checks/day: ${CY}content-grade activate${R}`);
|
|
547
566
|
blank();
|
|
548
|
-
console.log(` ${MG}
|
|
567
|
+
console.log(` ${MG}Upgrade to Pro ($9/mo) for unlimited analyses →${R} ${CY}https://content-grade.github.io/Content-Grade/${R}`);
|
|
549
568
|
blank();
|
|
550
569
|
process.exit(1);
|
|
551
570
|
}
|
|
@@ -625,8 +644,7 @@ async function cmdHeadline(text) {
|
|
|
625
644
|
console.log(` ${D}Compare two headlines: ${CY}content-grade start${R} → HeadlineGrader compare${R}`);
|
|
626
645
|
blank();
|
|
627
646
|
if (!isProUser()) {
|
|
628
|
-
console.log(` ${MG}
|
|
629
|
-
console.log(` ${D}⭐ Unlock deeper analysis →${R} ${CY}npm i content-grade && content-grade activate${R} ${D}| content-grade.github.io/Content-Grade/${R}`);
|
|
647
|
+
console.log(` ${MG}Upgrade to Pro ($9/mo) for unlimited analyses →${R} ${CY}https://content-grade.github.io/Content-Grade/${R}`);
|
|
630
648
|
blank();
|
|
631
649
|
}
|
|
632
650
|
}
|
|
@@ -858,6 +876,15 @@ async function cmdBatch(dirPath) {
|
|
|
858
876
|
process.exit(2);
|
|
859
877
|
}
|
|
860
878
|
|
|
879
|
+
const safeDirPath = sanitizeFilePath(dirPath);
|
|
880
|
+
if (!safeDirPath) {
|
|
881
|
+
blank();
|
|
882
|
+
fail(`Invalid directory path.`);
|
|
883
|
+
blank();
|
|
884
|
+
process.exit(2);
|
|
885
|
+
}
|
|
886
|
+
dirPath = safeDirPath;
|
|
887
|
+
|
|
861
888
|
const absDir = resolve(process.cwd(), dirPath);
|
|
862
889
|
if (!existsSync(absDir)) {
|
|
863
890
|
blank();
|
|
@@ -1084,6 +1111,45 @@ function cmdStart() {
|
|
|
1084
1111
|
});
|
|
1085
1112
|
}
|
|
1086
1113
|
|
|
1114
|
+
|
|
1115
|
+
// ── Check-updates command ─────────────────────────────────────────────────────
|
|
1116
|
+
|
|
1117
|
+
async function cmdCheckUpdates() {
|
|
1118
|
+
blank();
|
|
1119
|
+
console.log(` ${B}Checking for updates...${R}`);
|
|
1120
|
+
blank();
|
|
1121
|
+
try {
|
|
1122
|
+
const res = await new Promise((resolve, reject) => {
|
|
1123
|
+
const req = httpsGet('https://registry.npmjs.org/content-grade/latest', {
|
|
1124
|
+
headers: { 'User-Agent': 'content-grade-cli' },
|
|
1125
|
+
timeout: 8000,
|
|
1126
|
+
}, (r) => {
|
|
1127
|
+
let data = '';
|
|
1128
|
+
r.on('data', chunk => { data += chunk; });
|
|
1129
|
+
r.on('end', () => resolve(data));
|
|
1130
|
+
});
|
|
1131
|
+
req.on('error', reject);
|
|
1132
|
+
req.on('timeout', () => { req.destroy(); reject(new Error('timeout')); });
|
|
1133
|
+
});
|
|
1134
|
+
const { version: latest } = JSON.parse(res);
|
|
1135
|
+
if (latest === _version) {
|
|
1136
|
+
ok(`You're up to date — v${_version} is the latest.`);
|
|
1137
|
+
} else {
|
|
1138
|
+
warn(`Update available: ${RD}v${_version}${R} → ${GN}v${latest}${R}`);
|
|
1139
|
+
blank();
|
|
1140
|
+
console.log(` ${B}Update with:${R}`);
|
|
1141
|
+
console.log(` ${CY}npm install -g content-grade${R} ${D}(global install)${R}`);
|
|
1142
|
+
console.log(` ${CY}npm install content-grade@latest${R} ${D}(local install)${R}`);
|
|
1143
|
+
}
|
|
1144
|
+
} catch {
|
|
1145
|
+
warn('Could not reach npm registry. Check your internet connection.');
|
|
1146
|
+
blank();
|
|
1147
|
+
console.log(` ${D}Current version: v${_version}${R}`);
|
|
1148
|
+
console.log(` ${D}Check manually: ${CY}https://www.npmjs.com/package/content-grade${R}`);
|
|
1149
|
+
}
|
|
1150
|
+
blank();
|
|
1151
|
+
}
|
|
1152
|
+
|
|
1087
1153
|
// ── Help ──────────────────────────────────────────────────────────────────────
|
|
1088
1154
|
|
|
1089
1155
|
function cmdHelp() {
|
|
@@ -1100,7 +1166,8 @@ function cmdHelp() {
|
|
|
1100
1166
|
|
|
1101
1167
|
console.log(` ${B}USAGE${R}`);
|
|
1102
1168
|
blank();
|
|
1103
|
-
console.log(` ${CY}content-grade <command> [args]${R}`);
|
|
1169
|
+
console.log(` ${CY}npx content-grade <command> [args]${R} ${D}# zero install${R}`);
|
|
1170
|
+
console.log(` ${CY}content-grade <command> [args]${R} ${D}# after: npm install -g content-grade${R}`);
|
|
1104
1171
|
blank();
|
|
1105
1172
|
|
|
1106
1173
|
console.log(` ${B}COMMANDS${R}`);
|
|
@@ -1122,10 +1189,12 @@ function cmdHelp() {
|
|
|
1122
1189
|
console.log(` ${CY}start${R} Launch the full web dashboard`);
|
|
1123
1190
|
console.log(` ${D} 6 tools: headlines, pages, ads, threads, emails, audiences${R}`);
|
|
1124
1191
|
blank();
|
|
1125
|
-
console.log(` ${CY}init${R}
|
|
1192
|
+
console.log(` ${CY}init${R} Run if Claude CLI isn't detected or tools aren't working`);
|
|
1126
1193
|
blank();
|
|
1127
1194
|
console.log(` ${CY}telemetry [on|off]${R} View or toggle anonymous usage tracking`);
|
|
1128
1195
|
blank();
|
|
1196
|
+
console.log(` ${CY}check-updates${R} Check if a newer version is available on npm`);
|
|
1197
|
+
blank();
|
|
1129
1198
|
console.log(` ${CY}help${R} Show this help`);
|
|
1130
1199
|
blank();
|
|
1131
1200
|
console.log(` ${B}FLAGS${R}`);
|
|
@@ -1167,7 +1236,7 @@ function cmdHelp() {
|
|
|
1167
1236
|
console.log(` · Node.js 18+`);
|
|
1168
1237
|
blank();
|
|
1169
1238
|
|
|
1170
|
-
console.log(` ${B}PRO TIER${R} ${MG}$9/
|
|
1239
|
+
console.log(` ${B}PRO TIER${R} ${MG}$9/mo${R}`);
|
|
1171
1240
|
blank();
|
|
1172
1241
|
console.log(` · 100 analyses/day (vs 50 free)`);
|
|
1173
1242
|
console.log(` · Competitor headline A/B comparison`);
|
|
@@ -1176,7 +1245,8 @@ function cmdHelp() {
|
|
|
1176
1245
|
console.log(` · Email subject line optimizer`);
|
|
1177
1246
|
console.log(` · Audience archetype decoder`);
|
|
1178
1247
|
blank();
|
|
1179
|
-
console.log(` ${CY}content-grade.github.io/Content-Grade${R}
|
|
1248
|
+
console.log(` Purchase at: ${CY}content-grade.github.io/Content-Grade${R}`);
|
|
1249
|
+
console.log(` Then unlock: ${CY}content-grade activate <your-license-key>${R}`);
|
|
1180
1250
|
blank();
|
|
1181
1251
|
}
|
|
1182
1252
|
|
|
@@ -1398,9 +1468,15 @@ function fetchUrl(url) {
|
|
|
1398
1468
|
return new Promise((resolve, reject) => {
|
|
1399
1469
|
const get = url.startsWith('https') ? httpsGet : httpGet;
|
|
1400
1470
|
const req = get(url, { headers: { 'User-Agent': 'ContentGrade/1.0 (+https://github.com/content-grade/Content-Grade)' }, timeout: 15000 }, (res) => {
|
|
1401
|
-
// Follow one redirect
|
|
1471
|
+
// Follow one redirect — validate location is http/https before following
|
|
1402
1472
|
if ((res.statusCode === 301 || res.statusCode === 302) && res.headers.location) {
|
|
1403
|
-
|
|
1473
|
+
const loc = res.headers.location;
|
|
1474
|
+
if (!/^https?:\/\//i.test(loc)) {
|
|
1475
|
+
reject(new Error(`Redirect to non-HTTP location rejected: ${loc.slice(0, 80)}`));
|
|
1476
|
+
res.resume();
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
fetchUrl(loc).then(resolve).catch(reject);
|
|
1404
1480
|
res.resume();
|
|
1405
1481
|
return;
|
|
1406
1482
|
}
|
|
@@ -1650,6 +1726,18 @@ switch (cmd) {
|
|
|
1650
1726
|
cmdHelp();
|
|
1651
1727
|
break;
|
|
1652
1728
|
|
|
1729
|
+
case 'check-updates':
|
|
1730
|
+
case 'update':
|
|
1731
|
+
case 'upgrade-check':
|
|
1732
|
+
recordEvent({ event: 'command', command: 'check-updates' });
|
|
1733
|
+
cmdCheckUpdates().catch(err => {
|
|
1734
|
+
blank();
|
|
1735
|
+
fail(`Update check error: ${err.message}`);
|
|
1736
|
+
blank();
|
|
1737
|
+
process.exit(1);
|
|
1738
|
+
});
|
|
1739
|
+
break;
|
|
1740
|
+
|
|
1653
1741
|
case 'version':
|
|
1654
1742
|
case '--version':
|
|
1655
1743
|
case '-v':
|
package/package.json
CHANGED