@friedbotstudio/create-baseline 0.1.0 → 0.2.1
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 +5 -0
- package/bin/cli.js +8 -2
- package/obj/template/.claude/skills/audit-baseline/audit.sh +11 -5
- package/obj/template/.claude/skills/google-analytics/SKILL.md +129 -0
- package/obj/template/.claude/skills/google-analytics/references/audiences.md +389 -0
- package/obj/template/.claude/skills/google-analytics/references/bigquery.md +470 -0
- package/obj/template/.claude/skills/google-analytics/references/custom-dimensions.md +355 -0
- package/obj/template/.claude/skills/google-analytics/references/custom-events.md +383 -0
- package/obj/template/.claude/skills/google-analytics/references/data-management.md +416 -0
- package/obj/template/.claude/skills/google-analytics/references/debugview.md +364 -0
- package/obj/template/.claude/skills/google-analytics/references/events-fundamentals.md +398 -0
- package/obj/template/.claude/skills/google-analytics/references/gtag.md +502 -0
- package/obj/template/.claude/skills/google-analytics/references/gtm-integration.md +483 -0
- package/obj/template/.claude/skills/google-analytics/references/measurement-protocol.md +519 -0
- package/obj/template/.claude/skills/google-analytics/references/privacy.md +441 -0
- package/obj/template/.claude/skills/google-analytics/references/recommended-events.md +464 -0
- package/obj/template/.claude/skills/google-analytics/references/reporting.md +397 -0
- package/obj/template/.claude/skills/google-analytics/references/setup.md +344 -0
- package/obj/template/.claude/skills/google-analytics/references/user-tracking.md +417 -0
- package/obj/template/.claude/skills/optimize-seo/SKILL.md +313 -0
- package/obj/template/.claude/skills/optimize-seo/scripts/pagespeed.mjs +197 -0
- package/obj/template/.claude/skills/pagespeed-insights/LICENSE.md +37 -0
- package/obj/template/.claude/skills/pagespeed-insights/SKILL.md +446 -0
- package/obj/template/.claude/skills/pagespeed-insights/reference.md +50 -0
- package/obj/template/CLAUDE.md +3 -3
- package/obj/template/docs/init/seed.md +2 -2
- package/obj/template/manifest.json +27 -6
- package/package.json +7 -2
- package/src/CLAUDE.template.md +3 -3
- package/src/cli/install.js +14 -4
- package/src/seed.template.md +2 -2
- package/obj/template/.claude/hooks/lib/__pycache__/resume_writer.cpython-314.pyc +0 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: optimize-seo
|
|
3
|
+
description: "SEO + performance optimization orchestrator for the Friedbot Studio website. Drives a repeatable measure → diagnose → scope → fix → verify → commit loop. Use when performance scores drop, a PageSpeed audit surfaces issues, or before major traffic events. Trigger on: 'optimize the site', 'run a perf audit', 'fix pagespeed issues', 'improve core web vitals', 'check site performance'."
|
|
4
|
+
metadata:
|
|
5
|
+
version: 1.0.0
|
|
6
|
+
disable-model-invocation: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Optimize SEO — Performance & Accessibility Workflow
|
|
10
|
+
|
|
11
|
+
You are the SEO and performance optimization orchestrator. Your job is to take the site from a baseline measurement through diagnosis, targeted fixes, and verification — with repeatability and no guesswork.
|
|
12
|
+
|
|
13
|
+
This is the **parallel orchestrator to `/orchestrate`**. Where `/orchestrate` delivers new features (requirements → design → copy → build), `/optimize-seo` improves existing features (measure → diagnose → fix → verify).
|
|
14
|
+
|
|
15
|
+
## Workflow Pipeline
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
|
19
|
+
│ 1. MEASURE │───▶│ 2. DIAGNOSE │───▶│ 3. SCOPE │
|
|
20
|
+
│ PageSpeed │ │ Root causes │ │ Fix plan │
|
|
21
|
+
└─────────────┘ └─────────────┘ └─────────────┘
|
|
22
|
+
│
|
|
23
|
+
┌─────────────┐ ┌─────────────┐ ┌──────▼──────┐
|
|
24
|
+
│ 6. COMMIT │◀───│ 5. VERIFY │◀───│ 4. FIX │
|
|
25
|
+
│ Ship it │ │ Re-measure │ │ Apply fixes │
|
|
26
|
+
└─────────────┘ └─────────────┘ └─────────────┘
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
**Never skip phases.** Never fix before diagnosing. Never ship without re-measuring.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Task-Based Execution (mandatory)
|
|
34
|
+
|
|
35
|
+
At the start of every optimization cycle, create ALL tasks upfront using TaskCreate. Execute them strictly in order. Do not start a task until the previous one is marked complete.
|
|
36
|
+
|
|
37
|
+
### Task List Template
|
|
38
|
+
|
|
39
|
+
Create these tasks when starting a new cycle (replace `[scope]` with e.g. `home-page`, `site-wide`):
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
1. [Measure] Create progress tracker at docs/progress/optimize-[scope].md
|
|
43
|
+
2. [Measure] Run pagespeed.mjs for mobile + desktop, capture baseline scores
|
|
44
|
+
3. [Measure] Run Playwright MCP to capture baseline screenshots (desktop 1280, tablet 768, mobile 375)
|
|
45
|
+
4. [Diagnose] Invoke /pagespeed-insights to interpret the report and identify root causes
|
|
46
|
+
5. [Diagnose] Invoke /nextjs-performance for framework-specific optimizations
|
|
47
|
+
6. [Diagnose] Invoke /web-design-guidelines for accessibility audits
|
|
48
|
+
7. [Diagnose] Cross-reference diagnosis against the codebase (Grep, Read) to confirm root causes
|
|
49
|
+
8. [Scope] Draft fix plan in the progress tracker — batched by impact, with acceptance criteria
|
|
50
|
+
9. [Scope] Get user approval on scope and batch order
|
|
51
|
+
10. [Fix] Execute fix batches in order — each batch pauses at a visual verification gate
|
|
52
|
+
11. [Verify] Re-run pagespeed.mjs for mobile + desktop, diff against baseline
|
|
53
|
+
12. [Verify] Re-run Playwright screenshots, compare against baseline — no visual regressions
|
|
54
|
+
13. [Verify] Invoke /simplify-code on all changed files
|
|
55
|
+
14. [Verify] Present before/after report to user, confirm acceptance criteria met
|
|
56
|
+
15. [Commit] Invoke /plan-commits
|
|
57
|
+
16. [Commit] Finalize progress tracker — mark all phases Done with completion dates
|
|
58
|
+
17. [Commit] Execute commits — REQUIRES USER APPROVAL (destructive action)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Execution Rules
|
|
62
|
+
|
|
63
|
+
- **Mark each task complete immediately** after finishing it. Do not batch completions.
|
|
64
|
+
- **Do not skip tasks.** If a task is not applicable (e.g., no visual changes = skip screenshot diff), mark it complete with a note explaining why.
|
|
65
|
+
- **Task 1 creates the progress tracker**: Write `docs/progress/optimize-[scope].md` with all phases set to Pending. Update Measure to In Progress immediately.
|
|
66
|
+
- **Task 10 (Fix) is a parent task**: During scope (Task 8), break the fix plan into batches. For each batch, add child tasks dynamically via TaskCreate. Each batch must visually verify via Playwright before moving on.
|
|
67
|
+
- **Task 9 is a gate**: Present the scope document to the user and pause. Do not proceed until user approves the fix plan.
|
|
68
|
+
- **Task 14 is a gate**: Present the before/after report. Do not commit unless targets are met.
|
|
69
|
+
- **Task 17 MUST get explicit user approval** via AskUserQuestion before executing. This is destructive (git commits).
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Phase Details
|
|
74
|
+
|
|
75
|
+
### Phase 1: MEASURE
|
|
76
|
+
**Tasks**: 1-3
|
|
77
|
+
|
|
78
|
+
**Goal**: Establish a factual baseline. No guessing what's slow — measure it.
|
|
79
|
+
|
|
80
|
+
**Actions**:
|
|
81
|
+
|
|
82
|
+
1. Create the progress tracker file at `docs/progress/optimize-[scope].md` using the template in the Progress Tracking section. Set Measure to In Progress.
|
|
83
|
+
2. Run `node .claude/skills/optimize-seo/scripts/pagespeed.mjs --url=https://friedbotstudio.com --output=/tmp/psi-baseline.json`. Capture scores and failing audits for both mobile and desktop strategies.
|
|
84
|
+
3. Use **Playwright MCP** to capture screenshots at desktop (1280px), tablet (768px), and mobile (375px) widths. Save to `/tmp/screenshots-baseline/`.
|
|
85
|
+
|
|
86
|
+
**Required inputs**:
|
|
87
|
+
- `G_PAGESPEED_KEY` in `.env.local` (API key for PageSpeed Insights)
|
|
88
|
+
- Deployed live URL (default: `https://friedbotstudio.com`)
|
|
89
|
+
|
|
90
|
+
**Output**: Baseline scores + screenshots recorded in the progress tracker
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
### Phase 2: DIAGNOSE
|
|
95
|
+
**Tasks**: 4-7
|
|
96
|
+
|
|
97
|
+
**Goal**: Understand the root cause of every failing audit. Not the symptom — the cause.
|
|
98
|
+
|
|
99
|
+
**Actions**:
|
|
100
|
+
|
|
101
|
+
1. **(Task 4)** Invoke `/pagespeed-insights` — let the skill interpret the Lighthouse report, surface the most impactful issues, and explain what each audit measures.
|
|
102
|
+
2. **(Task 5)** Invoke `/nextjs-performance` — for any Next.js-specific findings (LCP, image optimization, RSC boundaries, bundle splitting, caching). This skill knows framework-level fixes.
|
|
103
|
+
3. **(Task 6)** Invoke `/web-design-guidelines` — for accessibility-related failures (contrast, heading hierarchy, focus states, ARIA).
|
|
104
|
+
4. **(Task 7)** Ground every diagnosis in code. Use Grep and Read to trace each failing audit to a specific file or pattern. Document file paths + line numbers in the progress tracker. If you cannot point to code, you do not have a root cause — keep investigating.
|
|
105
|
+
|
|
106
|
+
**Sub-skills invoked (mandatory):**
|
|
107
|
+
|
|
108
|
+
| Skill | Role |
|
|
109
|
+
| ------------------------ | -------------------------------------------------- |
|
|
110
|
+
| `/pagespeed-insights` | Interpret Lighthouse audits, prioritize fixes |
|
|
111
|
+
| `/nextjs-performance` | Next.js / React / Tailwind perf patterns |
|
|
112
|
+
| `/web-design-guidelines` | Accessibility and UX best practices |
|
|
113
|
+
|
|
114
|
+
**Output**: Root-cause map — for each failing audit, a concrete file/pattern reference
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
### Phase 3: SCOPE
|
|
119
|
+
**Tasks**: 8-9
|
|
120
|
+
|
|
121
|
+
**Goal**: A written, approved fix plan before any code changes.
|
|
122
|
+
|
|
123
|
+
**Actions**:
|
|
124
|
+
|
|
125
|
+
1. **(Task 8)** Draft the fix plan in the progress tracker. Structure:
|
|
126
|
+
- Baseline scores
|
|
127
|
+
- Target scores (explicit numbers)
|
|
128
|
+
- Findings and root causes (with file references from Phase 2)
|
|
129
|
+
- Fix plan, batched by impact (biggest wins first)
|
|
130
|
+
- Acceptance criteria
|
|
131
|
+
- Risks and mitigations
|
|
132
|
+
- Out of scope
|
|
133
|
+
2. **(Task 9)** Present the scope document to the user. Pause for approval. Do not proceed to fixes until user confirms batch order and acceptance criteria.
|
|
134
|
+
|
|
135
|
+
**Gate (Task 9)**: User approves the scope document before any code changes.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
### Phase 4: FIX
|
|
140
|
+
**Tasks**: 10 (with dynamic child tasks)
|
|
141
|
+
|
|
142
|
+
**Goal**: Execute fixes in batches. Each batch is independently verifiable.
|
|
143
|
+
|
|
144
|
+
**Actions**:
|
|
145
|
+
|
|
146
|
+
1. For each batch in the approved scope:
|
|
147
|
+
- Create child tasks via TaskCreate for the specific fixes in the batch
|
|
148
|
+
- Execute the fixes in order
|
|
149
|
+
- Use **Context7 MCP** for any library API lookups (do not rely on memory)
|
|
150
|
+
- Use `/code-structure` for all component changes (enforced by PostToolUse hook)
|
|
151
|
+
- After the batch, take a Playwright screenshot of affected pages at desktop + mobile
|
|
152
|
+
- Compare against baseline screenshots — no visual regressions allowed
|
|
153
|
+
2. Mark each batch complete before starting the next.
|
|
154
|
+
|
|
155
|
+
**Sub-skills invoked (mandatory):**
|
|
156
|
+
|
|
157
|
+
| Skill | Role |
|
|
158
|
+
| --------------------------- | --------------------------------------- |
|
|
159
|
+
| `/nextjs-performance` | Applied during every fix for guidance |
|
|
160
|
+
| `/code-structure` | Enforced on every TSX/JSX edit (hook) |
|
|
161
|
+
| `/tailwind-design-system` | For any design token changes |
|
|
162
|
+
|
|
163
|
+
**Tools invoked (mandatory):**
|
|
164
|
+
|
|
165
|
+
| Tool | Role |
|
|
166
|
+
| ------------------ | --------------------------------- |
|
|
167
|
+
| **Context7 MCP** | Live library documentation lookup |
|
|
168
|
+
| **Playwright MCP** | Visual verification per batch |
|
|
169
|
+
|
|
170
|
+
**Output**: Implemented fixes, visual parity confirmed per batch
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
### Phase 5: VERIFY
|
|
175
|
+
**Tasks**: 11-14
|
|
176
|
+
|
|
177
|
+
**Goal**: Prove the fixes worked. No wishful thinking — measure.
|
|
178
|
+
|
|
179
|
+
**Actions**:
|
|
180
|
+
|
|
181
|
+
1. **(Task 11)** Re-run `pagespeed.mjs` with `--baseline=/tmp/psi-baseline.json` to get a diff. Save to `/tmp/psi-after.json`.
|
|
182
|
+
2. **(Task 12)** Re-run Playwright screenshots at 3 widths. Compare against `/tmp/screenshots-baseline/`. Flag any unexpected visual changes.
|
|
183
|
+
3. **(Task 13)** Invoke `/simplify-code` on all changed files. Fix any High/Medium confidence issues before committing.
|
|
184
|
+
4. **(Task 14)** Present a before/after report:
|
|
185
|
+
- Scores table: baseline vs after, per category, per strategy
|
|
186
|
+
- Specific audits that improved (and by how much)
|
|
187
|
+
- Any audits that regressed (must be zero)
|
|
188
|
+
- Visual verification: pass/fail
|
|
189
|
+
- Code quality: pass/fail
|
|
190
|
+
- Acceptance criteria met: yes/no
|
|
191
|
+
5. If acceptance criteria are NOT met, loop back to Phase 4 (Fix) with a new batch. Do not commit under-delivered work.
|
|
192
|
+
|
|
193
|
+
**Sub-skills invoked (mandatory):**
|
|
194
|
+
|
|
195
|
+
| Skill | Role |
|
|
196
|
+
| ----------------- | --------------------------- |
|
|
197
|
+
| `/simplify-code` | Code quality + refactoring |
|
|
198
|
+
|
|
199
|
+
**Gate (Task 14)**: Acceptance criteria must be met before commit.
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
### Phase 6: COMMIT
|
|
204
|
+
**Tasks**: 15-17
|
|
205
|
+
|
|
206
|
+
**Goal**: Clean, conventional commits and a finalized progress tracker.
|
|
207
|
+
|
|
208
|
+
**Actions**:
|
|
209
|
+
|
|
210
|
+
1. **(Task 15)** Invoke `/plan-commits` — audit `.gitignore`, group changes into logical commits, run pre-commit checks.
|
|
211
|
+
2. **(Task 16)** Finalize the progress tracker — mark all phases Done with completion dates, attach the before/after report.
|
|
212
|
+
3. **(Task 17)** Execute commits. **REQUIRES EXPLICIT USER APPROVAL.** Use AskUserQuestion to confirm before staging any files.
|
|
213
|
+
|
|
214
|
+
**Gate (Task 17)**: User explicitly approves before commits are executed.
|
|
215
|
+
|
|
216
|
+
---
|
|
217
|
+
|
|
218
|
+
## How to Use This Skill
|
|
219
|
+
|
|
220
|
+
### Starting a new optimization cycle
|
|
221
|
+
|
|
222
|
+
```
|
|
223
|
+
User: /optimize-seo the home page
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
The skill will:
|
|
227
|
+
|
|
228
|
+
1. Create the progress tracker at `docs/progress/optimize-home-page.md`
|
|
229
|
+
2. Create all 17 tasks upfront
|
|
230
|
+
3. Execute tasks in order, pausing at gates for approval
|
|
231
|
+
|
|
232
|
+
### Resuming work
|
|
233
|
+
|
|
234
|
+
```
|
|
235
|
+
User: /optimize-seo — where are we on the home page optimization?
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
Check the task list for current status. Also check `docs/progress/optimize-[scope].md`.
|
|
239
|
+
|
|
240
|
+
### Running a scheduled audit (no fixes)
|
|
241
|
+
|
|
242
|
+
```
|
|
243
|
+
User: /optimize-seo audit only
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Run phases 1-2 only. Produce a baseline + diagnosis report. Stop before Scope.
|
|
247
|
+
|
|
248
|
+
---
|
|
249
|
+
|
|
250
|
+
## Progress Tracking
|
|
251
|
+
|
|
252
|
+
For each optimization cycle, maintain a progress file at `docs/progress/optimize-[scope].md`:
|
|
253
|
+
|
|
254
|
+
```markdown
|
|
255
|
+
# Performance Optimization — [Scope] — Progress
|
|
256
|
+
|
|
257
|
+
| Phase | Status | Date | Notes |
|
|
258
|
+
| -------- | ----------- | ---------- | ---------------- |
|
|
259
|
+
| Measure | Done | YYYY-MM-DD | Baseline captured |
|
|
260
|
+
| Diagnose | In Progress | — | — |
|
|
261
|
+
| Scope | Pending | — | — |
|
|
262
|
+
| Fix | Pending | — | — |
|
|
263
|
+
| Verify | Pending | — | — |
|
|
264
|
+
| Commit | Pending | — | — |
|
|
265
|
+
|
|
266
|
+
## Baseline Scores
|
|
267
|
+
|
|
268
|
+
| Category | Mobile | Desktop |
|
|
269
|
+
| -------------- | ------ | ------- |
|
|
270
|
+
| Performance | XX | XX |
|
|
271
|
+
| Accessibility | XX | XX |
|
|
272
|
+
| Best Practices | XX | XX |
|
|
273
|
+
| SEO | XX | XX |
|
|
274
|
+
|
|
275
|
+
## Target Scores
|
|
276
|
+
...
|
|
277
|
+
|
|
278
|
+
## Findings & Root Causes
|
|
279
|
+
...
|
|
280
|
+
|
|
281
|
+
## Fix Plan
|
|
282
|
+
...
|
|
283
|
+
|
|
284
|
+
## Acceptance Criteria
|
|
285
|
+
...
|
|
286
|
+
|
|
287
|
+
## Before/After (populated at Verify phase)
|
|
288
|
+
...
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
**Task 1 creates this file.** Task 16 finalizes it. Update phase statuses as you enter each phase.
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Rules
|
|
296
|
+
|
|
297
|
+
1. **Never fix without diagnosing** — guesswork burns time and introduces risk
|
|
298
|
+
2. **Every root cause must cite a file and/or line** — if you can't point to code, you don't have a root cause
|
|
299
|
+
3. **Every batch must visually verify** — perf wins that break the layout are regressions, not improvements
|
|
300
|
+
4. **Never commit without a before/after diff** — prove the fix worked
|
|
301
|
+
5. **Pause at gates** — scope approval (Task 9), verify report (Task 14), commit approval (Task 17)
|
|
302
|
+
6. **One scope at a time** — full site vs page-specific vs single-component. Don't mix.
|
|
303
|
+
7. **Sub-skill invocation is mandatory** — `/pagespeed-insights`, `/nextjs-performance`, `/web-design-guidelines`, `/simplify-code`, `/plan-commits` all have required invocation points
|
|
304
|
+
8. **Use Context7 MCP for library API lookups during Fix** — do not rely on training data for Next.js, React, Tailwind APIs
|
|
305
|
+
9. **Progress tracker is mandatory** — Task 1 creates it, Task 16 finalizes it before commits are executed
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
309
|
+
## Integration with `/orchestrate`
|
|
310
|
+
|
|
311
|
+
If a perf issue is discovered while delivering a new page, finish the `/orchestrate` cycle first. Run `/optimize-seo` as a separate follow-up cycle. Do not interleave.
|
|
312
|
+
|
|
313
|
+
If the same fix will ship alongside a new feature, scope it inside the `/orchestrate` Build phase, document it in the new page's progress tracker, and skip `/optimize-seo` for that cycle.
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PageSpeed Insights audit helper.
|
|
4
|
+
*
|
|
5
|
+
* Wraps the PageSpeed Insights API for repeatable baseline + diff audits.
|
|
6
|
+
* Reads G_PAGESPEED_KEY from .env.local at the project root.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node pagespeed.mjs --url=https://friedbotstudio.com
|
|
10
|
+
* node pagespeed.mjs --url=https://friedbotstudio.com --output=/tmp/psi.json
|
|
11
|
+
* node pagespeed.mjs --url=https://friedbotstudio.com --baseline=/tmp/psi-baseline.json
|
|
12
|
+
*
|
|
13
|
+
* Flags:
|
|
14
|
+
* --url Target URL to audit (required)
|
|
15
|
+
* --strategy "mobile" | "desktop" | "both" (default: "both")
|
|
16
|
+
* --output Write raw results to this JSON file
|
|
17
|
+
* --baseline Compare against a previous --output file, print a diff table
|
|
18
|
+
* --silent Suppress human-readable output (still writes --output if set)
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { readFileSync, writeFileSync, existsSync } from "node:fs";
|
|
22
|
+
import { resolve, dirname } from "node:path";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
24
|
+
|
|
25
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
26
|
+
const __dirname = dirname(__filename);
|
|
27
|
+
|
|
28
|
+
// ---------- argv parsing ----------
|
|
29
|
+
|
|
30
|
+
const args = Object.fromEntries(
|
|
31
|
+
process.argv.slice(2).map((a) => {
|
|
32
|
+
const [k, v] = a.replace(/^--/, "").split("=");
|
|
33
|
+
return [k, v ?? true];
|
|
34
|
+
})
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
if (!args.url) {
|
|
38
|
+
console.error("Error: --url is required");
|
|
39
|
+
console.error("Usage: node pagespeed.mjs --url=https://example.com [--strategy=mobile|desktop|both] [--output=file] [--baseline=file]");
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const strategy = args.strategy || "both";
|
|
44
|
+
const strategies = strategy === "both" ? ["mobile", "desktop"] : [strategy];
|
|
45
|
+
|
|
46
|
+
// ---------- env loading ----------
|
|
47
|
+
|
|
48
|
+
function loadEnvKey() {
|
|
49
|
+
if (process.env.G_PAGESPEED_KEY) return process.env.G_PAGESPEED_KEY;
|
|
50
|
+
// Walk up looking for .env.local
|
|
51
|
+
let dir = __dirname;
|
|
52
|
+
for (let i = 0; i < 6; i++) {
|
|
53
|
+
const candidate = resolve(dir, ".env.local");
|
|
54
|
+
if (existsSync(candidate)) {
|
|
55
|
+
const content = readFileSync(candidate, "utf8");
|
|
56
|
+
const match = content.match(/^G_PAGESPEED_KEY\s*=\s*(.+)$/m);
|
|
57
|
+
if (match) return match[1].trim();
|
|
58
|
+
}
|
|
59
|
+
dir = resolve(dir, "..");
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const apiKey = loadEnvKey();
|
|
65
|
+
if (!apiKey) {
|
|
66
|
+
console.error("Error: G_PAGESPEED_KEY not found in environment or .env.local");
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ---------- API call ----------
|
|
71
|
+
|
|
72
|
+
async function runAudit(url, strategy) {
|
|
73
|
+
const params = new URLSearchParams({
|
|
74
|
+
url,
|
|
75
|
+
strategy,
|
|
76
|
+
key: apiKey,
|
|
77
|
+
});
|
|
78
|
+
["PERFORMANCE", "ACCESSIBILITY", "BEST_PRACTICES", "SEO"].forEach((c) =>
|
|
79
|
+
params.append("category", c)
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
const endpoint = `https://www.googleapis.com/pagespeedonline/v5/runPagespeed?${params}`;
|
|
83
|
+
const res = await fetch(endpoint);
|
|
84
|
+
if (!res.ok) {
|
|
85
|
+
const text = await res.text();
|
|
86
|
+
throw new Error(`PageSpeed API ${res.status}: ${text}`);
|
|
87
|
+
}
|
|
88
|
+
return res.json();
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function extractSummary(raw) {
|
|
92
|
+
const categories = raw.lighthouseResult?.categories ?? {};
|
|
93
|
+
const audits = raw.lighthouseResult?.audits ?? {};
|
|
94
|
+
|
|
95
|
+
const scores = {};
|
|
96
|
+
for (const [id, cat] of Object.entries(categories)) {
|
|
97
|
+
scores[id] = Math.round((cat.score ?? 0) * 100);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const failures = [];
|
|
101
|
+
for (const audit of Object.values(audits)) {
|
|
102
|
+
if (
|
|
103
|
+
audit.score !== null &&
|
|
104
|
+
audit.score < 1 &&
|
|
105
|
+
!["informative", "notApplicable", "manual"].includes(audit.scoreDisplayMode)
|
|
106
|
+
) {
|
|
107
|
+
failures.push({
|
|
108
|
+
id: audit.id,
|
|
109
|
+
title: audit.title,
|
|
110
|
+
score: audit.score,
|
|
111
|
+
severity: audit.score === 0 ? "FAIL" : "WARN",
|
|
112
|
+
displayValue: audit.displayValue ?? null,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Sort failures: FAIL first, then by score ascending
|
|
118
|
+
failures.sort((a, b) => {
|
|
119
|
+
if (a.severity !== b.severity) return a.severity === "FAIL" ? -1 : 1;
|
|
120
|
+
return a.score - b.score;
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
return { scores, failures };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// ---------- reporting ----------
|
|
127
|
+
|
|
128
|
+
function printSummary(results) {
|
|
129
|
+
for (const [strategy, summary] of Object.entries(results)) {
|
|
130
|
+
console.log(`\n=== ${strategy.toUpperCase()} ===`);
|
|
131
|
+
console.log("Scores:");
|
|
132
|
+
for (const [cat, score] of Object.entries(summary.scores)) {
|
|
133
|
+
console.log(` ${cat.padEnd(16)} ${score}`);
|
|
134
|
+
}
|
|
135
|
+
if (summary.failures.length) {
|
|
136
|
+
console.log("\nFailing audits:");
|
|
137
|
+
for (const f of summary.failures) {
|
|
138
|
+
const display = f.displayValue ? ` — ${f.displayValue}` : "";
|
|
139
|
+
console.log(` [${f.severity}] ${f.title}${display}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function printDiff(baseline, current) {
|
|
146
|
+
console.log("\n=== DIFF (baseline → current) ===");
|
|
147
|
+
for (const strategy of Object.keys(current)) {
|
|
148
|
+
console.log(`\n${strategy.toUpperCase()}:`);
|
|
149
|
+
const base = baseline[strategy]?.scores ?? {};
|
|
150
|
+
const curr = current[strategy].scores;
|
|
151
|
+
for (const cat of Object.keys(curr)) {
|
|
152
|
+
const b = base[cat] ?? "—";
|
|
153
|
+
const c = curr[cat];
|
|
154
|
+
const delta = typeof b === "number" ? c - b : null;
|
|
155
|
+
const arrow = delta === null ? "" : delta > 0 ? ` ▲ +${delta}` : delta < 0 ? ` ▼ ${delta}` : " =";
|
|
156
|
+
console.log(` ${cat.padEnd(16)} ${String(b).padEnd(4)} → ${String(c).padEnd(4)}${arrow}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const baseFailures = new Set((baseline[strategy]?.failures ?? []).map((f) => f.id));
|
|
160
|
+
const currFailures = new Set(current[strategy].failures.map((f) => f.id));
|
|
161
|
+
const resolved = [...baseFailures].filter((id) => !currFailures.has(id));
|
|
162
|
+
const regressed = [...currFailures].filter((id) => !baseFailures.has(id));
|
|
163
|
+
if (resolved.length) {
|
|
164
|
+
console.log(` Resolved: ${resolved.length}`);
|
|
165
|
+
for (const id of resolved) console.log(` ✓ ${id}`);
|
|
166
|
+
}
|
|
167
|
+
if (regressed.length) {
|
|
168
|
+
console.log(` Regressed: ${regressed.length}`);
|
|
169
|
+
for (const id of regressed) console.log(` ✗ ${id}`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ---------- main ----------
|
|
175
|
+
|
|
176
|
+
const results = {};
|
|
177
|
+
for (const s of strategies) {
|
|
178
|
+
if (!args.silent) console.error(`Running ${s} audit for ${args.url}...`);
|
|
179
|
+
const raw = await runAudit(args.url, s);
|
|
180
|
+
results[s] = extractSummary(raw);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!args.silent) printSummary(results);
|
|
184
|
+
|
|
185
|
+
if (args.output) {
|
|
186
|
+
writeFileSync(args.output, JSON.stringify(results, null, 2));
|
|
187
|
+
if (!args.silent) console.log(`\nWrote results to ${args.output}`);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (args.baseline) {
|
|
191
|
+
if (!existsSync(args.baseline)) {
|
|
192
|
+
console.error(`Baseline file not found: ${args.baseline}`);
|
|
193
|
+
process.exit(1);
|
|
194
|
+
}
|
|
195
|
+
const baseline = JSON.parse(readFileSync(args.baseline, "utf8"));
|
|
196
|
+
printDiff(baseline, results);
|
|
197
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# License
|
|
2
|
+
|
|
3
|
+
## Skill License - Free Use
|
|
4
|
+
|
|
5
|
+
This skill (the documentation, structure, and implementation) was created by **Ender Puentes <Endev/>** and is provided for **free and open use**. You are free to:
|
|
6
|
+
|
|
7
|
+
- ✅ **Use** this skill in any project, personal or commercial
|
|
8
|
+
- ✅ **Modify** the skill to fit your needs
|
|
9
|
+
- ✅ **Distribute** the skill to others
|
|
10
|
+
- ✅ **Share** modified versions of the skill
|
|
11
|
+
- ✅ **Include** this skill in your own skill collections
|
|
12
|
+
|
|
13
|
+
**No restrictions apply** - this skill is available for unrestricted use. Attribution is appreciated but not required.
|
|
14
|
+
|
|
15
|
+
**Note**: This skill documents and references the PageSpeed Insights guidelines and Core Web Vitals specifications, but the skill itself is an independent work created for team use.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## PageSpeed Insights Documentation
|
|
20
|
+
|
|
21
|
+
This skill documents the [PageSpeed Insights documentation](https://developers.google.com/speed/docs/insights/v5/about?hl=es-419), which is a separate work created by Google.
|
|
22
|
+
|
|
23
|
+
**The PageSpeed Insights documentation is NOT owned by the skill author.** The documentation has its own license:
|
|
24
|
+
|
|
25
|
+
**Creative Commons Attribution 4.0 International (CC BY 4.0)**
|
|
26
|
+
|
|
27
|
+
For information about the PageSpeed Insights documentation license, visit: https://creativecommons.org/licenses/by/4.0/
|
|
28
|
+
|
|
29
|
+
**Documentation Source**: https://developers.google.com/speed/docs/insights/v5/about?hl=es-419
|
|
30
|
+
|
|
31
|
+
This skill simply documents and references the official PageSpeed Insights guidelines for educational and practical use. The skill author claims no ownership or rights over the PageSpeed Insights documentation itself.
|
|
32
|
+
|
|
33
|
+
## Skill Author
|
|
34
|
+
|
|
35
|
+
This skill was created by **Ender Puentes <Endev/>** - https://enderpuentes.com
|
|
36
|
+
|
|
37
|
+
The skill itself (as a documentation/implementation work) is released for free and unrestricted use. While attribution is appreciated, it is not required for using, modifying, or distributing this skill.
|