{"id":"e66f965e-9a19-4311-888e-13017c51e649","shortId":"cGtc8g","kind":"skill","title":"npm-downloads-to-leads","tagline":"Takes a list of npm package names (yours or competitors'), fetches 12 weeks of daily download data from the npm API, computes a breakout velocity score per package to identify hockey-stick growth, fetches maintainer profiles from the npm registry and GitHub API, and outputs a ran","description":"# npm Downloads to Leads\n\nTake a list of npm packages. Fetch 12 weeks of download data. Compute breakout velocity. Enrich maintainer profiles. Output a ranked lead brief per breakout package with contact signals and an outreach message.\n\n---\n\n**Critical rule:** Every package download figure in the output must come from the npm API response. Every maintainer GitHub handle or Twitter username must come from the GitHub API response -- not guessed from the npm username. If the GitHub API did not return a twitter_username field, write \"not found on GitHub\" -- do not invent one.\n\n---\n\n## Common Mistakes\n\n| The agent will want to... | Why that's wrong |\n|---|---|\n| Fetch GitHub profiles for every package in the list | Rate limit is 60 req/hr without a token. Enriching steady or declining packages wastes the budget before reaching breakout ones. Only fetch profiles for breakout and watching packages. |\n| Rank packages by raw weekly downloads | Raw downloads favor React and lodash, which are not leads. A package going from 1K to 8K/week is more actionable than React at 50M/week. Velocity score is the signal. |\n| Skip URL-encoding for scoped packages | @org/pkg without encoding causes a 404 from the npm API. Encode @ as %40 and / as %2F for every scoped package name. |\n| Stop the skill when the GitHub rate limit is hit | Degrade gracefully. Present the velocity leaderboard from npm data, skip remaining GitHub enrichments, and add a flag to data_quality_flags. Do not abort. |\n| Write outreach messages without naming the specific package | Generic \"I saw your project\" messages go unanswered. Every outreach message must name the package, its growth numbers, and a specific connection to the context the user provided. |\n| Include packages below 500 weekly downloads as leads | Below 500/week is noise. The maintainer has no meaningful audience yet. Flag as \"too early\" but do not present as a lead. |\n\n---\n\n## Step 1: Setup Check\n\n```bash\necho \"GITHUB_TOKEN: ${GITHUB_TOKEN:-not set, unauthenticated rate limit applies (60 req/hr -- enough for ~10 packages)}\"\n```\n\n**If GITHUB_TOKEN is not set:** Continue. Inform the user: \"GITHUB_TOKEN is not set. GitHub enrichment is limited to ~10 packages before hitting the rate limit. Add a token at github.com/settings/tokens (no scopes needed).\"\n\nNo required keys. The npm API and npm registry are fully public with no authentication.\n\n---\n\n## Step 2: Gather Input\n\nCollect from the conversation:\n- One or more npm package names (unscoped like `esbuild`, or scoped like `@hono/hono`)\n- Optional: a short product context string (used to personalize outreach messages)\n\nIf the user gives an npmjs.com URL, extract just the package name. Preserve the full scoped name including `@` and org prefix -- encoding is handled in Step 3.\n\n**If no packages are provided:** Ask: \"Which npm packages would you like to analyze? Provide your own, competitors, or a mix. Example: esbuild, @hono/hono, zod, valibot\"\n\n```bash\npython3 << 'PYEOF'\nimport json, sys\n\npackages_raw = \"PACKAGES_HERE\"  # comma or newline separated\nproduct_context = \"CONTEXT_HERE\"  # optional, can be empty string\n\npackages = [p.strip() for p in packages_raw.replace(\"\\n\", \",\").split(\",\") if p.strip()]\nif not packages:\n    print(\"ERROR: No packages provided.\")\n    sys.exit(1)\n\nprint(f\"Packages to analyze: {len(packages)}\")\nfor p in packages:\n    print(f\"  {p}\")\n\nwith open(\"/tmp/npl-input.json\", \"w\") as f:\n    json.dump({\"packages\": packages, \"product_context\": product_context}, f)\nPYEOF\n```\n\n---\n\n## Step 3: Fetch 12-Week Download Data\n\n**Use the standalone script if available -- it handles Steps 3, 4, and 5 in one call so you do not need to run the inline code blocks below.**\n\n```bash\n# Check if the script exists\nls scripts/fetch.py 2>/dev/null && echo \"script available\" || echo \"script not found\"\n```\n\n**If the script is available**, run it directly and skip to Step 6:\n\n```bash\npython3 scripts/fetch.py PACKAGES_HERE --context \"CONTEXT_HERE\" --output /tmp/npl-script-out.json\n```\n\nThen load the output into the enriched format Step 6 expects:\n\n```bash\npython3 << 'PYEOF'\nimport json\n\nout = json.load(open(\"/tmp/npl-script-out.json\"))\n# Script output has results array -- split into scored and enriched for Steps 6-8\nenriched = [r for r in out[\"results\"] if \"profile\" in r]\nscored = out[\"results\"]\njson.dump(scored, open(\"/tmp/npl-scored.json\", \"w\"), indent=2)\njson.dump(enriched, open(\"/tmp/npl-enriched.json\", \"w\"), indent=2)\njson.dump({\"packages\": [r[\"package\"] for r in scored], \"product_context\": out.get(\"product_context\", \"\")},\n          open(\"/tmp/npl-input.json\", \"w\"), indent=2)\nprint(f\"Loaded {len(scored)} packages | {out['breakout_count']} breakout | {out['watching_count']} watching\")\nPYEOF\n```\n\n**If the script is not available**, run the inline code below.\n\nFetch daily download data for each package from the npm Downloads API. Aggregate to weekly buckets.\n\n```bash\npython3 << 'PYEOF'\nimport json, urllib.request, sys, time\nfrom datetime import datetime, timedelta, timezone\nfrom collections import defaultdict\nimport urllib.parse\n\ndata = json.load(open(\"/tmp/npl-input.json\"))\npackages = data[\"packages\"]\n\nend_date = datetime.now(tz=timezone.utc)\nstart_date = end_date - timedelta(weeks=13)  # extra week buffer for partial weeks\nstart_str = start_date.strftime(\"%Y-%m-%d\")\nend_str = end_date.strftime(\"%Y-%m-%d\")\n\nresults = []\nfailed = []\n\nfor pkg in packages:\n    # URL-encode scoped packages: @ -> %40, / -> %2F\n    encoded = pkg.replace(\"@\", \"%40\").replace(\"/\", \"%2F\")\n    url = f\"https://api.npmjs.org/downloads/range/{start_str}:{end_str}/{encoded}\"\n\n    try:\n        req = urllib.request.Request(url, headers={\"User-Agent\": \"npm-downloads-to-leads/1.0\"})\n        with urllib.request.urlopen(req, timeout=20) as resp:\n            raw = json.loads(resp.read())\n\n        # Aggregate daily to weekly by ISO week\n        weekly = defaultdict(int)\n        for entry in raw.get(\"downloads\", []):\n            day = datetime.strptime(entry[\"day\"], \"%Y-%m-%d\")\n            week_key = day.isocalendar()[:2]  # (year, week_num)\n            weekly[week_key] += entry[\"downloads\"]\n\n        weeks = [v for k, v in sorted(weekly.items())]\n        # Take last 12 complete weekly buckets\n        weeks = weeks[-12:]\n\n        results.append({\n            \"package\": pkg,\n            \"weeks\": weeks,\n            \"total_weeks\": len(weeks),\n            \"current_weekly\": weeks[-1] if weeks else 0,\n            \"status\": \"ok\"\n        })\n        print(f\"  {pkg}: {len(weeks)} weeks, latest week {weeks[-1]:,} downloads\")\n\n    except urllib.error.HTTPError as e:\n        if e.code == 404:\n            failed.append(pkg)\n            results.append({\"package\": pkg, \"weeks\": [], \"total_weeks\": 0, \"current_weekly\": 0, \"status\": \"not_found\"})\n            print(f\"  {pkg}: NOT FOUND (404) -- will be skipped\")\n        else:\n            failed.append(pkg)\n            results.append({\"package\": pkg, \"weeks\": [], \"total_weeks\": 0, \"current_weekly\": 0, \"status\": f\"error_{e.code}\"})\n            print(f\"  {pkg}: HTTP {e.code} error\")\n    except Exception as e:\n        failed.append(pkg)\n        results.append({\"package\": pkg, \"weeks\": [], \"total_weeks\": 0, \"current_weekly\": 0, \"status\": f\"error\"})\n        print(f\"  {pkg}: fetch failed ({e})\")\n\n    time.sleep(0.2)  # gentle rate limiting\n\njson.dump(results, open(\"/tmp/npl-download-data.json\", \"w\"), indent=2)\nprint(f\"\\nFetch complete. OK: {len(results) - len(failed)} | Failed/Not found: {len(failed)}\")\nif failed:\n    print(f\"Skipped: {', '.join(failed)}\")\nPYEOF\n```\n\n**If all packages return 404 or errors:** Stop. Tell the user: \"No download data could be fetched. Check that the package names are correct and exist on npmjs.com. Scoped packages must include the full name: @org/package.\"\n\n---\n\n## Step 4: Compute Velocity Scores\n\nNo API call. Pure Python. Compute velocity score, growth ratio, and classify each package.\n\n```bash\npython3 << 'PYEOF'\nimport json\n\nraw_results = json.load(open(\"/tmp/npl-download-data.json\"))\nscored = []\n\nfor item in raw_results:\n    pkg = item[\"package\"]\n    weeks = item[\"weeks\"]\n    status = item[\"status\"]\n\n    if status != \"ok\" or len(weeks) < 4:\n        scored.append({**item, \"velocity_score\": 0, \"growth_pct\": 0, \"tier\": \"insufficient_data\",\n                       \"recent_4_avg\": 0, \"prior_4_avg\": 0})\n        continue\n\n    recent_4 = sum(weeks[-4:]) / 4\n    prior_4 = sum(weeks[-8:-4]) / max(len(weeks) - 4, 1) if len(weeks) >= 8 else sum(weeks[:4]) / max(len(weeks[:4]), 1)\n    recent_2 = sum(weeks[-2:]) / 2\n    mid_2 = sum(weeks[-4:-2]) / 2 if len(weeks) >= 4 else recent_2\n\n    growth_ratio = recent_4 / max(prior_4, 1)\n    acceleration = recent_2 / max(mid_2, 1)\n    growth_pct = round((growth_ratio - 1) * 100, 1)\n\n    # Sweet spot multiplier: 500-500K weekly downloads\n    if recent_4 < 500:\n        noise_factor = max(recent_4 / 500, 0.1)\n    elif recent_4 > 500_000:\n        noise_factor = max(500_000 / recent_4, 0.1)\n    else:\n        noise_factor = 1.0\n\n    velocity_score = round(growth_ratio * acceleration * noise_factor * 100, 1)\n\n    # Classify\n    if velocity_score > 80 and 500 < recent_4 < 500_000 and growth_ratio >= 1.5:\n        tier = \"breakout\"\n    elif velocity_score > 40 and recent_4 >= 500 and growth_ratio >= 1.2:\n        tier = \"watching\"\n    elif recent_4 < 500:\n        tier = \"too_early\"\n    elif recent_4 >= 500_000:\n        tier = \"established\"\n    else:\n        tier = \"steady\"\n\n    scored.append({\n        **item,\n        \"velocity_score\": velocity_score,\n        \"growth_pct\": growth_pct,\n        \"recent_4_avg\": round(recent_4),\n        \"prior_4_avg\": round(prior_4),\n        \"tier\": tier\n    })\n\n# Sort by velocity_score descending\nscored.sort(key=lambda x: x[\"velocity_score\"], reverse=True)\n\njson.dump(scored, open(\"/tmp/npl-scored.json\", \"w\"), indent=2)\n\nbreakout = [p for p in scored if p[\"tier\"] == \"breakout\"]\nwatching = [p for p in scored if p[\"tier\"] == \"watching\"]\ntoo_early = [p for p in scored if p[\"tier\"] == \"too_early\"]\n\nprint(f\"Velocity scoring complete:\")\nprint(f\"  BREAKOUT: {len(breakout)}\")\nprint(f\"  WATCHING: {len(watching)}\")\nprint(f\"  STEADY/ESTABLISHED: {len([p for p in scored if p['tier'] in ('steady','established')])}\")\nprint(f\"  TOO EARLY (<500/week): {len(too_early)}\")\nprint()\nfor p in scored[:10]:\n    print(f\"  {p['tier'].upper():12} {p['package']:30} score={p['velocity_score']:6.1f}  \"\n          f\"{p['recent_4_avg']:>8,}/wk  growth={p['growth_pct']:+.0f}%\")\n\n# Stop if nothing worth analyzing\nif not breakout and not watching:\n    all_too_early = all(p[\"tier\"] in (\"too_early\", \"insufficient_data\") for p in scored)\n    if all_too_early:\n        print(\"\\nERROR: All packages are below the 500 weekly downloads threshold for reliable velocity analysis.\")\n        print(\"Try packages with more community adoption.\")\n        import sys; sys.exit(1)\nPYEOF\n```\n\n**If all packages are below 500/week:** Stop with the message above.\n\n---\n\n## Step 5: Fetch Maintainer Profiles\n\nOnly for breakout and watching packages. Fetch npm registry metadata, then GitHub user profiles.\n\n```bash\npython3 << 'PYEOF'\nimport json, urllib.request, re, os, time\n\nscored = json.load(open(\"/tmp/npl-scored.json\"))\ntoken = os.environ.get(\"GITHUB_TOKEN\", \"\")\n\ngh_headers = {\"Accept\": \"application/vnd.github+json\", \"User-Agent\": \"npm-downloads-to-leads/1.0\"}\nif token:\n    gh_headers[\"Authorization\"] = f\"Bearer {token}\"\n\ntarget_packages = [p for p in scored if p[\"tier\"] in (\"breakout\", \"watching\")]\nprint(f\"Fetching profiles for {len(target_packages)} packages (breakout + watching)...\")\n\ngh_rate_remaining = 999\nenriched = []\n\nfor item in target_packages:\n    pkg = item[\"package\"]\n    profile = {\"package\": pkg, \"npm_maintainers\": [], \"description\": \"\", \"keywords\": [],\n               \"github_owner\": None, \"github_repo\": None, \"github_users\": [], \"npm_homepage\": \"\"}\n\n    # --- npm registry ---\n    encoded = pkg.replace(\"@\", \"%40\").replace(\"/\", \"%2F\")\n    reg_url = f\"https://registry.npmjs.org/{encoded}\"\n    try:\n        req = urllib.request.Request(reg_url, headers={\"User-Agent\": \"npm-downloads-to-leads/1.0\"})\n        with urllib.request.urlopen(req, timeout=20) as resp:\n            reg = json.loads(resp.read())\n\n        profile[\"description\"] = reg.get(\"description\", \"\")\n        profile[\"keywords\"] = (reg.get(\"keywords\") or [])[:6]\n        profile[\"npm_homepage\"] = reg.get(\"homepage\", \"\")\n        profile[\"npm_maintainers\"] = [m.get(\"name\", \"\") for m in reg.get(\"maintainers\", []) if m.get(\"name\")]\n\n        # Extract GitHub owner from repository URL\n        repo_field = reg.get(\"repository\") or {}\n        if isinstance(repo_field, dict):\n            repo_url = repo_field.get(\"url\", \"\")\n        else:\n            repo_url = str(repo_field)\n        gh_match = re.search(r\"github\\.com[/:]([^/]+)/([^/.]+)\", repo_url)\n        if gh_match:\n            profile[\"github_owner\"] = gh_match.group(1)\n            profile[\"github_repo\"] = gh_match.group(2).rstrip(\".git\")\n\n        print(f\"  {pkg}: registry OK | maintainers={profile['npm_maintainers'][:3]} | \"\n              f\"github_owner={profile['github_owner']}\")\n    except Exception as e:\n        print(f\"  {pkg}: registry fetch failed ({e})\")\n\n    time.sleep(0.1)\n\n    # --- GitHub user profiles ---\n    candidates = []\n    if profile[\"github_owner\"]:\n        candidates.append(profile[\"github_owner\"])\n    # Also try npm maintainer usernames (often match GitHub)\n    for m in profile[\"npm_maintainers\"][:2]:\n        if m and m not in candidates:\n            candidates.append(m)\n\n    for username in candidates[:3]:\n        if gh_rate_remaining <= 5:\n            print(f\"  GitHub rate limit low ({gh_rate_remaining} remaining) -- skipping {username}\")\n            break\n\n        gh_url = f\"https://api.github.com/users/{username}\"\n        req = urllib.request.Request(gh_url, headers=gh_headers)\n        try:\n            with urllib.request.urlopen(req, timeout=15) as resp:\n                gh_rate_remaining = int(resp.headers.get(\"X-RateLimit-Remaining\", 999))\n                gh_data = json.loads(resp.read())\n\n            profile[\"github_users\"].append({\n                \"username\": username,\n                \"name\": gh_data.get(\"name\") or username,\n                \"twitter_username\": gh_data.get(\"twitter_username\") or \"not found on GitHub\",\n                \"bio\": gh_data.get(\"bio\") or \"\",\n                \"blog\": gh_data.get(\"blog\") or \"\",\n                \"company\": gh_data.get(\"company\") or \"\",\n                \"followers\": gh_data.get(\"followers\", 0),\n                \"public_repos\": gh_data.get(\"public_repos\", 0),\n                \"github_url\": gh_data.get(\"html_url\", f\"https://github.com/{username}\")\n            })\n            print(f\"    GitHub @{username}: {gh_data.get('followers', 0)} followers | \"\n                  f\"twitter={gh_data.get('twitter_username') or 'none'} | rate_remaining={gh_rate_remaining}\")\n        except urllib.error.HTTPError as e:\n            if e.code == 404:\n                print(f\"    GitHub @{username}: not found\")\n            else:\n                print(f\"    GitHub @{username}: HTTP {e.code}\")\n        except Exception as e:\n            print(f\"    GitHub @{username}: failed ({e})\")\n\n        time.sleep(0.2)\n\n    enriched.append({**item, \"profile\": profile})\n\njson.dump(enriched, open(\"/tmp/npl-enriched.json\", \"w\"), indent=2)\njson.dump(scored, open(\"/tmp/npl-scored.json\", \"w\"), indent=2)\nprint(f\"\\nEnrichment complete. Profiles fetched: {len(enriched)}\")\nprint(f\"GitHub rate limit remaining: {gh_rate_remaining}\")\nPYEOF\n```\n\n---\n\n## Step 6: Generate Lead Briefs\n\nPrint enriched breakout and watching packages, then generate lead briefs and outreach messages.\n\n```bash\npython3 << 'PYEOF'\nimport json\n\nenriched = json.load(open(\"/tmp/npl-enriched.json\"))\ninput_data = json.load(open(\"/tmp/npl-input.json\"))\nproduct_context = input_data.get(\"product_context\", \"\")\n\nbreakout = [p for p in enriched if p[\"tier\"] == \"breakout\"]\nwatching = [p for p in enriched if p[\"tier\"] == \"watching\"]\n\nprint(\"=== DATA FOR LEAD BRIEF GENERATION ===\")\nprint(f\"Product context: {product_context or '(none provided)'}\")\nprint()\n\nfor item in breakout + watching:\n    pkg = item[\"package\"]\n    prof = item.get(\"profile\", {})\n    gh_users = prof.get(\"github_users\", [])\n    primary_gh = gh_users[0] if gh_users else {}\n\n    print(f\"PACKAGE: {pkg} ({item['tier'].upper()})\")\n    print(f\"  Velocity score: {item['velocity_score']} | Growth: {item['growth_pct']:+.0f}%\")\n    print(f\"  Recent 4-week avg: {item['recent_4_avg']:,}/week | Prior 4-week avg: {item['prior_4_avg']:,}/week\")\n    print(f\"  Weekly trend (last 8): {item['weeks'][-8:]}\")\n    print(f\"  Description: {prof.get('description', 'none')}\")\n    print(f\"  Keywords: {', '.join(prof.get('keywords', []))}\")\n    print(f\"  npm maintainers: {', '.join(prof.get('npm_maintainers', []))}\")\n    if primary_gh:\n        print(f\"  GitHub: @{primary_gh.get('username')} | {primary_gh.get('followers')} followers | \"\n              f\"{primary_gh.get('public_repos')} repos\")\n        print(f\"  Twitter: {primary_gh.get('twitter_username')}\")\n        print(f\"  Bio: {primary_gh.get('bio')}\")\n        print(f\"  Company: {primary_gh.get('company')}\")\n    else:\n        print(f\"  GitHub: no profile found\")\n    print()\nPYEOF\n```\n\nUsing the package data printed above, generate a lead brief for each BREAKOUT and WATCHING package.\n\nRules:\n- Every growth number in the brief must come from the printed data -- do not round or modify\n- Every GitHub handle and Twitter username must come from the printed data -- write \"not found on GitHub\" if the field says that\n- \"Why reach out now\" must reference the specific growth inflection (weeks, numbers) from the data\n- \"Suggested first message\" must name the package and its growth, and if product_context was provided, connect it specifically to that context\n- No em dashes. No forbidden words: powerful, robust, seamless, innovative, game-changing, streamline, leverage, transform\n\nWrite your lead briefs to `/tmp/npl-briefs.json` with this exact structure:\n\n```json\n{\n  \"lead_briefs\": [\n    {\n      \"package\": \"pkg-name\",\n      \"tier\": \"breakout\",\n      \"growth_summary\": \"1-sentence summary of the growth numbers\",\n      \"maintainer_handle\": \"@github_handle or npm username if no GitHub found\",\n      \"twitter\": \"@handle or not found on GitHub\",\n      \"github_followers\": 0,\n      \"why_now\": \"2-3 sentences specific to this package's inflection point\",\n      \"suggested_message\": \"2-4 sentences. Names the package, the growth, and connects to product_context if provided.\"\n    }\n  ]\n}\n```\n\nAfter writing the file, confirm with:\n\n```bash\npython3 -c \"\nimport json\nd = json.load(open('/tmp/npl-briefs.json'))\nprint(f'Lead briefs generated: {len(d.get(\\\"lead_briefs\\\", []))}')\nfor b in d['lead_briefs']:\n    print(f'  {b[\\\"package\\\"]} ({b[\\\"tier\\\"]}): maintainer={b[\\\"maintainer_handle\\\"]}')\n\"\n```\n\n---\n\n## Step 7: Self-QA\n\n```bash\npython3 << 'PYEOF'\nimport json\n\nscored = json.load(open(\"/tmp/npl-scored.json\"))\nenriched = json.load(open(\"/tmp/npl-enriched.json\"))\nbriefs = json.load(open(\"/tmp/npl-briefs.json\"))\n\nfailures = []\n\n# Verify: every brief has a real package name from the scored list\nreal_packages = {p[\"package\"] for p in scored}\nfor brief in briefs.get(\"lead_briefs\", []):\n    if brief.get(\"package\") not in real_packages:\n        failures.append(f\"Brief for unknown package '{brief.get('package')}' -- removed\")\n\nbriefs[\"lead_briefs\"] = [b for b in briefs.get(\"lead_briefs\", []) if b.get(\"package\") in real_packages]\n\n# Verify: velocity leaderboard is sorted correctly (checked on scored, not briefs)\nsorted_scores = sorted([(p[\"package\"], p[\"velocity_score\"]) for p in scored], key=lambda x: -x[1])\nif scored[0][\"velocity_score\"] < scored[-1][\"velocity_score\"]:\n    failures.append(\"Scored list not sorted by velocity_score -- re-sorted\")\n    scored.sort(key=lambda x: x[\"velocity_score\"], reverse=True)\n\n# Verify: no GitHub/Twitter handles in briefs that weren't in GitHub API responses\nenriched_gh = {}\nfor item in enriched:\n    for gh_user in item.get(\"profile\", {}).get(\"github_users\", []):\n        enriched_gh[gh_user[\"username\"]] = gh_user.get(\"twitter_username\", \"not found on GitHub\")\n\nfor brief in briefs.get(\"lead_briefs\", []):\n    twitter = brief.get(\"twitter\", \"\")\n    if twitter and twitter not in (\"not found on GitHub\", \"\") and not twitter.startswith(\"not found\"):\n        # Verify it came from the API\n        found = any(twitter.lstrip(\"@\") == v.lstrip(\"@\") for v in enriched_gh.values() if v != \"not found on GitHub\")\n        if not found:\n            failures.append(f\"Warning: Twitter handle '{twitter}' for {brief['package']} not verified in GitHub API data\")\n\n# Check required fields\nfor brief in briefs.get(\"lead_briefs\", []):\n    for field in [\"package\", \"tier\", \"growth_summary\", \"maintainer_handle\", \"twitter\", \"why_now\", \"suggested_message\"]:\n        if not brief.get(field):\n            failures.append(f\"Missing field '{field}' in brief for {brief.get('package', '?')}\")\n\n# Check for em dashes\nbriefs_str = json.dumps(briefs)\nif \"\\u2014\" in briefs_str:\n    briefs_str = briefs_str.replace(\"\\u2014\", \" - \")\n    briefs = json.loads(briefs_str)\n    failures.append(\"Fixed: em dash characters removed from briefs\")\n\n# Check for forbidden words\nforbidden = [\"powerful\", \"robust\", \"seamless\", \"innovative\", \"game-changing\", \"streamline\", \"leverage\", \"transform\"]\nfull_text = json.dumps(briefs).lower()\nfor word in forbidden:\n    if word in full_text:\n        failures.append(f\"Warning: forbidden word '{word}' found in briefs -- review before presenting\")\n\noutput = {\n    \"scored\": scored,\n    \"enriched\": enriched,\n    \"briefs\": briefs,\n    \"data_quality_flags\": failures\n}\n\njson.dump(output, open(\"/tmp/npl-output.json\", \"w\"), indent=2)\nprint(f\"QA complete. Issues: {len(failures)}\")\nfor f in failures:\n    print(f\"  - {f}\")\nif not failures:\n    print(\"All QA checks passed.\")\nPYEOF\n```\n\n---\n\n## Step 8: Save and Present Output\n\n```bash\npython3 << 'PYEOF'\nimport json, os\nfrom datetime import datetime, timezone\n\noutput = json.load(open(\"/tmp/npl-output.json\"))\nscored = output[\"scored\"]\nenriched_map = {e[\"package\"]: e for e in output[\"enriched\"]}\nbriefs_map = {b[\"package\"]: b for b in output[\"briefs\"].get(\"lead_briefs\", [])}\nflags = output[\"data_quality_flags\"]\ndate_str = datetime.now(tz=timezone.utc).strftime(\"%Y-%m-%d\")\n\nbreakout = [p for p in scored if p[\"tier\"] == \"breakout\"]\nwatching = [p for p in scored if p[\"tier\"] == \"watching\"]\ntoo_early = [p for p in scored if p[\"tier\"] == \"too_early\"]\nestablished = [p for p in scored if p[\"tier\"] == \"established\"]\n\nlines = [\n    f\"## npm Breakout Report\",\n    f\"Packages analyzed: {len(scored)} | Breakout: {len(breakout)} | Watching: {len(watching)} | Date: {date_str}\",\n    \"\",\n    \"---\",\n    \"\",\n    \"### Velocity Leaderboard\",\n    \"\",\n    \"| Rank | Package | Weekly Downloads | 8-Week Growth | Velocity Score | Status |\",\n    \"|---|---|---|---|---|---|\",\n]\n\nfor i, pkg in enumerate(scored[:15], 1):\n    status_label = {\"breakout\": \"BREAKOUT\", \"watching\": \"WATCHING\", \"steady\": \"steady\",\n                    \"established\": \"established\", \"too_early\": \"too early\", \"insufficient_data\": \"no data\"}.get(pkg[\"tier\"], pkg[\"tier\"])\n    growth_str = f\"{pkg['growth_pct']:+.0f}%\" if pkg.get(\"growth_pct\") else \"n/a\"\n    lines.append(\n        f\"| {i} | {pkg['package']} | {pkg['recent_4_avg']:,} | {growth_str} | \"\n        f\"{pkg['velocity_score']} | {status_label} |\"\n    )\n\nlines += [\"\", \"---\", \"\"]\n\nif breakout or watching:\n    lines += [\"### Lead Briefs\", \"\"]\n\n    for item in breakout + watching:\n        pkg = item[\"package\"]\n        brief = briefs_map.get(pkg, {})\n        profile = enriched_map.get(pkg, {}).get(\"profile\", {})\n        gh_users = profile.get(\"github_users\", [])\n        primary_gh = gh_users[0] if gh_users else {}\n\n        lines.append(f\"#### {pkg} ({item['tier'].upper()})\")\n        lines.append(f\"Weekly downloads: {item['recent_4_avg']:,}/week (was {item['prior_4_avg']:,} -- {item['growth_pct']:+.0f}% growth over 8 weeks)\")\n        if profile.get(\"description\"):\n            lines.append(f\"What it does: {profile['description']}\")\n        if profile.get(\"keywords\"):\n            lines.append(f\"Keywords: {', '.join(profile['keywords'])}\")\n        lines.append(\"\")\n\n        if primary_gh:\n            lines.append(f\"**Maintainer: @{primary_gh.get('username')}**\")\n            lines.append(f\"- GitHub: {primary_gh.get('followers', 0):,} followers | {primary_gh.get('public_repos', 0)} public repos\")\n            lines.append(f\"- Twitter: {primary_gh.get('twitter_username', 'not found on GitHub')}\")\n            if primary_gh.get(\"bio\"):\n                lines.append(f\"- Bio: \\\"{primary_gh['bio']}\\\"\")\n            if primary_gh.get(\"company\"):\n                lines.append(f\"- Company: {primary_gh['company']}\")\n            if primary_gh.get(\"blog\"):\n                lines.append(f\"- Website: {primary_gh['blog']}\")\n        elif profile.get(\"npm_maintainers\"):\n            lines.append(f\"**Maintainer (npm only):** {', '.join(profile['npm_maintainers'][:3])}\")\n            lines.append(\"- GitHub profile: not found\")\n\n        lines.append(\"\")\n        if brief.get(\"why_now\"):\n            lines.append(f\"**Why reach out now:** {brief['why_now']}\")\n        if brief.get(\"suggested_message\"):\n            lines.append(f\"\\n**Suggested first message:**\")\n            lines.append(f\"> {brief['suggested_message']}\")\n        lines.append(\"\")\n        lines.append(\"---\")\n        lines.append(\"\")\n\nif too_early:\n    lines += [f\"### Too Early ({len(too_early)} packages below 500 weekly downloads)\", \"\"]\n    for p in too_early:\n        lines.append(f\"- {p['package']}: ~{p['recent_4_avg']:,}/week -- revisit when above 500/week\")\n    lines.append(\"\")\n\nif established:\n    lines += [f\"### Established Packages (above 500K/week, velocity less meaningful)\", \"\"]\n    for p in established:\n        lines.append(f\"- {p['package']}: ~{p['recent_4_avg']:,}/week\")\n    lines.append(\"\")\n\nlines += [\"---\", \"\"]\nlines.append(f\"Data quality notes: {'; '.join(flags) if flags else 'None'}\")\n\noutput_path = f\"docs/npm-leads/{date_str}.md\"\nos.makedirs(\"docs/npm-leads\", exist_ok=True)\nopen(output_path, \"w\").write(\"\\n\".join(lines))\n\nprint(\"\\n\".join(lines))\nprint(f\"\\nSaved to: {output_path}\")\nPYEOF\n```\n\nClean up temp files:\n\n```bash\nrm -f /tmp/npl-input.json /tmp/npl-download-data.json /tmp/npl-scored.json \\\n      /tmp/npl-enriched.json /tmp/npl-briefs.json /tmp/npl-output.json\n```","tags":["npm","downloads","leads","opendirectory","varnan-tech","agent-skills","gtm","hermes-agent","marketing-skills","openclaw-skills","skill-pack","skills"],"capabilities":["skill","source-varnan-tech","skill-npm-downloads-to-leads","topic-agent-skills","topic-gtm","topic-hermes-agent","topic-marketing-skills","topic-openclaw-skills","topic-skill-pack","topic-skills","topic-technical-seo"],"categories":["opendirectory"],"synonyms":[],"warnings":[],"endpointUrl":"https://skills.sh/Varnan-Tech/opendirectory/npm-downloads-to-leads","protocol":"skill","transport":"skills-sh","auth":{"type":"none","details":{"cli":"npx skills add Varnan-Tech/opendirectory","source_repo":"https://github.com/Varnan-Tech/opendirectory","install_from":"skills.sh"}},"qualityScore":"0.593","qualityRationale":"deterministic score 0.59 from registry signals: · indexed on github topic:agent-skills · 286 github stars · SKILL.md body (26,431 chars)","verified":false,"liveness":"unknown","lastLivenessCheck":null,"agentReviews":{"count":0,"score_avg":null,"cost_usd_avg":null,"success_rate":null,"latency_p50_ms":null,"narrative_summary":null,"summary_updated_at":null},"enrichmentModel":"deterministic:skill-github:v1","enrichmentVersion":1,"enrichedAt":"2026-05-18T18:54:42.784Z","embedding":null,"createdAt":"2026-04-22T18:57:03.462Z","updatedAt":"2026-05-18T18:54:42.784Z","lastSeenAt":"2026-05-18T18:54:42.784Z","tsv":"'-1':958,974,2616 '-12':945 '-2':1229,1236 '-3':2435 '-4':1199,1206,1235,2447 '-500':1272 '-8':697,1205,2212 '/1.0':884,1627,1716 '/dev/null':633 '/downloads/range/':865 '/settings/tokens':413 '/tmp/npl-briefs.json':2388,2475,2522,3392 '/tmp/npl-download-data.json':1063,1152,3389 '/tmp/npl-enriched.json':722,2038,2093,2518,3391 '/tmp/npl-input.json':576,740,809,2098,3388 '/tmp/npl-output.json':2862,2909,3393 '/tmp/npl-scored.json':715,1403,1609,2045,2514,3390 '/tmp/npl-script-out.json':663,683 '/users/':1897 '/week':2194,2203,3136,3307,3336 '/wk':1504 '0':962,991,994,1016,1019,1042,1045,1179,1182,1189,1193,1964,1970,1985,2160,2431,2612,3117,3183,3188 '0.1':1286,1299,1832 '0.2':1056,2030 '000':1291,1296,1324,1356 '0f':1509,2183,3060,3145 '1':359,559,1211,1224,1252,1259,1265,1267,1313,1565,1796,2404,2609,3030 '1.0':1303 '1.2':1342 '1.5':1328 '10':378,400,1482 '100':1266,1312 '12':17,65,592,939,1488 '13':824 '15':1911,3029 '1k':215 '2':433,632,718,725,743,920,1066,1226,1230,1232,1237,1244,1255,1258,1406,1801,1859,2041,2048,2434,2446,2865 '20':889,1721 '2f':252,855,860,1696 '3':490,590,605,1813,1873,3241 '30':1491 '4':606,1125,1174,1187,1191,1196,1200,1202,1210,1219,1223,1241,1248,1251,1278,1284,1289,1298,1322,1337,1347,1354,1373,1377,1379,1383,1501,2187,2192,2196,2201,3074,3134,3140,3305,3334 '40':249,854,858,1334,1694 '404':242,982,1003,1092,2005 '5':608,1579,1878 '500':331,1271,1279,1285,1290,1295,1320,1323,1338,1348,1355,1547,3291 '500/week':337,1473,1572,3311 '500k/week':3320 '50m/week':224 '6':653,673,696,1736,2068 '6.1':1496 '60':170,374 '7':2502 '8':1215,1503,2209,2890,3017,3148 '80':1318 '8k/week':217 '999':1663,1923 'abort':291 'acceler':1253,1309 'accept':1616 'action':220 'add':282,407 'adopt':1561 'agent':150,878,1621,1710 'aggreg':782,895 'also':1845 'analysi':1554 'analyz':504,564,1514,2999 'api':26,49,105,119,130,246,422,781,1130,2650,2708,2739 'api.github.com':1896 'api.github.com/users/':1895 'api.npmjs.org':864 'api.npmjs.org/downloads/range/':863 'append':1931 'appli':373 'application/vnd.github':1617 'array':688 'ask':496 'audienc':345 'authent':431 'author':1632 'avail':601,636,645,764 'avg':1188,1192,1374,1380,1502,2189,2193,2198,2202,3075,3135,3141,3306,3335 'b':2486,2493,2495,2498,2569,2571,2925,2927,2929 'b.get':2577 'bash':362,517,624,654,675,786,1143,1597,2085,2467,2506,2895,3385 'bearer':1634 'bio':1949,1951,2257,2259,3203,3206,3209 'block':622 'blog':1953,1955,3221,3227 'break':1891 'breakout':29,71,82,185,191,751,753,1330,1407,1416,1446,1448,1517,1585,1647,1658,2074,2104,2113,2143,2286,2401,2950,2959,2995,3002,3004,3033,3034,3086,3095 'brief':80,2071,2081,2128,2283,2296,2386,2395,2479,2484,2490,2519,2526,2545,2549,2559,2566,2568,2575,2592,2644,2680,2684,2733,2745,2749,2774,2782,2785,2789,2791,2795,2797,2806,2825,2844,2853,2854,2923,2932,2935,3091,3100,3258,3273 'brief.get':2551,2563,2686,2766,2776,3249,3262 'briefs.get':2547,2573,2682,2747 'briefs_map.get':3101 'briefs_str.replace':2793 'bucket':785,942 'budget':182 'buffer':827 'c':2469 'call':611,1131 'came':2705 'candid':1836,1866,1872 'candidates.append':1841,1867 'caus':240 'chang':2379,2818 'charact':2803 'check':361,625,1105,2588,2741,2778,2807,2886 'classifi':1140,1314 'clean':3381 'code':621,768 'collect':436,801 'com':1786 'come':101,115,2298,2315 'comma':527 'common':147 'communiti':1560 'compani':1957,1959,2262,2264,3212,3215,3218 'competitor':15,508 'complet':940,1070,1443,2052,2869 'comput':27,70,1126,1134 'confirm':2465 'connect':321,2361,2455 'contact':85 'context':324,457,532,533,584,586,659,660,735,738,2100,2103,2133,2135,2358,2366,2458 'continu':386,1194 'convers':439 'correct':1111,2587 'could':1102 'count':752,756 'critic':91 'current':955,992,1017,1043 'd':836,842,916,2472,2488,2949 'd.get':2482 'daili':20,771,896 'dash':2369,2781,2802 'data':22,69,276,286,595,773,806,811,1101,1185,1531,1925,2095,2125,2277,2302,2319,2344,2740,2855,2938,3046,3048,3341 'date':814,819,821,2941,3008,3009,3354 'datetim':795,797,2902,2904 'datetime.now':815,2943 'datetime.strptime':911 'day':910,913 'day.isocalendar':919 'declin':178 'defaultdict':803,903 'degrad':268 'descend':1390 'descript':1678,1728,1730,2215,2217,3152,3159 'dict':1770 'direct':648 'docs/npm-leads':3353,3358 'download':3,21,55,68,95,200,202,333,594,772,780,881,909,928,975,1100,1275,1549,1624,1713,3016,3131,3293 'e':979,1033,1054,1823,1830,2002,2022,2028,2915,2917,2919 'e.code':981,1023,1028,2004,2018 'earli':350,1351,1428,1438,1472,1476,1523,1529,1539,2971,2981,3042,3044,3281,3285,3288,3298 'echo':363,634,637 'elif':1287,1331,1345,1352,3228 'els':961,1007,1216,1242,1300,1359,1775,2012,2164,2265,3065,3121,3348 'em':2368,2780,2801 'empti':538 'encod':233,239,247,485,851,856,870,1692,1701 'end':813,820,837,868 'end_date.strftime':839 'enough':376 'enrich':73,175,280,396,670,693,698,720,1664,2036,2056,2073,2090,2109,2119,2515,2652,2657,2667,2851,2852,2913,2922 'enriched.append':2031 'enriched_gh.values':2716 'enriched_map.get':3104 'entri':906,912,927 'enumer':3027 'error':554,1022,1029,1048,1094 'esbuild':448,513 'establish':1358,1468,2982,2991,3039,3040,3314,3317,3327 'everi':93,107,162,254,308,2291,2308,2525 'exact':2391 'exampl':512 'except':976,1030,1031,1820,1821,1999,2019,2020 'exist':629,1113,3359 'expect':674 'extra':825 'extract':471,1755 'f':561,572,579,587,745,862,966,999,1021,1025,1047,1050,1068,1083,1440,1445,1450,1455,1470,1484,1497,1498,1633,1650,1699,1805,1814,1825,1880,1894,1976,1980,1987,2007,2014,2024,2050,2058,2131,2166,2173,2185,2205,2214,2220,2226,2237,2244,2250,2256,2261,2267,2477,2492,2558,2727,2769,2837,2867,2874,2878,2879,2993,2997,3056,3068,3078,3123,3129,3154,3164,3174,3179,3192,3205,3214,3223,3233,3253,3266,3272,3283,3300,3316,3329,3340,3352,3375,3387 'factor':1281,1293,1302,1311 'fail':844,1053,1075,1079,1081,1086,1829,2027 'failed.append':983,1008,1034 'failed/not':1076 'failur':2523,2858,2872,2876,2882 'failures.append':2557,2619,2726,2768,2799,2836 'favor':203 'fetch':16,40,64,158,188,591,770,1052,1104,1580,1589,1651,1828,2054 'field':137,1762,1769,1780,2327,2743,2751,2767,2771,2772 'figur':96 'file':2464,3384 'first':2346,3269 'fix':2800 'flag':284,288,347,2857,2936,2940,3345,3347 'follow':1961,1963,1984,1986,2242,2243,2430,3182,3184 'forbidden':2371,2809,2811,2830,2839 'format':671 'found':140,640,997,1002,1077,1946,2011,2271,2322,2421,2426,2676,2695,2702,2709,2720,2725,2842,3198,3246 'full':478,1121,2822,2834 'fulli':427 'game':2378,2817 'game-chang':2377,2816 'gather':434 'generat':2069,2079,2129,2280,2480 'generic':300 'gentl':1057 'get':2664,2933,3049,3106 'gh':1614,1630,1660,1781,1790,1875,1885,1892,1901,1904,1914,1924,1996,2063,2151,2157,2158,2162,2235,2653,2659,2668,2669,3108,3114,3115,3119,3172,3208,3217,3226 'gh_data.get':1935,1941,1950,1954,1958,1962,1967,1973,1983,1989 'gh_match.group':1795,1800 'gh_user.get':2672 'git':1803 'github':48,109,118,129,142,159,263,279,364,366,381,390,395,1594,1612,1680,1683,1686,1756,1785,1793,1798,1815,1818,1833,1839,1843,1852,1881,1929,1948,1971,1981,2008,2015,2025,2059,2154,2238,2268,2309,2324,2413,2420,2428,2429,2649,2665,2678,2697,2722,2738,3111,3180,3200,3243 'github.com':412,1977 'github.com/settings/tokens':411 'github/twitter':2641 'give':467 'go':213,306 'grace':269 'growth':39,316,1137,1180,1245,1260,1263,1307,1326,1340,1368,1370,1505,1507,2179,2181,2292,2338,2354,2402,2409,2453,2755,3019,3054,3058,3063,3076,3143,3146 'guess':122 'handl':110,487,603,2310,2412,2414,2423,2500,2642,2730,2758 'header':875,1615,1631,1707,1903,1905 'hit':267,403 'hockey':37 'hockey-stick':36 'homepag':1689,1739,1741 'hono/hono':452,514 'html':1974 'http':1027,2017 'identifi':35 'import':520,678,789,796,802,804,1146,1562,1600,2088,2470,2509,2898,2903 'includ':328,481,1119 'indent':717,724,742,1065,1405,2040,2047,2864 'inflect':2339,2442 'inform':387 'inlin':620,767 'innov':2376,2815 'input':435,2094 'input_data.get':2101 'insuffici':1184,1530,3045 'int':904,1917 'invent':145 'isinst':1767 'iso':900 'issu':2870 'item':1155,1160,1163,1166,1176,1363,1666,1671,2032,2141,2146,2169,2176,2180,2190,2199,2210,2655,3093,3098,3125,3132,3138,3142 'item.get':2149,2662 'join':1085,2222,2229,3166,3237,3344,3368,3372 'json':521,679,790,1147,1601,1618,2089,2393,2471,2510,2899 'json.dump':580,712,719,726,1060,1400,2035,2042,2859 'json.dumps':2784,2824 'json.load':681,807,1150,1607,2091,2096,2473,2512,2516,2520,2907 'json.loads':893,1725,1926,2796 'k':932,1273 'key':419,918,926,1392,2605,2631 'keyword':1679,1732,1734,2221,2224,3162,3165,3168 'label':3032,3083 'lambda':1393,2606,2632 'last':938,2208 'latest':971 'lead':5,57,79,210,335,357,883,1626,1715,2070,2080,2127,2282,2385,2394,2478,2483,2489,2548,2567,2574,2683,2748,2934,3090 'leaderboard':273,2584,3012 'len':565,747,953,968,1072,1074,1078,1172,1208,1213,1221,1239,1447,1452,1457,1474,1654,2055,2481,2871,3000,3003,3006,3286 'less':3322 'leverag':2381,2820 'like':447,451,502 'limit':168,265,372,398,406,1059,1883,2061 'line':2992,3084,3089,3282,3315,3338,3369,3373 'lines.append':3067,3122,3128,3153,3163,3169,3173,3178,3191,3204,3213,3222,3232,3242,3247,3252,3265,3271,3276,3277,3278,3299,3312,3328,3337,3339 'list':8,60,166,2535,2621 'load':665,746 'lodash':206 'low':1884 'lower':2826 'ls':630 'm':835,841,915,1748,1854,1861,1863,1868,2948 'm.get':1745,1753 'maintain':41,74,108,341,1581,1677,1744,1751,1809,1812,1848,1858,2228,2232,2411,2497,2499,2757,3175,3231,3234,3240 'map':2914,2924 'match':1782,1791,1851 'max':1207,1220,1249,1256,1282,1294 'md':3356 'meaning':344,3323 'messag':90,294,305,310,463,1576,2084,2347,2445,2763,3264,3270,3275 'metadata':1592 'mid':1231,1257 'miss':2770 'mistak':148 'mix':511 'modifi':2307 'multipli':1270 'must':100,114,311,1118,2297,2314,2334,2348 'n':546,3267,3367,3371 'n/a':3066 'name':12,257,296,312,445,475,480,1109,1122,1746,1754,1934,1936,2349,2399,2449,2531 'need':416,616 'nenrich':2051 'nerror':1541 'newlin':529 'nfetch':1069 'nois':339,1280,1292,1301,1310 'none':1682,1685,1993,2137,2218,3349 'note':3343 'noth':1512 'npm':2,10,25,45,54,62,104,125,245,275,421,424,443,498,779,880,1590,1623,1676,1688,1690,1712,1738,1743,1811,1847,1857,2227,2231,2416,2994,3230,3235,3239 'npm-downloads-to-lead':1,879,1622,1711 'npmjs.com':469,1115 'nsave':3376 'num':923 'number':317,2293,2341,2410 'often':1850 'ok':964,1071,1170,1808,3360 'one':146,186,440,610 'open':575,682,714,721,739,808,1062,1151,1402,1608,2037,2044,2092,2097,2474,2513,2517,2521,2861,2908,3362 'option':453,535 'org':483 'org/package':1123 'org/pkg':237 'os':1604,2900 'os.environ.get':1611 'os.makedirs':3357 'out.get':736 'output':51,76,99,662,667,685,2848,2860,2894,2906,2911,2921,2931,2937,3350,3363,3378 'outreach':89,293,309,462,2083 'owner':1681,1757,1794,1816,1819,1840,1844 'p':543,568,573,1408,1410,1414,1418,1420,1424,1429,1431,1435,1458,1460,1464,1479,1485,1489,1493,1499,1506,1525,1533,1638,1640,1644,2105,2107,2111,2115,2117,2121,2538,2541,2596,2598,2602,2951,2953,2957,2961,2963,2967,2972,2974,2978,2983,2985,2989,3295,3301,3303,3325,3330,3332 'p.strip':541,549 'packag':11,33,63,83,94,163,179,194,196,212,236,256,299,314,329,379,401,444,474,493,499,523,525,540,552,556,562,566,570,581,582,657,727,729,749,776,810,812,848,853,947,986,1011,1037,1090,1108,1117,1142,1161,1490,1543,1557,1569,1588,1637,1656,1657,1669,1672,1674,2077,2147,2167,2276,2289,2351,2396,2440,2451,2494,2530,2537,2539,2552,2556,2562,2564,2578,2581,2597,2734,2753,2777,2916,2926,2998,3014,3071,3099,3289,3302,3318,3331 'packages_raw.replace':545 'partial':829 'pass':2887 'path':3351,3364,3379 'pct':1181,1261,1369,1371,1508,2182,3059,3064,3144 'per':32,81 'person':461 'pkg':846,948,967,984,987,1000,1009,1012,1026,1035,1038,1051,1159,1670,1675,1806,1826,2145,2168,2398,3025,3050,3052,3057,3070,3072,3079,3097,3102,3105,3124 'pkg-name':2397 'pkg.get':3062 'pkg.replace':857,1693 'point':2443 'power':2373,2812 'prefix':484 'present':270,354,2847,2893 'preserv':476 'primari':2156,2234,3113,3171,3207,3216,3225 'primary_gh.get':2239,2241,2245,2252,2258,2263,3176,3181,3185,3194,3202,3211,3220 'print':553,560,571,744,965,998,1024,1049,1067,1082,1439,1444,1449,1454,1469,1477,1483,1540,1555,1649,1804,1824,1879,1979,2006,2013,2023,2049,2057,2072,2124,2130,2139,2165,2172,2184,2204,2213,2219,2225,2236,2249,2255,2260,2266,2272,2278,2301,2318,2476,2491,2866,2877,2883,3370,3374 'prior':1190,1201,1250,1378,1382,2195,2200,3139 'product':456,531,583,585,734,737,2099,2102,2132,2134,2357,2457 'prof':2148 'prof.get':2153,2216,2223,2230 'profil':42,75,160,189,706,1582,1596,1652,1673,1727,1731,1737,1742,1792,1797,1810,1817,1835,1838,1842,1856,1928,2033,2034,2053,2150,2270,2663,3103,3107,3158,3167,3238,3244 'profile.get':3110,3151,3161,3229 'project':304 'provid':327,495,505,557,2138,2360,2460 'public':428,1965,1968,2246,3186,3189 'pure':1132 'pyeof':519,588,677,758,788,1087,1145,1566,1599,2066,2087,2273,2508,2888,2897,3380 'python':1133 'python3':518,655,676,787,1144,1598,2086,2468,2507,2896 'qa':2505,2868,2885 'qualiti':287,2856,2939,3342 'r':699,701,708,728,731,1784 'ran':53 'rank':78,195,3013 'rate':167,264,371,405,1058,1661,1876,1882,1886,1915,1994,1997,2060,2064 'ratelimit':1921 'ratio':1138,1246,1264,1308,1327,1341 'raw':198,201,524,892,1148,1157 'raw.get':908 're':1603,2628 're-sort':2627 're.search':1783 'reach':184,2331,3255 'react':204,222 'real':2529,2536,2555,2580 'recent':1186,1195,1225,1243,1247,1254,1277,1283,1288,1297,1321,1336,1346,1353,1372,1376,1500,2186,2191,3073,3133,3304,3333 'refer':2335 'reg':1697,1705,1724 'reg.get':1729,1733,1740,1750,1763 'registri':46,425,1591,1691,1807,1827 'registry.npmjs.org':1700 'reliabl':1552 'remain':278,1662,1877,1887,1888,1916,1922,1995,1998,2062,2065 'remov':2565,2804 'replac':859,1695 'repo':1684,1761,1768,1771,1776,1779,1787,1799,1966,1969,2247,2248,3187,3190 'repo_field.get':1773 'report':2996 'repositori':1759,1764 'req':872,887,1703,1719,1899,1909 'req/hr':171,375 'requir':418,2742 'resp':891,1723,1913 'resp.headers.get':1918 'resp.read':894,1726,1927 'respons':106,120,2651 'result':687,704,711,843,1061,1073,1149,1158 'results.append':946,985,1010,1036 'return':133,1091 'revers':1398,2637 'review':2845 'revisit':3308 'rm':3386 'robust':2374,2813 'round':1262,1306,1375,1381,2305 'rstrip':1802 'rule':92,2290 'run':618,646,765 'save':2891 'saw':302 'say':2328 'scope':235,255,415,450,479,852,1116 'score':31,226,691,709,713,733,748,1128,1136,1153,1178,1305,1317,1333,1365,1367,1389,1397,1401,1412,1422,1433,1442,1462,1481,1492,1495,1535,1606,1642,2043,2175,2178,2511,2534,2543,2590,2594,2600,2604,2611,2614,2615,2618,2620,2626,2636,2849,2850,2910,2912,2955,2965,2976,2987,3001,3021,3028,3081 'scored.append':1175,1362 'scored.sort':1391,2630 'script':599,628,635,638,643,684,761 'scripts/fetch.py':631,656 'seamless':2375,2814 'self':2504 'self-qa':2503 'sentenc':2405,2436,2448 'separ':530 'set':369,385,394 'setup':360 'short':455 'signal':86,229 'skill':260 'skill-npm-downloads-to-leads' 'skip':230,277,650,1006,1084,1889 'sort':935,1386,2586,2593,2595,2623,2629 'source-varnan-tech' 'specif':298,320,2337,2363,2437 'split':547,689 'spot':1269 'standalon':598 'start':818,831,866 'start_date.strftime':833 'status':963,995,1020,1046,1165,1167,1169,3022,3031,3082 'steadi':176,1361,1467,3037,3038 'steady/established':1456 'step':358,432,489,589,604,652,672,695,1124,1578,2067,2501,2889 'stick':38 'stop':258,1095,1510,1573 'str':832,838,867,869,1778,2783,2790,2792,2798,2942,3010,3055,3077,3355 'streamlin':2380,2819 'strftime':2946 'string':458,539 'structur':2392 'suggest':2345,2444,2762,3263,3268,3274 'sum':1197,1203,1217,1227,1233 'summari':2403,2406,2756 'sweet':1268 'sys':522,792,1563 'sys.exit':558,1564 'take':6,58,937 'target':1636,1655,1668 'tell':1096 'temp':3383 'text':2823,2835 'threshold':1550 'tier':1183,1329,1343,1349,1357,1360,1384,1385,1415,1425,1436,1465,1486,1526,1645,2112,2122,2170,2400,2496,2754,2958,2968,2979,2990,3051,3053,3126 'time':793,1605 'time.sleep':1055,1831,2029 'timedelta':798,822 'timeout':888,1720,1910 'timezon':799,2905 'timezone.utc':817,2945 'token':174,365,367,382,391,409,1610,1613,1629,1635 'topic-agent-skills' 'topic-gtm' 'topic-hermes-agent' 'topic-marketing-skills' 'topic-openclaw-skills' 'topic-skill-pack' 'topic-skills' 'topic-technical-seo' 'total':951,989,1014,1040 'transform':2382,2821 'trend':2207 'tri':871,1556,1702,1846,1906 'true':1399,2638,3361 'twitter':112,135,1939,1942,1988,1990,2251,2253,2312,2422,2673,2685,2687,2689,2691,2729,2731,2759,3193,3195 'twitter.lstrip':2711 'twitter.startswith':2700 'tz':816,2944 'u2014':2787,2794 'unansw':307 'unauthent':370 'unknown':2561 'unscop':446 'upper':1487,2171,3127 'url':232,470,850,861,874,1698,1706,1760,1772,1774,1777,1788,1893,1902,1972,1975 'url-encod':231,849 'urllib.error.httperror':977,2000 'urllib.parse':805 'urllib.request':791,1602 'urllib.request.request':873,1704,1900 'urllib.request.urlopen':886,1718,1908 'use':459,596,2274 'user':326,389,466,877,1098,1595,1620,1687,1709,1834,1930,2152,2155,2159,2163,2660,2666,2670,3109,3112,3116,3120 'user-ag':876,1619,1708 'usernam':113,126,136,1849,1870,1890,1898,1932,1933,1938,1940,1943,1978,1982,1991,2009,2016,2026,2240,2254,2313,2417,2671,2674,3177,3196 'v':930,933,2714,2718 'v.lstrip':2712 'valibot':516 'veloc':30,72,225,272,1127,1135,1177,1304,1316,1332,1364,1366,1388,1396,1441,1494,1553,2174,2177,2583,2599,2613,2617,2625,2635,3011,3020,3080,3321 'verifi':2524,2582,2639,2703,2736 'w':577,716,723,741,1064,1404,2039,2046,2863,3365 'want':152 'warn':2728,2838 'wast':180 'watch':193,755,757,1344,1417,1426,1451,1453,1520,1587,1648,1659,2076,2114,2123,2144,2288,2960,2969,3005,3007,3035,3036,3088,3096 'websit':3224 'week':18,66,199,332,593,784,823,826,830,898,901,902,917,922,924,925,929,941,943,944,949,950,952,954,956,957,960,969,970,972,973,988,990,993,1013,1015,1018,1039,1041,1044,1162,1164,1173,1198,1204,1209,1214,1218,1222,1228,1234,1240,1274,1548,2188,2197,2206,2211,2340,3015,3018,3130,3149,3292 'weekly.items':936 'weren':2646 'without':172,238,295 'word':2372,2810,2828,2832,2840,2841 'worth':1513 'would':500 'write':138,292,2320,2383,2462,3366 'wrong':157 'x':1394,1395,1920,2607,2608,2633,2634 'x-ratelimit-remain':1919 'y':834,840,914,2947 'year':921 'yet':346 'zod':515","prices":[{"id":"17c4cc23-6539-40ef-b8ca-059d86ea126f","listingId":"e66f965e-9a19-4311-888e-13017c51e649","amountUsd":"0","unit":"free","nativeCurrency":null,"nativeAmount":null,"chain":null,"payTo":null,"paymentMethod":"skill-free","isPrimary":true,"details":{"org":"Varnan-Tech","category":"opendirectory","install_from":"skills.sh"},"createdAt":"2026-04-22T18:57:03.462Z"}],"sources":[{"listingId":"e66f965e-9a19-4311-888e-13017c51e649","source":"github","sourceId":"Varnan-Tech/opendirectory/npm-downloads-to-leads","sourceUrl":"https://github.com/Varnan-Tech/opendirectory/tree/main/skills/npm-downloads-to-leads","isPrimary":false,"firstSeenAt":"2026-04-22T18:57:03.462Z","lastSeenAt":"2026-05-18T18:54:42.784Z"}],"details":{"listingId":"e66f965e-9a19-4311-888e-13017c51e649","quickStartSnippet":null,"exampleRequest":null,"exampleResponse":null,"schema":null,"openapiUrl":null,"agentsTxtUrl":null,"citations":[],"useCases":[],"bestFor":[],"notFor":[],"kindDetails":{"org":"Varnan-Tech","slug":"npm-downloads-to-leads","github":{"repo":"Varnan-Tech/opendirectory","stars":286,"topics":["agent-skills","gtm","hermes-agent","marketing-skills","openclaw-skills","skill-pack","skills","technical-seo"],"license":"mit","html_url":"https://github.com/Varnan-Tech/opendirectory","pushed_at":"2026-05-18T18:27:10Z","description":" AI Agent Skills built for Founders who hate Marketing","skill_md_sha":"b8c0c6fa2c5c028c998996f072273d1d26b8b3a6","skill_md_path":"skills/npm-downloads-to-leads/SKILL.md","default_branch":"main","skill_tree_url":"https://github.com/Varnan-Tech/opendirectory/tree/main/skills/npm-downloads-to-leads"},"layout":"multi","source":"github","category":"opendirectory","frontmatter":{"name":"npm-downloads-to-leads","description":"Takes a list of npm package names (yours or competitors'), fetches 12 weeks of daily download data from the npm API, computes a breakout velocity score per package to identify hockey-stick growth, fetches maintainer profiles from the npm registry and GitHub API, and outputs a ranked lead brief for each breakout package with who built it, how to reach them, and what to say. Use when asked to find evangelists before they are famous, track competitor package momentum, identify breakout npm packages, map npm maintainers to Twitter or GitHub, or find DevTools leads from package growth signals. Trigger when a user says \"find leads from npm packages\", \"who maintains these breakout packages\", \"track npm download trends\", \"find evangelists before they are famous\", or \"map npm maintainers to Twitter\".","compatibility":"[claude-code, gemini-cli, github-copilot]"},"skills_sh_url":"https://skills.sh/Varnan-Tech/opendirectory/npm-downloads-to-leads"},"updatedAt":"2026-05-18T18:54:42.784Z"}}