Files
openclaw/tools/github_openclaw_scanner.py
Clawd Bot ca9b510922 chore: align with upstream openclaw/openclaw and overlay local additions
- 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>
2026-03-03 07:40:46 +01:00

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))