@opendirectory.dev/skills 0.1.0
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/.claude/skills/claude-md-generator/.env.example +7 -0
- package/.claude/skills/claude-md-generator/README.md +78 -0
- package/.claude/skills/claude-md-generator/SKILL.md +248 -0
- package/.claude/skills/claude-md-generator/evals/evals.json +35 -0
- package/.claude/skills/claude-md-generator/references/section-guide.md +175 -0
- package/dist/e2e.test.d.ts +1 -0
- package/dist/e2e.test.js +62 -0
- package/dist/fs-adapters.d.ts +4 -0
- package/dist/fs-adapters.js +101 -0
- package/dist/fs-adapters.test.d.ts +1 -0
- package/dist/fs-adapters.test.js +108 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +211 -0
- package/dist/transformers.d.ts +6 -0
- package/dist/transformers.js +2 -0
- package/package.json +25 -0
- package/registry.json +226 -0
- package/skills/blog-cover-image-cli/.github/workflows/publish.yml +19 -0
- package/skills/blog-cover-image-cli/LICENSE +15 -0
- package/skills/blog-cover-image-cli/README.md +126 -0
- package/skills/blog-cover-image-cli/SKILL.md +7 -0
- package/skills/blog-cover-image-cli/agent-skill/blog-cover-generator/README.md +30 -0
- package/skills/blog-cover-image-cli/agent-skill/blog-cover-generator/SKILL.md +72 -0
- package/skills/blog-cover-image-cli/bin/cli.js +226 -0
- package/skills/blog-cover-image-cli/examples/100x_UX_Research_AI_Agent.png +0 -0
- package/skills/blog-cover-image-cli/examples/Firecrawl-supabase-bolt.png +0 -0
- package/skills/blog-cover-image-cli/examples/Git-City_Case_study_Cover_Image.jpg +0 -0
- package/skills/blog-cover-image-cli/examples/THE DISTRIBUTION LAYER (2).png +0 -0
- package/skills/blog-cover-image-cli/examples/canva-perplexity-duolingo-cover-image.png +0 -0
- package/skills/blog-cover-image-cli/examples/gamma-mistral-veed.png +0 -0
- package/skills/blog-cover-image-cli/examples/server-survival-case-study-cover-image(1).png +0 -0
- package/skills/blog-cover-image-cli/examples/viral-meme-automation.png +0 -0
- package/skills/blog-cover-image-cli/index.js +2 -0
- package/skills/blog-cover-image-cli/package-lock.json +2238 -0
- package/skills/blog-cover-image-cli/package.json +37 -0
- package/skills/blog-cover-image-cli/src/geminiGenerator.js +126 -0
- package/skills/blog-cover-image-cli/src/imageValidator.js +54 -0
- package/skills/blog-cover-image-cli/src/logoFetcher.js +86 -0
- package/skills/claude-md-generator/.env.example +7 -0
- package/skills/claude-md-generator/README.md +78 -0
- package/skills/claude-md-generator/SKILL.md +254 -0
- package/skills/claude-md-generator/evals/evals.json +35 -0
- package/skills/claude-md-generator/references/section-guide.md +175 -0
- package/skills/cook-the-blog/README.md +86 -0
- package/skills/cook-the-blog/SKILL.md +130 -0
- package/skills/dependency-update-bot/.env.example +13 -0
- package/skills/dependency-update-bot/README.md +101 -0
- package/skills/dependency-update-bot/SKILL.md +376 -0
- package/skills/dependency-update-bot/evals/evals.json +45 -0
- package/skills/dependency-update-bot/references/changelog-patterns.md +201 -0
- package/skills/docs-from-code/.env.example +13 -0
- package/skills/docs-from-code/README.md +97 -0
- package/skills/docs-from-code/SKILL.md +160 -0
- package/skills/docs-from-code/evals/evals.json +29 -0
- package/skills/docs-from-code/references/extraction-guide.md +174 -0
- package/skills/docs-from-code/references/output-template.md +135 -0
- package/skills/docs-from-code/scripts/extract_py.py +238 -0
- package/skills/docs-from-code/scripts/extract_ts.ts +284 -0
- package/skills/docs-from-code/scripts/package.json +18 -0
- package/skills/explain-this-pr/README.md +74 -0
- package/skills/explain-this-pr/SKILL.md +130 -0
- package/skills/explain-this-pr/evals/evals.json +35 -0
- package/skills/google-trends-api-skills/README.md +78 -0
- package/skills/google-trends-api-skills/SKILL.md +7 -0
- package/skills/google-trends-api-skills/google-trends-api/SKILL.md +163 -0
- package/skills/google-trends-api-skills/google-trends-api/references/api-responses.md +188 -0
- package/skills/google-trends-api-skills/google-trends-api/scripts/discover_keywords.py +344 -0
- package/skills/google-trends-api-skills/seo-keyword-research/SKILL.md +205 -0
- package/skills/google-trends-api-skills/seo-keyword-research/references/keyword-placement-guide.md +89 -0
- package/skills/google-trends-api-skills/seo-keyword-research/references/tech-blog-examples.md +207 -0
- package/skills/google-trends-api-skills/seo-keyword-research/scripts/blog_seo_research.py +373 -0
- package/skills/hackernews-intel/.env.example +33 -0
- package/skills/hackernews-intel/README.md +161 -0
- package/skills/hackernews-intel/SKILL.md +156 -0
- package/skills/hackernews-intel/evals/evals.json +35 -0
- package/skills/hackernews-intel/package.json +15 -0
- package/skills/hackernews-intel/scripts/monitor-hn.js +258 -0
- package/skills/kill-the-standup/.env.example +22 -0
- package/skills/kill-the-standup/README.md +84 -0
- package/skills/kill-the-standup/SKILL.md +169 -0
- package/skills/kill-the-standup/evals/evals.json +35 -0
- package/skills/kill-the-standup/references/standup-format.md +102 -0
- package/skills/linkedin-post-generator/.env.example +14 -0
- package/skills/linkedin-post-generator/README.md +107 -0
- package/skills/linkedin-post-generator/SKILL.md +228 -0
- package/skills/linkedin-post-generator/evals/evals.json +35 -0
- package/skills/linkedin-post-generator/references/linkedin-format.md +216 -0
- package/skills/linkedin-post-generator/references/output-template.md +154 -0
- package/skills/llms-txt-generator/.env.example +18 -0
- package/skills/llms-txt-generator/README.md +142 -0
- package/skills/llms-txt-generator/SKILL.md +176 -0
- package/skills/llms-txt-generator/evals/evals.json +35 -0
- package/skills/llms-txt-generator/references/llms-txt-spec.md +88 -0
- package/skills/llms-txt-generator/references/output-template.md +76 -0
- package/skills/llms-txt-generator/test-output/genzcareer.in/llms.txt +31 -0
- package/skills/luma-attendees-scraper/README.md +170 -0
- package/skills/luma-attendees-scraper/SKILL.md +7 -0
- package/skills/luma-attendees-scraper/luma_attendees_export.js +223 -0
- package/skills/meeting-brief-generator/.env.example +21 -0
- package/skills/meeting-brief-generator/README.md +90 -0
- package/skills/meeting-brief-generator/SKILL.md +275 -0
- package/skills/meeting-brief-generator/evals/evals.json +35 -0
- package/skills/meeting-brief-generator/references/brief-format.md +114 -0
- package/skills/meeting-brief-generator/references/output-template.md +150 -0
- package/skills/meta-ads-skill/README.md +100 -0
- package/skills/meta-ads-skill/SKILL.md +7 -0
- package/skills/meta-ads-skill/meta-ads-skill/SKILL.md +41 -0
- package/skills/meta-ads-skill/meta-ads-skill/references/report_templates.md +47 -0
- package/skills/meta-ads-skill/meta-ads-skill/references/workflows.md +51 -0
- package/skills/meta-ads-skill/meta-ads-skill/scripts/auth_check.py +22 -0
- package/skills/meta-ads-skill/meta-ads-skill/scripts/formatters.py +46 -0
- package/skills/newsletter-digest/.env.example +20 -0
- package/skills/newsletter-digest/README.md +147 -0
- package/skills/newsletter-digest/SKILL.md +221 -0
- package/skills/newsletter-digest/evals/evals.json +35 -0
- package/skills/newsletter-digest/feeds.json +7 -0
- package/skills/newsletter-digest/package.json +15 -0
- package/skills/newsletter-digest/references/digest-format.md +123 -0
- package/skills/newsletter-digest/references/output-template.md +136 -0
- package/skills/newsletter-digest/scripts/fetch-feeds.js +141 -0
- package/skills/newsletter-digest/scripts/ghost-publish.js +147 -0
- package/skills/noise2blog/.env.example +16 -0
- package/skills/noise2blog/README.md +107 -0
- package/skills/noise2blog/SKILL.md +229 -0
- package/skills/noise2blog/evals/evals.json +35 -0
- package/skills/noise2blog/references/blog-format.md +188 -0
- package/skills/noise2blog/references/output-template.md +184 -0
- package/skills/outreach-sequence-builder/.env.example +12 -0
- package/skills/outreach-sequence-builder/README.md +108 -0
- package/skills/outreach-sequence-builder/SKILL.md +248 -0
- package/skills/outreach-sequence-builder/evals/evals.json +36 -0
- package/skills/outreach-sequence-builder/references/output-template.md +171 -0
- package/skills/outreach-sequence-builder/references/sequence-format.md +167 -0
- package/skills/outreach-sequence-builder/references/signal-playbook.md +117 -0
- package/skills/position-me/README.md +71 -0
- package/skills/position-me/SKILL.md +7 -0
- package/skills/position-me/position-me/SKILL.md +50 -0
- package/skills/position-me/position-me/references/EVALUATION_SOP.md +40 -0
- package/skills/position-me/position-me/references/REPORT_TEMPLATE.md +58 -0
- package/skills/position-me/position-me/scripts/extract_links.py +49 -0
- package/skills/pr-description-writer/README.md +81 -0
- package/skills/pr-description-writer/SKILL.md +141 -0
- package/skills/pr-description-writer/evals/evals.json +35 -0
- package/skills/pr-description-writer/references/pr-format-guide.md +145 -0
- package/skills/producthunt-launch-kit/.env.example +7 -0
- package/skills/producthunt-launch-kit/README.md +95 -0
- package/skills/producthunt-launch-kit/SKILL.md +380 -0
- package/skills/producthunt-launch-kit/evals/evals.json +35 -0
- package/skills/producthunt-launch-kit/references/copy-rules.md +124 -0
- package/skills/reddit-icp-monitor/.env.example +16 -0
- package/skills/reddit-icp-monitor/README.md +117 -0
- package/skills/reddit-icp-monitor/SKILL.md +271 -0
- package/skills/reddit-icp-monitor/evals/evals.json +40 -0
- package/skills/reddit-icp-monitor/references/icp-format.md +131 -0
- package/skills/reddit-icp-monitor/references/reply-rules.md +110 -0
- package/skills/reddit-post-engine/.env.example +13 -0
- package/skills/reddit-post-engine/README.md +103 -0
- package/skills/reddit-post-engine/SKILL.md +303 -0
- package/skills/reddit-post-engine/evals/evals.json +35 -0
- package/skills/reddit-post-engine/references/subreddit-playbook.md +156 -0
- package/skills/schema-markup-generator/.env.example +19 -0
- package/skills/schema-markup-generator/README.md +114 -0
- package/skills/schema-markup-generator/SKILL.md +192 -0
- package/skills/schema-markup-generator/evals/evals.json +35 -0
- package/skills/schema-markup-generator/references/json-ld-spec.md +263 -0
- package/skills/schema-markup-generator/references/output-template.md +556 -0
- package/skills/show-hn-writer/.env.example +14 -0
- package/skills/show-hn-writer/README.md +88 -0
- package/skills/show-hn-writer/SKILL.md +303 -0
- package/skills/show-hn-writer/evals/evals.json +35 -0
- package/skills/show-hn-writer/references/hn-rules.md +74 -0
- package/skills/show-hn-writer/references/title-formulas.md +93 -0
- package/skills/stargazer/README.md +79 -0
- package/skills/stargazer/SKILL.md +7 -0
- package/skills/stargazer/stargazer-skill/SKILL.md +58 -0
- package/skills/stargazer/stargazer-skill/assets/.env.example +18 -0
- package/skills/stargazer/stargazer-skill/scripts/convert_to_csv.py +63 -0
- package/skills/stargazer/stargazer-skill/scripts/count_emails.py +52 -0
- package/skills/stargazer/stargazer-skill/scripts/stargazer_deep_extractor.py +450 -0
- package/skills/tweet-thread-from-blog/.env.example +14 -0
- package/skills/tweet-thread-from-blog/README.md +109 -0
- package/skills/tweet-thread-from-blog/SKILL.md +177 -0
- package/skills/tweet-thread-from-blog/evals/evals.json +35 -0
- package/skills/tweet-thread-from-blog/references/output-template.md +193 -0
- package/skills/tweet-thread-from-blog/references/thread-format.md +107 -0
- package/skills/twitter-GTM-find-skill/README.md +43 -0
- package/skills/twitter-GTM-find-skill/SKILL.md +7 -0
- package/skills/twitter-GTM-find-skill/twitter-GTM-find/SKILL.md +37 -0
- package/skills/twitter-GTM-find-skill/twitter-GTM-find/references/icp-checklist.md +35 -0
- package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/package.json +23 -0
- package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/run_pipeline.sh +8 -0
- package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/src/debug.ts +23 -0
- package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/src/extractor.ts +79 -0
- package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/src/icp-filter.ts +87 -0
- package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/src/index.ts +94 -0
- package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/src/scraper.ts +41 -0
- package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/tsconfig.json +13 -0
- package/skills/yc-intent-radar-skill/README.md +39 -0
- package/skills/yc-intent-radar-skill/SKILL.md +7 -0
- package/skills/yc-intent-radar-skill/yc-jobs-scraper/SKILL.md +59 -0
- package/skills/yc-intent-radar-skill/yc-jobs-scraper/scripts/auth.js +29 -0
- package/skills/yc-intent-radar-skill/yc-jobs-scraper/scripts/db.js +62 -0
- package/skills/yc-intent-radar-skill/yc-jobs-scraper/scripts/export_radar_candidates.js +40 -0
- package/skills/yc-intent-radar-skill/yc-jobs-scraper/scripts/package-lock.json +1525 -0
- package/skills/yc-intent-radar-skill/yc-jobs-scraper/scripts/package.json +12 -0
- package/skills/yc-intent-radar-skill/yc-jobs-scraper/scripts/scraper.js +217 -0
- package/src/e2e.test.ts +35 -0
- package/src/fs-adapters.test.ts +91 -0
- package/src/fs-adapters.ts +65 -0
- package/src/index.ts +182 -0
- package/src/transformers.ts +6 -0
- package/tsconfig.json +8 -0
|
@@ -0,0 +1,344 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Google Trends Keyword Discovery Script
|
|
4
|
+
|
|
5
|
+
Discovers trending keywords and related topics for a given query using SerpApi.
|
|
6
|
+
Designed for SEO keyword research before blog generation.
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
python discover_keywords.py "your topic"
|
|
10
|
+
python discover_keywords.py "your topic" --geo US --date "today 3-m"
|
|
11
|
+
python discover_keywords.py "your topic" --full # includes timeseries validation
|
|
12
|
+
|
|
13
|
+
Requirements:
|
|
14
|
+
pip install requests
|
|
15
|
+
|
|
16
|
+
Environment:
|
|
17
|
+
SERPAPI_KEY - your SerpApi API key (required)
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
import argparse
|
|
21
|
+
import json
|
|
22
|
+
import os
|
|
23
|
+
import sys
|
|
24
|
+
from datetime import datetime, timedelta
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
import requests
|
|
29
|
+
except ImportError:
|
|
30
|
+
print("Error: 'requests' package required. Install with: pip install requests")
|
|
31
|
+
sys.exit(1)
|
|
32
|
+
|
|
33
|
+
API_BASE = "https://serpapi.com/search"
|
|
34
|
+
CACHE_DIR = Path.home() / ".cache" / "google-trends-api"
|
|
35
|
+
CACHE_DAYS = 7
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def get_api_key():
|
|
39
|
+
key = os.environ.get("SERPAPI_KEY")
|
|
40
|
+
if not key:
|
|
41
|
+
print("Error: SERPAPI_KEY environment variable not set.")
|
|
42
|
+
print("Get a free key at https://serpapi.com/ (250 searches/month)")
|
|
43
|
+
sys.exit(1)
|
|
44
|
+
return key
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def get_cache_path(query, data_type, geo, date):
|
|
48
|
+
safe_name = f"{query}_{data_type}_{geo}_{date}".replace(" ", "_").replace("/", "_")
|
|
49
|
+
return CACHE_DIR / f"{safe_name}.json"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def load_cache(cache_path):
|
|
53
|
+
if not cache_path.exists():
|
|
54
|
+
return None
|
|
55
|
+
try:
|
|
56
|
+
data = json.loads(cache_path.read_text(encoding="utf-8"))
|
|
57
|
+
cached_at = datetime.fromisoformat(data.get("_cached_at", "2000-01-01"))
|
|
58
|
+
if datetime.now() - cached_at < timedelta(days=CACHE_DAYS):
|
|
59
|
+
return data
|
|
60
|
+
except (json.JSONDecodeError, ValueError):
|
|
61
|
+
pass
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def save_cache(cache_path, data):
|
|
66
|
+
CACHE_DIR.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
data["_cached_at"] = datetime.now().isoformat()
|
|
68
|
+
cache_path.write_text(json.dumps(data, indent=2), encoding="utf-8")
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def query_trends(query, data_type, api_key, geo="", date="today 3-m"):
|
|
72
|
+
cache_path = get_cache_path(query, data_type, geo, date)
|
|
73
|
+
cached = load_cache(cache_path)
|
|
74
|
+
if cached:
|
|
75
|
+
print(f" [cached] {data_type}")
|
|
76
|
+
return cached
|
|
77
|
+
|
|
78
|
+
params = {
|
|
79
|
+
"engine": "google_trends",
|
|
80
|
+
"q": query,
|
|
81
|
+
"data_type": data_type,
|
|
82
|
+
"date": date,
|
|
83
|
+
"api_key": api_key,
|
|
84
|
+
}
|
|
85
|
+
if geo:
|
|
86
|
+
params["geo"] = geo
|
|
87
|
+
|
|
88
|
+
print(f" [api call] {data_type}...")
|
|
89
|
+
resp = requests.get(API_BASE, params=params, timeout=30)
|
|
90
|
+
resp.raise_for_status()
|
|
91
|
+
data = resp.json()
|
|
92
|
+
|
|
93
|
+
if data.get("search_metadata", {}).get("status") != "Success":
|
|
94
|
+
error = data.get("error", "Unknown error")
|
|
95
|
+
print(f" [error] {data_type}: {error}")
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
save_cache(cache_path, data)
|
|
99
|
+
return data
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def extract_keywords(related_queries):
|
|
103
|
+
"""Extract and categorize keywords from RELATED_QUERIES response."""
|
|
104
|
+
if not related_queries:
|
|
105
|
+
return {"breakout": [], "high_growth": [], "moderate": [], "long_tail": [], "top": []}
|
|
106
|
+
|
|
107
|
+
rq = related_queries.get("related_queries", {})
|
|
108
|
+
rising = rq.get("rising", [])
|
|
109
|
+
top = rq.get("top", [])
|
|
110
|
+
|
|
111
|
+
question_words = ("how", "what", "why", "when", "where", "which", "can", "is", "does", "should")
|
|
112
|
+
|
|
113
|
+
breakout = []
|
|
114
|
+
high_growth = []
|
|
115
|
+
moderate = []
|
|
116
|
+
long_tail = []
|
|
117
|
+
|
|
118
|
+
for item in rising:
|
|
119
|
+
query = item.get("query", "")
|
|
120
|
+
formatted = item.get("formatted_value", "")
|
|
121
|
+
|
|
122
|
+
if formatted == "Breakout":
|
|
123
|
+
breakout.append({"query": query, "growth": "Breakout (5000%+)"})
|
|
124
|
+
elif "%" in formatted:
|
|
125
|
+
pct = int(formatted.replace("+", "").replace("%", "").replace(",", ""))
|
|
126
|
+
entry = {"query": query, "growth": formatted}
|
|
127
|
+
if pct >= 100:
|
|
128
|
+
high_growth.append(entry)
|
|
129
|
+
elif pct >= 50:
|
|
130
|
+
moderate.append(entry)
|
|
131
|
+
|
|
132
|
+
if query.lower().startswith(question_words):
|
|
133
|
+
long_tail.append(query)
|
|
134
|
+
|
|
135
|
+
# Also check top queries for long-tail
|
|
136
|
+
for item in top:
|
|
137
|
+
query = item.get("query", "")
|
|
138
|
+
if query.lower().startswith(question_words) and query not in long_tail:
|
|
139
|
+
long_tail.append(query)
|
|
140
|
+
|
|
141
|
+
top_kws = [{"query": item["query"], "score": item.get("value", 0)} for item in top[:10]]
|
|
142
|
+
|
|
143
|
+
return {
|
|
144
|
+
"breakout": breakout,
|
|
145
|
+
"high_growth": high_growth,
|
|
146
|
+
"moderate": moderate,
|
|
147
|
+
"long_tail": long_tail,
|
|
148
|
+
"top": top_kws,
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def extract_topics(related_topics):
|
|
153
|
+
"""Extract topics from RELATED_TOPICS response."""
|
|
154
|
+
if not related_topics:
|
|
155
|
+
return {"rising": [], "top": []}
|
|
156
|
+
|
|
157
|
+
rt = related_topics.get("related_topics", {})
|
|
158
|
+
|
|
159
|
+
rising = [
|
|
160
|
+
{"title": item["topic"]["title"], "type": item["topic"].get("type", "Topic"),
|
|
161
|
+
"growth": item.get("formatted_value", "")}
|
|
162
|
+
for item in rt.get("rising", [])
|
|
163
|
+
if "topic" in item
|
|
164
|
+
]
|
|
165
|
+
|
|
166
|
+
top = [
|
|
167
|
+
{"title": item["topic"]["title"], "type": item["topic"].get("type", "Topic"),
|
|
168
|
+
"score": item.get("extracted_value", 0)}
|
|
169
|
+
for item in rt.get("top", [])
|
|
170
|
+
if "topic" in item
|
|
171
|
+
]
|
|
172
|
+
|
|
173
|
+
return {"rising": rising[:10], "top": top[:10]}
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def check_trend_direction(timeseries_data):
|
|
177
|
+
"""Analyze TIMESERIES data to determine if trend is rising or falling."""
|
|
178
|
+
if not timeseries_data:
|
|
179
|
+
return None
|
|
180
|
+
|
|
181
|
+
timeline = timeseries_data.get("interest_over_time", {}).get("timeline_data", [])
|
|
182
|
+
if len(timeline) < 4:
|
|
183
|
+
return None
|
|
184
|
+
|
|
185
|
+
values = [entry["values"][0]["extracted_value"] for entry in timeline if entry.get("values")]
|
|
186
|
+
if not values:
|
|
187
|
+
return None
|
|
188
|
+
|
|
189
|
+
midpoint = len(values) // 2
|
|
190
|
+
first_half_avg = sum(values[:midpoint]) / midpoint
|
|
191
|
+
second_half_avg = sum(values[midpoint:]) / (len(values) - midpoint)
|
|
192
|
+
|
|
193
|
+
if second_half_avg > first_half_avg * 1.1:
|
|
194
|
+
direction = "RISING"
|
|
195
|
+
elif second_half_avg < first_half_avg * 0.9:
|
|
196
|
+
direction = "DECLINING"
|
|
197
|
+
else:
|
|
198
|
+
direction = "STABLE"
|
|
199
|
+
|
|
200
|
+
return {
|
|
201
|
+
"direction": direction,
|
|
202
|
+
"early_avg": round(first_half_avg, 1),
|
|
203
|
+
"recent_avg": round(second_half_avg, 1),
|
|
204
|
+
"change_pct": round(((second_half_avg - first_half_avg) / max(first_half_avg, 1)) * 100, 1),
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
def select_primary_keyword(keywords, original_query):
|
|
209
|
+
"""Select the best primary keyword for blog targeting."""
|
|
210
|
+
if keywords["breakout"]:
|
|
211
|
+
return keywords["breakout"][0]["query"], "BREAKOUT"
|
|
212
|
+
if keywords["high_growth"]:
|
|
213
|
+
return keywords["high_growth"][0]["query"], "HIGH_GROWTH"
|
|
214
|
+
if keywords["top"]:
|
|
215
|
+
return keywords["top"][0]["query"], "TOP"
|
|
216
|
+
return original_query, "ORIGINAL"
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def print_report(query, keywords, topics, trend=None):
|
|
220
|
+
"""Print a formatted keyword research report."""
|
|
221
|
+
primary, priority = select_primary_keyword(keywords, query)
|
|
222
|
+
|
|
223
|
+
print("\n" + "=" * 60)
|
|
224
|
+
print(f" KEYWORD RESEARCH REPORT: {query}")
|
|
225
|
+
print("=" * 60)
|
|
226
|
+
|
|
227
|
+
print(f"\n PRIMARY KEYWORD: {primary}")
|
|
228
|
+
print(f" PRIORITY LEVEL: {priority}")
|
|
229
|
+
|
|
230
|
+
if keywords["breakout"]:
|
|
231
|
+
print(f"\n BREAKOUT KEYWORDS (5000%+ growth):")
|
|
232
|
+
for kw in keywords["breakout"]:
|
|
233
|
+
print(f" >>> {kw['query']} — {kw['growth']}")
|
|
234
|
+
|
|
235
|
+
if keywords["high_growth"]:
|
|
236
|
+
print(f"\n HIGH-GROWTH KEYWORDS (100%+):")
|
|
237
|
+
for kw in keywords["high_growth"]:
|
|
238
|
+
print(f" >> {kw['query']} — {kw['growth']}")
|
|
239
|
+
|
|
240
|
+
if keywords["moderate"]:
|
|
241
|
+
print(f"\n MODERATE-GROWTH KEYWORDS (50-99%):")
|
|
242
|
+
for kw in keywords["moderate"]:
|
|
243
|
+
print(f" > {kw['query']} — {kw['growth']}")
|
|
244
|
+
|
|
245
|
+
if keywords["long_tail"]:
|
|
246
|
+
print(f"\n LONG-TAIL KEYWORDS (question-based):")
|
|
247
|
+
for q in keywords["long_tail"][:8]:
|
|
248
|
+
print(f" ? {q}")
|
|
249
|
+
|
|
250
|
+
if keywords["top"]:
|
|
251
|
+
print(f"\n TOP QUERIES (by popularity):")
|
|
252
|
+
for kw in keywords["top"][:5]:
|
|
253
|
+
print(f" - {kw['query']} (score: {kw['score']})")
|
|
254
|
+
|
|
255
|
+
if topics["rising"]:
|
|
256
|
+
print(f"\n RISING TOPICS (use as H2 headings):")
|
|
257
|
+
for t in topics["rising"][:5]:
|
|
258
|
+
print(f" ^ {t['title']} — {t['growth']}")
|
|
259
|
+
|
|
260
|
+
if topics["top"]:
|
|
261
|
+
print(f"\n TOP TOPICS:")
|
|
262
|
+
for t in topics["top"][:5]:
|
|
263
|
+
print(f" - {t['title']} (score: {t['score']})")
|
|
264
|
+
|
|
265
|
+
if trend:
|
|
266
|
+
arrow = {"RISING": "^", "DECLINING": "v", "STABLE": "="}
|
|
267
|
+
print(f"\n TREND DIRECTION: {arrow.get(trend['direction'], '?')} {trend['direction']}")
|
|
268
|
+
print(f" Early avg: {trend['early_avg']} -> Recent avg: {trend['recent_avg']} ({trend['change_pct']:+}%)")
|
|
269
|
+
|
|
270
|
+
# Blog structure suggestion
|
|
271
|
+
h2_topics = [t["title"] for t in (topics["rising"] + topics["top"])[:5]]
|
|
272
|
+
h3_questions = keywords["long_tail"][:8]
|
|
273
|
+
|
|
274
|
+
print(f"\n SUGGESTED BLOG STRUCTURE:")
|
|
275
|
+
print(f" Title: {primary} — Complete Guide {datetime.now().year}")
|
|
276
|
+
if h2_topics:
|
|
277
|
+
for i, topic in enumerate(h2_topics, 1):
|
|
278
|
+
print(f" H2 [{i}]: {topic}")
|
|
279
|
+
matching = [q for q in h3_questions if any(word in q.lower() for word in topic.lower().split())]
|
|
280
|
+
for q in matching[:2]:
|
|
281
|
+
print(f" H3: {q}")
|
|
282
|
+
print()
|
|
283
|
+
|
|
284
|
+
print("=" * 60)
|
|
285
|
+
return {
|
|
286
|
+
"primary_keyword": primary,
|
|
287
|
+
"priority": priority,
|
|
288
|
+
"keywords": keywords,
|
|
289
|
+
"topics": topics,
|
|
290
|
+
"trend": trend,
|
|
291
|
+
"h2_suggestions": h2_topics,
|
|
292
|
+
"h3_suggestions": h3_questions,
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
|
|
296
|
+
def main():
|
|
297
|
+
parser = argparse.ArgumentParser(description="Discover trending keywords via Google Trends")
|
|
298
|
+
parser.add_argument("query", help="The topic to research")
|
|
299
|
+
parser.add_argument("--geo", default="", help="Geographic filter (e.g., US, GB, US-CA)")
|
|
300
|
+
parser.add_argument("--date", default="today 3-m", help="Time range (default: today 3-m)")
|
|
301
|
+
parser.add_argument("--full", action="store_true", help="Include timeseries validation (uses extra API credit)")
|
|
302
|
+
parser.add_argument("--json", action="store_true", help="Output results as JSON")
|
|
303
|
+
parser.add_argument("--no-cache", action="store_true", help="Skip cache, force fresh API calls")
|
|
304
|
+
args = parser.parse_args()
|
|
305
|
+
|
|
306
|
+
if args.no_cache:
|
|
307
|
+
global CACHE_DAYS
|
|
308
|
+
CACHE_DAYS = 0
|
|
309
|
+
|
|
310
|
+
api_key = get_api_key()
|
|
311
|
+
|
|
312
|
+
print(f"\nResearching: \"{args.query}\"")
|
|
313
|
+
print(f"Region: {args.geo or 'Worldwide'} | Date: {args.date}")
|
|
314
|
+
print("-" * 40)
|
|
315
|
+
|
|
316
|
+
# Required calls (2 API credits)
|
|
317
|
+
rq_data = query_trends(args.query, "RELATED_QUERIES", api_key, args.geo, args.date)
|
|
318
|
+
rt_data = query_trends(args.query, "RELATED_TOPICS", api_key, args.geo, args.date)
|
|
319
|
+
|
|
320
|
+
keywords = extract_keywords(rq_data)
|
|
321
|
+
topics = extract_topics(rt_data)
|
|
322
|
+
|
|
323
|
+
# Optional: trend validation (1 extra API credit)
|
|
324
|
+
trend = None
|
|
325
|
+
if args.full:
|
|
326
|
+
ts_data = query_trends(args.query, "TIMESERIES", api_key, args.geo, "today 12-m")
|
|
327
|
+
trend = check_trend_direction(ts_data)
|
|
328
|
+
|
|
329
|
+
if args.json:
|
|
330
|
+
result = {
|
|
331
|
+
"query": args.query,
|
|
332
|
+
"primary_keyword": select_primary_keyword(keywords, args.query)[0],
|
|
333
|
+
"priority": select_primary_keyword(keywords, args.query)[1],
|
|
334
|
+
"keywords": keywords,
|
|
335
|
+
"topics": topics,
|
|
336
|
+
"trend": trend,
|
|
337
|
+
}
|
|
338
|
+
print(json.dumps(result, indent=2))
|
|
339
|
+
else:
|
|
340
|
+
print_report(args.query, keywords, topics, trend)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
if __name__ == "__main__":
|
|
344
|
+
main()
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: seo-keyword-research
|
|
3
|
+
description: SEO keyword research workflow for blog generation using Google Trends data. Use when writing blog posts, planning content calendars, or optimizing articles for search engines. Finds breakout keywords, builds content structure, and generates SEO-optimized blog outlines targeting tech and developer audiences.
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: Requires the google-trends-api skill (or direct SerpApi access with SERPAPI_KEY). Designed for Claude Code and similar AI coding agents.
|
|
6
|
+
metadata:
|
|
7
|
+
author: farizanjum
|
|
8
|
+
version: "2.0"
|
|
9
|
+
domain: tech-developer-blogs
|
|
10
|
+
allowed-tools: Bash(python:*) Bash(curl:*) Read
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# SEO Keyword Research Skill
|
|
14
|
+
|
|
15
|
+
Find trending, high-opportunity keywords BEFORE writing blog content. This skill turns generic blog topics into SEO-optimized content that ranks.
|
|
16
|
+
|
|
17
|
+
## When to Use
|
|
18
|
+
|
|
19
|
+
- User asks to write a blog post or article
|
|
20
|
+
- User wants keyword research for a topic
|
|
21
|
+
- User needs a content calendar or content plan
|
|
22
|
+
- User wants to optimize existing content for SEO
|
|
23
|
+
- Any blog generation task for a tech/developer-focused audience
|
|
24
|
+
|
|
25
|
+
## Core Principle
|
|
26
|
+
|
|
27
|
+
**Always research keywords BEFORE generating blog content.**
|
|
28
|
+
|
|
29
|
+
```
|
|
30
|
+
BAD: Write blog -> Hope it ranks -> Usually doesn't
|
|
31
|
+
GOOD: Research keywords -> Find breakout opportunity -> Write optimized blog -> Ranks well
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Keyword Priority System
|
|
35
|
+
|
|
36
|
+
When analyzing Google Trends RELATED_QUERIES results, prioritize keywords in this order:
|
|
37
|
+
|
|
38
|
+
### 1. Breakout Keywords — HIGHEST priority
|
|
39
|
+
- `formatted_value: "Breakout"` = 5000%+ growth
|
|
40
|
+
- Very low competition (trend is new)
|
|
41
|
+
- Use as PRIMARY blog keyword
|
|
42
|
+
- Create content IMMEDIATELY (first-mover advantage)
|
|
43
|
+
|
|
44
|
+
### 2. High-Growth Keywords — VERY HIGH priority
|
|
45
|
+
- `formatted_value: "+100%"` or higher
|
|
46
|
+
- Low to moderate competition
|
|
47
|
+
- Use as primary or strong secondary keyword
|
|
48
|
+
- Create content within 2-4 weeks
|
|
49
|
+
|
|
50
|
+
### 3. Moderate-Growth Keywords — HIGH priority
|
|
51
|
+
- `formatted_value: "+50%"` to `"+99%"`
|
|
52
|
+
- Moderate competition
|
|
53
|
+
- Use as secondary keywords in body content
|
|
54
|
+
|
|
55
|
+
### 4. Long-Tail Keywords — STRATEGIC priority
|
|
56
|
+
- Question-based queries (how, what, why, when, where)
|
|
57
|
+
- Low competition, high conversion
|
|
58
|
+
- Use as H3 headings — target featured snippets and voice search
|
|
59
|
+
|
|
60
|
+
### 5. Established Keywords — MODERATE priority
|
|
61
|
+
- Top queries with stable interest, high competition
|
|
62
|
+
- Use in body content, not as primary target
|
|
63
|
+
|
|
64
|
+
## SEO Research Workflow
|
|
65
|
+
|
|
66
|
+
### Step 1: Keyword Discovery (1 API call — REQUIRED)
|
|
67
|
+
|
|
68
|
+
Query `RELATED_QUERIES` with the user's blog topic:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
curl -s "https://serpapi.com/search?engine=google_trends&q=USER_TOPIC&data_type=RELATED_QUERIES&date=today+3-m&api_key=$SERPAPI_KEY"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
From the response, extract:
|
|
75
|
+
- **Breakout keywords** → candidate primary keywords
|
|
76
|
+
- **High-growth keywords** (+100%) → secondary candidates
|
|
77
|
+
- **Question-based queries** → H3 headings and featured snippet targets
|
|
78
|
+
|
|
79
|
+
Select the primary keyword:
|
|
80
|
+
1. First breakout keyword (if any)
|
|
81
|
+
2. Else first high-growth keyword
|
|
82
|
+
3. Else top query by score
|
|
83
|
+
|
|
84
|
+
### Step 2: Content Structure (1 API call — REQUIRED)
|
|
85
|
+
|
|
86
|
+
Query `RELATED_TOPICS` with the same topic:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
curl -s "https://serpapi.com/search?engine=google_trends&q=USER_TOPIC&data_type=RELATED_TOPICS&date=today+3-m&api_key=$SERPAPI_KEY"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Extract topic titles from rising + top results. These become your H2 section headings (pick 3-5).
|
|
93
|
+
|
|
94
|
+
### Step 3: Trend Validation (1 API call — OPTIONAL)
|
|
95
|
+
|
|
96
|
+
Only if choosing between multiple candidate keywords or validating viability:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
curl -s "https://serpapi.com/search?engine=google_trends&q=KEYWORD&data_type=TIMESERIES&date=today+12-m&api_key=$SERPAPI_KEY"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Compare recent 2-month average vs. earlier 2-month average. If recent > earlier, trend is rising — proceed. If declining, consider a different keyword.
|
|
103
|
+
|
|
104
|
+
### Step 4: Generate Blog Outline
|
|
105
|
+
|
|
106
|
+
Build the outline using this structure:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
Title: [Primary Keyword] — [Benefit/Number] [Year]
|
|
110
|
+
(max 60 characters, must include primary keyword)
|
|
111
|
+
|
|
112
|
+
Meta Description: (150-160 chars, primary + 1-2 secondary keywords)
|
|
113
|
+
|
|
114
|
+
# [H1 — same as or variation of title]
|
|
115
|
+
|
|
116
|
+
## Introduction (150 words)
|
|
117
|
+
- Primary keyword in first 100 words
|
|
118
|
+
- Hook with a problem or question
|
|
119
|
+
- Preview what they'll learn
|
|
120
|
+
|
|
121
|
+
## [H2: Related Topic 1 from Step 2]
|
|
122
|
+
### [H3: Long-tail question from Step 1]
|
|
123
|
+
Content answering the question (150-200 words)
|
|
124
|
+
### [H3: Another long-tail question]
|
|
125
|
+
Content (150-200 words)
|
|
126
|
+
|
|
127
|
+
## [H2: Related Topic 2]
|
|
128
|
+
### [H3: Long-tail question]
|
|
129
|
+
### [H3: Long-tail question]
|
|
130
|
+
|
|
131
|
+
## [H2: Related Topic 3]
|
|
132
|
+
### [H3: Long-tail question]
|
|
133
|
+
### [H3: Long-tail question]
|
|
134
|
+
|
|
135
|
+
## Conclusion (100 words)
|
|
136
|
+
- Summarize key points
|
|
137
|
+
- Primary keyword mentioned once
|
|
138
|
+
- Call-to-action
|
|
139
|
+
|
|
140
|
+
Target: 1500-2500 words total
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
## Keyword Placement Rules
|
|
144
|
+
|
|
145
|
+
| Location | Rule |
|
|
146
|
+
|----------|------|
|
|
147
|
+
| Title | Include primary keyword, max 60 chars |
|
|
148
|
+
| H1 | Same as title or slight variation |
|
|
149
|
+
| H2 headings (3-5) | Use related topics, natural language |
|
|
150
|
+
| H3 headings (8-12) | Use long-tail keywords, question format |
|
|
151
|
+
| First paragraph | Primary keyword in first 100 words |
|
|
152
|
+
| Body content | Primary keyword 1-2% density, secondary 0.5-1% |
|
|
153
|
+
| Conclusion | Primary keyword once |
|
|
154
|
+
| Meta description | Primary + 1-2 secondary, 150-160 chars |
|
|
155
|
+
|
|
156
|
+
**Never keyword-stuff.** Content must read naturally. Google penalizes unnatural repetition.
|
|
157
|
+
|
|
158
|
+
## Quality Checklist
|
|
159
|
+
|
|
160
|
+
Before generating the blog, verify:
|
|
161
|
+
|
|
162
|
+
- [ ] Found at least 1 breakout or +100% keyword (or justified using established keyword)
|
|
163
|
+
- [ ] Have 3-5 H2 topics from RELATED_TOPICS
|
|
164
|
+
- [ ] Have long-tail keywords for H3 headings
|
|
165
|
+
- [ ] Primary keyword is specific enough to rank for
|
|
166
|
+
- [ ] Blog structure follows the outline template above
|
|
167
|
+
- [ ] Meta description is written (150-160 chars)
|
|
168
|
+
- [ ] Target length is 1500-2500 words
|
|
169
|
+
|
|
170
|
+
If no breakout or high-growth keywords exist for the topic, inform the user that SEO opportunity is limited and suggest alternative angles or related topics that do have growth.
|
|
171
|
+
|
|
172
|
+
## Budget Awareness
|
|
173
|
+
|
|
174
|
+
**Free tier: 250 searches/month**
|
|
175
|
+
|
|
176
|
+
| Strategy | Calls/Blog | Monthly Capacity |
|
|
177
|
+
|----------|-----------|-----------------|
|
|
178
|
+
| Minimal (recommended) | 2 | 125 blogs |
|
|
179
|
+
| Standard | 3 | 83 blogs |
|
|
180
|
+
| Complete | 4 | 62 blogs |
|
|
181
|
+
|
|
182
|
+
Default to 2 calls (RELATED_QUERIES + RELATED_TOPICS). Only add TIMESERIES or GEO_MAP when specifically needed.
|
|
183
|
+
|
|
184
|
+
## Common Mistakes to Avoid
|
|
185
|
+
|
|
186
|
+
1. **Skipping research** — writing without checking trends misses breakout opportunities
|
|
187
|
+
2. **Ignoring breakout keywords** — using a generic term when a breakout variant exists
|
|
188
|
+
3. **Keyword stuffing** — repeating keywords unnaturally; keep density at 1-2%
|
|
189
|
+
4. **No long-tail keywords** — missing featured snippet and voice search opportunities
|
|
190
|
+
5. **Generic H2 headings** — always use RELATED_TOPICS data for section structure
|
|
191
|
+
|
|
192
|
+
## Example Script
|
|
193
|
+
|
|
194
|
+
For a complete working example, run:
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
python scripts/blog_seo_research.py "your blog topic"
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
See [scripts/blog_seo_research.py](scripts/blog_seo_research.py) for the implementation.
|
|
201
|
+
|
|
202
|
+
## References
|
|
203
|
+
|
|
204
|
+
- [references/keyword-placement-guide.md](references/keyword-placement-guide.md) — Detailed placement rules and examples
|
|
205
|
+
- [references/tech-blog-examples.md](references/tech-blog-examples.md) — Real-world examples for tech/developer blogs
|
package/skills/google-trends-api-skills/seo-keyword-research/references/keyword-placement-guide.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# Keyword Placement Guide
|
|
2
|
+
|
|
3
|
+
Detailed rules and examples for placing SEO keywords in blog content.
|
|
4
|
+
|
|
5
|
+
## Title (max 60 characters)
|
|
6
|
+
|
|
7
|
+
**Format**: `[Primary Keyword] — [Benefit/Number] [Year]`
|
|
8
|
+
|
|
9
|
+
**Good examples**:
|
|
10
|
+
- "Kubernetes Alternatives — 10 Best Options in 2026"
|
|
11
|
+
- "AI Code Review Tools — Complete Developer Guide"
|
|
12
|
+
- "Rust vs Go — Performance Comparison for Backend"
|
|
13
|
+
|
|
14
|
+
**Bad examples**:
|
|
15
|
+
- "Everything You Need to Know About Kubernetes" (no keyword focus)
|
|
16
|
+
- "Top Kubernetes Kubernetes Tools Kubernetes Guide" (stuffed)
|
|
17
|
+
|
|
18
|
+
## H1 Heading
|
|
19
|
+
|
|
20
|
+
Same as title or a slight variation. One H1 per page.
|
|
21
|
+
|
|
22
|
+
## H2 Headings (3-5 per article)
|
|
23
|
+
|
|
24
|
+
Source these from RELATED_TOPICS API results. Use natural language.
|
|
25
|
+
|
|
26
|
+
**Example**: If researching "kubernetes deployment", RELATED_TOPICS might return:
|
|
27
|
+
- "Container Orchestration" -> H2: Container Orchestration with Kubernetes
|
|
28
|
+
- "Docker" -> H2: Docker vs Kubernetes for Deployment
|
|
29
|
+
- "Helm" -> H2: Using Helm Charts for Kubernetes Deployment
|
|
30
|
+
- "CI/CD" -> H2: CI/CD Pipelines with Kubernetes
|
|
31
|
+
|
|
32
|
+
## H3 Headings (2-3 per H2 section)
|
|
33
|
+
|
|
34
|
+
Source from RELATED_QUERIES long-tail keywords. Prefer question format.
|
|
35
|
+
|
|
36
|
+
**Example**: Under "Container Orchestration with Kubernetes":
|
|
37
|
+
- H3: What Is Container Orchestration?
|
|
38
|
+
- H3: How Does Kubernetes Handle Container Scaling?
|
|
39
|
+
|
|
40
|
+
## First Paragraph
|
|
41
|
+
|
|
42
|
+
Include primary keyword within the first 100 words. Make it natural.
|
|
43
|
+
|
|
44
|
+
**Good**:
|
|
45
|
+
> Looking for the best **kubernetes deployment** strategies? As container adoption grows, understanding how to deploy applications on Kubernetes has become essential for modern engineering teams.
|
|
46
|
+
|
|
47
|
+
**Bad**:
|
|
48
|
+
> Kubernetes deployment is important. If you want kubernetes deployment, you need to learn about kubernetes deployment strategies for your kubernetes deployment needs.
|
|
49
|
+
|
|
50
|
+
## Body Content Density
|
|
51
|
+
|
|
52
|
+
**Primary keyword**: 1-2% of total word count
|
|
53
|
+
- 1500-word article = 15-30 natural mentions
|
|
54
|
+
- Spread evenly, not clustered
|
|
55
|
+
|
|
56
|
+
**Secondary keywords**: 0.5-1% each
|
|
57
|
+
- Use 2-4 secondary keywords throughout
|
|
58
|
+
|
|
59
|
+
**Semantic variations**: Use related terms naturally
|
|
60
|
+
- "kubernetes deployment" -> also use "k8s deploy", "deploying on kubernetes", "container deployment"
|
|
61
|
+
|
|
62
|
+
## Meta Description (150-160 characters)
|
|
63
|
+
|
|
64
|
+
Include primary keyword + 1-2 secondary keywords. Make it compelling.
|
|
65
|
+
|
|
66
|
+
**Good**:
|
|
67
|
+
> Learn kubernetes deployment best practices. Covers Helm charts, CI/CD pipelines, container orchestration, and scaling strategies for production.
|
|
68
|
+
|
|
69
|
+
**Bad**:
|
|
70
|
+
> This article is about kubernetes. Read to learn more about kubernetes deployment.
|
|
71
|
+
|
|
72
|
+
## Featured Snippet Optimization
|
|
73
|
+
|
|
74
|
+
For each long-tail H3 keyword:
|
|
75
|
+
1. Answer the question directly in 40-60 words immediately after the heading
|
|
76
|
+
2. Use bullet points or numbered lists
|
|
77
|
+
3. Provide a concise answer first, then expand
|
|
78
|
+
|
|
79
|
+
**Example**:
|
|
80
|
+
```markdown
|
|
81
|
+
### What Is Container Orchestration?
|
|
82
|
+
|
|
83
|
+
Container orchestration automates the deployment, scaling, networking,
|
|
84
|
+
and management of containerized applications across clusters of machines.
|
|
85
|
+
Tools like Kubernetes handle scheduling, load balancing, and self-healing,
|
|
86
|
+
allowing teams to manage thousands of containers efficiently.
|
|
87
|
+
|
|
88
|
+
In more detail, container orchestration solves several key challenges...
|
|
89
|
+
```
|