Communitygithub.com

SDK導入状況トラッカー

SDKまたはライブラリ名を指定すると、GitHubコード検索でそれをインポートしている公開リポジトリを検索し、企業組織、関連開発者、個人開発者、チュートリアルノイズに分類。採用シグナルの強さでスコアリングし、新規採用者を日付で検出、高シグナルの企業ごとにアウトリーチコンテキストを付けてランク付けしたレポートを出力します。

SDK導入状況トラッカー とは?

このスキル(SDK導入状況トラッカー)を使用すると、AgentがユーザーのSDKやライブラリを実際に使用しているGitHub上の公開リポジトリを、コード検索を活用して体系的に特定できるようになります。まず、指定されたSDK名からエコシステム(npm、Python、Goなど)を自動検出し、適切なインポートパターンを使用してGitHubコード検索を実行します。見つかったリポジトリを、組織アカウントか個人アカウントか、リポジトリ名や説明に「example」や「tutorial」といったノイズ指標が含まれているかなどに基づいて分類します。その後、高シグナルなリポジトリにGitHub APIを使用して詳細メタデータ(スター数、最終プッシュ日時、トップコントリビューターなど)を付加し、採用スコアを計算してランク付けされたレポートを生成します。このスキルは、Markdownファイルとシェルスクリプトで実装されており、Claude CodeやCursorのようなコード実行機能を持つAgentで動作し、ユーザーが「私のSDKは誰が使っている?」「このライブラリをインポートしているリポジトリを見つけて」などと尋ねたときにトリガーされます。最後のステップでは、高シグナルなリポジトリのデータを要約し、各企業の開発者向けにパーソナライズされたアウトリーチメッセージを生成するためのAI分析を実行します。

対応✓Claude Code~Codex CLI~Cursor✓Gemini CLI
npx skills add https://github.com/Varnan-Tech/opendirectory/tree/main/skills/sdk-adoption-tracker

お気に入りのAIに質問する

このエージェントスキルを事前に読み込んだ状態で新しいチャットを開きます。

ドキュメント

SDK導入状況トラッカー は何をしますか?

SDK名を入力します。GitHubでそれをインポートしている公開リポジトリを検索します。各リポジトリを、企業シグナル、アクティビティ、ノイズ指標に基づいてスコアリングします。高シグナルリポジトリには、所有者とコントリビューターデータを付加します。企業採用者向けのアウトリーチコンテキストを付けた、ランク付けされた導入レポートを出力します。


重要なルール: 出力内のすべてのリポジトリは、GitHubコード検索APIのレスポンスに存在している必要があります。すべての企業名は、GitHubユーザーまたは組織APIの company フィールドまたは name フィールドから取得する必要があります。すべてのコントリビューターハンドルは、GitHubコントリビューターAPIのレスポンスから取得する必要があります。APIでフィールドが空の場合は「not listed」と記述し、推測、類推、外挿は行わないでください。


よくある間違い

Agentがやりたがることなぜそれが間違っているか
GITHUB_TOKENなしでコード検索を実行する認証されていないコード検索は、3 req/minのセカンダリレート制限に達し、意味のあるスキャンはすべて失敗します。GITHUB_TOKENは必須です。不足している場合は、ステップ1で明確なエラーを表示して停止してください。
SDK自体のフォークを含めるSDKをフォークしたリポジトリは、コントリビューターまたはミラーであり、採用者ではありません。fork == true かつリポジトリ名がSDK名と一致するリポジトリはフィルタリングしてください。
500件すべての生の検索結果をAIに送信するコード検索では最大500件の結果が返されますが、そのほとんどはノイズです。まずローカルでフィルタリングとスコアリングを行ってください。AI分析ステップには、上位20件の高シグナルリポジトリのみを送信してください。
チュートリアルやサンプルリポジトリを採用者として報告するリポジトリ名や説明に「example」「tutorial」「demo」「learn」「sample」「playground」「starter」が含まれているものは、本番環境ユーザーではありません。「tutorial_noise」としてマークし、リード概要から除外してください。
企業名や連絡先ハンドルをでっち上げるすべての企業名はGitHubの company フィールドまたは組織の name フィールドから取得する必要があります。すべてのコントリビューターハンドルは、コントリビューターAPIのレスポンスから取得する必要があります。フィールドが空の場合は「not listed」と記述してください。
すべてのエコシステムに対して1つのインポートパターンを使用するrequire('sdk') ではPythonユーザーは見つかりません。SDK名からエコシステムを自動検出し、エコシステム固有のパターンを構築してください。自動検出があいまいな場合はユーザーに確認してください。

ステップ1: セットアップチェック

if [ -z "$GITHUB_TOKEN" ]; then
  echo "ERROR: GITHUB_TOKEN is required for code search."
  echo "Add a token at github.com/settings/tokens (no scopes needed for public repos)."
  echo "Without it, GitHub code search hits a 3 req/min secondary rate limit and fails."
  exit 1
fi
echo "GITHUB_TOKEN: set"
curl -s -H "Authorization: Bearer $GITHUB_TOKEN" \
     -H "Accept: application/vnd.github+json" \
     "https://api.github.com/rate_limit" | python3 -c "
import json, sys
d = json.load(sys.stdin)
search = d['resources']['search']
core = d['resources']['core']
print(f'Search rate: {search[\"remaining\"]}/{search[\"limit\"]} remaining')
print(f'Core rate:   {core[\"remaining\"]}/{core[\"limit\"]} remaining')
"

search remainingが0の場合: 停止します。X-RateLimit-Reset からのリセット時間をユーザーに伝えてください。


ステップ2: 入力の収集

会話から収集します:

  • SDK名 (例: @company/my-sdk、requests、github.com/org/go-sdk)
  • オプション: エコシステムの上書き (npm、python、go、gem) -- 提供されなければ自動検出
  • オプション: 結果から除外する組織/ユーザー (SDK所有者自身のリポジトリ)
  • オプション: プロダクトコンテキスト文字列 (アウトリーチメッセージのパーソナライズに使用)

エコシステムの自動検出:

  • @ で始まる、または - を含む: npm
  • スネークケースで / や - を含まない: python
  • github.com/ を含む: go
  • それ以外: ユーザーに確認

SDK名が提供されない場合: 「追跡したいSDKまたはライブラリはどれですか?import文に表示されるパッケージ名を入力してください (例: stripe, @clerk/nextjs, requests)。」と尋ねます。

python3 << 'PYEOF'
import json, sys, re

sdk_name = "SDK_NAME_HERE"
ecosystem_override = ""  # leave empty for auto-detect
exclude_owner = ""       # optional: owner name to exclude (usually the SDK publisher)
product_context = ""     # optional: what your product does

# Auto-detect ecosystem
if ecosystem_override:
    ecosystem = ecosystem_override
elif sdk_name.startswith("@") or "-" in sdk_name:
    ecosystem = "npm"
elif re.match(r'^[a-z][a-z0-9_]*$', sdk_name):
    ecosystem = "python"
elif "github.com/" in sdk_name:
    ecosystem = "go"
else:
    ecosystem = "generic"

print(f"SDK: {sdk_name}")
print(f"Ecosystem: {ecosystem}")
print(f"Exclude owner: {exclude_owner or '(none)'}")

with open("/tmp/sat-input.json", "w") as f:
    json.dump({
        "sdk_name": sdk_name,
        "ecosystem": ecosystem,
        "exclude_owner": exclude_owner,
        "product_context": product_context
    }, f)
PYEOF

ステップ3: GitHubコードの検索

まずスタンドアロンスクリプトを確認します -- これはステップ3〜5を1回の呼び出しで処理します。

ls scripts/fetch.py 2>/dev/null && echo "script available" || echo "script not found"

スクリプトが利用可能な場合、それを実行してステップ6にスキップします:

python3 scripts/fetch.py "$(python3 -c "import json; d=json.load(open('/tmp/sat-input.json')); print(d['sdk_name'])"" \
  --ecosystem "$(python3 -c "import json; d=json.load(open('/tmp/sat-input.json')); print(d['ecosystem'])"" \
  --exclude "$(python3 -c "import json; d=json.load(open('/tmp/sat-input.json')); print(d.get('exclude_owner',''))"" \
  --output /tmp/sat-script-out.json

次に、ステップ6〜8が期待する一時ファイル形式にロードします:

python3 << 'PYEOF'
import json

out = json.load(open("/tmp/sat-script-out.json"))
json.dump(out["raw_results"], open("/tmp/sat-raw-results.json", "w"), indent=2)
json.dump(out["scored"], open("/tmp/sat-scored.json", "w"), indent=2)
json.dump(out["enriched"], open("/tmp/sat-enriched.json", "w"), indent=2)
print(f"Loaded: {len(out['raw_results'])} raw | {len(out['scored'])} scored | {len(out['enriched'])} enriched")
PYEOF

スクリプトが利用できない場合、以下のインラインコードを実行します。

インポートパターンを構築し、各パターンでGitHubコードを検索します:

python3 << 'PYEOF'
import json, urllib.request, ssl, time, os
from datetime import datetime, timezone

ctx = ssl._create_unverified_context()
token = os.environ["GITHUB_TOKEN"]
headers = {
    "Accept": "application/vnd.github+json",
    "Authorization": f"Bearer {token}",
    "User-Agent": "sdk-adoption-tracker/1.0"
}

data = json.load(open("/tmp/sat-input.json"))
sdk_name = data["sdk_name"]
ecosystem = data["ecosystem"]

# Build query patterns per ecosystem
if ecosystem == "npm":
    bare = sdk_name.lstrip("@").replace("/", "/")
    queries = [
        f'require("{sdk_name}")',
        f"require('{sdk_name}')",
        f'from "{sdk_name}"',
        f"from '{sdk_name}'",
    ]
elif ecosystem == "python":
    queries = [
        f"import {sdk_name}",
        f"from {sdk_name} import",
        f"from {sdk_name}.",
    ]
elif ecosystem == "go":
    queries = [f'"{sdk_name}"']
else:
    queries = [sdk_name]

print(f"Building queries for {sdk_name} ({ecosystem}):")
for q in queries:
    print(f"  {q}")

seen_repos = {}  # full_name -> first match
search_rate_remaining = 30
flags = []

for i, query in enumerate(queries):
    if search_rate_remaining <= 2:
        flags.append(f"Code search rate limit low ({search_rate_remaining}) -- skipped remaining patterns")
        print(f"  Rate limit low ({search_rate_remaining}), stopping early")
        break

    url = f"https://api.github.com/search/code?q={urllib.parse.quote(query)}&per_page=100"
    req = urllib.request.Request(url, headers=headers)

    try:
        with urllib.request.urlopen(req, timeout=20, context=ctx) as resp:
            search_rate_remaining = int(resp.headers.get("X-RateLimit-Remaining", 10))
            raw = json.loads(resp.read())

        items = raw.get("items", [])
        total = raw.get("total_count", 0)
        print(f"  Pattern '{query}': {total} total, {len(items)} fetched | rate_remaining={search_rate_remaining}")

        for item in items:
            repo = item.get("repository", {})
            full_name = repo.get("full_name", "")
            if full_name and full_name not in seen_repos:
                seen_repos[full_name] = {
                    "full_name": full_name,
                    "name": repo.get("name", ""),
                    "owner_login": repo.get("owner", {}).get("login", ""),
                    "owner_type": repo.get("owner", {}).get("type", ""),
                    "file_path": item.get("path", ""),
                    "matched_pattern": query,
                    "html_url": repo.get("html_url", ""),
                    "description": repo.get("description") or "",
                }

    except urllib.error.HTTPError as e:
        if e.code == 403:
            flags.append(f"Code search rate limit hit on pattern '{query}'")
            print(f"  Rate limit hit (403) on '{query}'")
            break
        else:
            print(f"  HTTP {e.code} on '{query}'")
    except Exception as e:
        print(f"  Error on '{query}': {e}")

    # Respect 10 req/min code search limit
    if i < len(queries) - 1:
        time.sleep(6)

results = list(seen_repos.values())
json.dump(results, open("/tmp/sat-raw-results.json", "w"), indent=2)
print(f"\nTotal unique repos found: {len(results)}")
if flags:
    print("Flags:", flags)

import urllib.parse  # ensure imported
PYEOF

結果が0件の場合: ユーザーに伝えます: 「{sdk_name} をインポートしているリポジトリは見つかりませんでした。新しいパッケージの場合、GitHubコード検索のインデックス作成には1〜4週間かかります。SDKが確立されている場合は、references/import-patterns.md のインポートパターンを確認してください。」


ステップ4: リポジトリのスコアリングと分類

APIコールはありません。純粋なPythonです。ノイズをフィルタリングし、採用スコアを計算し、各リポジトリを分類します。

python3 << 'PYEOF'
import json
from datetime import datetime, timezone

data = json.load(open("/tmp/sat-input.json"))
exclude_owner = data.get("exclude_owner", "").lower()
sdk_name = data["sdk_name"].lower().split("/")[-1].replace("@", "")

results = json.load(open("/tmp/sat-raw-results.json"))

TUTORIAL_WORDS = {"example", "tutorial", "demo", "learn", "sample", "starter",
                  "boilerplate", "template", "playground", "test", "course", "workshop"}

scored = []
now = datetime.now(tz=timezone.utc)

for repo in results:
    full_name = repo["full_name"]
    owner_login = repo.get("owner_login", "").lower()
    repo_name = repo.get("name", "").lower()
    description = (repo.get("description") or "").lower()
    owner_type = repo.get("owner_type", "User")

    # Exclude the SDK owner's own repos
    if exclude_owner and owner_login == exclude_owner.lower():
        continue

    # Detect tutorial noise
    name_words = set(repo_name.replace("-", " ").replace("_", " ").split())
    desc_words = set(description.split())
    is_tutorial = bool((name_words | desc_words) & TUTORIAL_WORDS)
    # Also exclude if repo name IS the SDK name (likely a fork)
    if repo_name == sdk_name or repo_name.startswith(sdk_name + "-"):
        is_tutorial = True  # treat as noise

    # Classification tier
    if is_tutorial:
        tier = "tutorial_noise"
    elif owner_type == "Organization":
        tier = "company_org"
    else:
        tier = "solo_dev"  # will be upgraded to affiliated_dev in Step 5 if company field populated

    # Adoption score (filled with partial data now, enriched in Step 5)
    score = 0
    if owner_type == "Organization": score += 50
    if not is_tutorial:              score += 20
    # stars, days_since_push, is_fork, is_archived added in Step 5

    scored.append({
        **repo,
        "tier": tier,
        "is_tutorial": is_tutorial,
        "adoption_score": score,
        "enriched": False
    })

# Sort: company_org first, then by tier
tier_order = {"company_org": 0, "affiliated_dev": 1, "solo_dev": 2, "tutorial_noise": 3}
scored.sort(key=lambda x: (tier_order.get(x["tier"], 9), -x["adoption_score"]))

json.dump(scored, open("/tmp/sat-scored.json", "w"), indent=2)

tiers = {}
for r in scored:
    tiers[r["tier"]] = tiers.get(r["tier"], 0) + 1

print(f"Classification:")
for tier, count in sorted(tiers.items(), key=lambda x: tier_order.get(x[0], 9)):
    print(f"  {tier}: {count}")
print(f"Total: {len(scored)} repos")

non_noise = [r for r in scored if r["tier"] != "tutorial_noise"]
print(f"\nTop repos for enrichment (non-noise): {len(non_noise)}")
for r in non_noise[:5]:
    print(f"  {r['full_name']} ({r['tier']}) -- {r.get('description','')[:60]}")
PYEOF

すべてのリポジトリがtutorial_noiseの場合: 停止します。ユーザーに伝えます: 「見つかったすべてのリポジトリはチュートリアルまたはサンプルであると思われます。公開GitHubでは本番環境の採用者は検出されませんでした。SDKが新しすぎるか、パッケージ名が一般的すぎて、検索結果がサンプルに支配されている可能性があります。」


ステップ5: 高シグナルリポジトリのエンリッチメント

ノイズでないリポジトリについて、完全なリポジトリメタデータ、所有者プロファイル、トップコントリビューターを取得します。tutorial_noiseリポジトリは完全にスキップします。

python3 << 'PYEOF'
import json, urllib.request, ssl, os, time
from datetime import datetime, timezone

ctx = ssl._create_unverified_context()
token = os.environ["GITHUB_TOKEN"]
headers = {
    "Accept": "application/vnd.github+json",
    "Authorization": f"Bearer {token}",
    "User-Agent": "sdk-adoption-tracker/1.0"
}

scored = json.load(open("/tmp/sat-scored.json"))
core_remaining = 5000
flags = []

def gh_get(path):
    global core_remaining
    req = urllib.request.Request(f"https://api.github.com{path}", headers=headers)
    try:
        with urllib.request.urlopen(req, timeout=15, context=ctx) as resp:
            remaining = resp.headers.get("X-RateLimit-Remaining")
            if remaining:
                core_remaining = int(remaining)
            return json.loads(resp.read())
    except urllib.error.HTTPError as e:
        if e.code == 404:
            return None
        raise
    except Exception:
        return None

target = [r for r in scored if r["tier"] != "tutorial_noise"]
print(f"Enriching {len(target)} repos (skipping tutorial_noise)...")

enriched = []

for item in target:
    full_name = item["full_name"]
    owner_login = item["owner_login"]
    owner_type = item.get("owner_type", "User")

    # Fetch repo metadata
    repo_data = gh_get(f"/repos/{full_name}")
    if repo_data is None:
        print(f"  Skipping {full_name} (repo not found)")
        continue

    # Fetch owner profile for company field
    owner_profile = gh_get(f"/users/{owner_login}") if owner_type == "User" else gh_get(f"/orgs/{owner_login}")

    if core_remaining < 50:
        flags.append(f"Core API rate limited near end ({core_remaining} remaining)")
        print("  Core API rate low, stopping early")
        break

    # Fetch top contributors (max 5)
    contributors = gh_get(f"/repos/{full_name}/contributors?per_page=5")
    if contributors and isinstance(contributors, list):
        contributor_list = [{"login": c["login"], "contributions": c["contributions"]} for c in contributors if not c.get("login", "").startswith("dependabot")]
    else:
        contributor_list = []

    # Build enriched output
    company_name = "not listed"
    if owner_profile:
        company_name = owner_profile.get("company") or "not listed"
        if company_name == "not listed" and owner_type == "Organization":
            company_name = owner_profile.get("name") or "not listed"

    repo_name = repo_data.get("name", "")
    stars = repo_data.get("stargazers_count", 0)
    forks = repo_data.get("forks_count", 0)
    is_fork = repo_data.get("fork", False)
    is_archived = repo_data.get("archived", False)
    pushed_at_str = repo_data.get("pushed_at", "")
    days_since_push = 9999
    if pushed_at_str:
        pushed_at = datetime.fromisoformat(pushed_at_str.replace("Z", "+00:00"))
        days_since_push = (datetime.now(timezone.utc) - pushed_at).days

    # Update classification tier
    tier = item["tier"]
    if tier == "solo_dev" and company_name not in ["not listed", None, ""]:
        tier = "affiliated_dev"

    # Recalculate adoption score
    score = 0
    if owner_type == "Organization": score += 50
    if tier == "affiliated_dev": score += 30
    if not item["is_tutorial"]: score += 20
    score += min(stars, 200) // 10  # up to 20 points for stars
    days_score = max(0, 30 - days_since_push)
    score += days_score  # up to 30 points for recency
    if is_fork: score -= 40
    if is_archived: score -= 60

    enriched.append({
        "full_name": full_name,
        "repo_url": repo_data.get("html_url", ""),
        "description": repo_data.get("description") or "",
        "owner_login": owner_login,
        "owner_type": owner_type,
        "company_name": company_name,
        "stars": stars,
        "forks": forks,
        "is_fork": is_fork,
        "is_archived": is_archived,
        "pushed_at": pushed_at_str,
        "days_since_push": days_since_push,
        "created_at": repo_data.get("created_at", ""),
        "top_contributors": contributor_list,
        "matched_pattern": item["matched_pattern"],
        "tier": tier,
        "adoption_score": max(0, score)
    })

    time.sleep(0.1)  # be gentle

# Sort by score descending
enriched.sort(key=lambda x: -x["adoption_score"])

json.dump(enriched, open("/tmp/sat-enriched.json", "w"), indent=2)

print(f"\nEnriched: {len(enriched)} repos")
if flags:
    print("Flags:", flags)

# Summary
for r in enriched[:5]:
    print(f"  {r['full_name']} [score={r['adoption_score']}, tier={r['tier']}, company={r['company_name']}]")
PYEOF

ステップ6: 採用者レポートの生成

エンリッチされたデータをランク付けされたMarkdownレポートにフォーマットします。

python3 << 'PYEOF'
import json

enriched = json.load(open("/tmp/sat-enriched.json"))
input_data = json.load(open("/tmp/sat-input.json"))
sdk_name = input_data["sdk_name"]

# Filter noise for report body
company_repos = [r for r in enriched if r["tier"] == "company_org"]
affiliated_repos = [r for r in enriched if r["tier"] == "affiliated_dev"]
solo_repos = [r for r in enriched if r["tier"] == "solo_dev"]

print(f"# SDK Adoption Report: {sdk_name}\n")
print(f"**Generated:** $(date -u +%Y-%m-%dT%H:%M:%SZ)\n")
print(f"**Total repositories found:** {len(enriched)} (excluding tutorial noise)\n")

print("## Executive Summary\n")
print(f"- **Company organizations:** {len(company_repos)}")
print(f"- **Affiliated developers:** {len(affiliated_repos)}")
print(f"- **Solo developers:** {len(solo_repos)}")
print(f"- **Tutorial noise excluded:** {len([r for r in json.load(open('/tmp/sat-scored.json')) if r['tier'] == 'tutorial_noise'])}")
print()

if company_repos:
    print("## Company Adopters (Ranked)\n")
    for i, repo in enumerate(company_repos, 1):
        dept = repo.get("department", "not specified")
        company = repo["company_name"]
        print(f"### {i}. {repo['full_name']}")
        print(f"- **Company:** {company}")
        print(f"- **Score:** {repo['adoption_score']}")
        print(f"- **Stars:** {repo['stars']} | **Forks:** {repo['forks']}")
        print(f"- **Last push:** {repo['days_since_push']} days ago")
        print(f"- **Description:** {repo['description'][:150] if repo['description'] else 'not listed'}")
        if repo['top_contributors']:
            print(f"- **Top contributors:** {', '.join([c['login'] for c in repo['top_contributors'][:3]])}")
        print()

if affiliated_repos:
    print("## Affiliated Developers\n")
    for i, repo in enumerate(affiliated_repos, 1):
        print(f"### {i}. {repo['full_name']}")
        print(f"- **Company:** {repo['company_name']}")
        print(f"- **Score:** {repo['adoption_score']}")
        print(f"- **Stars:** {repo['stars']} | **Forks:** {repo['forks']}")
        print()

if solo_repos:
    print("## Other Individual Developers\n")
    for i, repo in enumerate(solo_repos[:10], 1):
        print(f"{i}. **{repo['full_name']}** — Score: {repo['adoption_score']}, Stars: {repo['stars']}")
    if len(solo_repos) > 10:
        print(f"\n... and {len(solo_repos) - 10} more solo developer repos\n")

# Notify about unflagged potential
raw_results = json.load(open("/tmp/sat-raw-results.json"))
if len(raw_results) > len(enriched):
    print(f"\n**Note:** {len(raw_results)} repos were found, but {len(raw_results) - len(enriched)} could not be enriched (API error or rate limit).\n")

print("---\n")
print("## Next Steps\n")
print("### Suggested Outreach Targets\n")
for repo in company_repos[:5]:
    company = repo["company_name"]
    print(f"- **{company}** ({repo['full_name']})")
    print(f"  - Outreach angle: This organization uses {sdk_name}. Reach out to their team.")
    print()

print("### Notes\n")
print("- All company names verified against GitHub API")
print("- Tutorial and example repos filtered out")
print("- Scores are relative indicators, not absolute quality metrics")
PYEOF

ステップ7: AI分析 (オプション)

採用データを要約し、パーソナライズされたアウトリーチメッセージ案を生成するためにAIを使用します。重要: 上位20件の高シグナルリポジトリのみを送信します。すべてを送信しないでください。

各企業のアウトリーチメッセージには以下を含めます:

  • リポジトリ名とそれがSDKをどのように使用しているか
  • 会社名 (GitHub APIから検証済み)
  • SDKの使用を認めたトップコントリビューター (依存関係ボットは除外)
  • 製品コンテキスト (提供されている場合)
  • パーソナライズされたアウトリーチの草案
# Example structure for AI analysis input (top 20 high-signal repos)
python3 << 'PYEOF'
import json

enriched = json.load(open("/tmp/sat-enriched.json"))[:20]
input_data = json.load(open("/tmp/sat-input.json"))

summary = {
    "sdk_name": input_data["sdk_name"],
    "product_context": input_data.get("product_context", "not provided"),
    "total_repos_found": len(enriched),
    "tiers": {},
    "top_repos": enriched
}

# Count tiers
for r in enriched:
    t = r["tier"]
    summary["tiers"][t] = summary["tiers"].get(t, 0) + 1

print(json.dumps(summary, indent=2))
PYEOF

ステップ8: 完了と共有

結果をユーザーに提示します:

  1. 採用レポートを表示するかどうかを尋ねます
  2. 提供する場合はレポートの全文を表示します
  3. 企業リポジトリごとにパーソナライズされたアウトリーチメッセージを提案します
  4. let the user know if any flags were raised (rate limits, skipped patterns, etc.)

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

Varnan-Tech/opendirectory

Use when the user asks to generate a blog cover image, thumbnail, or article header. Automatically uses modern typography, brand logos, and Google Search grounding to create beautiful 16:9 images with Gemini 3.1 Flash Image Preview.

Varnan-Tech/opendirectory

World-class brand strategist and naming expert. Uses an interrogation-led discovery phase to extract your brand's DNA, then applies scientific naming frameworks (Phonosemantics) and automated multi-TLD domain checking.

Varnan-Tech/opendirectory

Use when the user asks to generate or update a project's CLAUDE or AGENTS context file from a codebase scan. Writes a focused file under 100 lines containing only the non-obvious build commands, conventions, and gotchas Claude Code needs.

Varnan-Tech/opendirectory

Use when the user wants to verify cold emails, enrich a lead list, or autonomously guess email addresses from a CSV using ValidEmail.co or the open-source Reacher engine.

Varnan-Tech/opendirectory

Competitive intelligence orchestrator tracking companies across 8+ platforms (GitHub, Twitter, Reddit, HN, PH, YC Jobs) with heat scores and AI briefings.

Varnan-Tech/opendirectory

Give it your product URL or description. It finds your top 5 competitors, runs three-track PR research across all of them (editorial, podcasts, communities), identifies which channels appear most frequently, looks up the journalist or host for each, and returns a tiered outreach list with story angles and ready-to-send cold pitch drafts tailored to your product. Use when asked to find PR opportunities, discover where competitors got featured, build a media outreach list, find which journalists cover my space, or get pitch templates for press coverage.

Varnan-Tech/opendirectory

Generate high-converting, deep-dive growth case studies in MDX format. Use this skill when asked to write a case study or blog post about a company's growth, tech stack, or product-led strategy. It handles the full pipeline (researching the company via Tavily, generating a 16:9 cover image, quality checking the draft, uploading assets to cloud storage, and pushing directly to the target repository).

Varnan-Tech/opendirectory

Scans your project for outdated npm, pip, Cargo, Go, or Ruby packages. Runs a CVE security audit. Fetches changelogs, summarizes breaking changes with Gemini, and opens one PR per risk group (patch, minor, major). Includes Diagnosis Mode for install conflicts. Use when asked to update dependencies, check for outdated packages, open dependency PRs, scan for package updates, audit for CVEs, or flag breaking changes in upgrades. Trigger when a user says "check for outdated packages", "update my dependencies", "open PRs for dependency updates", "scan for CVEs", or "which packages need upgrading".

Varnan-Tech/opendirectory

Generates and updates README.md and API reference docs by reading your codebase's functions, routes, types, schemas, and architecture. Uses graphify to build a knowledge graph first, then writes accurate docs from it. Use when asked to write docs, generate a README, document an API, update stale docs, create an API reference from code, add an architecture section, or document a project in any language. Trigger when a user says their docs are missing, outdated, or wants to document their codebase without writing it manually.

Varnan-Tech/opendirectory

Evaluates expired domain candidates against a target niche, scores them by topical relevance, historical activity level, and history cleanliness, then outputs a ranked shortlist with explainable reasoning and risk flags.

Varnan-Tech/opendirectory

Brutally honest developer-experience audit for a GitHub repo. Scores 10 DX dimensions (time-to-first-success, README clarity, visual proof, install, quick-start, docs, examples, community, trust, marketing), writes a shareable roast in the requested tone (brutal/honest/kind), produces a prioritized action plan ranked by impact × effort, and sketches an ideal README. Trigger when user says "roast my repo", "audit my README", "dx audit", "developer experience review", "score my GitHub project", "before launch checklist", or "make my repo shareable".

Varnan-Tech/opendirectory

Drafts and designs a complete HTML email newsletter from a topic or content brief. Outputs paste-ready HTML for Loops, Mailchimp, Beehiiv, Resend, or any standard email platform. Includes subject line options and plain-text fallback. Trigger when a user says "write a newsletter", "draft an email newsletter", "create an HTML email", "design an email for my subscribers", or "write a newsletter about [topic]".

Varnan-Tech/opendirectory

Takes a GitHub PR URL or the current branch and writes a plain-English explanation of what it does and why, then posts it as a PR comment. Use when asked to explain a PR, summarize a pull request, write a plain-English description of a PR, add a summary comment to a PR, or understand what a PR changes. Trigger when a user says "explain this PR", "summarize this pull request", "what does this PR do", "add a comment explaining the PR", or shares a GitHub PR URL and asks what it does.

Varnan-Tech/opendirectory

Audit how often LLMs recommend your brand vs competitors and generate a GEO action plan.

Varnan-Tech/opendirectory

Takes a competitor's public GitHub repo URL, fetches their open issues via the GitHub REST API, filters noise locally, clusters issues into 6 demand categories, computes a demand score per issue and per cluster, and outputs a ranked demand gap report with a GTM messaging brief. Use when asked to scan a competitor's GitHub issues, find what their users are begging for, turn GitHub complaints into product positioning, identify competitor feature gaps, or generate messaging from real user demand. Trigger when a user says "scan competitor issues", "what are users asking for on X repo", "find demand gaps in Y", "turn GitHub issues into messaging", or "what should I build based on competitor complaints".

Varnan-Tech/opendirectory

Find recurring confusion in your repo's GitHub Discussions, rank it by urgency, and draft the actual docs fixes and content angles — with verbatim community quotes and source links as evidence.

Varnan-Tech/opendirectory

SEO keyword research workflow for blog generation using Google Trends data. Use when writing blog posts, planning content calendars, or optimizing articles for search engines. Finds breakout keywords, builds content structure, and generates SEO-optimized blog outlines targeting tech and developer audiences.

Varnan-Tech/opendirectory

Generates a professionally designed case study PDF for B2B SaaS sales and marketing. Supports 7 page layouts, 9 style presets, 1-4 page output. Trigger when user says "create a case study", "write a customer story", "make a case study PDF", "design a success story", "turn this customer win into a PDF".

Varnan-Tech/opendirectory

Generates data visualization charts (bar, line, area, pie, doughnut, scatter, radar, treemap) as PNG using Apache ECharts v6. 1080×1080px default, 5 style presets, highlight annotations. Trigger when user says "create a chart", "visualize data", "make a bar chart", "line graph", "pie chart", "data visualization", "chart this data", "plot", "graph", or "visualize these numbers".

Varnan-Tech/opendirectory

Fetches low-star App Store and Google Play reviews, clusters them into broken-promise patterns, and generates a ranked copy brief with positioning opportunities.

関連スキル