npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

surf-skill

v4.1.0

Published

Multi-skill bundle for AI coding agents: surf-search-skill (multi-provider web search via Tavily + Parallel + Brave) + surf-plan-skill (research-driven execution planning). Includes `surf` interactive setup with live key validation, automatic provider fal

Downloads

1,032

Readme


Two skills. Three providers. One install. npm i -g surf-skill now bundles both surf-search-skill (multi-provider web search) and surf-plan-skill (research-driven execution planning), plus a friendly surf setup wrapper with live key validation.

                  ┌──▶ Tavily   (search, extract, crawl, map, research)
search   ─┐       │
extract  ─┤       │
crawl   ──┼──▶ surf-search-skill ──▶ Parallel (search, extract, research async)
map     ──┤       │
research ─┘       │
                  └──▶ Brave    (search only — own index)

plan / design ──▶ surf-plan-skill ──┐
architect / spec ──────────────────►│  (calls surf-search-skill for web research)
                                    └──▶ Markdown plan file with [^N] citations

| | | |---|---| | Status | v4.1.0 (npm) | | Install | npm i -g surf-skill (Linux · macOS · Windows) | | Skills shipped | surf-search-skill (search) + surf-plan-skill (planning) | | Bins shipped | surf (interactive setup + validation), surf-search-skill, surf-plan-skill | | Runtime | Node ≥ 18. Zero npm deps. | | Storage | ~/.config/surf/keys.json (chmod 600). Never read from env at runtime by the CLI. | | Supported agents | Claude Code · GitHub Copilot CLI · Pi Coding Agent · OpenCode · Codex CLI | | Spec | Anthropic Agent Skills |

Quickstart (60 seconds)

npm i -g surf-skill          # installs BOTH skills + 3 bins (cross-OS)
surf                         # interactive: add keys with LIVE validation
                             #   ✓ valid (tavily, HTTP 200, 1.2s, 1 credit)
                             #   ✗ invalid (auth, HTTP 401) — NOT saved

# Use directly:
surf-search-skill search "claude 4.7 release notes" --max 3
surf-search-skill search "X" --provider brave --mode fast

# Or ask an AI agent:
> make a plan for adding rate limiting to my Express API
# → surf-plan-skill kicks in: reads project, runs surf-search-skill searches,
#   asks 3-5 researched questions, writes ~/.claude/plans/<slug>-<ts>.md
#
# Research is gated, not optional: the agent may not present a plan —
# including for plan-mode approval — before the searches are done. When
# the harness blocks Bash (e.g. Claude Code plan mode), the skill falls
# back to the harness's native WebSearch/WebFetch instead of skipping.

Quickstart (30 seconds)

# One-liner cross-OS install (Linux, macOS, Windows)
npm i -g surf-skill

# That's it — postinstall creates symlinks into all supported harnesses,
# initializes ~/.config/surf/keys.json, and prints a hint.
# On first run, an interactive wizard auto-launches in TTY:

surf-search-skill search "your query"
# → "No keys configured. Launching setup wizard…"
# → prompts for Tavily key #1, #2, …, Parallel key #1, #2, …
# → resumes your command

# In each project where you'll use surf-search-skill (REQUIRED for GH Copilot CLI):
cd path/to/your-project
surf-search-skill project-config

You can also run surf-search-skill setup manually anytime to add more keys.

Use as a Node library

npm i surf-skill
import { search, extract, research } from 'surf-skill';

// Auto-discovers keys: opts > process.env > .env > ~/.config/surf/keys.json
const r = await search('claude api', { max: 3 });
console.log(r.data.results[0].url);

// Or pass keys explicitly (great for serverless / Next.js API routes)
const r2 = await search('x', {
  tavilyKeys: [process.env.MY_TAVILY_1, process.env.MY_TAVILY_2],
  depth: 'advanced',
});

// Batch search (single call, N queries, partial-failure tolerant)
const batch = await search(['topic A', 'topic B', 'topic C'], { max: 2 });

// Deep research
const job = await research('compare X vs Y', { model: 'mini' });
console.log(job.data.content);

Library works server-side (Node / Next.js API routes / Express). Not for browser bundles — Tavily and Parallel don't enable CORS for browser origins.


Why this exists

You have a Tavily key. Maybe a Parallel one too. Maybe several Tavily keys to spread cost across accounts. Today every agent skill is 1-to-1 with a provider — when a key dies or a provider has an outage, your agent loop breaks.

surf-search-skill is a connector:

  • Multi-key per provider. Add as many keys as you want; rotation is automatic on 401/403/402 (auth, insufficient credits) or persistent 5xx. Burned keys auto-reset on the first day of the next calendar month (assuming monthly billing).
  • Provider fallback. If all Tavily keys are burned, search/extract fail over to Parallel — transparently. crawl and map stay on Tavily (Parallel doesn't have them). research defaults to Parallel first because its Task API is the strongest deep-research surface.
  • Hot-path memory. The last successful provider/key is remembered in ~/.config/surf/keys.json. The next call starts there — no cold-start cost.
  • Predictable output. --json returns the same normalized envelope no matter which provider answered.

Supported agents

The installer configures every harness it can. The user only has to manually configure GitHub Copilot CLI (per project) because it has no global timeout setting.

Claude Code

npm i -g surf-skill
# Installer writes ~/.claude/settings.json:
#   { "env": { "BASH_DEFAULT_TIMEOUT_MS": "300000",
#              "BASH_MAX_TIMEOUT_MS": "600000" } }

The skill becomes available at ~/.claude/skills/surf-search-skill/. In a Claude Code session, just ask: "search the web for X" — the agent will invoke surf-search-skill via Bash. For commands that may exceed 5 min, the agent can pass timeout: 600000 on the Bash call (10 min hard cap), or set run_in_background: true and monitor via /tasks.

GitHub Copilot CLI

⚠️ Default bash timeout is 30 s — the most fragile of the three.

npm i -g surf-skill
# Symlink created at ~/.copilot/skills/ (via ~/.agents/skills/surf-search-skill).

Per-project, run inside the project root:

surf-search-skill project-config
# writes .github/copilot-hooks.json with { "timeoutSec": 300 }
# detects .github/ automatically; use --harness copilot --yes to force

Without this, any surf-search-skill command other than --help, --version, keys list/add, or search --max 1 will time out. With it, you can use the full command set up to ~5 min per call.

For longer operations, use Copilot CLI's async pattern: /delegate the surf-search-skill research-start ... call, then poll with surf-search-skill research-poll <id> from a regular session.

If surf-search-skill detects the agent will likely kill the call before it can finish, it now aborts early with LikelyAgentTimeout and tells the agent to suggest surf-search-skill project-config to the user — instead of dying silently to SIGTERM.

Pi Coding Agent

npm i -g surf-skill
# Installer writes ~/.pi/agent/settings.json:
#   { "env": { "PI_BASH_DEFAULT_TIMEOUT_SECONDS": "300",
#              "PI_BASH_MAX_TIMEOUT_SECONDS": "600" } }

The skill becomes available at ~/.pi/agent/skills/surf-search-skill/. Pi reads the timeout from env, so the settings.json above is enough. For long-running work, Pi supports subagents with --bg and the await tool.

OpenCode & Codex CLI

Also auto-configured by the installer (~/.agents/skills/surf-search-skill/ and ~/.codex/skills/surf-search-skill/). OpenCode gets mcp_timeout + bash.timeout_ms set to 600 000 ms in ~/.config/opencode/opencode.json.


Timeouts at a glance

| Agent | Default bash | Max | After install | Most likely to time out? | |---|---|---|---|---| | Claude Code | 120 s | 600 s (hard) | 300 s default | Long crawls > 5 min | | GitHub Copilot CLI | 30 s | NÃO DOCUMENTADO | unchanged (no global config) | YES — most commands | | Pi Coding Agent | 120 s | 600 s | 300 s default | Long crawls > 5 min | | OpenCode | varies | 600 s | 600 s default | Rarely |

If you see timeouts, the order of fixes:

  1. Use surf-search-skill research-start + research-poll instead of sync research.
  2. Reduce --limit / --max / --max-depth.
  3. Bump the per-harness timeout (see the relevant card above).
  4. Set SURF_TIMEOUT_MS=300000 (caps the HTTP request itself at 5 min).

Commands

| Command | What it does | Provider(s) | |---|---|---| | setup | Interactive wizard to add keys (TTY) | n/a | | project-config | Write per-project bash-timeout config | n/a | | search <q> [q2 ...] | Web search; multiple positional args = batch | tavily, parallel, brave | | extract <url> ... | Pull markdown from URLs | tavily, parallel | | crawl <url> | Recursive site crawl | tavily | | map <url> | Sitemap discovery | tavily | | research <topic> | Sync deep research (50 s budget) | parallel, tavily | | research-start <topic> | Start async research | parallel, tavily | | research-poll <id> | Poll an async research job | (sticky to provider) | | usage --provider <name> | Provider's usage endpoint | per provider | | cache-clear | Purge response cache | n/a | | cost [--reset] | Local credit ledger (per-provider) | n/a | | keys <subcmd> | add, remove, list, reset, clear | n/a |

Full reference: skills/surf-search-skill/SKILL.md.

Global flags every command accepts:

--provider <tavily|parallel|brave>  Force provider (disables fallback)
--mode <fast|normal|slow>           Search tier. Per-provider mapping:
                                      fast   = Tavily depth=fast / Brave count=5
                                      normal = default
                                      slow   = Tavily depth=advanced / Brave count=20
                                      (Parallel ignores — single mode.)
--no-fallback                       Keep default provider, no cross-provider fallback
--no-cache                          Skip response cache
--json                              Normalized envelope as JSON
--raw-json                          Raw provider response (bypasses cache)
--confirm-expensive                 Allow operations estimated > 10 credits
--quiet                             Silence progress logs (stderr)

Search modes

surf-search-skill search "X" --mode fast    # 5 results / 1 credit Tavily / minimal latency
surf-search-skill search "X" --mode normal  # 10 results / default everywhere
surf-search-skill search "X" --mode slow    # 20 results / Tavily advanced / deeper signal

Want to force a specific provider for a given mode?

surf-search-skill search "X" --provider brave --mode slow    # 20 brave results, no fallback
surf-search-skill search "X" --provider tavily --mode fast   # Tavily fast tier

Batch your queries

When you need to research multiple angles of the same topic, batch them in a single call. Each positional arg is an independent query:

surf-search-skill search "compare X vs Y" "alternatives to X" "X security issues"
  • Runs sequentially (avoids rate-limit thrashing on a single key).
  • Partial failures are reported inline — the command exits 0 if at least one query succeeded.
  • Total credits and timing surface in the markdown header and --json envelope.
  • Progress logs (see below) show [i/N] per query.

This is the recommended way for an agent to gather multi-source context in one shot, instead of looping with N separate bash calls.


Progress logs (stderr)

Every operation emits one self-contained line per event to stderr, so both humans and the calling LLM can see what's happening without parsing the main result on stdout.

[surf 17:58:12] ▸ search → tavily (key #0)
[surf 17:58:14] ✓ search tavily 1234ms (2 credits)
[surf 17:58:14] ↻ tavily 429 — backoff 1500ms (attempt 1/3)
[surf 17:58:18] ⚠ tavily key #0 burned (401)
[surf 17:58:18] ▸ search → parallel (key #0)
[surf 17:58:20] ✓ search parallel 2102ms (2 credits)
[surf 17:58:20] ⏱ batch done: 3/3 ok, 0 failed (8200ms, 6 credits)

The format is stable for grep/parse. Use --quiet or SURF_QUIET=1 to silence (CI, piping, tests). Stdout stays clean either way.


Multi-key & fallback

state.json (per provider):
  keys:       [key0, key1, key2]
  current:    1                       ← starts here next call
  burned:     [{ index: 0, reason: "401", at: "2026-05-15..." }]
                                      ← auto-reset on the 1st of next month

call flow:
  ┌─ load state, auto-reset burned ──┐
  │                                  │
  └─▶ chain = [last_ok_provider,    ─┤
              ...rest_of_capability_chain]
                                     │
  for provider in chain:             │
    for key in usable_keys(provider):│
      try call                       │
        200 ─▶ save last_ok, return  │
        401/403/402 ─▶ burn key, next│
        5xx x3 ─▶ burn key, next     │
        429 ─▶ backoff, retry        │
        4xx ─▶ raise (no fallback)   │
    (no usable keys) ─▶ next provider│
  raise AllProvidersExhausted ───────┘

Force a specific provider for debugging:

surf-search-skill search "x" --provider parallel
# 'parallel' fails ⇒ command fails (no fallback when --provider is set)

Onboarding (3 ways)

# 1. Wizard (recommended in a TTY)
surf-search-skill setup

# 2. Direct
surf-search-skill keys add --provider tavily tvly-...
surf-search-skill keys add --provider parallel <key>

# 3. Auto-launch in TTY: just run any command without keys
surf-search-skill search "test"
# → "No keys configured. Launching setup wizard…" → prompts → resumes search

# 4. Library mode: env vars / .env / explicit opts (no setup needed)
TAVILY_API_KEY=tvly-... node -e "import('surf-skill').then(m => m.search('x'))"

Inspect what was stored (keys are masked):

surf-search-skill keys list
# **Surf keys** (config: ~/.config/surf/keys.json)
# last_ok_provider: `tavily`
# ## tavily (2 keys)
# - [0] tvly-…ab12  *(current)*
# - [1] tvly-…cd34

Troubleshooting

❌ Error [NoProviderAvailable]: operation 'X' requires one of [...] → The op needs a key for a provider you haven't configured. In a TTY the error already suggests surf-search-skill setup. Outside TTY, run surf-search-skill keys add --provider <name> <key>.

❌ Error [AllProvidersExhausted]: ... → Every key on every eligible provider failed. Check surf-search-skill keys list — if everything is burned, you've either rotated keys mid-billing-cycle or the providers are down. Run surf-search-skill keys reset to retry.

Command timed out in GH Copilot CLI → Run surf-search-skill project-config inside the project root. See the Copilot CLI card above.

❌ Error [LikelyAgentTimeout]: ... → surf-search-skill detected the harness will kill the call before it finishes (typical on Copilot CLI without per-project config). Run surf-search-skill project-config in the project, then retry. Don't retry the same call without fixing the timeout first.

❌ Error [KilledBySignal]: surf-search-skill received SIGTERM/SIGINT → The harness killed us mid-flight. Same fix as LikelyAgentTimeout. The SIGTERM handler exists as a fallback — the self-budget check should fire first when env vars are set.

❌ Error: EXPENSIVE_BLOCKED ... → Pass --confirm-expensive after confirming the cost with the user. Or export SURF_ALLOW_EXPENSIVE=1 for the session.

Refusing sync research with model=pro → Use surf-search-skill research-start --model pro ... then surf-search-skill research-poll <id>. Sync research is capped at 50 s on purpose.


Repository layout (v4.1.0)

.
├── package.json                       ← name: surf-skill (npm), version 4.1.0, 3 bins
├── README.md           ← you're here
├── CHANGELOG.md
├── LICENSE
├── logo.png
├── SKILL.md                           ← surf-search-skill (search skill, root of pkg)
├── bin/
│   ├── surf.mjs                       ← interactive setup + key validation
│   ├── surf-search-skill.mjs          ← multi-provider web search CLI
│   └── surf-plan-skill.mjs            ← planning workflow CLI
├── skills/
│   └── surf-plan-skill/
│       └── SKILL.md                   ← surf-plan-skill (planning skill)
├── src/
│   ├── index.mjs                      ← library entry (search/extract/research/...)
│   ├── env.mjs                        ← key discovery (opts > env > .env > config)
│   ├── plan/                          ← plan-file, plans-dir, slug (planning lib)
│   ├── validators/                    ← per-provider key validators (live API)
│   ├── lib/
│   │   ├── state.mjs                  ← ~/.config/surf/keys.json I/O
│   │   ├── cache.mjs                  ← TTL response cache
│   │   ├── audit.mjs                  ← audit + usage JSONL
│   │   ├── flags.mjs, cost.mjs, format.mjs
│   │   ├── dispatch.mjs               ← provider/key fallback + self-budget
│   │   ├── keys-cmd.mjs               ← surf-search-skill keys add/remove/...
│   │   ├── setup.mjs                  ← interactive onboarding (with validation)
│   │   ├── project-config.mjs         ← surf-search-skill project-config
│   │   ├── progress.mjs               ← stderr progress events
│   │   ├── check-surf-skill.mjs       ← detect companion CLI in PATH
│   │   ├── harness-install.mjs        ← cross-OS symlink install for 2 skills
│   │   ├── api/                       ← library search/extract/crawl/map/research
│   │   └── providers/
│   │       ├── index.mjs              ← capability map (search + 3 providers)
│   │       ├── tavily.mjs
│   │       ├── parallel.mjs
│   │       └── brave.mjs
│   └── install/
│       ├── postinstall.mjs            ← cross-OS symlinks + skeleton keys.json
│       └── preuninstall.mjs           ← cleanup our symlinks
└── references/
    ├── tavily-api.md
    ├── parallel-api.md
    ├── plan-workflow.md               ← deeper docs on the 6-phase planning workflow
    └── COSTS.md

Security

  • This repository contains no real API keys. The installer only uses placeholders.
  • Keys are stored exclusively in ~/.config/surf/keys.json (chmod 600). surf-search-skill does not read keys from env at runtime.
  • The audit log records only provider name and key index, never the key itself. surf-search-skill keys list masks every key (tvly-…ab12).
  • The skill never executes content returned from the web — it just prints it.
  • Review any skill before installing. Skills can instruct agents to run commands.

License

MIT.