- Profile auditor (bio quality, posting patterns, growth readiness) - Tweet composer (hooks, threads, validation, 30+ proven patterns) - Content planner (weekly calendars with format mix) - Competitor analyzer (competitive intel via data import) - Growth tracker (snapshot-based progress tracking + milestone projection) - Algorithm reference doc (ranking signals, timing, format performance) - 226-line SKILL.md with practical playbook (no fluff) - Security audit: PASS (0 findings)
211 lines
7.2 KiB
Python
211 lines
7.2 KiB
Python
#!/usr/bin/env python3
|
||
"""
|
||
X/Twitter Content Planner — Generate weekly posting calendars.
|
||
|
||
Creates structured content plans with topic suggestions, format mix,
|
||
optimal posting times, and engagement targets.
|
||
|
||
Usage:
|
||
python3 content_planner.py --niche "AI engineering" --frequency 5 --weeks 2
|
||
python3 content_planner.py --niche "SaaS growth" --frequency 3 --weeks 1 --json
|
||
"""
|
||
|
||
import argparse
|
||
import json
|
||
import sys
|
||
from datetime import datetime, timedelta
|
||
from dataclasses import dataclass, field, asdict
|
||
|
||
CONTENT_FORMATS = {
|
||
"atomic_tweet": {"growth_weight": 0.3, "effort": "low", "description": "Single tweet — observation, tip, or hot take"},
|
||
"thread": {"growth_weight": 0.35, "effort": "high", "description": "5-12 tweet deep dive — highest reach potential"},
|
||
"question": {"growth_weight": 0.15, "effort": "low", "description": "Engagement bait — drives replies"},
|
||
"quote_tweet": {"growth_weight": 0.10, "effort": "low", "description": "Add value to someone else's content"},
|
||
"reply_session": {"growth_weight": 0.10, "effort": "medium", "description": "30 min focused engagement on target accounts"},
|
||
}
|
||
|
||
OPTIMAL_TIMES = {
|
||
"weekday": ["07:00-08:00", "12:00-13:00", "17:00-18:00", "20:00-21:00"],
|
||
"weekend": ["09:00-10:00", "14:00-15:00", "19:00-20:00"],
|
||
}
|
||
|
||
TOPIC_ANGLES = [
|
||
"Lessons learned (personal experience)",
|
||
"Framework/system breakdown",
|
||
"Tool recommendation (with honest take)",
|
||
"Myth busting (challenge common belief)",
|
||
"Behind the scenes (process, workflow)",
|
||
"Industry trend analysis",
|
||
"Beginner guide (explain like I'm 5)",
|
||
"Comparison (X vs Y — which is better?)",
|
||
"Prediction (what's coming next)",
|
||
"Case study (real example with numbers)",
|
||
"Mistake I made (vulnerability + lesson)",
|
||
"Quick tip (tactical, immediately useful)",
|
||
"Controversial take (spicy but defensible)",
|
||
"Curated list (best resources, tools, accounts)",
|
||
]
|
||
|
||
|
||
@dataclass
|
||
class DayPlan:
|
||
date: str
|
||
day_of_week: str
|
||
posts: list = field(default_factory=list)
|
||
engagement_target: str = ""
|
||
|
||
|
||
@dataclass
|
||
class PostSlot:
|
||
time: str
|
||
format: str
|
||
topic_angle: str
|
||
topic_suggestion: str
|
||
notes: str = ""
|
||
|
||
|
||
@dataclass
|
||
class WeekPlan:
|
||
week_number: int
|
||
start_date: str
|
||
end_date: str
|
||
days: list = field(default_factory=list)
|
||
thread_count: int = 0
|
||
total_posts: int = 0
|
||
focus_theme: str = ""
|
||
|
||
|
||
def generate_plan(niche: str, posts_per_day: int, weeks: int, start_date: datetime) -> list:
|
||
plans = []
|
||
angle_idx = 0
|
||
time_idx = 0
|
||
|
||
for week in range(weeks):
|
||
week_start = start_date + timedelta(weeks=week)
|
||
week_end = week_start + timedelta(days=6)
|
||
|
||
week_plan = WeekPlan(
|
||
week_number=week + 1,
|
||
start_date=week_start.strftime("%Y-%m-%d"),
|
||
end_date=week_end.strftime("%Y-%m-%d"),
|
||
focus_theme=TOPIC_ANGLES[week % len(TOPIC_ANGLES)],
|
||
)
|
||
|
||
for day in range(7):
|
||
current = week_start + timedelta(days=day)
|
||
day_name = current.strftime("%A")
|
||
is_weekend = day >= 5
|
||
|
||
times = OPTIMAL_TIMES["weekend" if is_weekend else "weekday"]
|
||
actual_posts = max(1, posts_per_day - (1 if is_weekend else 0))
|
||
|
||
day_plan = DayPlan(
|
||
date=current.strftime("%Y-%m-%d"),
|
||
day_of_week=day_name,
|
||
engagement_target="15 min reply session" if is_weekend else "30 min reply session",
|
||
)
|
||
|
||
for p in range(actual_posts):
|
||
# Determine format based on day position
|
||
if day in [1, 3] and p == 0: # Tue/Thu first slot = thread
|
||
fmt = "thread"
|
||
elif p == actual_posts - 1 and not is_weekend:
|
||
fmt = "question" # Last post = engagement driver
|
||
elif day == 4 and p == 0: # Friday first = quote tweet
|
||
fmt = "quote_tweet"
|
||
else:
|
||
fmt = "atomic_tweet"
|
||
|
||
angle = TOPIC_ANGLES[angle_idx % len(TOPIC_ANGLES)]
|
||
angle_idx += 1
|
||
|
||
slot = PostSlot(
|
||
time=times[p % len(times)],
|
||
format=fmt,
|
||
topic_angle=angle,
|
||
topic_suggestion=f"{angle} about {niche}",
|
||
notes="Pin if performs well" if fmt == "thread" else "",
|
||
)
|
||
day_plan.posts.append(asdict(slot))
|
||
|
||
if fmt == "thread":
|
||
week_plan.thread_count += 1
|
||
week_plan.total_posts += 1
|
||
|
||
week_plan.days.append(asdict(day_plan))
|
||
|
||
plans.append(asdict(week_plan))
|
||
|
||
return plans
|
||
|
||
|
||
def print_plan(plans: list, niche: str):
|
||
print(f"\n{'='*70}")
|
||
print(f" X/TWITTER CONTENT PLAN — {niche.upper()}")
|
||
print(f"{'='*70}")
|
||
|
||
for week in plans:
|
||
print(f"\n WEEK {week['week_number']} ({week['start_date']} to {week['end_date']})")
|
||
print(f" Theme: {week['focus_theme']}")
|
||
print(f" Posts: {week['total_posts']} | Threads: {week['thread_count']}")
|
||
print(f" {'─'*66}")
|
||
|
||
for day in week['days']:
|
||
print(f"\n {day['day_of_week']:9} {day['date']}")
|
||
for post in day['posts']:
|
||
fmt_icon = {
|
||
"thread": "🧵",
|
||
"atomic_tweet": "💬",
|
||
"question": "❓",
|
||
"quote_tweet": "🔄",
|
||
"reply_session": "💬",
|
||
}.get(post['format'], "📝")
|
||
|
||
print(f" {fmt_icon} {post['time']:12} [{post['format']:<14}] {post['topic_angle']}")
|
||
if post['notes']:
|
||
print(f" ℹ️ {post['notes']}")
|
||
|
||
print(f" 📊 Engagement: {day['engagement_target']}")
|
||
|
||
print(f"\n{'='*70}")
|
||
print(f" WEEKLY TARGETS")
|
||
print(f" • Reply to 10+ accounts in your niche daily")
|
||
print(f" • Quote tweet 2-3 relevant posts per week")
|
||
print(f" • Update pinned tweet if a thread outperforms current pin")
|
||
print(f" • Review analytics every Sunday — double down on what works")
|
||
print(f"{'='*70}\n")
|
||
|
||
|
||
def main():
|
||
parser = argparse.ArgumentParser(
|
||
description="Generate X/Twitter content calendars",
|
||
formatter_class=argparse.RawDescriptionHelpFormatter)
|
||
|
||
parser.add_argument("--niche", required=True, help="Your content niche")
|
||
parser.add_argument("--frequency", type=int, default=3, help="Posts per day (default: 3)")
|
||
parser.add_argument("--weeks", type=int, default=2, help="Weeks to plan (default: 2)")
|
||
parser.add_argument("--start", default="", help="Start date YYYY-MM-DD (default: next Monday)")
|
||
parser.add_argument("--json", action="store_true", help="Output JSON")
|
||
|
||
args = parser.parse_args()
|
||
|
||
if args.start:
|
||
start = datetime.strptime(args.start, "%Y-%m-%d")
|
||
else:
|
||
today = datetime.now()
|
||
days_until_monday = (7 - today.weekday()) % 7
|
||
if days_until_monday == 0:
|
||
days_until_monday = 7
|
||
start = today + timedelta(days=days_until_monday)
|
||
|
||
plans = generate_plan(args.niche, args.frequency, args.weeks, start)
|
||
|
||
if args.json:
|
||
print(json.dumps(plans, indent=2))
|
||
else:
|
||
print_plan(plans, args.niche)
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|