Atlas / Skills / nowork-studio / Content Planner

Content PlannerSAFE

skills/nowork-studio/content-planner

Open-source SEO, GEO, and marketing skills for AI agents.

Verdict
SAFE
Grade
B
Trust score
89 /100
Version
—
Hosts
—
License
MIT
Stars
3,908
01

Overview

Open-source SEO, GEO, and marketing skills for AI agents.

Read from source at commit f08bca773eb5OBSERVED · 2026-10-08
02

What it tells the agent

The instruction file, verbatim from the audited commit — this is the text the model reads, and the surface the audit's instruction layer examines. Quoted here so you can judge it without cloning anything.

---
name: content-planner
argument-hint: "<site URL or 'plan from GSC'>"
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".
---

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

```json
{
  "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 markdo
03

Trust audit

SAFEgrade B · trust 89/100 Nothing in the source contradicts what it says it does. Grade A is reserved for packages that have also passed the behavioural sandbox.

LayerWhat it checksResult
L0Provenance & inventoryPASS
L1Static analysis of the codeNA
L2Instruction surface (what it tells the agent)PASS
L3Class-specific surfacePASS
L4Behavioural (sandbox)SKIPPED

What the source does

Filesystem
none-observed
Network
none-observed
Shell
none-observed
Dependencies
pinned
Secrets in source
none-found

Findings (0)

No findings outside the package's declared scope.

Gates applied: no_behavioural_pass.

Audited 2026-10-08 · audit v0.4.1 · source sha f08bca773eb5full audit observations/trust-audit/skill/nowork-studio__content-planner.json · Report an issue / request a re-scan
04

Audit history

Every audit this skill has had.

DateSourceVerdictGradeScoreChange
2026-10-08f08bca773eb5SAFEB89first audit
05

Questions

What does the Content Planner skill do?

Open-source SEO, GEO, and marketing skills for AI agents.

Is Content Planner safe to install?

The audit found nothing in the source that contradicts what it says it does, and graded it B (89/100). Grade A is held back for packages that have also passed a sandboxed behavioural run, which is why a clean skill reads B.

What can Content Planner access on my machine?

The audit observed no filesystem, network or shell use at all in its source.

How current is this page?

The grade is for one exact copy of the source (f08bca773eb5), read on 2026-10-08. The repository is watched, and a new audit runs when it changes — this is the first audit.

Advertisement