Use when publishing SEO blog articles to qaskills.sh, e.g. "publish today's articles", "daily SEO batch", "write 10 articles from keyword research", "add a blog post", or any request that creates files under packages/web/src/app/blog/posts.
npx skills add https://github.com/PramodDutta/qaskills --skill publish-seo-batch
The daily content pipeline: source topics -> dedup slugs -> write posts -> register -> build -> commit -> deploy -> prove live -> ping IndexNow. Default batch size is 10 unless the user says otherwise. Every step has a check; a batch is not "published" until step 8 passes, then step 9 nudges the search engines.
git -C /Users/promode/qaskills status --short # note pre-existing WIP; you will NOT stage it
date +%F # today's date, used in every post and the commit message
Pre-existing modified/untracked files are the user's WIP. Leave them alone.
Saved GSC reports in docs/seo/KEYWORD-OPPORTUNITIES-*.md are historical and fully published; do not source topics from them. Instead:
docs/seo/KEYWORD-OPPORTUNITIES-YYYY-MM.md and include it in the commit.Batch arrays are spread into the registries LAST, so a colliding slug silently replaces a real article. This has shipped a stub over a full article before.
cd /Users/promode/qaskills
for s in slug-one slug-two; do
hits=$(grep -rn "$s" packages/web/src/app/blog/posts/ | grep -v "$s-something-longer" | wc -l)
echo "$s: $hits hits"
done
Any hit (filename, posts map key, postList entry, batch array entry): choose a different slug or drop the topic. Also scan titles in posts/index.ts for near-duplicate topics; a second article on the same query cannibalizes the first.
File: packages/web/src/app/blog/posts/<slug>.ts
import type { BlogPost } from './index';
export const post: BlogPost = {
title: 'Primary Keyword In Natural Title',
description: 'Meta description, 140-170 chars, contains the keyword, states the payoff.',
date: 'YYYY-MM-DD', // today
category: 'Guide', // Guide | Reference | Tutorial | Comparison | AI Testing | API Testing | Migration | BDD | Performance
content: `
# Same Title As Above
Opening paragraphs that answer the query directly...
`,
};
Per-article bar (all required):
title\\\\\\`)/blog/<existing-slug>); verify each target exists in posts/index.ts before linking${ in prose unless intentionally interpolating; escape as \\${ if literal## Frequently Asked Questions section (3-4 ### Question? H3s with short answers) as the LAST H2. It counts toward the word bar and src/lib/extract-faqs.ts turns it into FAQPage JSON-LD for AI-search citation. Without it, articles tend to land short of 1200 words.packages/web/src/app/blog/posts/index.ts needs three edits per post:
import { post as camelCaseName } from './<slug>'; (with the other imports)posts map entry: '<slug>': camelCaseName, placed BEFORE the batch ...Object.fromEntries spreadspostList entry: { slug: '<slug>', ...camelCaseName }, placed before the batch spreads at the endMissing 1 or 2 = the page 404s. Missing 3 = invisible on /blog and absent from the sitemap. Do not touch sitemap.ts; it derives from postList.
cd /Users/promode/qaskills
# exactly 2 quoted occurrences per slug in index.ts (map key + postList)
for s in slug-one slug-two; do
c=$(grep -c "'$s'" packages/web/src/app/blog/posts/index.ts)
[ "$c" -eq 2 ] || echo "REGISTRATION WRONG: $s count=$c"
done
# no em dashes in the new files
grep -l '—' packages/web/src/app/blog/posts/<each-new-slug>.ts && echo "EM DASH FOUND"
# build must pass
pnpm --filter @qaskills/shared build && pnpm --filter @qaskills/web build
Fix and re-run until all three are clean. Build failure in a post file is almost always an unescaped backtick or ${ in the template literal.
git -C /Users/promode/qaskills add \
packages/web/src/app/blog/posts/<slug-one>.ts \
packages/web/src/app/blog/posts/<slug-two>.ts \
packages/web/src/app/blog/posts/index.ts
git -C /Users/promode/qaskills diff --cached --stat # only your files
git -C /Users/promode/qaskills commit -m "feat: publish 10 SEO articles (YYYY-MM-DD) from keyword research"
git -C /Users/promode/qaskills push origin main
Explicit paths only. No trailers, no footers, no em dash in the message.
REQUIRED SUB-SKILL: ship-prod. Follow it exactly (worktree if the tree is dirty, explicit project IDs, its verification checklist).
for s in slug-one slug-two; do
code=$(curl -s -o /dev/null -w '%{http_code}' "https://qaskills.sh/blog/$s")
echo "$s: $code"
done
curl -s https://qaskills.sh/sitemap.xml | grep -c '<one-new-slug>' # >= 1
Every slug must return 200 and appear in the sitemap. Report the checked URLs in the final summary.
After the new slugs are confirmed live (step 8), notify IndexNow so Bing and the other participating engines crawl them fast. Submit only the NEW slugs, not the whole sitemap. The verification key file is already hosted at https://qaskills.sh/f1e4781767e4472e9061ad0f853449d3.txt (committed in packages/web/public/); do not regenerate it.
# Build the payload from just this batch's new slugs
python3 - "$@" <<'PY'
import json, sys
slugs = ["slug-one", "slug-two"] # replace with this batch's new slugs
urls = [f"https://qaskills.sh/blog/{s}" for s in slugs]
json.dump({
"host": "qaskills.sh",
"key": "f1e4781767e4472e9061ad0f853449d3",
"keyLocation": "https://qaskills.sh/f1e4781767e4472e9061ad0f853449d3.txt",
"urlList": urls,
}, open("/tmp/indexnow-batch.json", "w"))
print("urls:", len(urls))
PY
curl -s -o /dev/null -w 'Bing IndexNow: %{http_code}\n' -X POST 'https://www.bing.com/indexnow' \
-H 'Content-Type: application/json; charset=utf-8' --data @/tmp/indexnow-batch.json
curl -s -o /dev/null -w 'IndexNow.org: %{http_code}\n' -X POST 'https://api.indexnow.org/indexnow' \
-H 'Content-Type: application/json; charset=utf-8' --data @/tmp/indexnow-batch.json
200 or 202 means accepted. A 403 SiteVerificationNotCompleted means the key file was not reachable; confirm the txt URL returns the bare key as text/plain and retry. This is a fire-and-forget hint, not a gate: a non-200 here does not un-publish the batch, but report it. Google is not an IndexNow participant; it discovers the posts through the sitemap and robots.txt as before.
| Symptom | Cause | Fix |
|---|---|---|
| Build: Unexpected token / template error in a post | Unescaped backtick or ${ in content | Escape as \\\` and \\${ |
| Article 404 live | Missing posts map entry, or deploy did not actually run | Step 5 count check, then re-run ship-prod verification |
| Article absent from /blog and sitemap | Missing postList entry | Add it, rebuild, redeploy |
| An OLD article changed content | New slug collided with a batch-array slug | Rename the new slug, restore, redeploy |
| Sitemap grep = 0 but page is 200 | Deployed stale HEAD or sitemap cached | Confirm commit is in HEAD, redeploy, re-check |
git add -A or staging files you did not createA set of resources to help me write all kinds of internal communications, using the formats that my company likes to use. Claude should use this skill whenever asked to write some sort of internal communications (status reports, leadership updates, 3P updates, company newsletters, FAQs, incident reports, project updates, etc.).
Extracts and analyzes competitors' ads from ad libraries (Facebook, LinkedIn, etc.) to understand what messaging, problems, and creative approaches are working. Helps inspire and improve your own ad campaigns.
Identifies high-quality leads for your product or service by analyzing your business, searching for target companies, and providing actionable contact strategies. Perfect for sales, business development, and marketing professionals.
Analyzes your recent Claude Code chat history to identify coding patterns, development gaps, and areas for improvement, curates relevant learning resources from HackerNews, and automatically sends a personalized growth report to your Slack DMs.
Complete App Store Optimization (ASO) toolkit for researching, optimizing, and tracking mobile app performance on Apple App Store and Google Play Store
NGS analysis toolkit. BAM to bigWig conversion, QC (correlation, PCA, fingerprints), heatmaps/profiles (TSS, peaks), for ChIP-seq, RNA-seq, ATAC-seq visualization.
Materials science toolkit. Crystal structures (CIF, POSCAR), phase diagrams, band structure, DOS, Materials Project integration, format conversion, for computational materials science.
Transforms vague UI ideas into polished, Stitch-optimized prompts. Enhances specificity, adds UI/UX keywords, injects design system context, and structures output for better generation results.
Take pramoddutta/publish-seo-batch from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.