@cliftonc/leo
v1.1.4
Published
SEO management CLI for Google Search Console
Maintainers
Readme
leo
SEO management CLI for Google Search Console. Inspect indexing status, request indexing, check search performance, and find coverage gaps — all from your terminal.
Setup
1. Install
# Via npm (recommended)
npm install -g @cliftonc/leo
# Or run directly without installing
npx @cliftonc/leo
# Or from source
git clone https://github.com/cliftonc/leo.git
cd leo
npm install
npm run build
npm link2. Google Cloud Project
You need a GCP project with two APIs enabled:
- Go to Google Cloud Console
- Create a new project (or use an existing one)
- Enable these APIs (APIs & Services → Library):
- Google Search Console API — for inspecting URLs, search analytics, and metadata
- Web Search Indexing API — for requesting indexing of URLs
3. OAuth Credentials
- Go to APIs & Services → Credentials
- Click Create Credentials → OAuth client ID
- Application type: Desktop app
- Name it whatever you like (e.g. "leo")
- Click Create, then Download JSON
- Save the downloaded file as
~/.leo/credentials.json:
mkdir -p ~/.leo
mv ~/Downloads/client_secret_*.json ~/.leo/credentials.json4. Authenticate
leo authThis opens a browser for Google OAuth consent. After authorizing, the token is saved to ~/.leo/token.json and refreshes automatically.
5. Verify Your Domain
Your domain must be verified in Google Search Console. Leo uses the sc-domain: property format (domain-level), so add your domain there if you haven't already.
Check that leo can see it:
leo sitesCommands
leo auto <domain>
The main command. Automatically finds and submits non-indexed pages. Runs the full pipeline: fetch sitemap → inspect every URL → submit non-indexed ones.
# Preview what's not indexed (no submissions)
leo auto drizzle-cube.dev --dry-run
# Find and submit non-indexed pages
leo auto drizzle-cube.dev
# Limit how many get submitted
leo auto drizzle-cube.dev --limit 20| Option | Default | Description |
|---|---|---|
| --dry-run | — | Inspect and report only, skip submission |
| --limit <n> | 50 | Max URLs to submit |
| --inspect-rpm <n> | 30 | Inspection requests per minute |
| --submit-rpm <n> | 10 | Submission requests per minute |
leo inspect <domain> [urls...]
Check the indexing status of URLs using the URL Inspection API.
# Inspect all sitemap URLs (default limit: 50)
leo inspect drizzle-cube.dev --all --limit 100
# Inspect specific URLs
leo inspect drizzle-cube.dev https://drizzle-cube.dev/docs/getting-started
# Show detailed info for non-indexed URLs
leo inspect drizzle-cube.dev --all --not-indexed| Option | Default | Description |
|---|---|---|
| --all | — | Inspect all URLs from sitemap |
| --not-indexed | — | Show detailed results for non-indexed URLs |
| --limit <n> | 50 | Max URLs to inspect |
| --rpm <n> | 30 | Requests per minute |
leo submit <domain> [urls...]
Request indexing for URLs via the Indexing API.
# Submit specific URLs
leo submit drizzle-cube.dev https://example.com/new-page
# Inspect first, then submit only non-indexed
leo submit drizzle-cube.dev --not-indexed --limit 20
# Dry run
leo submit drizzle-cube.dev --not-indexed --dry-run| Option | Default | Description |
|---|---|---|
| --all | — | Submit all sitemap URLs |
| --not-indexed | — | Inspect first, submit only non-indexed |
| --limit <n> | 20 | Max URLs to submit |
| --rpm <n> | 10 | Requests per minute |
| --dry-run | — | Show what would be submitted |
leo ping <domain>
Notify Google of sitemap changes via WebSub (PubSubHubbub). This is an officially supported, unlimited mechanism that triggers Google to re-crawl your sitemap.
leo ping drizzle-cube.devleo coverage <domain>
Compare sitemap URLs against pages that have search impressions. Finds pages in your sitemap that Google hasn't shown to anyone, and pages getting impressions that aren't in your sitemap.
leo coverage drizzle-cube.dev
leo coverage drizzle-cube.dev --days 90| Option | Default | Description |
|---|---|---|
| --days <n> | 28 | Days of analytics data to check |
leo performance <domain>
Show search performance: clicks, impressions, CTR, and average position.
# By page (default)
leo performance drizzle-cube.dev
# By search query, last 90 days, top 50
leo performance drizzle-cube.dev --days 90 --by query --limit 50
# By country
leo performance drizzle-cube.dev --by country| Option | Default | Description |
|---|---|---|
| --days <n> | 28 | Days to look back |
| --by <dim> | page | Group by: page, query, device, country |
| --limit <n> | 25 | Max rows to show |
leo sitemap <domain>
Fetch and display all URLs from the domain's sitemap. Tries GSC-registered sitemaps first, then common paths (/sitemap.xml, /sitemap-index.xml, etc.).
leo sitemap drizzle-cube.devleo sitemaps <domain>
List sitemaps registered in Google Search Console for a domain.
leo sitemaps drizzle-cube.devleo sites
List all verified properties in your Search Console account.
leo sitesleo auth
Run the OAuth flow interactively. Opens a browser, saves the token to ~/.leo/token.json.
leo authRate Limits
Google enforces daily quotas on these APIs:
| API | Daily Quota | Default RPM in leo | |---|---|---| | URL Inspection | ~2,000 requests/day | 30 | | Indexing | ~200 requests/day | 10 | | Search Analytics | ~25,000 requests/day | unlimited |
All rate limits are configurable via --rpm, --inspect-rpm, or --submit-rpm depending on the command.
How Indexing Works
Leo uses multiple mechanisms to get your pages indexed:
- Indexing API (
submit,auto) — Sends URL notifications to Google, triggering a crawl. Google's docs say this is for JobPosting/BroadcastEvent pages, but in practice it works for all page types and is widely used (see google-indexing-script with 6k+ stars). - WebSub ping (
ping) — Officially supported, unlimited. Notifies Google's PubSubHubbub hub that your sitemap has changed, triggering a re-crawl. - URL Inspection API (
inspect,auto) — Checks indexing status of individual URLs. Fully supported for all page types.
File Locations
| File | Purpose |
|---|---|
| ~/.leo/credentials.json | OAuth client credentials (from GCP) |
| ~/.leo/token.json | Saved auth token (auto-refreshes) |
| ~/.leo/cache/<domain>.json | Cached indexing status per domain |
