# npm, PyPI & crates.io Package Intelligence (`optirefine/package-intelligence`) Actor

Bulk package health, vulnerabilities and dependency data for npm, PyPI and crates.io. One row per package: version, licence, maintainers, downloads, OSV advisories, deps.dev dependency counts, and a 0-100 health score.

- **URL**: https://apify.com/optirefine/package-intelligence.md
- **Developed by:** [adam corriveau](https://apify.com/optirefine) (community)
- **Categories:** Agents, Developer tools, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.00005 / actor start

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/platform/actors/running/actors-in-store#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Package & Dependency Intelligence API (x402)

A pay-per-call API selling npm/PyPI package health, dependency-graph, and vulnerability
data to AI coding agents over the [x402](https://github.com/x402-foundation/x402) payment
protocol — plus an MCP server so agents in Claude Desktop/Cursor can call it and pay
automatically.

Defaults to **Base Sepolia testnet** via the free public facilitator. Going to mainnet is
an explicit config change (see [Going to mainnet](#going-to-mainnet)).

### Endpoints

Raw passthrough of the upstream sources is **free**: npm, PyPI, OSV and deps.dev are
themselves free and unauthenticated, so charging for a relay of them prices against zero.
What gets charged for is the consolidation — the score.

| Endpoint | Method | Price | Returns |
|---|---|---|---|
| `/v1/package/:ecosystem/:name` | GET | free | Consolidated snapshot |
| `/v1/vulns/:ecosystem/:name` | GET | free | Known vulnerabilities (OSV.dev) |
| `/v1/deps/:ecosystem/:name` | GET | free | Dependency graph (deps.dev) |
| `/v1/downloads/:ecosystem/:name` | GET | free | Download counts |
| `/v1/health/:ecosystem/:name` | GET | $0.01 | Health/risk score 0-100 |
| `/v1/batch` | POST | $0.02 | Batched health scores (≤50 packages) |

`:ecosystem` is `npm` or `pypi`. Also unpaid: `/healthz`, `/v1/sample` (canned example
response), `/.well-known/x402` (discovery manifest).

Free routes are rate limited to **60/min and 2000/day per caller** — a runaway agent loop
is how we would get our egress IP blocked by npm or OSV. Paid routes are exempt; their
price is the limiter. Exceeding a limit returns `429` with `Retry-After`.

Tier, price, description, and discovery metadata all come from `src/catalog.ts` — edit
there and the payment middleware, rate limiter, manifest, and Bazaar declarations stay in
sync. `tier` is a required discriminant, so a new endpoint cannot default into being free.

#### Trusting the caller's address

The rate limiter counts per client IP, but the service sits behind a Worker proxy and a
tunnel, so every request arrives from the same address. The proxy forwards the real one as
`x-stable-ip` **signed with `PROXY_SECRET`**, and the origin honours it only when the
secret matches. Anything else — wrong secret, no secret, or a request straight to the
tunnel hostname — shares a single bucket. Without that signature a caller could forge a
fresh address per request, or skip the proxy, and get unmetered upstream fan-out.

Set the same value in both places:

```bash
## .env for the origin, plus:
npx wrangler secret put PROXY_SECRET
```

The server warns at startup if it is missing on mainnet.

### Local setup (testnet)

```bash
npm install
npm run gen-wallet
```

`gen-wallet` prints two **testnet-only** keypairs — never fund these with real assets:

- **Seller** — put its address in `.env` as `PAY_TO` (where payments land).
- **Buyer** — put its private key in `.env` as `BUYER_PRIVATE_KEY` (used by the test
  script to simulate a paying agent).

Copy `.env.example` to `.env` and fill those in. Then fund the **buyer** with Base Sepolia
USDC at [faucet.circle.com](https://faucet.circle.com) (select Base Sepolia; no account
needed). No testnet ETH is required — x402's `exact` scheme uses EIP-3009, so the buyer
only signs off-chain and the facilitator pays gas.

```bash
npm run dev
```

Verify: `curl http://localhost:4021/healthz` → 200, and
`curl -i http://localhost:4021/v1/health/npm/express` → 402 with payment instructions.

### Test the payment flow

```bash
npm run test-buyer                              # GET /v1/health/npm/express (default)
npm run test-buyer -- /v1/deps/npm/express
npm run test-buyer -- /v1/batch
```

On Git Bash/Windows, prefix with `MSYS_NO_PATHCONV=1` so the leading `/` isn't rewritten
into a Windows path.

A request for a nonexistent package returns 404 **without charging** — the x402 middleware
skips settlement entirely on any 4xx/5xx response, so failures are free.

### MCP server (how agents consume this)

`mcp-client/` is a standalone npm package (`package-intel-mcp`) — a stdio MCP server that
runs on the *buyer's* machine. It is published separately from this server so an install
does not drag in Hono and the x402 server stack; it imports nothing from `src/`.

**It runs with no configuration at all.** With no wallet it registers the four free tools
(`package_snapshot`, `package_vulns`, `package_deps`, `package_downloads`). Requiring a
funded hot wallet before the tool did anything was the single biggest adoption blocker, so
that is now the default path, not an error.

```bash
claude mcp add package-intel -- npx -y package-intel-mcp
```

Setting `X402_PRIVATE_KEY` additionally registers `package_health` and
`package_batch_health`, which call the paid endpoints and, on a 402, sign a USDC payment
from that wallet and retry — the agent just sees data. A malformed key is a hard error
rather than a silent drop back to free mode, since that would look like the paid tools
vanishing for no reason.

```json
{
  "mcpServers": {
    "package-intel": {
      "command": "npx",
      "args": ["-y", "package-intel-mcp"],
      "env": { "X402_PRIVATE_KEY": "0x..." }
    }
  }
}
```

`X402_PRIVATE_KEY` is the *agent operator's* wallet, funded with USDC on `NETWORK`. Use a
dedicated low-balance wallet — it is a hot key that spends automatically.

To run it from source against a local server: `npm run mcp`.

#### Getting an agent to actually call it

Installing a tool does not make an agent reach for it. A line in the consuming repo's
`AGENTS.md` / `CLAUDE.md` / `.cursor/rules` does, on every relevant task — that is the
mechanism that produces recurring invocation, not registry listings. `mcp-client/README.md`
ships a copy-paste block for this.

### GitHub Action

`action.yml` publishes this repository as a dependency-review action. On a pull request that
touches `package.json`, `requirements*.txt` or `Cargo.toml`, it looks up every **newly added**
dependency and comments with advisories, deprecation, staleness and missing licences.

```yaml
name: Dependency review
on:
  pull_request:
    paths: ["**/package.json", "**/requirements*.txt", "**/Cargo.toml"]

permissions:
  contents: read
  pull-requests: write

jobs:
  review:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0        # needed: the action diffs base against head
      - uses: adam121393/package-intel@v1
        with:
          fail-on: critical     # none | low | moderate | high | critical
```

Uses the free endpoints only — no wallet, no API key, no signup. Zero runtime dependencies, so
adding it to a pipeline is not a supply-chain ask.

| Input | Default | Purpose |
|---|---|---|
| `fail-on` | `none` | Fail the check at this severity or above |
| `comment` | `true` | Post and update a PR comment |
| `github-token` | `${{ github.token }}` | Needs `pull-requests: write` |
| `api-url` | hosted service | Override to run against your own instance |

Two behaviours worth knowing. Only **added** dependencies are reviewed, not version bumps of
existing ones, so the comment does not become noise people learn to scroll past. And advisories
are scoped to a version: an exact pin is checked as written, while a range is checked against the
package's current release. That distinction matters — querying without a version returns every
advisory ever filed, which reports a fully patched `lodash` as critical.

A dependency that cannot be looked up is never a failure. An upstream outage must not block an
unrelated pull request.

### Coinbase CDP setup

Two **different** CDP credentials, easy to conflate:

| Credential | Needed for |
|---|---|
| `CDP_API_KEY_ID` + `CDP_API_KEY_SECRET` | The **facilitator** — verifying and settling payments |
| `CDP_WALLET_SECRET` | The **wallet SDK** — creating/controlling CDP-managed accounts |

Receiving payments needs only a public address. The server never holds key material to
get paid — `CDP_WALLET_SECRET` is only for `npm run cdp-wallet`.

```bash
## 1. Add CDP_API_KEY_ID + CDP_API_KEY_SECRET to .env, then:
npm run cdp-check          # verifies keys, prints which networks CDP actually serves

## 2. Add CDP_WALLET_SECRET, then create a TEE-backed receiving account:
npm run cdp-wallet                  # prints an address to use as PAY_TO
npm run cdp-wallet -- --faucet      # also request Base Sepolia test funds
```

`cdp-check` exists because CDP's docs list supported networks as "Base, Polygon, Arbitrum,
World, Solana" without saying whether Base *Sepolia* is included, and `/supported` requires
auth. It answers that empirically and tells you whether the testnet rehearsal below is
possible.

#### Rehearsing the CDP path on testnet

If `cdp-check` reports Base Sepolia is supported, set `USE_CDP_FACILITATOR=true` while
leaving `NETWORK=eip155:84532`. You then exercise the real CDP credentials and settlement
path against **test** funds. If it isn't supported, leave the flag unset — the CDP path
will first run on mainnet, so make that first payment a small one.

### Going to mainnet

1. **Receiving wallet** — use a dedicated address (ideally from `npm run cdp-wallet`), never
   a personal wallet. Only the public address goes in `PAY_TO`.
2. **Set `NETWORK=eip155:8453`.** The server switches to the CDP facilitator automatically
   and refuses to boot without CDP keys, rather than silently using a testnet facilitator.
3. **Set `PUBLIC_URL`** to the real origin so the manifest advertises reachable URLs.
4. **Deploy** (below), then make 2–3 real settled payments — the CDP Bazaar only catalogs a
   service after its first successful settlement.

Start small and confirm settlement on [BaseScan](https://basescan.org) against your `PAY_TO`
address before promoting the endpoint anywhere.

### Deploy (Railway)

`railway.json` is included (Nixpacks, `npm run start:api`, `/healthz` health check). Push the repo,
create a Railway project from it, and set the environment variables from `.env.example` in
Railway's variables UI — **not** in a committed file. Point uptime monitoring at `/healthz`.

### Getting listed

- **CDP Bazaar** — automatic once on mainnet via the CDP facilitator, after the first
  settled payment. Each route already declares discovery metadata with a *valid* sample
  input (`npm`/`express`); this matters because the Bazaar probes with that input and only
  indexes endpoints that answer **402** — a placeholder ecosystem would 400 and never list.
- **`/.well-known/x402`** — already served, for agentic.market / x402scan / x402-list.
- **MCP registries** — publish to the official MCP Registry, then Glama, Smithery, PulseMCP.

### Notes

- **Caching:** in-process LRU with TTLs from 1h (vulns) to 24h (downloads/deps). On upstream
  failure a stale value is served with `stale: true` rather than erroring.
- **Validation before payment:** unsupported ecosystems 400 in middleware *before* the
  payment check, so they're never charged.
- **Version-scoped vulnerabilities:** health scores query OSV for the resolved current
  version. Querying without a version returns every advisory in the package's history,
  which badly misrepresents maintained packages.
- **pypistats rate limits** aggressively (429 after a couple of rapid calls). Download
  counts are best-effort: a failure omits that field rather than failing the request. Warm
  the cache for popular packages if this matters.
- The health score in `src/domain/health.ts` is a documented v1 heuristic — tune the weights
  as real usage data arrives.

# Actor input Schema

## `packages` (type: `array`):

Package names to check. Prefix with an ecosystem to mix them in one run — npm:express, pypi:requests, crates:serde. Unprefixed names use the default ecosystem below.

## `ecosystem` (type: `string`):

Used for any package name without an ecosystem prefix.

## `includeDependencies` (type: `boolean`):

Adds direct, transitive and deprecated dependency counts from deps.dev. Costs one extra upstream call per package, so runs take longer.

## `maxConcurrency` (type: `integer`):

Packages fetched in parallel. Higher is faster but heavier on the public registries this reads from.

## Actor input object example

```json
{
  "packages": [
    "express",
    "pypi:django"
  ],
  "ecosystem": "npm",
  "includeDependencies": false,
  "maxConcurrency": 5
}
```

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "packages": [
        "express",
        "npm:lodash",
        "pypi:requests",
        "crates:serde"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("optirefine/package-intelligence").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "packages": [
        "express",
        "npm:lodash",
        "pypi:requests",
        "crates:serde",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("optirefine/package-intelligence").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "packages": [
    "express",
    "npm:lodash",
    "pypi:requests",
    "crates:serde"
  ]
}' |
apify call optirefine/package-intelligence --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,optirefine/package-intelligence"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/zP0aWTqZa89fBCA0h/builds/rU0NAPi5nFV4mad4L/openapi.json
