Communitygithub.com

Varnan-Tech/opendirectory

Analysiert npm-Pakete, berechnet Wachstumsgeschwindigkeit, reichert Maintainer-Profile an und erstellt eine priorisierte Liste mit Kontaktdaten und Nachrichtenvorschlägen.

Was ist opendirectory?

Diese Fähigkeit identifiziert frühzeitig vielversprechende Open-Source-Maintainer („Evangelisten“) durch die Analyse von npm-Download-Trends. Sie holt 12 Wochen tägliche Download-Daten über die öffentliche npm-API, berechnet eine „Breakout-Velocity“-Kennzahl zur Erkennung von Hockey-Stick-Wachstum und reichert die Profile der Maintainer mit Daten aus dem npm-Registry und der GitHub-API an. Das Ergebnis ist eine nach Priorität geordnete Liste mit Breakout-Paketen, den zugehörigen Maintainern, Kontakthinweisen und einer personalisierten Outreach-Nachricht. Unterstützt wird eine nahtlose Nutzung über ein gebündeltes Python-Skript (scripts/fetch.py) oder, falls nicht verfügbar, über einzelne Inline-Code-Blöcke. Die Fähigkeit behandelt wichtige Eckfälle wie das URL-Encoding für Scoped Packages (@ → %40, / → %2F), das Graceful-Handling des GitHub-Rate-Limits (60 req/h ohne Token) und die korrekte Klassifizierung von Paketen (Breakout, Watching, Steady, Established, Too Early). Pakete unter 500 wöchentlichen Downloads werden als verfrüht eingestuft und nicht als Leads präsentiert.

Funktioniert mit✓Claude Code~Codex CLI~Cursor✓Gemini CLI
npx skills add https://github.com/Varnan-Tech/opendirectory/tree/main/skills/npm-downloads-to-leads

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

npm Downloads zu Leads

Nimm eine Liste von npm-Paketen, hole 12 Wochen Download-Daten, berechne die Breakout-Velocity, reichere Maintainer-Profile an und gebe eine priorisierte Liste pro Breakout-Paket mit Kontakthinweisen und einer Outreach-Nachricht aus.


Wichtige Regel: Jede Download-Zahl im Ergebnis muss aus der npm-API-Antwort stammen. Jeder GitHub-Handle oder Twitter-Username eines Maintainers muss aus der GitHub-API-Antwort stammen – nicht aus dem npm-Username geraten. Wenn die GitHub-API kein Feld twitter_username zurückgibt, schreibe „not found on GitHub“ – erfinde keinen.


Häufige Fehler

Der Agent wird dazu neigen...Warum das falsch ist
GitHub-Profile für jedes Paket in der Liste abrufenDas Rate-Limit liegt bei 60 req/h ohne Token. Das Anreichern stabiler oder rückläufiger Pakete verbraucht das Budget, bevor man zu den Breakout-Paketen kommt. Hole nur Profile für Breakout- und Watching-Pakete.
Pakete nach rohen wöchentlichen Downloads ordnenRohe Downloads begünstigen React und Lodash, die keine Leads sind. Ein Paket, das von 1K auf 8K/Woche steigt, ist relevanter als React mit 50M/Woche. Die Velocity-Kennzahl ist das Signal.
URL-Encoding für Scoped Packages überspringen@org/pkg ohne Encoding führt zu einem 404 von der npm-API. Kodiere @ als %40 und / als %2F für jedes Scoped Package.
Die Fähigkeit abbrechen, wenn das GitHub-Rate-Limit erreicht istMache einen Graceful-Downgrade. Präsentiere die Velocity-Rangliste aus npm-Daten, überspringe verbleibende GitHub-Anreicherungen und füge ein Flag zu data_quality_flags hinzu. Brich nicht ab.
Outreach-Nachrichten ohne Nennung des spezifischen Pakets schreibenGenerische „Ich habe Ihr Projekt gesehen“-Nachrichten bleiben unbeantwortet. Jede Outreach-Nachricht muss das Paket, seine Wachstumszahlen und einen spezifischen Bezug zum vom Benutzer gegebenen Kontext nennen.
Pakete unter 500 wöchentlichen Downloads als Leads einschließenUnter 500/Woche ist Rauschen. Der Maintainer hat noch keine relevante Zielgruppe. Markiere als „too early“, aber präsentiere es nicht als Lead.

Schritt 1: Setup-Check

echo "GITHUB_TOKEN: ${GITHUB_TOKEN:-nicht gesetzt, unauthentifiziertes Rate-Limit gilt (60 req/h – genug für ~10 Pakete)}"

Wenn GITHUB_TOKEN nicht gesetzt ist: Fahre fort. Informiere den Benutzer: „GITHUB_TOKEN ist nicht gesetzt. Die GitHub-Anreicherung ist auf ~10 Pakete beschränkt, bevor das Rate-Limit erreicht wird. Füge ein Token unter github.com/settings/tokens hinzu (keine Berechtigungen nötig).“

Keine erforderlichen Schlüssel. Die npm-API und das npm-Registry sind vollständig öffentlich und benötigen keine Authentifizierung.


Schritt 2: Eingabe sammeln

Sammle aus dem Gespräch:

  • Einen oder mehrere npm-Paketnamen (unscoped wie esbuild oder scoped wie @hono/hono)
  • Optional: einen kurzen Produktkontext-String (wird für personalisierte Outreach-Nachrichten verwendet)

Wenn der Benutzer eine npmjs.com-URL angibt, extrahiere nur den Paketnamen. Bewahre den vollständigen Scoped-Namen inklusive @ und Org-Präfix – das Encoding erfolgt in Schritt 3.

Wenn keine Pakete angegeben werden: Frage: „Welche npm-Pakete möchtest du analysieren? Gib eigene, Konkurrenz- oder eine Mischung an. Beispiel: esbuild, @hono/hono, zod, valibot“

python3 << 'PYEOF'
import json, sys

packages_raw = "PACKAGES_HERE"  # komma- oder zeilenumbruch-getrennt
product_context = "CONTEXT_HERE"  # optional, kann leerer String sein

packages = [p.strip() for p in packages_raw.replace("\n", ",").split(",") if p.strip()]
if not packages:
    print("ERROR: Keine Pakete angegeben.")
    sys.exit(1)

print(f"Zu analysierende Pakete: {len(packages)}")
for p in packages:
    print(f"  {p}")

with open("/tmp/npl-input.json", "w") as f:
    json.dump({"packages": packages, "product_context": product_context}, f)
PYEOF

Schritt 3: 12-Wochen-Download-Daten abrufen

Verwende das eigenständige Skript, falls verfügbar – es erledigt die Schritte 3, 4 und 5 in einem Aufruf, sodass du die Inline-Code-Blöcke unten nicht ausführen musst.

# Prüfe, ob das Skript existiert
ls scripts/fetch.py 2>/dev/null && echo "Skript verfügbar" || echo "Skript nicht gefunden"

Wenn das Skript verfügbar ist, führe es direkt aus und springe zu Schritt 6:

python3 scripts/fetch.py PAKETE_HIER --context "KONTEXT_HIER" --output /tmp/npl-script-out.json

Lade dann die Ausgabe in das Format, das Schritt 6 erwartet:

python3 << 'PYEOF'
import json

out = json.load(open("/tmp/npl-script-out.json"))
# Skript-Ausgabe hat ein results-Array – teile es in scored und enriched für Schritte 6-8 auf
enriched = [r for r in out["results"] if "profile" in r]
scored = out["results"]
json.dump(scored, open("/tmp/npl-scored.json", "w"), indent=2)
json.dump(enriched, open("/tmp/npl-enriched.json", "w"), indent=2)
json.dump({"packages": [r["package"] for r in scored], "product_context": out.get("product_context", "")},
          open("/tmp/npl-input.json", "w"), indent=2)
print(f"Geladen: {len(scored)} Pakete | {out['breakout_count']} Breakout | {out['watching_count']} Watching")
PYEOF

Wenn das Skript nicht verfügbar ist, führe den Inline-Code unten aus.

Rufe tägliche Download-Daten für jedes Paket von der npm Downloads API ab. Aggregiere zu wöchentlichen Buckets.

python3 << 'PYEOF'
import json, urllib.request, sys, time
from datetime import datetime, timedelta, timezone
from collections import defaultdict
import urllib.parse

data = json.load(open("/tmp/npl-input.json"))
packages = data["packages"]

end_date = datetime.now(tz=timezone.utc)
start_date = end_date - timedelta(weeks=13)  # extra Pufferwoche für Teilwochen
start_str = start_date.strftime("%Y-%m-%d")
end_str = end_date.strftime("%Y-%m-%d")

results = []
failed = []

for pkg in packages:
    # URL-Encoding für Scoped Packages: @ -> %40, / -> %2F
    encoded = pkg.replace("@", "%40").replace("/", "%2F")
    url = f"https://api.npmjs.org/downloads/range/{start_str}:{end_str}/{encoded}"

    try:
        req = urllib.request.Request(url, headers={"User-Agent": "npm-downloads-to-leads/1.0"})
        with urllib.request.urlopen(req, timeout=20) as resp:
            raw = json.loads(resp.read())

        # Täglich auf ISO-Woche aggregieren
        weekly = defaultdict(int)
        for entry in raw.get("downloads", []):
            day = datetime.strptime(entry["day"], "%Y-%m-%d")
            week_key = day.isocalendar()[:2]  # (year, week_num)
            weekly[week_key] += entry["downloads"]

        weeks = [v for k, v in sorted(weekly.items())]
        # Nimm die letzten 12 vollständigen wöchentlichen Buckets
        weeks = weeks[-12:]

        results.append({
            "package": pkg,
            "weeks": weeks,
            "total_weeks": len(weeks),
            "current_weekly": weeks[-1] if weeks else 0,
            "status": "ok"
        })
        print(f"  {pkg}: {len(weeks)} Wochen, letzte Woche {weeks[-1]:,} Downloads")

    except urllib.error.HTTPError as e:
        if e.code == 404:
            failed.append(pkg)
            results.append({"package": pkg, "weeks": [], "total_weeks": 0, "current_weekly": 0, "status": "not_found"})
            print(f"  {pkg}: NICHT GEFUNDEN (404) – wird übersprungen")
        else:
            failed.append(pkg)
            results.append({"package": pkg, "weeks": [], "total_weeks": 0, "current_weekly": 0, "status": f"error_{e.code}"})
            print(f"  {pkg}: HTTP {e.code} Fehler")
    except Exception as e:
        failed.append(pkg)
        results.append({"package": pkg, "weeks": [], "total_weeks": 0, "current_weekly": 0, "status": f"error"})
        print(f"  {pkg}: Abruf fehlgeschlagen ({e})")

    time.sleep(0.2)  # sanftes Rate-Limiting

json.dump(results, open("/tmp/npl-download-data.json", "w"), indent=2)
print(f"\nAbruf abgeschlossen. OK: {len(results) - len(failed)} | Fehlgeschlagen/Nicht gefunden: {len(failed)}")
if failed:
    print(f"Übersprungen: {', '.join(failed)}")
PYEOF

Wenn alle Pakete 404 oder Fehler zurückgeben: Stoppe. Sage dem Benutzer: „Es konnten keine Download-Daten abgerufen werden. Überprüfe, ob die Paketnamen korrekt sind und auf npmjs.com existieren. Scoped Packages müssen den vollständigen Namen enthalten: @org/package.“


Schritt 4: Velocity-Scores berechnen

Kein API-Aufruf. Reines Python. Berechne Velocity-Score, Wachstumsrate und klassifiziere jedes Paket.

python3 << 'PYEOF'
import json

raw_results = json.load(open("/tmp/npl-download-data.json"))
scored = []

for item in raw_results:
    pkg = item["package"]
    weeks = item["weeks"]
    status = item["status"]

    if status != "ok" or len(weeks) < 4:
        scored.append({**item, "velocity_score": 0, "growth_pct": 0, "tier": "insufficient_data",
                       "recent_4_avg": 0, "prior_4_avg": 0})
        continue

    recent_4 = sum(weeks[-4:]) / 4
    prior_4 = sum(weeks[-8:-4]) / max(len(weeks) - 4, 1) if len(weeks) >= 8 else sum(weeks[:4]) / max(len(weeks[:4]), 1)
    recent_2 = sum(weeks[-2:]) / 2
    mid_2 = sum(weeks[-4:-2]) / 2 if len(weeks) >= 4 else recent_2

    growth_ratio = recent_4 / max(prior_4, 1)
    acceleration = recent_2 / max(mid_2, 1)
    growth_pct = round((growth_ratio - 1) * 100, 1)

    # Sweet-Spot-Multiplikator: 500-500K wöchentliche Downloads
    if recent_4 < 500:
        noise_factor = max(recent_4 / 500, 0.1)
    elif recent_4 > 500_000:
        noise_factor = max(500_000 / recent_4, 0.1)
    else:
        noise_factor = 1.0

    velocity_score = round(growth_ratio * acceleration * noise_factor * 100, 1)

    # Klassifizierung
    if velocity_score > 80 and 500 < recent_4 < 500_000 and growth_ratio >= 1.5:
        tier = "breakout"
    elif velocity_score > 40 and recent_4 >= 500 and growth_ratio >= 1.2:
        tier = "watching"
    elif recent_4 < 500:
        tier = "too_early"
    elif recent_4 >= 500_000:
        tier = "established"
    else:
        tier = "steady"

    scored.append({
        **item,
        "velocity_score": velocity_score,
        "growth_pct": growth_pct,
        "recent_4_avg": round(recent_4),
        "prior_4_avg": round(prior_4),
        "tier": tier
    })

# Sortiere nach velocity_score absteigend
scored.sort(key=lambda x: x["velocity_score"], reverse=True)

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

breakout = [p for p in scored if p["tier"] == "breakout"]
watching = [p for p in scored if p["tier"] == "watching"]
too_early = [p for p in scored if p["tier"] == "too_early"]

print(f"Velocity-Bewertung abgeschlossen:")
print(f"  BREAKOUT: {len(breakout)}")
print(f"  WATCHING: {len(watching)}")
print(f"  STEADY/ESTABLISHED: {len([p for p in scored if p['tier'] in ('steady','established')])}")
print(f"  TOO EARLY (<500/Woche): {len(too_early)}")
print()
for p in scored[:10]:
    print(f"  {p['tier'].upper():12} {p['package']:30} score={p['velocity_score']:6.1f}  "
          f"{p['recent_4_avg']:>8,}/wk  growth={p['growth_pct']:+.0f}%")

# Stoppe, wenn nichts Analysierenswertes vorhanden ist
if not breakout and not watching:
    all_too_early = all(p["tier"] in ("too_early", "insufficient_data") for p in scored)
    if all_too_early:
        print("\nERROR: Alle Pakete liegen unter dem Schwellenwert von 500 wöchentlichen Downloads für eine zuverlässige Velocity-Analyse.")
        print("Versuche Pakete mit stärkerer Community-Adoption.")
        import sys; sys.exit(1)
PYEOF

Wenn alle Pakete unter 500/Woche liegen: Stoppe mit der obigen Meldung.


Schritt 5: Maintainer-Profile abrufen

Nur für Breakout- und Watching-Pakete. Rufe npm-Registry-Metadaten und dann GitHub-Benutzerprofile ab.

python3 << 'PYEOF'
import json, urllib.request, re, os, time

scored = json.load(open("/tmp/npl-scored.json"))
token = os.environ.get("GITHUB_TOKEN", "")

gh_headers = {"Accept": "application/vnd.github+json", "User-Agent": "npm-downloads-to-leads/1.0"}
if token:
    gh_headers["Authorization"] = f"Bearer {token}"

target_packages = [p for p in scored if p["tier"] in ("breakout", "watching")]
print(f"Hole Profile für {len(target_packages)} Pakete (Breakout + Watching)...")

gh_rate_remaining = 999
enriched = []

for item in target_packages:
    pkg = item["package"]
    profile = {"package": pkg, "npm_maintainers": [], "description": "", "keywords": [],
               "github_owner": None, "github_repo": None, "github_users": [], "npm_homepage": ""}

    # --- npm Registry ---
    encoded = pkg.replace("@", "%40").replace("/", "%2F")
    reg_url = f"https://registry.npmjs.org/{encoded}"
    try:
        req = urllib.request.Request(reg_url, headers={"User-Agent": "npm-downloads-to-leads/1.0"})
        with urllib.request.urlopen(req, timeout=20) as resp:
            reg = json.loads(resp.read())

        profile["description"] = reg.get("description", "")
        profile["keywords"] = (reg.get("keywords") or [])[:6]
        profile["npm_homepage"] = reg.get("homepage", "")
        profile["npm_maintainers"] = [m.get("name", "") for m in reg.get("maintainers", []) if m.get("name")]

        # Extrahiere GitHub-Besitzer aus Repository-URL
        repo_field = reg.get("repository") or {}
        if isinstance(repo_field, dict):
            repo_url = repo_field.get("url", "")
        else:
            repo_url = str(repo_field)
        gh_match = re.search(r"github\.com[/:]([^/]+)/([^/.]+)", repo_url)
        if gh_match:
            profile["github_owner"] = gh_match.group(1)
            profile["github_repo"] = gh_match.group(2).rstrip(".git")

        print(f"  {pkg}: Registry OK | Maintainer={profile['npm_maintainers'][:3]} | "
              f"github_owner={profile['github_owner']}")
    except Exception as e:
        print(f"  {pkg}: Registry-Abruf fehlgeschlagen ({e})")

    time.sleep(0.1)

    # --- GitHub-Benutzerprofile ---
    candidates = []
    if profile["github_owner"]:
        candidates.append(profile["github_owner"])
    # Versuche auch npm-Maintainer-Usernamen (oft identisch mit GitHub)
    for m in profile["npm_maintainers"][:2]:
        if m and m not in candidates:
            candidates.append(m)

    for username in candidates[:3]:
        if gh_rate_remaining <= 5:
            print(f"  GitHub-Rate-Limit niedrig ({gh_rate_remaining} verbleibend) – überspringe {username}")
            break

        gh_url = f"https://api.github.com/users/{username}"
        req = urllib.request.Request(gh_url, headers=gh_headers)
        try:
            with urllib.request.urlopen(req, timeout=15) as resp:
                gh_rate_remaining = int(resp.headers.get("X-RateLimit-Remaining", 0))
                user_data = json.loads(resp.read())

            user_info = {
                "github_login": user_data.get("login"),
                "github_name": user_data.get("name") or "",
                "github_bio": (user_data.get("bio") or "")[:200],
                "github_location": user_data.get("location") or "",
                "github_twitter": user_data.get("twitter_username") or "not found on GitHub",
                "github_blog": user_data.get("blog") or "",
                "github_followers": user_data.get("followers", 0),
                "github_public_repos": user_data.get("public_repos", 0),
            }
            profile["github_users"].append(user_info)
            print(f"    GitHub: {username} | name={user_info['github_name']} | "
                  f"twitter={user_info['github_twitter'][:20]} | followers={user_info['github_followers']}")
        except urllib.error.HTTPError as e:
            if e.code == 403:
                print(f"    GitHub: Rate-Limit für {username} erreicht (403)")
                break
            elif e.code == 404:
                print(f"    GitHub: {username} nicht gefunden (404)")
            else:
                print(f"    GitHub: HTTP {e.code} für {username}")
        except Exception as e:
            print(f"    GitHub: Abruf für {username} fehlgeschlagen ({e})")

        time.sleep(0.1)

    enriched.append({**item, "profile": profile})

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

print(f"\nProfilabruf abgeschlossen.")
print(f"Anreicherungsversuche: {len(enriched)}")
print(f"Verbleibendes GitHub-Rate-Limit: {gh_rate_remaining}")
print(f"Letzten 5 Profile: {[p['profile']['npm_maintainers'][:2] for p in enriched[-5:]]}")

# Datenqualitäts-Flags
flags = []
if gh_rate_remaining <= 10:
    flags.append("GitHub-Rate-Limit fast erreicht – einige Profile könnten unvollständig sein")
if len(enriched) < len(target_packages):
    flags.append(f"Nur {len(enriched)} von {len(target_packages)} Zielpaketen konnten angereichert werden")
if flags:
    print(f"Datenqualitäts-Flags: {flags}")
PYEOF

Schritt 6: Lead-Brief ausgeben

Formatiere die Ergebnisse als lesbaren Lead-Brief. Jeder Eintrag enthält: Paketname, Velocity-Score, wöchentliches Wachstum, Maintainer-Details und eine Outreach-Nachricht.

python3 << 'PYEOF'
import json

scored = json.load(open("/tmp/npl-scored.json"))
enriched = json.load(open("/tmp/npl-enriched.json"))
data = json.load(open("/tmp/npl-input.json"))
product_context = data.get("product_context", "")

print("=" * 60)
print("  NPM DOWLOADS TO LEADS – LEAD-BRIEF")
print("=" * 60)
print()

data_quality_flags = []

# --- Zusammenfassung ---
all_scored = sorted(scored, key=lambda x: x["velocity_score"], reverse=True)

breakout = [p for p in all_scored if p["tier"] == "breakout"]
watching = [p for p in all_scored if p["tier"] == "watching"]
steady = [p for p in all_scored if p["tier"] in ("steady", "established")]
too_early = [p for p in all_scored if p["tier"] in ("too_early", "insufficient_data")]

print(f"Analysierte Pakete: {len(all_scored)}")
print(f"  Breakout (AKTION): {len(breakout)}")
print(f"  Watching (BEOBACHTEN): {len(watching)}")
print(f"  Etabliert/Stabil: {len(steady)}")
print(f"  Zu früh (<500/Woche): {len(too_early)}")
print()

# --- Breakout-Details ---
if breakout:
    print("-" * 60)
    print("  BREAKOUT-PAKETE – Höchste Priorität")
    print("-" * 60)
    print()

    for i, p in enumerate(breakout, 1):
        enr = next((e for e in enriched if e["package"] == p["package"]), None)
        prof = enr.get("profile", {}) if enr else {}
        maintainers = prof.get("npm_maintainers", [])
        description = prof.get("description", "")
        keywords = prof.get("keywords", [])
        homepage = prof.get("npm_homepage", "")
        gh_users = prof.get("github_users", [])

        print(f"  #{i}: {p['package']}")
        print(f"  " + "-" * 56)
        print(f"  Velocity-Score: {p['velocity_score']:.1f}")
        print(f"  Aktuell: ~{p['current_weekly']:,}/Woche (4-Wochen-Schnitt: ~{p['recent_4_avg']:,})")
        print(f"  Vorher: ~{p['prior_4_avg']:,}/Woche")
        print(f"  Wachstum: {p['growth_pct']:+.0f}%")
        print(f"  Beschreibung: {description[:150] if description else 'Nicht verfügbar'}")
        if keywords:
            print(f"  Schlagwörter: {', '.join(keywords[:5])}")
        if maintainers:
            print(f"  npm-Maintainer: {', '.join(maintainers[:4])}")
        if gh_users:
            for u in gh_users:
                print(f"  GitHub: {u['github_login']} ({u['github_name']})")
                print(f"    Bio: {u['github_bio'][:100] if u['github_bio'] else 'Nicht verfügbar'}")
                print(f"    Standort: {u['github_location'] if u['github_location'] else 'Nicht verfügbar'}")
                print(f"    Twitter: {u['github_twitter'] if u['github_twitter'] != 'not found on GitHub' else 'Nicht auf GitHub gefunden'}")
                print(f"    Blog: {u['github_blog'] if u['github_blog'] else 'Nicht verfügbar'}")
                print(f"    Follower: {u['github_followers']} | Repos: {u['github_public_repos']}")

        # Erstelle Outreach-Nachricht
        if maintainers:
            maintainer_name = gh_users[0]['github_name'] if gh_users and gh_users[0]['github_name'] else maintainers[0]
            outreach = f"Hallo {maintainer_name.split()[0] if ' ' in maintainer_name else maintainer_name},\n\n"
            outreach += f"Ich bin auf dein Paket **{p['package']}** gestoßen und bin beeindruckt vom jüngsten Wachstum: "
            outreach += f"von {p['prior_4_avg']:,} auf {p['recent_4_avg']:,} Downloads pro Woche "
            outreach += f"– ein Anstieg von {p['growth_pct']:+.0f}% in den letzten Wochen. "
            if description:
                outreach += f"Insbesondere {description[:120]} "
            if product_context:
                outreach += f"\n\nDa ich an {product_context} arbeite, glaube ich, dass sich eine tolle Gelegenheit für eine Zusammenarbeit ergibt. "
            outreach += "\n\nHättest du in den nächsten Tagen kurz Zeit für ein Gespräch?"

            print(f"\n  -> Outreach-Nachricht:")
            print(f"  {outreach}")

        print()

# --- Watching-Details (verkürzt) ---
if watching:
    print("-" * 60)
    print(f"  WATCHING-PAKETE ({len(watching)}) – Beobachten für zukünftige Leads")
    print("-" * 60)
    for p in watching[:10]:
        enr = next((e for e in enriched if e["package"] == p["package"]), None)
        maintainer_info = " | ".join(enr["profile"]["npm_maintainers"][:3]) if enr and enr.get("profile", {}).get("npm_maintainers") else "-"
        print(f"  {p['package']:30} score={p['velocity_score']:6.1f}  {p['recent_4_avg']:>8,}/wk  growth={p['growth_pct']:+.0f}%  maintainers={maintainer_info}")
    if len(watching) > 10:
        print(f"  ... und {len(watching) - 10} weitere Watching-Pakete")
    print()

# --- Datenqualitäts-Flags sammeln ---
print("-" * 60)
print("  QUALITÄTSINDIKATOREN & HINWEISE")
print("-" * 60)
breakout_count = len(breakout)
if breakout_count == 0:
    data_quality_flags.append("Keine Breakout-Pakete gefunden – überprüfe die Liste oder den Schwellenwert")

missing_github = sum(1 for e in enriched if not e.get("profile", {}).get("github_users"))
if missing_github:
    data_quality_flags.append(f"{missing_github} Pakete haben keine GitHub-Benutzerdaten – das Erstellen maßgeschneiderter Outreach-Nachrichten ist eingeschränkt")

if product_context:
    data_quality_flags.append(f"Verwendete Produkt- oder Projektkontext: '{product_context}'")

for flag in data_quality_flags:
    print(f"  * {flag}")

if not data_quality_flags:
    print("  Keine Flags – Datenqualität ist gut")

print()
print("=" * 60)
print(f"  SKILL ABGESCHLOSSEN – {breakout_count} Breakout-Leads gefunden")
print("=" * 60)
PYEOF

Schritt 7: Ergebnisse dem Benutzer präsentieren

Fasse nach der Ausführung von Schritt 6 die Ergebnisse in natürlicher Sprache zusammen:

  • Anzahl der Breakout-Leads und ihre Namen
  • Wichtigste Erkenntnisse aus den Wachstumsdaten
  • Kontaktdaten für jeden Lead (Twitter, GitHub, Blog)
  • Bereits formulierte Outreach-Nachrichten zur Verwendung oder Anpassung
  • Qualitätsflags, wenn Daten fehlen (z. B. GitHub-Rate-Limit erreicht, keine Twitter-Informationen)

Wenn keine Breakout- und keine Watching-Pakete gefunden wurden: Sage es dem Benutzer deutlich. Biete an, die Analyse mit einer anderen Liste oder nach einer gewissen Zeit erneut durchzuführen.


Schritt 8: Wichtige Überlegungen

  • GitHub-Rate-Limit: Ohne Token sind ~10 API-Aufrufe pro Stunde möglich. Das reicht für eine kleine Liste von Breakout-Paketen. Für größere Listen wird ein Token benötigt.
  • Scoped Packages: Müssen URL-kodiert werden (@ → %40, / → %2F). Dieses Handbuch behandelt das korrekt.
  • Pakete unter 500/Woche: Werden als „too_early“ markiert und nicht als Leads präsentiert.
  • Große Frameworks: Werden als „established“ eingestuft, da ihr Wachstum selten signifikant genug ist, um einen Lead zu rechtfertigen.
  • Outreach-Nachrichten: Müssen spezifisch sein. Ein Paketnamen und konkrete Zahlen sind der Schlüssel. Generische Nachrichten werden ignoriert.

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.

Verwandte Skills