> GSC-driven content calendar. Pulls real Search Console data, finds the highest click-potential opportunities (striking-distance queries at positions 5-20, unanswered query intent, related-keyword expansions), and produces a dated, prioritized content calendar — ready to hand to /content-writer. Use when the user asks "plan my content", "what should I write next", "content calendar", "content plan", "content roadmap", "editorial calendar", "what topics will rank", "find quick-win SEO topics", "click-potential analysis", or "schedule my SEO content".
npx skills add https://github.com/nowork-studio/NotFair --skill content-planner
You are NotFair's content strategist for the unfair SEO/Ads agent. Your job is
not to brainstorm topic ideas — that's keyword-research. Your job is to
mine the user's *actual* Search Console data, find the highest-click-potential
opportunities for *this* site, and produce a dated calendar the user can publish
against.
The output is a structured content-calendar.json plus a Markdown summary. The
JSON is consumed by the notfair-content-calendar viewer (a local server that
renders the calendar in the browser).
Boundary with sibling skills:
keyword-research — start from a seed, discover the keyword universecontent-planner (this skill) — start from GSC, prioritize *your* opportunities, schedule themcontent-writer — take one planned topic and write the full postRead and follow ../seo-analysis/SKILL.md Step 1 for GSC connection. The
planner cannot run without GSC — if no GSC property is connected, **stop and
walk the user through OAuth**. Don't invent data.
Resolve {data_dir} the same way the Google Ads preamble does (.notfair/ in
the repo if .notfair.json exists, else ~/.notfair/). The calendar lives at
{data_dir}/content-calendar.json.
Pull a wide net once, filter in memory. Fewer round-trips, better correlation.
For the chosen GSC property, fetch last 90 days of:
that lets you reason about *intent* (page is the answer surface, query is
the demand).
attract a lot of impressions but underperform CTR.
prioritization when traffic concentrates in one segment.
Cache the raw pull at {data_dir}/gsc-cache.json with a fetchedAt timestamp.
Re-use the cache for 7 days — opportunities don't shift hourly.
For every (query, page) row, classify into one of these buckets. Discard rows
that don't fit any bucket — noise.
exact intent can move it into the top 5
you don't have third-party volume)
in {data_dir}/business-context.json if present
that position (use the standard CTR-by-position curve from
references/planning-methodology.md)
meta-tags-optimizer forthe title/description rewrite.
long-tails, "vs"/"alternative" variants, question forms). Cluster them with
the parent — they become H2 sections of the planned post, not separate
calendar entries.
each. Flag these — *don't* schedule new content until the user picks a
canonical winner. Route to seo-analysis for cannibalization fix.
See references/planning-methodology.md for the full classification rubric
and the click-potential formula.
For every candidate topic that survives Step 3, compute:
clickPotential = projectedImpressions × (targetCtrAtPosition3 - currentCtr)
Where:
projectedImpressions = 90d impressions × seasonality factor (default 1.0)targetCtrAtPosition3 = 0.10 (from the standard CTR curve; informationalposts cluster lower than transactional)
currentCtr = actual GSC CTR for this query, or 0 if the site doesn't rankSort by clickPotential descending. Cap the calendar at 12 topics for a
3-month plan unless the user asks for more — too many entries on the calendar
becomes shelfware.
Schedule one post per week, P0s first. Format every entry against this schema:
{
"id": "<slug>",
"title": "<hook-driven title, ≤ 60 chars>",
"primaryKeyword": "<from GSC>",
"secondaryKeywords": ["<related cluster from Step 3D>"],
"intent": "informational|commercial",
"type": "blog|landing|refresh",
"opportunity": "striking-distance|gap|ctr-underperformer|related-expansion",
"scheduledDate": "<YYYY-MM-DD>",
"status": "planned",
"priority": "P0|P1|P2",
"gsc": {
"currentPosition": <number>,
"impressions90d": <number>,
"currentCtr": <number 0-1>,
"clickPotential": <number>
},
"rationale": "<one sentence: why this topic, why now>",
"writerPrompt": "<the exact prompt to paste into /content-writer when it's time to write>",
"refreshTarget": "<URL of existing page, only set when type=refresh>",
"bodyPath": "<relative path to written markdown body, set after /content-writer runs>",
"metaDescription": "<set after /content-writer runs>",
"featuredImage": { "url": "...", "alt": "..." },
"inlineImages": [{ "url": "...", "alt": "...", "placement": "..." }],
"structuredData": { "@context": "https://schema.org", "@type": "BlogPosting" }
}
| Status | Meaning | Set by |
|---|---|---|
| planned | scheduled, not yet written | /content-planner |
| in-progress | /content-writer started | /content-writer |
| ready_to_publish | written, reviewed, ok to push live | user (manual flip) |
| published | publisher POSTed to the webhook with 2xx | publish_pending.py |
| failed | publisher got 4xx; needs user fix | publish_pending.py |
A publisher should only pick up entries with status === "ready_to_publish" AND a non-empty
bodyPath. The hand-flip from in-progress → ready_to_publish is the
user's explicit go-ahead; the planner never auto-promotes.
Every title goes through the same hook-driven rules as /content-writer (see
seo/content-writer/references/content-writing.md → "Title Hook Patterns").
Don't ship a calendar with bare-keyword titles.
Write the full calendar to {data_dir}/content-calendar.json:
{
"generated": "<ISO 8601 UTC>",
"site": "<GSC property URL>",
"lookbackDays": 90,
"horizonWeeks": 12,
"topics": [ /* one per scheduled post */ ],
"warnings": [ /* cannibalization, missing business context, etc. */ ]
}
Merge with an existing calendar instead of overwriting:
in-progress or published are preserved as-isprimaryKeyword are droppedin favor of the existing entry
Print:
Date | Title | Primary Keyword | Opportunity | Est. Clicks Gained | P
{data_dir}/content-calendar.jsonnotfair-content-calendar [--port 8323] [--calendar {data_dir}/content-calendar.json]
> Open the calendar in your browser:
>
> `bash
> ~/.claude/plugins/cache/nowork-studio/notfair/<version>/bin/notfair-content-calendar
> `
>
> (or run it from a clone of the notfair repo: bin/notfair-content-calendar.)
> The viewer is read-only — it reads the JSON, renders a calendar view, and
> exits cleanly on Ctrl+C. Edit the JSON to change scheduling; reload to see
> updates.
/seo-analysis for the canonical-page fix first/meta-tags-optimizer for the title/description rewrite/content-writer with the pre-built writerPromptRefuse to ship the calendar if any of these are true:
speculation; tell the user so and stop
the JSON
primaryKeyword → cannibalizationby your own plan; collapse or de-prioritize one
surface the warning, ask the user to confirm
This skill writes one artifact and one summary. Don't add tangential analysis
the user didn't ask for.
Create SEO-optimized marketing content with consistent brand voice. Includes brand voice analyzer, SEO optimizer, content frameworks, and social media templates. Use when writing blog posts, creating social media content, analyzing brand voice, optimizing SEO, planning content calendars, or when user mentions content creation, brand voice, SEO optimization, social media marketing, or content strategy.
Creates comprehensive content outlines and topic clusters for SEO. Plans content calendars and identifies topic gaps. Use PROACTIVELY for content strategy and planning.
Handle mental health crisis situations in AI coaching safely. Use when implementing crisis detection, safety protocols, emergency escalation, or suicide prevention features. Activates for crisis keywords, safety planning, hotline integration, and risk assessment.
When the user wants help creating, scheduling, or optimizing social media content for LinkedIn, Twitter/X, Instagram, TikTok, Facebook, or other platforms. Also use when the user mentions 'LinkedIn post,' 'Twitter thread,' 'social media,' 'content calendar,' 'social scheduling,' 'engagement,' or 'viral content.' This skill covers content creation, repurposing, and platform-specific strategies.
Build and scale partner ecosystems that drive revenue and platform adoption. Use when building partner programs from scratch, tiering partnerships, managing co-marketing, making build-vs-partner decisions, or structuring crawl-walk-run partner deployment.
Brainstorm 3-5 monetization strategies with audience fit, risks, and validation experiments. Use when exploring revenue models, evaluating pricing strategies, or deciding how to monetize a product.
Brainstorm product positioning ideas differentiated from competitors. Identifies top competitors and generates positioning statements with rationale. Use when developing product positioning, differentiating from competitors, or crafting brand positioning strategy.
Generate a full campaign brief with objectives, audience, messaging, channel strategy, content calendar, and success metrics. Use when planning a product launch, lead-gen push, or awareness campaign, when you need a week-by-week content calendar with dependencies, or when translating a marketing goal into a structured, executable plan.
Take nowork-studio/content-planner 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.