Agent Skillsnowork-studio/notfair-plugin › content-planner

content-planner

GitHub

基于Google Search Console数据,分析搜索查询与页面表现,识别高点击潜力机会(如位置5-20、未回答意图),生成结构化内容日历,辅助SEO内容规划。

seo/content-planner/SKILL.md nowork-studio/notfair-plugin

Trigger Scenarios

plan my content what should I write next content calendar content plan editorial calendar what topics will rank find quick-win SEO topics click-potential analysis schedule my SEO content

Install

npx skills add nowork-studio/notfair-plugin --skill content-planner -g -y
More Options

Non-standard path

npx skills add https://github.com/nowork-studio/notfair-plugin/tree/main/seo/content-planner -g -y

Use without installing

npx skills use nowork-studio/notfair-plugin@content-planner

指定 Agent (Claude Code)

npx skills add nowork-studio/notfair-plugin --skill content-planner -a claude-code -g -y

安装 repo 全部 skill

npx skills add nowork-studio/notfair-plugin --all -g -y

预览 repo 内 skill

npx skills add nowork-studio/notfair-plugin --list

SKILL.md

Frontmatter
{
    "name": "content-planner",
    "description": "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\".",
    "argument-hint": "<site URL or 'plan from GSC'>"
}

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 universe
  • content-planner (this skill) — start from GSC, prioritize your opportunities, schedule them
  • content-writer — take one planned topic and write the full post

Step 1 — Setup (config + GSC)

Read 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.


Step 2 — Pull GSC opportunity data

Pull a wide net once, filter in memory. Fewer round-trips, better correlation.

For the chosen GSC property, fetch last 90 days of:

  1. Query × Page report — top 5000 rows. The Cartesian view is the only one that lets you reason about intent (page is the answer surface, query is the demand).
  2. Page-only report — top 1000 rows. Lets you spot pages that already attract a lot of impressions but underperform CTR.
  3. Country + device breakdowns for the top 100 pages — needed for 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.


Step 3 — Classify opportunities

For every (query, page) row, classify into one of these buckets. Discard rows that don't fit any bucket — noise.

A. Striking-distance queries (highest priority)

  • Position 5-20, impressions ≥ 100/90d, query is informational
  • The page already ranks; a content refresh or net-new post targeting the exact intent can move it into the top 5

B. Unanswered intent (gaps)

  • Query is informational and no page on the site ranks (position > 20)
  • Query has search volume (use GSC impressions × position × 100 as a proxy if you don't have third-party volume)
  • The site sells/operates in the topic area — verify against business context in {data_dir}/business-context.json if present

C. CTR underperformers (refresh, not new content)

  • Position 1-10, impressions ≥ 500/90d, CTR < 50% of expected for that position (use the standard CTR-by-position curve from references/planning-methodology.md)
  • Output a refresh task, not a new post. Route to meta-tags-optimizer for the title/description rewrite.

D. Related-keyword expansions

  • For each query in buckets A and B, derive 2-4 related queries (synonyms, long-tails, "vs"/"alternative" variants, question forms). Cluster them with the parent — they become H2 sections of the planned post, not separate calendar entries.

E. Cannibalization warning (planning blocker)

  • Same query, multiple pages on the same site rank with > 20 impressions 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.


Step 4 — Score click potential

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; informational posts cluster lower than transactional)
  • currentCtr = actual GSC CTR for this query, or 0 if the site doesn't rank

Sort 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.


Step 5 — Build the calendar

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 lifecycle

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-progressready_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:

  • Topics already marked in-progress or published are preserved as-is
  • New planned topics are appended; duplicates by primaryKeyword are dropped in favor of the existing entry

Step 6 — Present + offer the viewer

Print:

  1. A Markdown summary table (top 12 topics by click potential), with columns: Date | Title | Primary Keyword | Opportunity | Est. Clicks Gained | P
  2. The path to the JSON: {data_dir}/content-calendar.json
  3. The viewer command, with the exact invocation:
notfair-content-calendar [--port 8323] [--calendar {data_dir}/content-calendar.json]

Open the calendar in your browser:

~/.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.

  1. Conditional handoffs:
    • Cannibalization flagged → offer /seo-analysis for the canonical-page fix first
    • CTR-underperformer entries present → offer /meta-tags-optimizer for the title/description rewrite
    • First P0 topic ready to write now → offer /content-writer with the pre-built writerPrompt

Step 7 — Quality gate

Refuse to ship the calendar if any of these are true:

  • GSC didn't connect → stop, prompt OAuth
  • < 50 (query, page) rows pulled → site has too little data, plan would be speculation; tell the user so and stop
  • Any topic has a bare-keyword title → rewrite with a hook before writing the JSON
  • Two scheduled topics share the same primaryKeyword → cannibalization by your own plan; collapse or de-prioritize one
  • Cannibalization warning present and ignored in the calendar → block, 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.

Version History

  • 82a79b7 Current 2026-07-25 05:07

Same Skill Collection

analytics/google-analytics/SKILL.md
analytics/search-console/SKILL.md
gohighlevel/SKILL.md
google-ads/assets/SKILL.md
google-ads/audit/SKILL.md
google-ads/copy/SKILL.md
google-ads/manage/SKILL.md
meta-ads/audit/SKILL.md
meta-ads/creative/SKILL.md
meta-ads/manage/SKILL.md
notfair-upgrade-skill/SKILL.md
paid-ads/paid-ads-amazon/SKILL.md
paid-ads/paid-ads-chatgpt/SKILL.md
paid-ads/paid-ads-creative/SKILL.md
paid-ads/paid-ads-guide/SKILL.md
paid-ads/paid-ads-integrations/SKILL.md
paid-ads/paid-ads-launch/SKILL.md
paid-ads/paid-ads-linkedin/SKILL.md
paid-ads/paid-ads-optimize/SKILL.md
paid-ads/paid-ads-reddit/SKILL.md
paid-ads/paid-ads-review/SKILL.md
paid-ads/paid-ads-setup/SKILL.md
paid-ads/paid-ads-tiktok/SKILL.md
paid-ads/paid-ads-x/SKILL.md
paid-ads/paid-ads/SKILL.md
seo/broken-link-checker/SKILL.md
seo/meta-tags-optimizer/SKILL.md
seo/schema-markup-generator/SKILL.md
seo/setup-cms/SKILL.md
skills/broken-link-checker/SKILL.md
skills/gohighlevel/SKILL.md
skills/google-ads-assets/SKILL.md
skills/google-ads-audit/SKILL.md
skills/google-ads-copy/SKILL.md
skills/google-ads/SKILL.md
skills/google-analytics/SKILL.md
skills/meta-ads-audit/SKILL.md
skills/meta-ads-creative/SKILL.md
skills/meta-ads/SKILL.md
skills/meta-tags-optimizer/SKILL.md
skills/paid-ads-amazon/SKILL.md
skills/paid-ads-chatgpt/SKILL.md
skills/paid-ads-creative/SKILL.md
skills/paid-ads-guide/SKILL.md
skills/paid-ads-integrations/SKILL.md
skills/paid-ads-launch/SKILL.md
skills/paid-ads-linkedin/SKILL.md
skills/paid-ads-optimize/SKILL.md
skills/paid-ads-reddit/SKILL.md
skills/paid-ads-review/SKILL.md

Metadata

Files
0
Version
68566c0
Hash
3d76afd3
Indexed
2026-07-25 05:07

Accueil - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-09-22 16:13
浙ICP备14020137号-1