#!/usr/bin/env python3 """Scan GitHub for new OpenClaw skills, tools, and plugins. Uses GitHub CLI to find recently updated repos with openclaw-related content. Filters for concrete builds (not forks, not clones of main repo). Usage: github_openclaw_scanner.py --days 7 --output /tmp/github-openclaw.jsonl """ from __future__ import annotations import argparse import json import subprocess import sys from datetime import datetime, timedelta, timezone def gh_search(query: str, limit: int = 100) -> list[dict]: """Search GitHub repos via gh CLI.""" cmd = [ "gh", "search", "repos", query, "--limit", str(limit), "--json", "fullName,description,stargazersCount,url,pushedAt,language,isFork", "--sort", "updated" ] result = subprocess.run(cmd, capture_output=True, text=True, check=True) return json.loads(result.stdout) def is_openclaw_build(repo: dict) -> bool: """Filter for OpenClaw skills/tools (not main repo or forks).""" name = repo.get("fullName", "").lower() desc = (repo.get("description") or "").lower() # Exclude main openclaw repo and exact forks if name in ["openclaw/openclaw", "openclaw/skills", "openclaw/clawhub"]: return False if repo.get("isFork"): return False # Must be openclaw-related openclaw_keywords = ["openclaw", "clawdbot", "moltbot"] if not any(kw in name or kw in desc for kw in openclaw_keywords): return False # Prefer skills/tools/plugins build_keywords = ["skill", "plugin", "tool", "extension", "integration"] has_build = any(kw in name or kw in desc for kw in build_keywords) # Exclude irrelevant/overhyped categories exclude_keywords = [ "trading", "crypto", "defi", "polymarket", "pump.fun", # Crypto trading "token", "wallet", "blockchain", "solana", "dating", "tinder", "moltbook", # Social apps "marketplace", "directory", # Just lists ] is_excluded = any(kw in name or kw in desc for kw in exclude_keywords) return has_build and not is_excluded def main(argv: list[str]) -> int: ap = argparse.ArgumentParser() ap.add_argument("--days", type=int, default=7, help="look back N days") ap.add_argument("--limit", type=int, default=100, help="max results per query") ap.add_argument("--output", help="output JSONL file") args = ap.parse_args(argv[1:]) cutoff = datetime.now(timezone.utc) - timedelta(days=args.days) # Search queries queries = [ "openclaw skill", "openclaw plugin", "openclaw extension", "openclaw tool", "clawdbot skill", "moltbot skill", ] seen = set() results = [] for query in queries: repos = gh_search(query, args.limit) for repo in repos: full_name = repo.get("fullName") if not full_name or full_name in seen: continue pushed_at = datetime.fromisoformat(repo["pushedAt"].replace("Z", "+00:00")) if pushed_at < cutoff: continue if not is_openclaw_build(repo): continue seen.add(full_name) results.append({ "source": "github", "repo": full_name, "url": repo["url"], "description": repo.get("description"), "stars": repo.get("stargazersCount", 0), "language": repo.get("language"), "updated": repo["pushedAt"], }) # Sort by stars descending results.sort(key=lambda r: r["stars"], reverse=True) if args.output: with open(args.output, "w") as f: for r in results: f.write(json.dumps(r) + "\n") else: for r in results: print(json.dumps(r)) print(f"\n# Found {len(results)} OpenClaw builds updated in last {args.days} days", file=sys.stderr) return 0 if __name__ == "__main__": raise SystemExit(main(sys.argv))