@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.
Files changed (212) hide show
  1. package/.claude/skills/claude-md-generator/.env.example +7 -0
  2. package/.claude/skills/claude-md-generator/README.md +78 -0
  3. package/.claude/skills/claude-md-generator/SKILL.md +248 -0
  4. package/.claude/skills/claude-md-generator/evals/evals.json +35 -0
  5. package/.claude/skills/claude-md-generator/references/section-guide.md +175 -0
  6. package/dist/e2e.test.d.ts +1 -0
  7. package/dist/e2e.test.js +62 -0
  8. package/dist/fs-adapters.d.ts +4 -0
  9. package/dist/fs-adapters.js +101 -0
  10. package/dist/fs-adapters.test.d.ts +1 -0
  11. package/dist/fs-adapters.test.js +108 -0
  12. package/dist/index.d.ts +2 -0
  13. package/dist/index.js +211 -0
  14. package/dist/transformers.d.ts +6 -0
  15. package/dist/transformers.js +2 -0
  16. package/package.json +25 -0
  17. package/registry.json +226 -0
  18. package/skills/blog-cover-image-cli/.github/workflows/publish.yml +19 -0
  19. package/skills/blog-cover-image-cli/LICENSE +15 -0
  20. package/skills/blog-cover-image-cli/README.md +126 -0
  21. package/skills/blog-cover-image-cli/SKILL.md +7 -0
  22. package/skills/blog-cover-image-cli/agent-skill/blog-cover-generator/README.md +30 -0
  23. package/skills/blog-cover-image-cli/agent-skill/blog-cover-generator/SKILL.md +72 -0
  24. package/skills/blog-cover-image-cli/bin/cli.js +226 -0
  25. package/skills/blog-cover-image-cli/examples/100x_UX_Research_AI_Agent.png +0 -0
  26. package/skills/blog-cover-image-cli/examples/Firecrawl-supabase-bolt.png +0 -0
  27. package/skills/blog-cover-image-cli/examples/Git-City_Case_study_Cover_Image.jpg +0 -0
  28. package/skills/blog-cover-image-cli/examples/THE DISTRIBUTION LAYER (2).png +0 -0
  29. package/skills/blog-cover-image-cli/examples/canva-perplexity-duolingo-cover-image.png +0 -0
  30. package/skills/blog-cover-image-cli/examples/gamma-mistral-veed.png +0 -0
  31. package/skills/blog-cover-image-cli/examples/server-survival-case-study-cover-image(1).png +0 -0
  32. package/skills/blog-cover-image-cli/examples/viral-meme-automation.png +0 -0
  33. package/skills/blog-cover-image-cli/index.js +2 -0
  34. package/skills/blog-cover-image-cli/package-lock.json +2238 -0
  35. package/skills/blog-cover-image-cli/package.json +37 -0
  36. package/skills/blog-cover-image-cli/src/geminiGenerator.js +126 -0
  37. package/skills/blog-cover-image-cli/src/imageValidator.js +54 -0
  38. package/skills/blog-cover-image-cli/src/logoFetcher.js +86 -0
  39. package/skills/claude-md-generator/.env.example +7 -0
  40. package/skills/claude-md-generator/README.md +78 -0
  41. package/skills/claude-md-generator/SKILL.md +254 -0
  42. package/skills/claude-md-generator/evals/evals.json +35 -0
  43. package/skills/claude-md-generator/references/section-guide.md +175 -0
  44. package/skills/cook-the-blog/README.md +86 -0
  45. package/skills/cook-the-blog/SKILL.md +130 -0
  46. package/skills/dependency-update-bot/.env.example +13 -0
  47. package/skills/dependency-update-bot/README.md +101 -0
  48. package/skills/dependency-update-bot/SKILL.md +376 -0
  49. package/skills/dependency-update-bot/evals/evals.json +45 -0
  50. package/skills/dependency-update-bot/references/changelog-patterns.md +201 -0
  51. package/skills/docs-from-code/.env.example +13 -0
  52. package/skills/docs-from-code/README.md +97 -0
  53. package/skills/docs-from-code/SKILL.md +160 -0
  54. package/skills/docs-from-code/evals/evals.json +29 -0
  55. package/skills/docs-from-code/references/extraction-guide.md +174 -0
  56. package/skills/docs-from-code/references/output-template.md +135 -0
  57. package/skills/docs-from-code/scripts/extract_py.py +238 -0
  58. package/skills/docs-from-code/scripts/extract_ts.ts +284 -0
  59. package/skills/docs-from-code/scripts/package.json +18 -0
  60. package/skills/explain-this-pr/README.md +74 -0
  61. package/skills/explain-this-pr/SKILL.md +130 -0
  62. package/skills/explain-this-pr/evals/evals.json +35 -0
  63. package/skills/google-trends-api-skills/README.md +78 -0
  64. package/skills/google-trends-api-skills/SKILL.md +7 -0
  65. package/skills/google-trends-api-skills/google-trends-api/SKILL.md +163 -0
  66. package/skills/google-trends-api-skills/google-trends-api/references/api-responses.md +188 -0
  67. package/skills/google-trends-api-skills/google-trends-api/scripts/discover_keywords.py +344 -0
  68. package/skills/google-trends-api-skills/seo-keyword-research/SKILL.md +205 -0
  69. package/skills/google-trends-api-skills/seo-keyword-research/references/keyword-placement-guide.md +89 -0
  70. package/skills/google-trends-api-skills/seo-keyword-research/references/tech-blog-examples.md +207 -0
  71. package/skills/google-trends-api-skills/seo-keyword-research/scripts/blog_seo_research.py +373 -0
  72. package/skills/hackernews-intel/.env.example +33 -0
  73. package/skills/hackernews-intel/README.md +161 -0
  74. package/skills/hackernews-intel/SKILL.md +156 -0
  75. package/skills/hackernews-intel/evals/evals.json +35 -0
  76. package/skills/hackernews-intel/package.json +15 -0
  77. package/skills/hackernews-intel/scripts/monitor-hn.js +258 -0
  78. package/skills/kill-the-standup/.env.example +22 -0
  79. package/skills/kill-the-standup/README.md +84 -0
  80. package/skills/kill-the-standup/SKILL.md +169 -0
  81. package/skills/kill-the-standup/evals/evals.json +35 -0
  82. package/skills/kill-the-standup/references/standup-format.md +102 -0
  83. package/skills/linkedin-post-generator/.env.example +14 -0
  84. package/skills/linkedin-post-generator/README.md +107 -0
  85. package/skills/linkedin-post-generator/SKILL.md +228 -0
  86. package/skills/linkedin-post-generator/evals/evals.json +35 -0
  87. package/skills/linkedin-post-generator/references/linkedin-format.md +216 -0
  88. package/skills/linkedin-post-generator/references/output-template.md +154 -0
  89. package/skills/llms-txt-generator/.env.example +18 -0
  90. package/skills/llms-txt-generator/README.md +142 -0
  91. package/skills/llms-txt-generator/SKILL.md +176 -0
  92. package/skills/llms-txt-generator/evals/evals.json +35 -0
  93. package/skills/llms-txt-generator/references/llms-txt-spec.md +88 -0
  94. package/skills/llms-txt-generator/references/output-template.md +76 -0
  95. package/skills/llms-txt-generator/test-output/genzcareer.in/llms.txt +31 -0
  96. package/skills/luma-attendees-scraper/README.md +170 -0
  97. package/skills/luma-attendees-scraper/SKILL.md +7 -0
  98. package/skills/luma-attendees-scraper/luma_attendees_export.js +223 -0
  99. package/skills/meeting-brief-generator/.env.example +21 -0
  100. package/skills/meeting-brief-generator/README.md +90 -0
  101. package/skills/meeting-brief-generator/SKILL.md +275 -0
  102. package/skills/meeting-brief-generator/evals/evals.json +35 -0
  103. package/skills/meeting-brief-generator/references/brief-format.md +114 -0
  104. package/skills/meeting-brief-generator/references/output-template.md +150 -0
  105. package/skills/meta-ads-skill/README.md +100 -0
  106. package/skills/meta-ads-skill/SKILL.md +7 -0
  107. package/skills/meta-ads-skill/meta-ads-skill/SKILL.md +41 -0
  108. package/skills/meta-ads-skill/meta-ads-skill/references/report_templates.md +47 -0
  109. package/skills/meta-ads-skill/meta-ads-skill/references/workflows.md +51 -0
  110. package/skills/meta-ads-skill/meta-ads-skill/scripts/auth_check.py +22 -0
  111. package/skills/meta-ads-skill/meta-ads-skill/scripts/formatters.py +46 -0
  112. package/skills/newsletter-digest/.env.example +20 -0
  113. package/skills/newsletter-digest/README.md +147 -0
  114. package/skills/newsletter-digest/SKILL.md +221 -0
  115. package/skills/newsletter-digest/evals/evals.json +35 -0
  116. package/skills/newsletter-digest/feeds.json +7 -0
  117. package/skills/newsletter-digest/package.json +15 -0
  118. package/skills/newsletter-digest/references/digest-format.md +123 -0
  119. package/skills/newsletter-digest/references/output-template.md +136 -0
  120. package/skills/newsletter-digest/scripts/fetch-feeds.js +141 -0
  121. package/skills/newsletter-digest/scripts/ghost-publish.js +147 -0
  122. package/skills/noise2blog/.env.example +16 -0
  123. package/skills/noise2blog/README.md +107 -0
  124. package/skills/noise2blog/SKILL.md +229 -0
  125. package/skills/noise2blog/evals/evals.json +35 -0
  126. package/skills/noise2blog/references/blog-format.md +188 -0
  127. package/skills/noise2blog/references/output-template.md +184 -0
  128. package/skills/outreach-sequence-builder/.env.example +12 -0
  129. package/skills/outreach-sequence-builder/README.md +108 -0
  130. package/skills/outreach-sequence-builder/SKILL.md +248 -0
  131. package/skills/outreach-sequence-builder/evals/evals.json +36 -0
  132. package/skills/outreach-sequence-builder/references/output-template.md +171 -0
  133. package/skills/outreach-sequence-builder/references/sequence-format.md +167 -0
  134. package/skills/outreach-sequence-builder/references/signal-playbook.md +117 -0
  135. package/skills/position-me/README.md +71 -0
  136. package/skills/position-me/SKILL.md +7 -0
  137. package/skills/position-me/position-me/SKILL.md +50 -0
  138. package/skills/position-me/position-me/references/EVALUATION_SOP.md +40 -0
  139. package/skills/position-me/position-me/references/REPORT_TEMPLATE.md +58 -0
  140. package/skills/position-me/position-me/scripts/extract_links.py +49 -0
  141. package/skills/pr-description-writer/README.md +81 -0
  142. package/skills/pr-description-writer/SKILL.md +141 -0
  143. package/skills/pr-description-writer/evals/evals.json +35 -0
  144. package/skills/pr-description-writer/references/pr-format-guide.md +145 -0
  145. package/skills/producthunt-launch-kit/.env.example +7 -0
  146. package/skills/producthunt-launch-kit/README.md +95 -0
  147. package/skills/producthunt-launch-kit/SKILL.md +380 -0
  148. package/skills/producthunt-launch-kit/evals/evals.json +35 -0
  149. package/skills/producthunt-launch-kit/references/copy-rules.md +124 -0
  150. package/skills/reddit-icp-monitor/.env.example +16 -0
  151. package/skills/reddit-icp-monitor/README.md +117 -0
  152. package/skills/reddit-icp-monitor/SKILL.md +271 -0
  153. package/skills/reddit-icp-monitor/evals/evals.json +40 -0
  154. package/skills/reddit-icp-monitor/references/icp-format.md +131 -0
  155. package/skills/reddit-icp-monitor/references/reply-rules.md +110 -0
  156. package/skills/reddit-post-engine/.env.example +13 -0
  157. package/skills/reddit-post-engine/README.md +103 -0
  158. package/skills/reddit-post-engine/SKILL.md +303 -0
  159. package/skills/reddit-post-engine/evals/evals.json +35 -0
  160. package/skills/reddit-post-engine/references/subreddit-playbook.md +156 -0
  161. package/skills/schema-markup-generator/.env.example +19 -0
  162. package/skills/schema-markup-generator/README.md +114 -0
  163. package/skills/schema-markup-generator/SKILL.md +192 -0
  164. package/skills/schema-markup-generator/evals/evals.json +35 -0
  165. package/skills/schema-markup-generator/references/json-ld-spec.md +263 -0
  166. package/skills/schema-markup-generator/references/output-template.md +556 -0
  167. package/skills/show-hn-writer/.env.example +14 -0
  168. package/skills/show-hn-writer/README.md +88 -0
  169. package/skills/show-hn-writer/SKILL.md +303 -0
  170. package/skills/show-hn-writer/evals/evals.json +35 -0
  171. package/skills/show-hn-writer/references/hn-rules.md +74 -0
  172. package/skills/show-hn-writer/references/title-formulas.md +93 -0
  173. package/skills/stargazer/README.md +79 -0
  174. package/skills/stargazer/SKILL.md +7 -0
  175. package/skills/stargazer/stargazer-skill/SKILL.md +58 -0
  176. package/skills/stargazer/stargazer-skill/assets/.env.example +18 -0
  177. package/skills/stargazer/stargazer-skill/scripts/convert_to_csv.py +63 -0
  178. package/skills/stargazer/stargazer-skill/scripts/count_emails.py +52 -0
  179. package/skills/stargazer/stargazer-skill/scripts/stargazer_deep_extractor.py +450 -0
  180. package/skills/tweet-thread-from-blog/.env.example +14 -0
  181. package/skills/tweet-thread-from-blog/README.md +109 -0
  182. package/skills/tweet-thread-from-blog/SKILL.md +177 -0
  183. package/skills/tweet-thread-from-blog/evals/evals.json +35 -0
  184. package/skills/tweet-thread-from-blog/references/output-template.md +193 -0
  185. package/skills/tweet-thread-from-blog/references/thread-format.md +107 -0
  186. package/skills/twitter-GTM-find-skill/README.md +43 -0
  187. package/skills/twitter-GTM-find-skill/SKILL.md +7 -0
  188. package/skills/twitter-GTM-find-skill/twitter-GTM-find/SKILL.md +37 -0
  189. package/skills/twitter-GTM-find-skill/twitter-GTM-find/references/icp-checklist.md +35 -0
  190. package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/package.json +23 -0
  191. package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/run_pipeline.sh +8 -0
  192. package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/src/debug.ts +23 -0
  193. package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/src/extractor.ts +79 -0
  194. package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/src/icp-filter.ts +87 -0
  195. package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/src/index.ts +94 -0
  196. package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/src/scraper.ts +41 -0
  197. package/skills/twitter-GTM-find-skill/twitter-GTM-find/scripts/tsconfig.json +13 -0
  198. package/skills/yc-intent-radar-skill/README.md +39 -0
  199. package/skills/yc-intent-radar-skill/SKILL.md +7 -0
  200. package/skills/yc-intent-radar-skill/yc-jobs-scraper/SKILL.md +59 -0
  201. package/skills/yc-intent-radar-skill/yc-jobs-scraper/scripts/auth.js +29 -0
  202. package/skills/yc-intent-radar-skill/yc-jobs-scraper/scripts/db.js +62 -0
  203. package/skills/yc-intent-radar-skill/yc-jobs-scraper/scripts/export_radar_candidates.js +40 -0
  204. package/skills/yc-intent-radar-skill/yc-jobs-scraper/scripts/package-lock.json +1525 -0
  205. package/skills/yc-intent-radar-skill/yc-jobs-scraper/scripts/package.json +12 -0
  206. package/skills/yc-intent-radar-skill/yc-jobs-scraper/scripts/scraper.js +217 -0
  207. package/src/e2e.test.ts +35 -0
  208. package/src/fs-adapters.test.ts +91 -0
  209. package/src/fs-adapters.ts +65 -0
  210. package/src/index.ts +182 -0
  211. package/src/transformers.ts +6 -0
  212. 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
@@ -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
+ ```