# Npm Downloads To Leads

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

- **Type:** Skill
- **Install:** `agentstack add skill-varnan-tech-opendirectory-npm-downloads-to-leads`
- **Verified:** Yes — security-reviewed for prompt injection and unsafe behavior
- **Seller:** [Varnan-Tech](https://agentstack.voostack.com/s/varnan-tech)
- **Installs:** 0
- **Category:** [Developer Tools](https://agentstack.voostack.com/c/developer-tools)
- **Latest version:** 0.1.0
- **License:** MIT
- **Upstream author:** [Varnan-Tech](https://github.com/Varnan-Tech)
- **Source:** https://github.com/Varnan-Tech/opendirectory/tree/main/skills/npm-downloads-to-leads
- **Website:** https://www.opendirectory.dev

## Install

```sh
agentstack add skill-varnan-tech-opendirectory-npm-downloads-to-leads
```

Requires the [AgentStack CLI](https://agentstack.voostack.com/docs/cli). Works with Claude Code, Cursor, and any MCP-compatible agent.

## About

# npm Downloads to Leads

Take 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.

---

**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.

---

## Common Mistakes

| The agent will want to... | Why that's wrong |
|---|---|
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |
| 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. |

---

## Step 1: Setup Check

```bash
echo "GITHUB_TOKEN: ${GITHUB_TOKEN:-not set, unauthenticated rate limit applies (60 req/hr -- enough for ~10 packages)}"
```

**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)."

No required keys. The npm API and npm registry are fully public with no authentication.

---

## Step 2: Gather Input

Collect from the conversation:
- One or more npm package names (unscoped like `esbuild`, or scoped like `@hono/hono`)
- Optional: a short product context string (used to personalize outreach messages)

If 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.

**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"

```bash
python3 /dev/null && echo "script available" || echo "script not found"
```

**If the script is available**, run it directly and skip to Step 6:

```bash
python3 scripts/fetch.py PACKAGES_HERE --context "CONTEXT_HERE" --output /tmp/npl-script-out.json
```

Then load the output into the enriched format Step 6 expects:

```bash
python3  %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())

        # Aggregate daily to weekly by ISO week
        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())]
        # Take last 12 complete weekly 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)} weeks, latest week {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}: NOT FOUND (404) -- will be skipped")
        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} error")
    except Exception as e:
        failed.append(pkg)
        results.append({"package": pkg, "weeks": [], "total_weeks": 0, "current_weekly": 0, "status": f"error"})
        print(f"  {pkg}: fetch failed ({e})")

    time.sleep(0.2)  # gentle rate limiting

json.dump(results, open("/tmp/npl-download-data.json", "w"), indent=2)
print(f"\nFetch complete. OK: {len(results) - len(failed)} | Failed/Not found: {len(failed)}")
if failed:
    print(f"Skipped: {', '.join(failed)}")
PYEOF
```

**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."

---

## Step 4: Compute Velocity Scores

No API call. Pure Python. Compute velocity score, growth ratio, and classify each package.

```bash
python3 = 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 multiplier: 500-500K weekly downloads
    if 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)

    # Classify
    if velocity_score > 80 and 500 = 1.5:
        tier = "breakout"
    elif velocity_score > 40 and recent_4 >= 500 and growth_ratio >= 1.2:
        tier = "watching"
    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
    })

# Sort by velocity_score descending
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 scoring complete:")
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 (8,}/wk  growth={p['growth_pct']:+.0f}%")

# Stop if nothing worth analyzing
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: All packages are below the 500 weekly downloads threshold for reliable velocity analysis.")
        print("Try packages with more community adoption.")
        import sys; sys.exit(1)
PYEOF
```

**If all packages are below 500/week:** Stop with the message above.

---

## Step 5: Fetch Maintainer Profiles

Only for breakout and watching packages. Fetch npm registry metadata, then GitHub user profiles.

```bash
python3  {brief['suggested_message']}")
        lines.append("")
        lines.append("---")
        lines.append("")

if too_early:
    lines += [f"### Too Early ({len(too_early)} packages below 500 weekly downloads)", ""]
    for p in too_early:
        lines.append(f"- {p['package']}: ~{p['recent_4_avg']:,}/week -- revisit when above 500/week")
    lines.append("")

if established:
    lines += [f"### Established Packages (above 500K/week, velocity less meaningful)", ""]
    for p in established:
        lines.append(f"- {p['package']}: ~{p['recent_4_avg']:,}/week")
    lines.append("")

lines += ["---", ""]
lines.append(f"Data quality notes: {'; '.join(flags) if flags else 'None'}")

output_path = f"docs/npm-leads/{date_str}.md"
os.makedirs("docs/npm-leads", exist_ok=True)
open(output_path, "w").write("\n".join(lines))

print("\n".join(lines))
print(f"\nSaved to: {output_path}")
PYEOF
```

Clean up temp files:

```bash
rm -f /tmp/npl-input.json /tmp/npl-download-data.json /tmp/npl-scored.json \
      /tmp/npl-enriched.json /tmp/npl-briefs.json /tmp/npl-output.json
```

## Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

- **Author:** [Varnan-Tech](https://github.com/Varnan-Tech)
- **Source:** [Varnan-Tech/opendirectory](https://github.com/Varnan-Tech/opendirectory)
- **License:** MIT
- **Homepage:** https://www.opendirectory.dev

Install and usage instructions live in the source repository linked above.

## Pricing

- **Free** — Free

## Security capabilities

Automated source analysis of v0.1.0 — what this tool can access:

- **Network access:** yes
- **Filesystem access:** yes
- **Shell / process execution:** no
- **Environment & secrets:** yes
- **Dynamic code execution:** no

*"Yes" means the capability is present in the source — more access means more to trust, not that it is unsafe.*


## Versions

- **0.1.0** — security scan: passed — Imported from the upstream source.

## Links

- Listing page: https://agentstack.voostack.com/l/skill-varnan-tech-opendirectory-npm-downloads-to-leads
- Seller: https://agentstack.voostack.com/s/varnan-tech
- Browse the marketplace: https://agentstack.voostack.com/browse

---
Listed on AgentStack — the marketplace for AI agent skills and MCP servers. Every listing is security-reviewed. Creators keep 70%.
