Proxychains Network Fallback
2025Emma/vibe-coding-cn
Retries failed network commands through proxychains4 and a local proxy when it sees timeouts, DNS failures or blocked access.
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…
$ npx skills add Varnan-Tech/opendirectory --skill npm-downloads-to-leads -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install Varnan-Tech/opendirectory npm-downloads-to-leads --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/Varnan-Tech/opendirectory.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/npm-downloads-to-leads .claude/skills/npm-downloads-to-leads && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "npm-downloads-to-leads" agent skill from https://github.com/Varnan-Tech/opendirectory/tree/main/skills/npm-downloads-to-leads into .claude/skills/npm-downloads-to-leads/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "npm-downloads-to-leads", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/Varnan-Tech/opendirectory/tree/main/skills/npm-downloads-to-leadsType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add Varnan-Tech/opendirectory --skill npm-downloads-to-leads -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install Varnan-Tech/opendirectory npm-downloads-to-leads --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/Varnan-Tech/opendirectory.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/npm-downloads-to-leads .agents/skills/npm-downloads-to-leads && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "npm-downloads-to-leads" agent skill from https://github.com/Varnan-Tech/opendirectory/tree/main/skills/npm-downloads-to-leads into .agents/skills/npm-downloads-to-leads/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "npm-downloads-to-leads", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add Varnan-Tech/opendirectory --skill npm-downloads-to-leads -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install Varnan-Tech/opendirectory npm-downloads-to-leads --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/Varnan-Tech/opendirectory.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/npm-downloads-to-leads .cursor/skills/npm-downloads-to-leads && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "npm-downloads-to-leads" agent skill from https://github.com/Varnan-Tech/opendirectory/tree/main/skills/npm-downloads-to-leads into .cursor/skills/npm-downloads-to-leads/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "npm-downloads-to-leads", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/Varnan-Tech/opendirectory.git --path skills/npm-downloads-to-leads--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add Varnan-Tech/opendirectory --skill npm-downloads-to-leads -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install Varnan-Tech/opendirectory npm-downloads-to-leads --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/Varnan-Tech/opendirectory.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/npm-downloads-to-leads .gemini/skills/npm-downloads-to-leads && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "npm-downloads-to-leads" agent skill from https://github.com/Varnan-Tech/opendirectory/tree/main/skills/npm-downloads-to-leads into .gemini/skills/npm-downloads-to-leads/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "npm-downloads-to-leads", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install Varnan-Tech/opendirectory npm-downloads-to-leadsInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add Varnan-Tech/opendirectory --skill npm-downloads-to-leads -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/Varnan-Tech/opendirectory.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/npm-downloads-to-leads .github/skills/npm-downloads-to-leads && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "npm-downloads-to-leads" agent skill from https://github.com/Varnan-Tech/opendirectory/tree/main/skills/npm-downloads-to-leads into .github/skills/npm-downloads-to-leads/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "npm-downloads-to-leads", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add Varnan-Tech/opendirectory --skill npm-downloads-to-leads -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install Varnan-Tech/opendirectory npm-downloads-to-leads --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/Varnan-Tech/opendirectory.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/npm-downloads-to-leads .opencode/skills/npm-downloads-to-leads && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "npm-downloads-to-leads" agent skill from https://github.com/Varnan-Tech/opendirectory/tree/main/skills/npm-downloads-to-leads into .opencode/skills/npm-downloads-to-leads/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "npm-downloads-to-leads", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
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…
npm Downloads To Leads is an agent skill from Varnan-Tech/opendirectory. 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…
Its SKILL.md is about 6.8k tokens, which your agent loads only when the skill is triggered. The skill folder holds 9 other files, including scripts and reference files (for example `README.md`, `evals/evals.json` and `references/outreach-timing.md`). Compatibility notes: ["claude-code","gemini-cli","github-copilot"]
It works with npm, GitHub and X (Twitter). The repository describes itself as: AI Agent Skills built for Founders who hate Marketing. The licence is MIT.
8 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 62e437a. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
Ships 1 file in scripts/ (Python), which the agent can run.
Shell commands in SKILL.md call:
python3From the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
api.npmjs.orgregistry.npmjs.orgapi.github.comgithub.comFrom URLs in SKILL.md, links to its own repository left out.
Names these keys or tokens, usually read from environment variables:
GITHUB_TOKENFrom names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
["claude-code","gemini-cli","github-copilot"]
From compatibility in the SKILL.md frontmatter.
npm Downloads To Leads loads about 6.8k tokens when it runs, and up to ~11k if it reads all its reference files. Until then it costs about 206 tokens; SKILL.md has 758 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); the scripts in this folder are not scanned.
The full file from Varnan-Tech/opendirectory at commit 62e437a, republished under its MIT licence (© Varnan-Tech). 758 words, ~6,836 tokens.
.claude/skills/npm-downloads-to-leads/SKILL.md (or your agent's skills folder). This skill also uses 6 other files; get the full folder from GitHub.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.
| 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. |
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.
Collect from the conversation:
esbuild, or scoped like @hono/hono)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"
python3 << 'PYEOF'
import json, sys
packages_raw = "PACKAGES_HERE" # comma or newline separated
product_context = "CONTEXT_HERE" # optional, can be empty string
packages = [p.strip() for p in packages_raw.replace("\n", ",").split(",") if p.strip()]
if not packages:
print("ERROR: No packages provided.")
sys.exit(1)
print(f"Packages to analyze: {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)
PYEOFUse 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.
# Check if the script exists
ls scripts/fetch.py 2>/dev/null && echo "script available" || echo "script not found"If the script is available, run it directly and skip to Step 6:
python3 scripts/fetch.py PACKAGES_HERE --context "CONTEXT_HERE" --output /tmp/npl-script-out.jsonThen load the output into the enriched format Step 6 expects:
python3 << 'PYEOF'
import json
out = json.load(open("/tmp/npl-script-out.json"))
# Script output has results array -- split into scored and enriched for Steps 6-8
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"Loaded {len(scored)} packages | {out['breakout_count']} breakout | {out['watching_count']} watching")
PYEOFIf the script is not available, run the inline code below.
Fetch daily download data for each package from the npm Downloads API. Aggregate to weekly 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 week buffer for partial weeks
start_str = start_date.strftime("%Y-%m-%d")
end_str = end_date.strftime("%Y-%m-%d")
results = []
failed = []
for pkg in packages:
# URL-encode 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())
# 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)}")
PYEOFIf 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."
No API call. Pure Python. Compute velocity score, growth ratio, and classify each package.
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 multiplier: 500-500K weekly 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)
# Classify
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
})
# 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 (<500/week): {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}%")
# 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)
PYEOFIf all packages are below 500/week: Stop with the message above.
Only for breakout and watching packages. Fetch npm registry metadata, then GitHub user profiles.
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"Fetching profiles for {len(target_packages)} packages (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")]
# Extract GitHub owner from 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 | maintainers={profile['npm_maintainers'][:3]} | "
f"github_owner={profile['github_owner']}")
except Exception as e:
print(f" {pkg}: registry fetch failed ({e})")
time.sleep(0.1)
# --- GitHub user profiles ---
candidates = []
if profile["github_owner"]:
candidates.append(profile["github_owner"])
# Also try npm maintainer usernames (often match 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 low ({gh_rate_remaining} remaining) -- skipping {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", 999))
gh_data = json.loads(resp.read())
profile["github_users"].append({
"username": username,
"name": gh_data.get("name") or username,
"twitter_username": gh_data.get("twitter_username") or "not found on GitHub",
"bio": gh_data.get("bio") or "",
"blog": gh_data.get("blog") or "",
"company": gh_data.get("company") or "",
"followers": gh_data.get("followers", 0),
"public_repos": gh_data.get("public_repos", 0),
"github_url": gh_data.get("html_url", f"https://github.com/{username}")
})
print(f" GitHub @{username}: {gh_data.get('followers', 0)} followers | "
f"twitter={gh_data.get('twitter_username') or 'none'} | rate_remaining={gh_rate_remaining}")
except urllib.error.HTTPError as e:
if e.code == 404:
print(f" GitHub @{username}: not found")
else:
print(f" GitHub @{username}: HTTP {e.code}")
except Exception as e:
print(f" GitHub @{username}: failed ({e})")
time.sleep(0.2)
enriched.append({**item, "profile": profile})
json.dump(enriched, open("/tmp/npl-enriched.json", "w"), indent=2)
json.dump(scored, open("/tmp/npl-scored.json", "w"), indent=2)
print(f"\nEnrichment complete. Profiles fetched: {len(enriched)}")
print(f"GitHub rate limit remaining: {gh_rate_remaining}")
PYEOFPrint enriched breakout and watching packages, then generate lead briefs and outreach messages.
python3 << 'PYEOF'
import json
enriched = json.load(open("/tmp/npl-enriched.json"))
input_data = json.load(open("/tmp/npl-input.json"))
product_context = input_data.get("product_context", "")
breakout = [p for p in enriched if p["tier"] == "breakout"]
watching = [p for p in enriched if p["tier"] == "watching"]
print("=== DATA FOR LEAD BRIEF GENERATION ===")
print(f"Product context: {product_context or '(none provided)'}")
print()
for item in breakout + watching:
pkg = item["package"]
prof = item.get("profile", {})
gh_users = prof.get("github_users", [])
primary_gh = gh_users[0] if gh_users else {}
print(f"PACKAGE: {pkg} ({item['tier'].upper()})")
print(f" Velocity score: {item['velocity_score']} | Growth: {item['growth_pct']:+.0f}%")
print(f" Recent 4-week avg: {item['recent_4_avg']:,}/week | Prior 4-week avg: {item['prior_4_avg']:,}/week")
print(f" Weekly trend (last 8): {item['weeks'][-8:]}")
print(f" Description: {prof.get('description', 'none')}")
print(f" Keywords: {', '.join(prof.get('keywords', []))}")
print(f" npm maintainers: {', '.join(prof.get('npm_maintainers', []))}")
if primary_gh:
print(f" GitHub: @{primary_gh.get('username')} | {primary_gh.get('followers')} followers | "
f"{primary_gh.get('public_repos')} repos")
print(f" Twitter: {primary_gh.get('twitter_username')}")
print(f" Bio: {primary_gh.get('bio')}")
print(f" Company: {primary_gh.get('company')}")
else:
print(f" GitHub: no profile found")
print()
PYEOFUsing the package data printed above, generate a lead brief for each BREAKOUT and WATCHING package.
Rules:
Write your lead briefs to /tmp/npl-briefs.json with this exact structure:
{
"lead_briefs": [
{
"package": "pkg-name",
"tier": "breakout",
"growth_summary": "1-sentence summary of the growth numbers",
"maintainer_handle": "@github_handle or npm username if no GitHub found",
"twitter": "@handle or not found on GitHub",
"github_followers": 0,
"why_now": "2-3 sentences specific to this package's inflection point",
"suggested_message": "2-4 sentences. Names the package, the growth, and connects to product_context if provided."
}
]
}After writing the file, confirm with:
python3 -c "
import json
d = json.load(open('/tmp/npl-briefs.json'))
print(f'Lead briefs generated: {len(d.get(\"lead_briefs\", []))}')
for b in d['lead_briefs']:
print(f' {b[\"package\"]} ({b[\"tier\"]}): maintainer={b[\"maintainer_handle\"]}')
"python3 << 'PYEOF'
import json
scored = json.load(open("/tmp/npl-scored.json"))
enriched = json.load(open("/tmp/npl-enriched.json"))
briefs = json.load(open("/tmp/npl-briefs.json"))
failures = []
# Verify: every brief has a real package name from the scored list
real_packages = {p["package"] for p in scored}
for brief in briefs.get("lead_briefs", []):
if brief.get("package") not in real_packages:
failures.append(f"Brief for unknown package '{brief.get('package')}' -- removed")
briefs["lead_briefs"] = [b for b in briefs.get("lead_briefs", []) if b.get("package") in real_packages]
# Verify: velocity leaderboard is sorted correctly (checked on scored, not briefs)
sorted_scores = sorted([(p["package"], p["velocity_score"]) for p in scored], key=lambda x: -x[1])
if scored[0]["velocity_score"] < scored[-1]["velocity_score"]:
failures.append("Scored list not sorted by velocity_score -- re-sorted")
scored.sort(key=lambda x: x["velocity_score"], reverse=True)
# Verify: no GitHub/Twitter handles in briefs that weren't in GitHub API responses
enriched_gh = {}
for item in enriched:
for gh_user in item.get("profile", {}).get("github_users", []):
enriched_gh[gh_user["username"]] = gh_user.get("twitter_username", "not found on GitHub")
for brief in briefs.get("lead_briefs", []):
twitter = brief.get("twitter", "")
if twitter and twitter not in ("not found on GitHub", "") and not twitter.startswith("not found"):
# Verify it came from the API
found = any(twitter.lstrip("@") == v.lstrip("@") for v in enriched_gh.values() if v != "not found on GitHub")
if not found:
failures.append(f"Warning: Twitter handle '{twitter}' for {brief['package']} not verified in GitHub API data")
# Check required fields
for brief in briefs.get("lead_briefs", []):
for field in ["package", "tier", "growth_summary", "maintainer_handle", "twitter", "why_now", "suggested_message"]:
if not brief.get(field):
failures.append(f"Missing field '{field}' in brief for {brief.get('package', '?')}")
# Check for em dashes
briefs_str = json.dumps(briefs)
if "\u2014" in briefs_str:
briefs_str = briefs_str.replace("\u2014", " - ")
briefs = json.loads(briefs_str)
failures.append("Fixed: em dash characters removed from briefs")
# Check for forbidden words
forbidden = ["powerful", "robust", "seamless", "innovative", "game-changing", "streamline", "leverage", "transform"]
full_text = json.dumps(briefs).lower()
for word in forbidden:
if word in full_text:
failures.append(f"Warning: forbidden word '{word}' found in briefs -- review before presenting")
output = {
"scored": scored,
"enriched": enriched,
"briefs": briefs,
"data_quality_flags": failures
}
json.dump(output, open("/tmp/npl-output.json", "w"), indent=2)
print(f"QA complete. Issues: {len(failures)}")
for f in failures:
print(f" - {f}")
if not failures:
print("All QA checks passed.")
PYEOFpython3 << 'PYEOF'
import json, os
from datetime import datetime, timezone
output = json.load(open("/tmp/npl-output.json"))
scored = output["scored"]
enriched_map = {e["package"]: e for e in output["enriched"]}
briefs_map = {b["package"]: b for b in output["briefs"].get("lead_briefs", [])}
flags = output["data_quality_flags"]
date_str = datetime.now(tz=timezone.utc).strftime("%Y-%m-%d")
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"]
established = [p for p in scored if p["tier"] == "established"]
lines = [
f"## npm Breakout Report",
f"Packages analyzed: {len(scored)} | Breakout: {len(breakout)} | Watching: {len(watching)} | Date: {date_str}",
"",
"---",
"",
"### Velocity Leaderboard",
"",
"| Rank | Package | Weekly Downloads | 8-Week Growth | Velocity Score | Status |",
"|---|---|---|---|---|---|",
]
for i, pkg in enumerate(scored[:15], 1):
status_label = {"breakout": "BREAKOUT", "watching": "WATCHING", "steady": "steady",
"established": "established", "too_early": "too early", "insufficient_data": "no data"}.get(pkg["tier"], pkg["tier"])
growth_str = f"{pkg['growth_pct']:+.0f}%" if pkg.get("growth_pct") else "n/a"
lines.append(
f"| {i} | {pkg['package']} | {pkg['recent_4_avg']:,} | {growth_str} | "
f"{pkg['velocity_score']} | {status_label} |"
)
lines += ["", "---", ""]
if breakout or watching:
lines += ["### Lead Briefs", ""]
for item in breakout + watching:
pkg = item["package"]
brief = briefs_map.get(pkg, {})
profile = enriched_map.get(pkg, {}).get("profile", {})
gh_users = profile.get("github_users", [])
primary_gh = gh_users[0] if gh_users else {}
lines.append(f"#### {pkg} ({item['tier'].upper()})")
lines.append(f"Weekly downloads: {item['recent_4_avg']:,}/week (was {item['prior_4_avg']:,} -- {item['growth_pct']:+.0f}% growth over 8 weeks)")
if profile.get("description"):
lines.append(f"What it does: {profile['description']}")
if profile.get("keywords"):
lines.append(f"Keywords: {', '.join(profile['keywords'])}")
lines.append("")
if primary_gh:
lines.append(f"**Maintainer: @{primary_gh.get('username')}**")
lines.append(f"- GitHub: {primary_gh.get('followers', 0):,} followers | {primary_gh.get('public_repos', 0)} public repos")
lines.append(f"- Twitter: {primary_gh.get('twitter_username', 'not found on GitHub')}")
if primary_gh.get("bio"):
lines.append(f"- Bio: \"{primary_gh['bio']}\"")
if primary_gh.get("company"):
lines.append(f"- Company: {primary_gh['company']}")
if primary_gh.get("blog"):
lines.append(f"- Website: {primary_gh['blog']}")
elif profile.get("npm_maintainers"):
lines.append(f"**Maintainer (npm only):** {', '.join(profile['npm_maintainers'][:3])}")
lines.append("- GitHub profile: not found")
lines.append("")
if brief.get("why_now"):
lines.append(f"**Why reach out now:** {brief['why_now']}")
if brief.get("suggested_message"):
lines.append(f"\n**Suggested first message:**")
lines.append(f"> {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}")
PYEOFClean up temp files:
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© Varnan-Tech, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 6 other files (scripts, references) in skills/npm-downloads-to-leads of Varnan-Tech/opendirectory.
Open the folder on GitHubat commit 62e437a
npm Downloads To Leads next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| npm Downloads To Leads this skillVarnan-Tech/opendirectory | 674 | — | ~6.8k | Automated safety check: Pass | MIT | |
| Proxychains Network Fallback2025Emma/vibe-coding-cn | 23k | 1 repos | ~1.1k | Automated safety check: Notes | MIT | |
| Cutting A ReleaseTriliumNext/Trilium | 38k | — | ~3.2k | Automated safety check: Pass | AGPL-3.0 | |
| Verdaccio Pull Request Workflowverdaccio/verdaccio | 18k | — | ~1.9k | Automated safety check: Pass | MIT | |
| Update .NET Supported OS Matrixdotnet/core | 22k | — | ~4.1k | Automated safety check: Pass | MIT | |
| Hunk Release Workflowmodem-dev/hunk | 9.6k | — | ~3.8k | Automated safety check: Pass | MIT |
2025Emma/vibe-coding-cn
Retries failed network commands through proxychains4 and a local proxy when it sees timeouts, DNS failures or blocked access.
TriliumNext/Trilium
A skill your agent uses when cutting, preparing, or debugging a Trilium release — bumping the monorepo version, tagging, or diagnosing a failed "Release" workflow run.
verdaccio/verdaccio
Takes a change through a verdaccio pull request: branch, local checks, changeset, title and body, labels, CI and review rounds, and ports to other release lines.
dotnet/core
Audits and updates the supported-os.json files for .NET releases, checking them against upstream lifecycle data and regenerating the markdown with the release-notes tool.
modem-dev/hunk
Maintainer workflow for preparing, publishing, verifying and curating Hunk releases, with confirmation gates before tags, publishes and public edits.
tw93/Waza
Fetches web pages and PDFs and returns a source-grounded summary, clean Markdown, quotes or citations, routing each kind of link to a suitable fetch method.
Varnan-Tech/opendirectory
Creates professionally designed B2B SaaS e-books in HTML + CSS, exported as print-ready PDF.
Varnan-Tech/opendirectory
Generates and updates README.md and API reference docs by reading your codebase's functions, routes, types, schemas, and architecture.
Varnan-Tech/opendirectory
Generates data visualization charts (bar, line, area, pie, doughnut, scatter, radar, treemap) as PNG using Apache ECharts v6.
Varnan-Tech/opendirectory
Creates animated looping GIFs from CSS animations (default) or AI image-to-video.
Varnan-Tech/opendirectory
Given a product description, category keywords, or competitor names (any combination), searches Reddit, Hacker News, GitHub Issues, G2, and Google Trends for the real pains your market experiences…
Varnan-Tech/opendirectory
Aggregates RSS feeds from the past week, synthesizes the top stories using Gemini, and publishes a newsletter digest to Ghost CMS.
Works with
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…. npm Downloads To Leads is an agent skill from Varnan-Tech/opendirectory. 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.
npm Downloads To Leads fits situations like: asked to find evangelists before they are famous; track competitor package momentum; identify breakout npm packages; map npm maintainers to Twitter.
Run `npx skills add Varnan-Tech/opendirectory --skill npm-downloads-to-leads -a claude-code`. Or copy the skill folder (skills/npm-downloads-to-leads in Varnan-Tech/opendirectory) into .claude/skills/npm-downloads-to-leads in your project. Claude Code loads it when a task matches its description.
Run `npx skills add Varnan-Tech/opendirectory --skill npm-downloads-to-leads -a codex`. Or copy the skill folder (skills/npm-downloads-to-leads in Varnan-Tech/opendirectory) into .agents/skills/npm-downloads-to-leads in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add Varnan-Tech/opendirectory --skill npm-downloads-to-leads -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/npm-downloads-to-leads, .gemini/skills/npm-downloads-to-leads, .github/skills/npm-downloads-to-leads and .opencode/skills/npm-downloads-to-leads in your project.
Going by SKILL.md and its folder, npm Downloads To Leads needs Python for the scripts in its folder, the command-line tools its instructions call (python3) and credentials named GITHUB_TOKEN. Our summary lists: Python 3; A credential in GITHUB_TOKEN. Compatibility (from SKILL.md): ["claude-code","gemini-cli","github-copilot"].
SKILL.md names 4 domains. In commands or code: api.npmjs.org, registry.npmjs.org, api.github.com and github.com; the agent is likely to contact these when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. The check reads SKILL.md only: the scripts in the folder are not scanned, so read them before running anything.
npm Downloads To Leads is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 6.8k tokens (SKILL.md is roughly 27k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 3.7k tokens, read only when the agent opens those files.
Skills that share tags, products or a category with npm Downloads To Leads: Proxychains Network Fallback (2025Emma/vibe-coding-cn, 23k stars), Cutting A Release (TriliumNext/Trilium, 38k stars), Verdaccio Pull Request Workflow (verdaccio/verdaccio, 18k stars) and Update .NET Supported OS Matrix (dotnet/core, 22k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
Varnan-Tech (a GitHub organization) maintains it in Varnan-Tech/opendirectory, which has 674 GitHub stars. The repository holds 61 skills in this directory. The repository was last updated on August 16, 2026.
Source: Varnan-Tech/opendirectory on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.