Skip to main content

github-repo-mining

Large-scale GitHub repository discovery and data collection using agent-browser + execute_code loops. Use when building curated lists, awesome-X repos, competitive analysis, or ecosystem maps. Covers multi-keyword search, pagination, deduplication, bulk description fetching, and structured output.

Source facts

Repository
itgoyo/hermes-skills
Last source activity
April 22, 2026 at 06:22
Detected SKILL.md language
English
Stars
79
Forks
13

Install options

The review-first prompt is selected by default. You can switch to a direct command or download a local copy.

Review the source files

Read SKILL.md and any companion files shown by SkillsMP before deciding whether to install.

Showing SKILL.md

SKILL.md
Source instructions · Read-only preview
name
github-repo-mining
description
Large-scale GitHub repository discovery and data collection using agent-browser + execute_code loops. Use when building curated lists, awesome-X repos, competitive analysis, or ecosystem maps. Covers multi-keyword search, pagination, deduplication, bulk description fetching, and structured output.
tags
["github","research","scraping","agent-browser","awesome-list","data-collection"]
related_skills
["agent-browser","github-repo-management"]
# GitHub Repository Mining at Scale Use when you need to discover 50–200+ repos on a topic, collect their metadata (stars, descriptions), and organize into structured output like an awesome-list. ## Core Approach Combine `agent-browser` (for GitHub search page scraping) with `execute_code` (for looping, deduplication, and batch processing). Do NOT manually call pages one by one in conversation — use `execute_code` loops. ## Step 1 — Define Search Query Set GitHub search is noisy. Use **10–30 varied queries** to maximize coverage: ```python queries = [ "primary+keyword", # Main term, pages 1-8 "topic:your-topic", # GitHub topic tag "keyword+tool", "keyword+framework", # Compound terms "keyword+ui", "keyword+config", # Feature-specific "keyword+hooks", "keyword+mcp", # Ecosystem-specific "keyword+tutorial", "keyword+guide", # Educational "keyword+template", "keyword+boilerplate", "SomeProperNoun", # Related brands/projects ] ``` ## Step 2 — Scrape with execute_code Loop ```python from hermes_tools import terminal import re, json def parse_page(raw_text): """Extract repos from agent-browser accessibility tree snapshot.""" repos = [] lines = raw_text.split('\n') i = 0 while i < len(lines): line = lines[i] # Level-3 headings are repo names in GitHub search results m = re.search(r'heading "([^/"\s]+/[^"\s]+)".*\[level=3', line) if m: repo = m.group(1).strip() if '/' in repo: stars, desc = "", "" for j in range(i+1, min(i+30, len(lines))): # Stars: look for "Xk" or plain number pattern sm = re.search(r'StaticText "(\d+\.?\d*[kK]?)"', lines[j]) if sm and not stars: stars = sm.group(1) # Description: any long StaticText not matching noise dm = re.search(r'StaticText "([^"]{20,})"', lines[j]) if dm and not desc: c = dm.group(1) if not any(x in c for x in ['Updated', ' ago', 'Press', 'Sponsor', 'Read', 'open source', 'every piece']): desc = c repos.append((repo, stars, desc)) i += 1 return repos def safe_fetch(url): # Include proxy env vars if needed (common in CN/restricted environments) r = terminal(f'agent-browser open "{url}" 2>&1 | head -1 && agent-browser snapshot 2>&1') return r.get("output", "") all_repos = {} # Paginate main keyword (up to 8 pages = ~80 repos) for p in range(1, 9): url = f"https://github.com/search?q=your+keyword&type=repositories&s=stars&o=desc&p={p}" raw = safe_fetch(url) for repo, stars, desc in parse_page(raw): if repo not in all_repos: all_repos[repo] = {"stars": stars, "desc": desc} # Additional queries (first page only, sorted by stars) for q in queries: url = f"https://github.com/search?q={q}&type=repositories&s=stars&o=desc" raw = safe_fetch(url) for repo, stars, desc in parse_page(raw): if repo not in all_repos: all_repos[repo] = {"stars": stars, "desc": desc} print(f"Total unique: {len(all_repos)}") # Save — execute_code sessions don't share state between calls with open('/tmp/repos.json', 'w') as f: json.dump(all_repos, f, ensure_ascii=False, indent=2) ``` **Critical:** Save to `/tmp/repos.json` after each `execute_code` block — state is NOT shared between calls. ## Step 3 — Fetch Accurate Descriptions (Chunked) execute_code has a **50 tool-call cap**. Process repos in chunks of 50: ```python from hermes_tools import terminal import re, json with open('/tmp/repos.json') as f: all_repos = json.load(f) def get_repo_desc(repo): """GitHub page title format: 'GitHub - owner/repo: description · GitHub'""" r = terminal(f'agent-browser open "https://github.com/{repo}" 2>&1 | head -1') title = r.get("output", "") # Title separator may be · or - before "GitHub" m = re.search(r'GitHub - [^:]+: (.+?)(?:\s[·•]\s| - )GitHub', title) if m: return m.group(1).strip() # Fallback: grab everything after colon m2 = re.search(r'GitHub - [^:]+: (.+?)$', title.strip()) return m2.group(1).strip()[:120] if m2 else "" repos_list = list(all_repos.items()) # Process chunk N (call this block multiple times with different slices) for repo, info in repos_list[0:50]: # Change slice each run: [50:100], [100:150]... new_desc = get_repo_desc(repo) if new_desc: all_repos[repo]["desc"] = new_desc with open('/tmp/repos.json', 'w') as f: json.dump(all_repos, f, ensure_ascii=False, indent=2) print(f"Done chunk. Total: {len(all_repos)}") ``` ## Step 3b — Description Fallback Strategy (3-tier) When `get_repo_desc()` returns empty for some repos, use this cascade: 1. **Snapshot fallback** — fetch the repo page and scan accessibility tree for long StaticText: ```python def get_desc_via_snapshot(repo): r = terminal(f'agent-browser open "https://github.com/{repo}" 2>&1 | head -1 && agent-browser snapshot 2>&1 | head -80') for line in r.get("output","").split('\n'): m = re.search(r'StaticText "([^"]{25,120})"', line) if m: c = m.group(1) if not any(x in c for x in ['Star', 'Fork', 'Watch', 'Code', 'Issues', 'Pull', 'Actions']): return c return "" ``` 2. **GitHub REST API** — free, no auth needed for public repos: ```python def get_desc_via_api(repo): r = terminal(f'curl -s "https://api.github.com/repos/{repo}" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get(\'description\',\'\') or \'\')"') return r.get("output","").strip() ``` 3. **Manual fallback dict** — for repos that resist all scraping: ```python manual_descs = {"owner/repo": "Hand-written one-liner description"} for repo, desc in manual_descs.items(): if repo in all_repos and not all_repos[repo]['desc']: all_repos[repo]['desc'] = desc ``` ## Step 4 — Classify Repos **Classify by repo name first, description second.** Descriptions are noisy and sometimes missing; repo names are reliable. ```python def classify(repo, desc): repo_l = repo.lower() desc_l = (desc or '').lower() # Check repo name FIRST — more reliable than description if 'anthropics/' in repo_l: return "Official & Core" if 'awesome' in repo_l: return "Awesome Lists" if 'mcp' in repo_l: return "MCP Tools" if any(x in repo_l for x in ['skill', 'plugin']): return "Skills & Plugins" if any(x in repo_l for x in ['router', 'proxy', 'relay']): return "Routing & Proxy" if any(x in repo_l for x in ['mem', 'memory', 'context']): return "Memory & Context" # Fall through to description keywords if 'memory' in desc_l: return "Memory & Context" if 'mcp' in desc_l: return "MCP Tools" return "Other" ``` Also filter self-referential noise in descriptions (e.g. `owner/repo-name` literally appearing as the description when GitHub shows card header): ```python noise_patterns = [f'{repo.split("/")[0]}/', 'owner/repo-prefix'] if any(p in desc for p in noise_patterns): desc = "" # treat as missing ``` ## Step 5 — Output Format (Awesome List) **Default: 3-column table, NO rank/sequence column.** Users typically do NOT want sequence numbers — they clutter the view. Only add a rank column if explicitly requested. **Writing style for Chinese-primary READMEs**: When the user says "按照之前的写作风格" or "no # headings, no sequence numbers", use this pattern: - Top-level title: plain text on first line (no `#` prefix), e.g. `learn-crypto` - Section headers: bold text with emoji, e.g. `**⛓️ 公链与底层协议**` — NEVER use `#`, `##`, `###` - No ordered lists (1. 2. 3.) anywhere in the doc; use `-` bullets for TOC - Badge row, then `---` dividers between sections - Footer uses `**贡献**` and `**许可证**` bold headers, not `##` ```markdown | Repository | Stars | Description | |-----------|------:|-------------| | [owner/repo](https://github.com/owner/repo) | 13.5k ⭐ | One-line description | ``` **Removing a rank column from an existing README** (if a user asks to clean it up): ```python text = text.replace('| # | Repository | Stars | Description |', '| Repository | Stars | Description |') text = text.replace('|:---:|-----------|------:|-------------|', '|-----------|------:|-------------|') # Remove rank number from data rows: "| 3 | [repo](...) |" -> "| [repo](...) |" text = re.sub(r'\|\s*\d+\s*\|\s*(\[)', r'| \1', text) ``` **Incrementally expanding (101-200, etc.)** — insert new rows into existing category sections without rebuilding: ```python # Find the category header line, then find the last data row in that section, # insert new rows immediately after it for cat, new_repos_list in new_classified.items(): header_idx = next((i for i, l in enumerate(lines) if l.strip() == CAT_HEADER_MAP[cat]), None) if header_idx is None: continue last_row_idx = None for i in range(header_idx + 1, len(lines)): if lines[i].startswith('## ') and i != header_idx: break if lines[i].startswith('| [') and 'github.com' in lines[i]: last_row_idx = i if last_row_idx is None: continue new_rows = [f"| [{repo}](https://github.com/{repo}) | {stars} | {desc} |" for repo, info in new_repos_list] lines = lines[:last_row_idx+1] + new_rows + lines[last_row_idx+1:] ``` Save the second batch to a separate `/tmp/new_repos_101_200.json` to avoid contaminating the first batch. Deduplicate against the original pool when collecting. Include a **global rank column** (optional) so readers can compare cross-category popularity: ```python all_sorted = sorted(all_repos.items(), key=lambda x: parse_stars(x[1]['stars']), reverse=True) rank_map = {repo: i+1 for i, (repo, _) in enumerate(all_sorted)} ``` Table format with rank: ```markdown | Rank | Repository | Stars | Description | |:----:|-----------|------:|-------------| | #1 | [owner/repo](https://github.com/owner/repo) | 13.5k ⭐ | One-line description | ``` For bilingual (EN + CN) awesome lists:
View on GitHub
This SKILL.md is very large, so SkillsMP previews the first section here. View on GitHub