SEO SKILL PACK BY ADDITION
Search Console performance review
Find query opportunities and investigate traffic changes with clear data limits.
For site owners and SEO teams with Search Console property access. Install with Node.js and npm; retrieval needs Python, a Google OAuth client and the two listed packages.
npx skills add addition-labs/skills --skill search-console-analysis
search-console-analysisby Additionv2.0.0released 2026-09-15commit 931d10bMIT licencereport a problem
Review a named property’s search performance across two explicit windows of the same length. Set up the included read-only OAuth puller, or write a run directory in its layout from exports you already have, then investigate query opportunities and changes in page performance. The report records its data scope, what was not measured and the next checks, so you can decide what to investigate before editing your site.
The puller retrieves more rows than the interface export: query, page and device rows plus page-only rows for both windows, 25,000 a request until the API returns no more, into a new run directory with a manifest that records property, windows, row counts and whether the pull completed. It collects the rows the API exposes; it does not recover anonymised queries or guarantee a complete table, and the report says so.
The analyzer refuses an incomplete run and writes two lists from a completed one: query-page candidates at averaged position 5 through 15 with real impressions and clicks, and pages whose impressions fell a fifth or more against the previous window, compared unrounded, with a page missing from the current table reported as unknown rather than zero. Click-through gaps are not scored unless you opt into a labelled, unvalidated curve. The agent writes the summary and lists what the data could not answer.
When to use it
Ask for the review you need
Ask in your own words or name the installed skill explicitly. Requests like these load it:
- analyse Search Console
- what should I fix first in GSC
- striking distance
- which pages lost impressions
What you can review
Investigate query opportunities and changes in search demand
The pull
One-time browser consent, then query × page × device rows and page-only rows for both windows, paged until the API returns no more, into a new run directory with a manifest. Anonymised queries are not recovered; the manifest records what was collected.
Striking distance
Query-page candidates at averaged position 5 through 15 inclusive, at least 500 impressions, CTR at least 1%, compared unrounded. Review whether the page meets the query’s intent before choosing a revision, another page or no change.
CTR gap
Not measured by default. No verified curve ships with the pack; a comparison needs a named, dated curve with its market, device and query scope, and even then a gap is a review candidate, not proof that a title caused lost clicks.
Lost impressions
Pages present in both page-only tables, down 20% or more unrounded against the previous window, at least 500 previous impressions, ranked by absolute loss. A page absent from the current table is unknown, not zero.
Brand flag
Queries containing your brand phrases (whole words) are flagged, never removed. If every striking-distance candidate is a brand term, the write-up says so first.
Not measured, by name
Conversions, AI Overview or AI Mode presence, the per-device split and the CTR gap are listed as not measured in every findings file, so the write-up cannot claim them.
What the inputs cannot establish
- Completeness: Google anonymises rare queries and does not guarantee all rows; the pull collects what the API exposes and the manifest records the count. Query totals do not reconcile to property totals.
- Why a page lost impressions: a fall against the prior window is an observation; a season, a core update or a demand change needs a comparable historical window to separate.
- Whether a low click-through is a title problem: no curve is scored by default, and a scored gap would still be a review candidate; inspect the current results before proposing a rewrite.
- Conversions, AI Overview or AI Mode presence, the per-device split and the CTR gap: listed as not measured in every findings file.
See it in action
One real run, in full
Analyzer demonstration only, on a run directory written by hand in the puller's layout for a fictional property (example-store.test; the manifest note says so). The OAuth and API retrieval workflow has not been validated in this run; no live property results are shown.
Start with the request, then read the complete result. The input and output files are linked below the run.
The completed pull is in runs/example-store/2026-09-15 (manifest complete). Run scripts/gsc_analyze.py on that directory with the brand phrase "example store", then read findings.json and write the summary with the template in the SKILL.md. Copy the numbers; say what was not measured.
$ python3 scripts/gsc_analyze.py --dir runs/example-store/2026-09-15 --brand-terms "example store"
striking 12 · ctr_gap 0 · lost_impressions 2 (unknown 0) · rows 72 → runs/example-store/2026-09-15/findings.json
Search Console review, example-store.test, 2026-08-18 to 2026-09-14 against 2026-07-21 to 2026-08-17
WHAT HAPPENED: 24 query-page candidates in the current window; 12 within striking distance; 0 CTR gaps scored (no verified comparison curve was supplied); 2 pages with an impression loss of 20% or more, 0 pages unknown. 72 query rows in each window; 8 pages in each page-only table. No brand-only warning: none of the striking-distance candidates matches the brand phrase.
Striking distance (averaged position 5 through 15 inclusive, at least 500 impressions, CTR at least 1%, compared unrounded), by impressions:
| query | page | impressions | averaged position | CTR |
|---|---|---|---|---|
| linen blazer women | /collections/linen-blazers | 6,100 | 7.37 | 2.08% |
| womens dress size guide | /guides/womens-dress-size-guide | 5,200 | 9.08 | 1.69% |
| linen summer dress | /collections/summer-dresses | 3,900 | 9.67 | 1.31% |
| midi linen dress | /collections/summer-dresses | 1,700 | 5.57 | 2.59% |
| blazer fit guide | /guides/how-should-a-blazer-fit | 1,500 | 7.16 | 2.40% |
| womens linen blazer uk | /collections/linen-blazers | 1,300 | 5.77 | 2.85% |
| linen shrink | /guides/how-to-wash-linen | 1,200 | 5.81 | 3.00% |
| dress size 12 measurements | /guides/womens-dress-size-guide | 1,100 | 5.78 | 1.18% |
| blazer shoulder fit | /guides/how-should-a-blazer-fit | 919 | 11.96 | 1.41% |
| linen blazer navy | /products/timeless-linen-blazer | 879 | 6.25 | 1.48% |
| linen blazer sale | /collections/linen-blazers | 719 | 9.19 | 1.11% |
| linen blazer petite | /products/timeless-linen-blazer | 609 | 8.67 | 1.97% |
Lost impressions (page-only tables, at least 500 previous impressions, decline of 20% or more unrounded):
| page | previous | current | change |
|---|---|---|---|
| /collections/summer-dresses | 27,276 | 17,600 | −35.5% |
| /collections/sale | 3,462 | 1,649 | −52.4% |
WHAT IT MEANS: These query-page pairs are candidates for investigation. Averaged position does not establish how far a page is from a stable top result, and without a verified curve no click-through comparison was scored, so nothing here identifies a title or description problem. The linen blazer collection carries the largest candidate by impressions (6,100 at an averaged 7.37). Two collection pages lost impressions against the previous window; the window runs from late summer into September, and a seasonal explanation needs a comparable historical window before it is accepted.
WHAT TO DO:
- /collections/linen-blazers: inspect whether the collection explains fit, fabric and returns before drafting additions for
linen blazer womenandwomens linen blazer uk. - /guides/womens-dress-size-guide: check whether the guide already answers
dress size 12 measurementsbefore proposing a new section. - /guides/how-should-a-blazer-fit: inspect the current results for
blazer fit guideandblazer shoulder fitbefore deciding between a revision, another page or no change. - /collections/summer-dresses: compare page-level demand against the same window a year earlier before treating the decline as content decay.
- /collections/sale: same check; a sale that ended inside the window would explain the fall.
- Do not translate averaged position into a page number, and do not propose a title rewrite from this run.
NOT MEASURED: conversions; AI Overview or AI Mode presence; the per-device split (aggregated here); CTR gap against a benchmark curve.
Files from this run
- command.txt The exact command, script version and environment.
- manifest.json Input: property, windows, files, completion status; note says the table is synthetic.
- current.csv Input: 72 query-page-device rows, current window.
- previous.csv Input: 72 rows, previous window.
- current_pages.csv Input: 8 page-only rows, current window.
- previous_pages.csv Input: 8 page-only rows, previous window.
- findings.json Output: candidates, losses, thresholds, warnings, not measured.
What this run could not do
The pull itself. gsc_pull.py needs an OAuth consent in the account owner's browser; a run on a live property replaces this one.
Before you install
Files you can inspect before installing
The agent loads this skill when your task matches its description. Supporting files are read when the workflow calls for them.
search-console-analysis/SKILL.md- The SKILL.md: setup, the two commands, the reading rules that bind the write-up, and the template.
scripts/gsc_pull.py- OAuth desktop flow against your own Google account, token at a path you pass, 25,000-row pages; writes current.csv, previous.csv, the two page-only tables and manifest.json into a new run directory.
scripts/gsc_analyze.py- Striking-distance candidates and page impression losses over one completed run directory (--dir required); standard library only; writes findings.json with thresholds, warnings and not-measured.
PROMPTS.md- The prompts for setup, the pull, the analysis, the write-up and the brand-term flag.
CHECKLIST.md- Before the pull, after the analysis, and before acting on a lost-impressions page.
README.md- What it does, inside, needs, safety, example run and limits, in one page.
LICENSE- MIT licence text as installed with the folder.
CHANGELOG.md- What changed in each version.
First run
- Install the pack into your agent from a working folder for the property.
npx skills add addition-labs/skills --skill search-console-analysis - In your own Google Cloud project enable the Search Console API, configure the consent screen, create a desktop OAuth client and download its file to a path you control; install the two packages in a virtual environment.
pip install google-api-python-client google-auth-oauthlib - Pull the two windows into a new run directory; the first run opens a browser for consent and caches the token at the path you pass.
python3 "/path/to/search-console-analysis/scripts/gsc_pull.py" --site "sc-domain:example.com" --client-secret "/path/to/client_secret.json" --out "/path/to/property/runs" - Run the analysis on the exact run directory the puller printed, with your brand phrases, then ask the agent for the write-up with the prompt in PROMPTS.md.
python3 "/path/to/search-console-analysis/scripts/gsc_analyze.py" --dir "/path/to/property/runs/<run>" --brand-terms "example,examplestore"
Reading the results
What you can conclude from these results
Use Search Console observations to choose pages for investigation. Inspect the page and the current search results before proposing content changes. Page-level measurements, a comparable historical window and business context are needed to explain a decline.
The pack ranks striking-distance queries by impressions. Prioritise with the page’s business role, measured value and effort when those inputs are available; page type alone does not establish value.
Lost impressions are a candidate, not a diagnosis. The pack names the window; compare it against the same window a year earlier and against the core update calendar before a page is touched.
Background: How to run an ecommerce SEO audit
SKILL.md
The file the agent reads
MIT licence, copyright Addition Labs LLC; the file as it is in addition-labs/skills, commit 931d10b.
Open SKILL.md (1,127 words)
---
name: search-console-analysis
description: >-
Analyze Google Search Console performance for a named property and explicit
comparison windows. Use when the user requests query opportunities, CTR
investigation or a search-traffic decline review. Read compatible exports or
retrieve data through the included read-only OAuth script after setup.
Validate scope and completeness, then report supported findings and missing
evidence. CTR comparisons require a declared baseline; page-loss claims
require comparable page-level data. Does not edit the site.
metadata:
version: 2.0.0
released: 2026-09-15
author: addition-labs.com
---
# Search Console analysis: more rows than the interface export, two windows, named limits
## What this does
1. `scripts/gsc_pull.py` authenticates with YOUR Google account (OAuth, one-time browser consent) and retrieves, for
the current window and the previous window of the same length, query x page x device rows AND page-only rows,
25,000 rows per request until no further rows are returned for that request. It writes a new run directory with
four CSVs and a manifest (property, search type, filters, dimensions, reporting timezone, date windows, row counts,
completion status). Pagination collects the rows the API exposes; it does not recover anonymized queries or
guarantee a complete query table.
2. `scripts/gsc_analyze.py` reads one completed run directory and writes `findings.json` with:
- **striking_distance**: query-page candidates with inclusive averaged position 5 through 15, at least 500
impressions and CTR of at least 1%, compared unrounded. Review whether the existing page meets the query's intent
before choosing a revision, another page or no change.
- **lost_impressions**: pages present in both page-only tables with at least 500 impressions in the previous window
and an unrounded decline of at least 20%. A page absent from the current page result is listed as unknown, never
as a 100% loss.
- **ctr_gap**: not measured by default. No verified comparison curve ships with this skill.
These are configurable Addition screening rules, not Google requirements or evidence of causal improvement.
3. You (the agent) read `findings.json` and write the plain-language summary using the template below. Numbers are
copied, never rounded up, never estimated.
## Setup (once)
Create a Google Cloud project, enable Search Console API, configure its OAuth consent screen and audience, and create a
Desktop OAuth client. If the app is in testing, add the consenting account as a test user. That account must have access
to the exact Search Console property. Save the downloaded client file at the path supplied to the puller; do not paste
its contents into chat. Create a virtual environment, install the listed dependencies there, and run the puller with that
environment's Python. Setup duration is not measured.
```bash
python3 -m venv .venv
.venv/bin/python -m pip install google-api-python-client google-auth-oauthlib
```
## Run
```bash
.venv/bin/python "/absolute/path/to/installed/search-console-analysis/scripts/gsc_pull.py" \
--site "sc-domain:example.com" \
--client-secret "/absolute/path/to/config/client_secret.json" \
--out "/absolute/path/to/gsc-runs" # optional: --days 28 --lag 3 or --end YYYY-MM-DD
python3 "/absolute/path/to/installed/search-console-analysis/scripts/gsc_analyze.py" \
--dir "/exact/directory/printed/by/the/pull" --brand-terms "example,examplestore"
```
The puller prints the run directory; pass exactly that directory to the analyzer. The analyzer never selects a
directory on its own, so it cannot pick another property's results. Brand queries are flagged (whole words), not removed.
Operational contract: every pull writes to a new directory. A run is complete only after all CSVs and the manifest have
been written successfully. The manifest records exact property, dates, dimensions, filters, search type, row counts and
completion status. Analysis requires that completion record. On authentication or quota failure the puller reports the
affected request and recovery action, retains the incomplete run as incomplete, and the analyzer refuses it. Dates use
Search Console's Pacific reporting date; the default lag of 3 days allows for finalization. An empty table is reported as
no rows returned, not as an error; an absent file is an error.
## Reading rules (binding for the write-up)
- Average position is an average. A query at "position 6" can be #2 on mobile and #12 on desktop. Say "averaged
position", never "ranks #6".
- The unit is query-page candidates, not queries. Say so.
- If every striking-distance candidate is a brand term, say so first: the non-brand list is the real opportunity.
- Query-row totals can differ from property totals because of query omissions and aggregation differences.
- Record overlapping updates from Google's official Search Status Dashboard and separately list documented site changes.
An overlap is context, not attribution. Without evidence identifying a cause, report the cause as unknown.
- CTR comparisons need a named, dated curve with its market, device, query scope and source values. A site-specific
comparison is preferable when the data supports it. A CTR gap is a review candidate, not proof that a title or
description caused lost clicks. No comparison is scored when the curve is unavailable. `--legacy-ctr-curve` exposes an
unvalidated legacy configuration for research only; its output is labelled as such and is not a benchmark.
- Zero rows is a result, not an error: "no candidate met the thresholds" is a valid finding.
## Write-up template
```
WHAT HAPPENED: <n> query-page candidates within striking distance, <n> pages lost impressions
(<current window> vs <previous window>, property <name>).
WHAT IT MEANS: one sentence per list, plain language, the biggest number in bold.
WHAT TO DO: at most seven actions, each tied to one page and one number, lowest effort first.
NOT MEASURED: every item from findings.json not_measured (conversions, AI Overview presence, CTR gap, ...).
```
## Safety
**Network:** The puller uses Google's OAuth authorization and token services and the Search Console API. First consent
opens a browser and a temporary local callback server. The analyzer makes no network calls. The agent's write-up uses the
model service configured in your agent.
**Commands:** You run Python commands. The puller opens the consent browser; neither script contains a shell-command runner.
**Credentials:** The puller reads the OAuth client file at the path you pass and creates or updates the token file beside
it (or at `--token`). Its requested Search Console scope is read-only. Keep credential contents out of prompts and reports.
**Files:** The puller writes CSVs and manifest.json into a new run directory under `--out` and creates or updates the token
file. The analyzer writes findings.json in the directory selected by `--dir` and overwrites a previous findings.json there.
Neither script calls a delete API or changes the website or Search Console property.
## Limits, stated
- Google documents a maximum of 50,000 rows per day per search type and does not guarantee that all rows are returned;
page/query grouping can drop data. Two whole-window calls do not implement Google's daily retrieval pattern. For larger
properties, pull daily with consistent dimensions and aggregate clicks and impressions by sum and position by
impression weighting; the completeness warning still applies.
- The API returns at most 16 months of history.
- This skill reads. It does not write to Search Console and it does not touch your site.
Questions
Requirements and troubleshooting
What do I install first?
Node.js with npm for the installer, Python 3.10 or newer, and the two packages the pull needs. The installer adds the instructions and the scripts to your agent; it does not create the Google OAuth client or authorise anything.
Where do my files go?
Choose a working folder per property. The OAuth client file and the token live at paths you pass; the puller writes a new run directory under --out each time, and the analyzer only reads the directory you name with --dir, so two properties cannot be mixed up by accident.
Why did Google authorisation fail?
Check that the Search Console API is enabled in the project, that the OAuth app allows the account you consent with, and that the account has access to the exact property string (sc-domain: or the full URL). Re-authorise in the browser if the token was revoked. Do not paste client or token contents into a prompt.
What does an empty result mean?
One of three things, and the report names which: the pull returned no rows, no query met a list’s thresholds, or the pull did not complete. An incomplete run keeps its manifest marked incomplete and the analyzer refuses it; run again into a new directory.
Will installing this connect my Google account?
No. Installation adds files. The connection happens only when you create the OAuth client and run gsc_pull.py yourself, and it is read-only.