$npx -y skills add Varnan-Tech/opendirectory --skill npm-downloads-to-leadsTakes 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
| 1 | # npm Downloads to Leads |
| 2 | |
| 3 | 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. |
| 4 | |
| 5 | --- |
| 6 | |
| 7 | **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. |
| 8 | |
| 9 | --- |
| 10 | |
| 11 | ## Common Mistakes |
| 12 | |
| 13 | | The agent will want to... | Why that's wrong | |
| 14 | |---|---| |
| 15 | | 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. | |
| 16 | | 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. | |
| 17 | | 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. | |
| 18 | | 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. | |
| 19 | | 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. | |
| 20 | | 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. | |
| 21 | |
| 22 | --- |
| 23 | |
| 24 | ## Step 1: Setup Check |
| 25 | |
| 26 | ```bash |
| 27 | echo "GITHUB_TOKEN: ${GITHUB_TOKEN:-not set, unauthenticated rate limit applies (60 req/hr -- enough for ~10 packages)}" |
| 28 | ``` |
| 29 | |
| 30 | **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)." |
| 31 | |
| 32 | No required keys. The npm API and npm registry are fully public with no authentication. |
| 33 | |
| 34 | --- |
| 35 | |
| 36 | ## Step 2: Gather Input |
| 37 | |
| 38 | Collect from the conversation: |
| 39 | - One or more npm package names (unscoped like `esbuild`, or scoped like `@hono/hono`) |
| 40 | - Optional: a short product context string (used to personalize outreach messages) |
| 41 | |
| 42 | 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. |
| 43 | |
| 44 | **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" |
| 45 | |
| 46 | ```bash |
| 47 | python3 << 'PYEOF' |
| 48 | import json, sys |
| 49 | |
| 50 | packages_raw = "PACKAGES_HERE" # comma or newline separated |
| 51 | product_context = "CONTEXT_HERE" # optional, can be empty string |
| 52 | |
| 53 | packages = [p.strip() for p in packages_raw.replace("\n", ",").split(",") if p.strip()] |
| 54 | if not packages: |
| 55 | print("ERROR: No packages provided.") |
| 56 | sys.exit(1) |
| 57 | |
| 58 | print(f"Packages to analyze: {len(packages)}") |
| 59 | for p in packages: |
| 60 | print(f" {p}") |
| 61 | |
| 62 | with open("/tmp/npl-input.json", "w") as f: |
| 63 | json.dump({"packages": packages, "product_context": product_context}, f) |
| 64 | PYEOF |
| 65 | ``` |
| 66 | |
| 67 | --- |
| 68 | |
| 69 | ## Step 3: Fetch 12-Week Download Data |
| 70 | |
| 71 | **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.** |
| 72 | |
| 73 | ```bash |
| 74 | # Check if the script exists |
| 75 | ls scripts/fetch.py 2>/dev/null && echo "script available" || echo "script not found" |
| 76 | ``` |
| 77 | |
| 78 | **If the script is available**, run it directly and skip to Step 6: |
| 79 | |
| 80 | ```bash |
| 81 | python3 scripts/fetch.py PACKAGES_HERE --context "CONTEXT_HERE" --output /tmp/npl-script-out.json |
| 82 | ``` |
| 83 | |
| 84 | Then load the output into the enriched format Step 6 expects: |
| 85 | |
| 86 | ```bash |
| 87 | python3 |