- Reset master to upstream/main (16,697 commits) - Overlay 2,271 local-only files (skills, tools, workspace, configs, apps) - Restore IDENTITY.md and USER.md templates - Build verified, gateway running, Discord working Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
130 lines
4.0 KiB
Python
130 lines
4.0 KiB
Python
#!/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))
|