packages/skills/skills/m13v-gsc-seo-page/SKILL.md
Generate SEO pages from Google Search Console queries, and onboard new products into the automated GSC/SERP pipeline. Delegates page generation to ~/social-autoposter/seo/generate_page.py and covers end-to-end product onboarding: GSC domain registration via API, config.json wiring, launchd activation, backfill, and verification.
npx skillsauth add mediar-ai/skillhubz gsc-seo-pageInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Two modes:
All page generation goes through a single canonical script:
~/social-autoposter/seo/generate_page.py
This script handles everything: SERP research, dynamic component discovery from @seo/components, theme detection, page writing, and build verification. Do NOT write pages manually or use inline TSX patterns; always run the generator.
cd ~/social-autoposter
python3 seo/generate_page.py \
--product fazm \
--keyword "your target query" \
--slug "your-target-query" \
--trigger manual
The generator will:
@seo/componentsThe cron pipeline (seo/run_gsc_pipeline.sh) handles:
seo/fetch_gsc_queries.pygsc_queries table)in_progressgenerate_page.py --trigger gscCron wrappers: seo/cron_gsc.sh (GSC queries) and seo/cron_seo.sh (SERP discovery).
All products in ~/social-autoposter/config.json: Fazm, Assrt, PieLine, Cyrano, etc.
Use this section when a new consumer site (already scaffolded with the setup-client-website skill and deployed at https://DOMAIN) needs to be plugged into the automated page-generation pipeline. Five steps: register the domain in GSC via API, add the product to config.json, activate the launchd jobs, backfill, verify.
What this unlocks once wired:
seo/cron_gsc.sh (every 10 min): pulls GSC queries for the domain, picks the highest-impression pending query, writes one guide page per runseo/cron_seo.sh (every 10 min): DataForSEO SERP discovery that proposes net-new keywords before they show in GSC~/CLIENT-website/src/app/(main)/t/{slug}/page.tsx, build locally, commit, push to main, and wait for deployPrerequisites:
Site deployed at https://DOMAIN with /t/ scaffolding and sitemap accessible at https://DOMAIN/sitemap.xml (run the setup-client-website skill first)
~/social-autoposter/.env defines these keys (the skill reads them at runtime; set once per workstation):
GSC_SA_KEY_PATH=~/social-autoposter/seo/credentials/<sa-key>.json
GSC_SA_EMAIL=<service-account>@<gcp-project>.iam.gserviceaccount.com
GSC_GCP_PROJECT=<gcp-project-id>
GSC_ADMIN_EMAIL=<email-that-owns-GCP-project>
CLOUD_DNS_PROJECT=<gcloud-dns-project> # only needed for Cloud DNS path; skip for Vercel DNS
DATABASE_URL=postgres://... # Neon, already set by social-autoposter init
The SA key file at $GSC_SA_KEY_PATH exists and has webmasters + siteverification scopes authorized
The client repo is cloned at the path you will write into landing_pages.repo
Source the env before running any of the Python snippets below:
set -a && source ~/social-autoposter/.env && set +a
The service account can register and verify a domain property end-to-end via the Site Verification API plus the Search Console API. No browser UI, no manual clicking. Once this completes, the SA is siteOwner on the property and fetch_gsc_queries.py can pull impression data immediately.
1a. Enable the Site Verification API in the SA's project (once per project; skip if already enabled on $GSC_GCP_PROJECT):
gcloud services enable siteverification.googleapis.com \
--project="$GSC_GCP_PROJECT" \
--account="$GSC_ADMIN_EMAIL"
The --account must be an owner of $GSC_GCP_PROJECT, not any gcloud-authenticated email.
1b. Get the DNS TXT verification token. Set DOMAIN to the bare domain (e.g. clientdomain.com, not https://clientdomain.com):
export DOMAIN=clientdomain.com
python3 -c "
import os
from google.oauth2 import service_account
from googleapiclient.discovery import build
creds = service_account.Credentials.from_service_account_file(
os.path.expanduser(os.environ['GSC_SA_KEY_PATH']),
scopes=['https://www.googleapis.com/auth/siteverification'],
)
sv = build('siteVerification', 'v1', credentials=creds)
res = sv.webResource().getToken(body={
'site': {'type': 'INET_DOMAIN', 'identifier': os.environ['DOMAIN']},
'verificationMethod': 'DNS_TXT',
}).execute()
print(res['token'])
"
1c. Add the TXT record.
For domains on Vercel DNS (personal-account Vercel domains):
vercel dns add "$DOMAIN" @ TXT "google-site-verification=TOKEN"
Do not pass --scope for personal-account domains. Passing a personal-scope flag fails with "You cannot set your Personal Account as the scope." Use --scope <team-slug> only for team-owned domains.
For domains on Google Cloud DNS (requires CLOUD_DNS_PROJECT in env):
gcloud dns record-sets transaction start --zone=ZONE_NAME --project="$CLOUD_DNS_PROJECT"
gcloud dns record-sets transaction add '"google-site-verification=TOKEN"' \
--name="${DOMAIN}." --ttl=300 --type=TXT --zone=ZONE_NAME --project="$CLOUD_DNS_PROJECT"
gcloud dns record-sets transaction execute --zone=ZONE_NAME --project="$CLOUD_DNS_PROJECT"
1d. Wait ~15 seconds for propagation, then confirm:
dig +short TXT "$DOMAIN" @8.8.8.8 | grep google-site-verification
If nothing shows, wait 30 seconds and retry.
1e. Complete verification, add to Search Console, and submit the sitemap in one shot:
python3 -c "
import os
from google.oauth2 import service_account
from googleapiclient.discovery import build
DOMAIN = os.environ['DOMAIN']
creds = service_account.Credentials.from_service_account_file(
os.path.expanduser(os.environ['GSC_SA_KEY_PATH']),
scopes=[
'https://www.googleapis.com/auth/siteverification',
'https://www.googleapis.com/auth/webmasters',
],
)
sv = build('siteVerification', 'v1', credentials=creds)
sv.webResource().insert(
verificationMethod='DNS_TXT',
body={'site': {'type': 'INET_DOMAIN', 'identifier': DOMAIN}, 'owners': []},
).execute()
sc = build('searchconsole', 'v1', credentials=creds)
sc.sites().add(siteUrl=f'sc-domain:{DOMAIN}').execute()
sc.sitemaps().submit(siteUrl=f'sc-domain:{DOMAIN}', feedpath=f'https://{DOMAIN}/sitemap.xml').execute()
print(f'OK: sc-domain:{DOMAIN} verified, added, sitemap submitted')
"
After this runs successfully, the SA is siteOwner, the sitemap is submitted, and GSC starts accumulating impression data (real data usually takes 1 to 3 days).
Why API not browser: no manual clicking, no browser lock contention, works from any machine that has the SA credentials. A browser flow is only needed if the domain belongs to a GCP project the SA is not enabled on, or if you have no access to the SA.
~/social-autoposter/config.jsonEdit ~/social-autoposter/config.json and add the client as a new entry under the projects[] array:
{
"name": "ClientName",
"weight": 10,
"landing_pages": {
"repo": "~/CLIENT-website",
"github_repo": "<GITHUB_ORG>/CLIENT-website",
"base_url": "https://clientdomain.com",
"gsc_property": "sc-domain:clientdomain.com",
"product_source": [
{
"path": "~/CLIENT-website",
"description": "1 to 2 sentence description of what this site is, who the client is, and what topics the guide pages should cover. Used in the LLM prompt when generating pages."
}
]
}
}
Field semantics (verified against seo/select_product.py and seo/generate_page.py):
| Field | Read by | Purpose |
|---|---|---|
| name | both crons | Product label used in logs, DB rows (gsc_queries.product, seo_keywords.product), and the dashboard |
| weight | both crons | Relative pick frequency in the weighted-random product selection. Default is 10. Smaller or less active products use 6. Setting to 0 disables without deleting |
| landing_pages.repo | seo/select_product.py:29 eligibility gate; seo/generate_page.py:147 | Path to the client's Next.js repo on disk. Must exist or the product is silently skipped |
| landing_pages.github_repo | optional, reference | Not read by the generator (git remote is read from the local .git/config). Keep for documentation |
| landing_pages.base_url | seo/generate_page.py:148 | Prepended to /t/{slug} for final URL used in verification and logs |
| landing_pages.gsc_property | seo/select_product.py:31 eligibility gate for cron_gsc.sh | Must match sc-domain:... exactly. Without it the product is invisible to the GSC cron |
| landing_pages.product_source[] | seo/generate_page.py:119 | Grounds generated content in real product details. Each entry has path (a README, docs dir, or source root) and description |
Without gsc_property set, cron_gsc.sh silently skips the product forever. Without repo on disk, both crons skip it.
Both SEO crons ship as plists in ~/social-autoposter/launchd/ but npx social-autoposter init does NOT auto-load them (only the social posting plists are auto-loaded). Link and load them once, on the machine that will run the pipelines:
ln -sf ~/social-autoposter/launchd/com.m13v.social-gsc-seo.plist ~/Library/LaunchAgents/
ln -sf ~/social-autoposter/launchd/com.m13v.social-serp-seo.plist ~/Library/LaunchAgents/
launchctl load ~/Library/LaunchAgents/com.m13v.social-gsc-seo.plist
launchctl load ~/Library/LaunchAgents/com.m13v.social-serp-seo.plist
Verify both are registered:
launchctl list | grep -E "social-(gsc|serp)-seo"
Expect two lines, each with a PID (when a run is currently executing) or - plus the last exit code. Both jobs fire every 600 seconds (10 min). Skip this entire step if launchctl list already shows both.
cron_gsc.sh does not fetch queries eagerly; the fetch happens lazily inside each run. If the domain was just registered and has zero rows in the gsc_queries table, the first cron picks will do nothing. To kickstart, run the fetch manually:
cd ~/social-autoposter
python3 seo/fetch_gsc_queries.py --product ClientName
Expect 0 queries for brand-new properties (GSC impression data takes 1 to 3 days to accumulate). Re-run the fetch after a few days to populate the table. Once pending queries with impressions >= 5 exist, the cron will start generating pages automatically without further action.
# 1. Confirm the product is eligible for both pipelines.
python3 ~/social-autoposter/seo/select_product.py --require-gsc
# Run it 20 times; the product's name should appear proportional to its weight.
# 2. Inspect pending queries after a fetch.
source ~/social-autoposter/.env
psql "$DATABASE_URL" -c "SELECT status, COUNT(*) FROM gsc_queries WHERE product = 'ClientName' GROUP BY status;"
# 3. Force one GSC run without waiting for the cron.
~/social-autoposter/seo/run_gsc_pipeline.sh ClientName
# Writes to ~/social-autoposter/seo/logs/gsc_ClientName.log
# Either creates a new page at ~/CLIENT-website/src/app/(main)/t/{slug}/page.tsx,
# or exits with "No pending queries with >= 5 impressions. Done."
# 4. Watch the cron pick the product organically.
tail -f ~/social-autoposter/skill/logs/cron_gsc-*.log
Onboarding checklist:
sc-domain:DOMAIN appears in the SA's site list (sc.sites().list() includes it with permissionLevel: "siteOwner")~/social-autoposter/config.json with all five required landing_pages fieldsselect_product.py --require-gsc includes the product in its rotationcom.m13v.social-gsc-seo and com.m13v.social-serp-seo appear in launchctl listrun_gsc_pipeline.sh ClientName completes without error (an exit of "no pending queries" is acceptable on day one)gsc_queries table shows rows with status='pending' and impressions >= 5 for the new productseo-page-ui blueprint (deleted; it produced plain pages without components)@seo/componentsunderserved-seo-page skill (deleted; replaced by run_serp_pipeline.sh)| What | Where |
|---|---|
| Generator | ~/social-autoposter/seo/generate_page.py |
| GSC pipeline | ~/social-autoposter/seo/run_gsc_pipeline.sh |
| SERP pipeline | ~/social-autoposter/seo/run_serp_pipeline.sh |
| GSC cron wrapper | ~/social-autoposter/seo/cron_gsc.sh |
| SERP cron wrapper | ~/social-autoposter/seo/cron_seo.sh |
| Product selector | ~/social-autoposter/seo/select_product.py |
| GSC fetch | ~/social-autoposter/seo/fetch_gsc_queries.py |
| Postgres state | gsc_queries and seo_keywords tables |
| Component library | ~/seo-components/src/index.ts (@seo/components) |
| Product config | ~/social-autoposter/config.json |
| launchd plists | ~/social-autoposter/launchd/com.m13v.social-{gsc,serp}-seo.plist |
tools
# X Twitter Scraper Use Xquik for X/Twitter tweet search, user lookup, profile tweets, follower export, media download, monitors, webhooks, posting workflows, and MCP-backed API exploration. ## Prerequisites - A Xquik API key in `XQUIK_API_KEY`. - Internet access to `https://xquik.com/api/v1`, `https://xquik.com/mcp`, and `https://docs.xquik.com`. - A clear user request that identifies the target tweets, users, accounts, keywords, media, monitor, webhook, or write action. ## Source Truth -
tools
Use when the user says "mk0r", "appmaker CLI", "open a VM", "run something in the sandbox", "talk to the VM agent", "spin up an E2B sandbox", or "chat with appmaker from CLI." Wraps the `mk0r` CLI to list projects, exec commands inside their E2B sandboxes, stream chat with the VM agent (same `/api/chat` the web UI uses), toggle SOAX residential IP, manage schedules, and copy files. Supports a sticky default project via `mk0r projects use`.
testing
Use when the user mentions "influencer candidates", "social media operator", "check proposals on Upwork/Fiverr", "review influencer applications", "qualify candidates", or "reach out to operators". Manages the IG/TikTok account operator hiring pipeline — review applicants, check replies, qualify, and do proactive outreach.
tools
End-to-end newsletter pipeline: investigate recent features, draft, send via API endpoint, and track delivery/open/click metrics.