Files
claude-skills-reference/marketing-skill/x-twitter-growth/scripts/growth_tracker.py
Leo 66dcf674c5 feat(marketing): add x-twitter-growth skill with 5 Python tools
- 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)
2026-03-10 17:52:02 +01:00

260 lines
9.7 KiB
Python

#!/usr/bin/env python3
"""
X/Twitter Growth Tracker — Track and analyze account growth over time.
Stores periodic snapshots of account metrics and calculates growth trends,
engagement patterns, and milestone projections.
Usage:
python3 growth_tracker.py --record --handle @user --followers 5200 --eng-rate 2.1
python3 growth_tracker.py --report --handle @user
python3 growth_tracker.py --report --handle @user --period 30d --json
python3 growth_tracker.py --milestone --handle @user --target 10000
"""
import argparse
import json
import os
import sys
from datetime import datetime, timedelta
from pathlib import Path
DATA_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".growth-data")
def get_data_file(handle: str) -> str:
clean = handle.lstrip("@").lower()
os.makedirs(DATA_DIR, exist_ok=True)
return os.path.join(DATA_DIR, f"{clean}.jsonl")
def record_snapshot(handle: str, followers: int, following: int = 0,
eng_rate: float = 0, posts_week: float = 0, notes: str = ""):
entry = {
"timestamp": datetime.now().isoformat(),
"handle": handle,
"followers": followers,
"following": following,
"engagement_rate": eng_rate,
"posts_per_week": posts_week,
"notes": notes,
}
filepath = get_data_file(handle)
with open(filepath, "a") as f:
f.write(json.dumps(entry) + "\n")
return entry
def load_snapshots(handle: str, period_days: int = 0) -> list:
filepath = get_data_file(handle)
if not os.path.exists(filepath):
return []
entries = []
cutoff = None
if period_days > 0:
cutoff = datetime.now() - timedelta(days=period_days)
with open(filepath) as f:
for line in f:
line = line.strip()
if not line:
continue
entry = json.loads(line)
if cutoff:
ts = datetime.fromisoformat(entry["timestamp"])
if ts < cutoff:
continue
entries.append(entry)
return entries
def generate_report(handle: str, entries: list) -> dict:
if not entries:
return {"handle": handle, "error": "No data found"}
report = {
"handle": handle,
"data_points": len(entries),
"first_record": entries[0]["timestamp"],
"last_record": entries[-1]["timestamp"],
"current_followers": entries[-1]["followers"],
}
if len(entries) >= 2:
first = entries[0]
last = entries[-1]
follower_change = last["followers"] - first["followers"]
days_span = (datetime.fromisoformat(last["timestamp"]) -
datetime.fromisoformat(first["timestamp"])).days
days_span = max(days_span, 1)
report["follower_change"] = follower_change
report["days_tracked"] = days_span
report["daily_growth"] = round(follower_change / days_span, 1)
report["weekly_growth"] = round((follower_change / days_span) * 7, 1)
report["monthly_projection"] = round((follower_change / days_span) * 30)
if first["followers"] > 0:
pct_change = ((last["followers"] - first["followers"]) / first["followers"]) * 100
report["growth_percent"] = round(pct_change, 1)
# Engagement trend
eng_rates = [e["engagement_rate"] for e in entries if e.get("engagement_rate", 0) > 0]
if len(eng_rates) >= 2:
mid = len(eng_rates) // 2
first_half_avg = sum(eng_rates[:mid]) / mid
second_half_avg = sum(eng_rates[mid:]) / (len(eng_rates) - mid)
report["engagement_trend"] = "improving" if second_half_avg > first_half_avg else "declining"
report["avg_engagement_rate"] = round(sum(eng_rates) / len(eng_rates), 2)
return report
def project_milestone(handle: str, entries: list, target: int) -> dict:
if len(entries) < 2:
return {"error": "Need at least 2 data points for projection"}
current = entries[-1]["followers"]
if current >= target:
return {"handle": handle, "target": target, "status": "Already reached!"}
first = entries[0]
last = entries[-1]
days_span = (datetime.fromisoformat(last["timestamp"]) -
datetime.fromisoformat(first["timestamp"])).days
days_span = max(days_span, 1)
daily_growth = (last["followers"] - first["followers"]) / days_span
if daily_growth <= 0:
return {"handle": handle, "target": target, "status": "Not growing — can't project",
"daily_growth": round(daily_growth, 1)}
remaining = target - current
days_needed = remaining / daily_growth
target_date = datetime.now() + timedelta(days=days_needed)
return {
"handle": handle,
"current": current,
"target": target,
"remaining": remaining,
"daily_growth": round(daily_growth, 1),
"days_needed": round(days_needed),
"projected_date": target_date.strftime("%Y-%m-%d"),
}
def print_report(report: dict):
print(f"\n{'='*60}")
print(f" GROWTH REPORT — {report['handle']}")
print(f"{'='*60}")
if "error" in report:
print(f"\n ⚠️ {report['error']}")
print(f" Record data first: python3 growth_tracker.py --record --handle {report['handle']} --followers N")
print()
return
print(f"\n Current followers: {report['current_followers']:,}")
print(f" Data points: {report['data_points']}")
print(f" Tracking since: {report['first_record'][:10]}")
if "follower_change" in report:
change_icon = "📈" if report["follower_change"] > 0 else "📉" if report["follower_change"] < 0 else "➡️"
print(f"\n {change_icon} Change: {report['follower_change']:+,} followers over {report['days_tracked']} days")
print(f" Daily avg: {report.get('daily_growth', 0):+.1f}/day")
print(f" Weekly avg: {report.get('weekly_growth', 0):+.1f}/week")
print(f" 30-day projection: {report.get('monthly_projection', 0):+,}")
if "growth_percent" in report:
print(f" Growth rate: {report['growth_percent']:+.1f}%")
if "engagement_trend" in report:
trend_icon = "📈" if report["engagement_trend"] == "improving" else "📉"
print(f" Engagement: {trend_icon} {report['engagement_trend']} (avg {report['avg_engagement_rate']}%)")
print(f"\n{'='*60}\n")
def main():
parser = argparse.ArgumentParser(
description="Track X/Twitter account growth over time",
formatter_class=argparse.RawDescriptionHelpFormatter)
parser.add_argument("--record", action="store_true", help="Record a new snapshot")
parser.add_argument("--report", action="store_true", help="Generate growth report")
parser.add_argument("--milestone", action="store_true", help="Project when target will be reached")
parser.add_argument("--handle", required=True, help="X handle")
parser.add_argument("--followers", type=int, default=0, help="Current follower count")
parser.add_argument("--following", type=int, default=0, help="Current following count")
parser.add_argument("--eng-rate", type=float, default=0, help="Current engagement rate (pct)")
parser.add_argument("--posts-week", type=float, default=0, help="Posts per week")
parser.add_argument("--notes", default="", help="Notes for this snapshot")
parser.add_argument("--period", default="all", help="Report period: 7d, 30d, 90d, all")
parser.add_argument("--target", type=int, default=0, help="Follower milestone target")
parser.add_argument("--json", action="store_true", help="Output JSON")
args = parser.parse_args()
if not args.handle.startswith("@"):
args.handle = f"@{args.handle}"
if args.record:
if args.followers <= 0:
print("Error: --followers required for recording", file=sys.stderr)
sys.exit(1)
entry = record_snapshot(args.handle, args.followers, args.following,
args.eng_rate, args.posts_week, args.notes)
if args.json:
print(json.dumps(entry, indent=2))
else:
print(f" ✅ Recorded: {args.handle}{args.followers:,} followers")
print(f" File: {get_data_file(args.handle)}")
elif args.report:
period_days = 0
if args.period != "all":
period_days = int(args.period.rstrip("d"))
entries = load_snapshots(args.handle, period_days)
report = generate_report(args.handle, entries)
if args.json:
print(json.dumps(report, indent=2))
else:
print_report(report)
elif args.milestone:
if args.target <= 0:
print("Error: --target required for milestone projection", file=sys.stderr)
sys.exit(1)
entries = load_snapshots(args.handle)
result = project_milestone(args.handle, entries, args.target)
if args.json:
print(json.dumps(result, indent=2))
else:
if "error" in result:
print(f" ⚠️ {result['error']}")
elif "status" in result and "days_needed" not in result:
print(f" 🎉 {result['status']}")
else:
print(f"\n 🎯 Milestone Projection: {result['handle']}")
print(f" Current: {result['current']:,}")
print(f" Target: {result['target']:,}")
print(f" Gap: {result['remaining']:,}")
print(f" Growth: {result['daily_growth']:+.1f}/day")
print(f" ETA: {result['projected_date']} (~{result['days_needed']} days)")
print()
else:
parser.print_help()
if __name__ == "__main__":
main()