Transforms rough notes, bullet points, voice transcripts, or tweet dumps into polished, publication-ready blog posts. Optionally enriches with Tavily research.
npx claudepluginhub varnan-tech/opendirectory --plugin opendirectory-gtm-skillsThis skill uses the workspace's default tool permissions.
Take any rough input (bullet points, voice transcripts, tweet dumps, or short drafts) and produce a polished, publication-ready blog post. Every claim traces to the source material or Tavily-verified research.
Generates publication-ready blog posts from topics or sources: listicles, tutorials, how-to guides, narratives, thought leadership. Handles research, outlining, drafting, SEO, polishing.
Writes SEO-optimized blog posts, how-to tutorials, opinion pieces, and listicles with hooks, headlines, CTAs, and structure for web readability.
Generates SEO-optimized blog posts with metadata, structured sections, readability tweaks, and linking suggestions via topic, audience, keyword prompts.
Share bugs, ideas, or general feedback.
Take any rough input (bullet points, voice transcripts, tweet dumps, or short drafts) and produce a polished, publication-ready blog post. Every claim traces to the source material or Tavily-verified research.
Critical rule: DO NOT INVENT SPECIFICS. Every claim, metric, and example in the blog post must come from the raw input or a Tavily search result. Never fabricate data, quotes, or outcomes.
Confirm required env vars are set:
echo "GEMINI_API_KEY: ${GEMINI_API_KEY:+set}"
echo "TAVILY_API_KEY: ${TAVILY_API_KEY:-not set, Tavily enrichment will be skipped}"
If GEMINI_API_KEY is missing: Stop. Tell the user: "GEMINI_API_KEY is required. Get it at aistudio.google.com → Get API key. Add it to your .env file."
If TAVILY_API_KEY is missing: Continue. Note that Tavily enrichment will be skipped. The blog post will be based entirely on the provided content. This is fine for personal stories, tutorials from experience, or opinion pieces.
Confirm input is present. The user must provide one of:
If no input, ask: "Share your rough notes, bullet points, or transcript. Paste them directly, or give me a URL to fetch the source."
If input is a URL: Fetch the page content using WebFetch. Extract: title, author, publish date, all body text, key statistics, numbered lists, subheadings, quotes.
If input is pasted text: Read it directly. Identify the input type:
QA checkpoint: State before continuing:
If you cannot identify a core thesis, ask: "What's the single most important thing you want readers to take away from this?"
Four styles. Auto-detect from content signals. User override always respected.
| Style | When to use | Signals |
|---|---|---|
| Technical Tutorial | Step-by-step guide, how-to, code walkthrough | Numbered steps, commands, code snippets, "how to" in content |
| Case Study | Before/after story, build log, lessons learned | Specific results, timelines, first-person journey |
| Thought Leadership | Opinion, argument, counterintuitive claim | "I think", "the problem with X", contrarian position, debate framing |
| Explainer | What is X, why it matters, how it works | Concept-first, comparison-heavy, "most people don't know" |
Detection logic:
State chosen style and reasoning. If ambiguous, pick one and note the choice.
Skip this step silently if TAVILY_API_KEY is not set.
Search for supporting evidence for claims in the raw content that could benefit from verification or data. Good candidates:
Run one Tavily search per claim that needs verification. Limit to 3 searches maximum to avoid over-sourcing:
curl -s -X POST "https://api.tavily.com/search" \
-H "Content-Type: application/json" \
-d '{
"api_key": "'"$TAVILY_API_KEY"'",
"query": "SPECIFIC_CLAIM_OR_TOPIC",
"search_depth": "advanced",
"max_results": 5,
"include_answer": true
}'
Keep results with score >= 0.65. Extract: title, url, content snippet.
Rules for using Tavily results:
Read references/blog-format.md in full. Select the matching template from references/output-template.md. Internalize all rules before generating.
Write the Gemini request to a temp file to handle special characters safely:
cat > /tmp/noise2blog-request.json << 'ENDJSON'
{
"system_instruction": {
"parts": [{
"text": "You are a tech writer who sounds like a real person. Rules: Active voice only. Short paragraphs, 1-3 lines max, then a blank line. Use contractions naturally (don't, won't, it's, can't, you're, they're). No em dashes — use a comma or period instead. No semicolons. Every sentence needs a concrete detail: a number, a tool name, a file name, a command, a result. No filler phrases: no 'In today's rapidly evolving', no 'Let's dive in', no 'It's worth noting', no 'In conclusion', no 'I hope this was helpful'. No banned words: incredible, amazing, leveraging, synergize, game-changing, groundbreaking, revolutionary, paradigm, cutting-edge, seamless, robust, unprecedented, delve, harness, utilize, transformative, disruptive, unlock, comprehensive, actionable, crucial, pivotal. Title must not start with I, My, or We. Open with a hook paragraph that does not announce the topic. Close with something actionable. Do not invent claims, metrics, or outcomes not present in the source material."
}]
},
"contents": [{
"parts": [{
"text": "RAW_CONTENT_AND_INSTRUCTIONS_HERE"
}]
}],
"generationConfig": {
"temperature": 0.7,
"maxOutputTokens": 4096
}
}
ENDJSON
Replace RAW_CONTENT_AND_INSTRUCTIONS_HERE with:
Post the request:
curl -s -X POST \
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=$GEMINI_API_KEY" \
-H "Content-Type: application/json" \
-d @/tmp/noise2blog-request.json \
| python3 -c "import sys,json; d=json.load(sys.stdin); print(d['candidates'][0]['content']['parts'][0]['text'])"
Also produce:
Run every check and fix violations before presenting:
Fix any violation before presenting. State the final word count.
Present the full blog post in a code block.
Present the meta description and alternative title below the main post.
Ask: "Ready to copy this to your editor? If you're publishing to a specific platform, let me know and I can format the frontmatter."
On platform-specific request:
Ghost:
---
title: "POST_TITLE"
date: YYYY-MM-DD
tags: [tag1, tag2]
status: draft
---
dev.to:
---
title: POST_TITLE
description: META_DESCRIPTION
tags: [tag1, tag2, tag3]
published: false
---
Substack: Present as plain Markdown. Substack's editor imports markdown directly.
Hashnode:
---
title: POST_TITLE
subtitle: META_DESCRIPTION
tags: [tag1, tag2]
---