Skip to main content
Glama
README.md
# @profullstack/proxy

Call the web through the residential proxies we pay for, with each provider's
own authentication and targeting. One package, every surface:

| surface | how |
| --- | --- |
| library | `proxyFetch(url, init, { country, session })`, `proxyUrl()`, `exitIp()`, `status()` |
| CLI | `proxy <url>`, `proxy ip`, `proxy url`, `proxy status` |
| API | `proxy serve`: a local forward proxy that holds the credentials, plus a JSON API |
| MCP | `proxy mcp`: tools `proxy_fetch`, `proxy_ip`, `proxy_url`, `proxy_status` |
| TUI | `proxy tui`: accounts, renewals, and a live exit test |

Providers:

- **Proxiware** Unlimited Residential (`unlimited.proxiware.com:1337`). Targeting
  rides in the username: `-country-us`, `-state-…`, `-city-…`, sticky
  `-sid-<n>-ttl-<min>`. Defaults to a US exit.
- **Webshare** (`p.webshare.io:80`). `-rotate`, `-US-rotate`, or `-<n>` to pin
  proxy n. With only `WEBSHARE_API_KEY`, the proxy login is read from the API.

## Install

```sh
npm install -g @profullstack/proxy     # the `proxy` command
npm install @profullstack/proxy        # the library
```

Node 22.19+ (or Bun). In the Profullstack fleet, `cli-tools` ships `proxy` with
the shared vault keys already wired in.

## Credentials

Environment only; the package never reads or writes a file.

| variable | |
| --- | --- |
| `PROXIWARE_PROXY_USER`, `PROXIWARE_PROXY_PASSWORD` | proxy login (Proxiware dashboard, or `GET /v1/unlimited/subscriptions`) |
| `PROXIWARE_API_KEY` | account status (`proxy status`) |
| `PROXIWARE_PROXY_HOST`, `PROXIWARE_PROXY_PORT` | override `unlimited.proxiware.com:1337` |
| `WEBSHARE_API_KEY` | account status, and the proxy login when no user/password is set |
| `WEBSHARE_PROXY_USER`, `WEBSHARE_PROXY_PASSWORD` | proxy login, skips the API lookup |
| `WEBSHARE_PROXY_HOST`, `WEBSHARE_PROXY_PORT` | override `p.webshare.io:80` |
| `PROXY_PROVIDER` | default provider when several are configured (else proxiware, then webshare) |
| `PROXY_SERVE_TOKEN` | default `--token` for `proxy serve` |

## CLI

```sh
proxy https://example.com                    # body to stdout, US residential exit
proxy -i -c gb https://example.com           # status + headers, UK exit
proxy -s job42 --ttl 10 https://example.com  # sticky: same IP for 10 minutes
proxy -X POST -H 'content-type: application/json' -d @body.json https://api.example.com
proxy -o page.html https://example.com
proxy ip                                     # 75.179.96.136  US Ohio …  AS10796 Charter  (proxiware)
proxy url                                    # http://user-country-us:***@unlimited.proxiware.com:1337
export HTTPS_PROXY="$(proxy url --reveal)"   # hand the real URL to another tool
proxy status                                 # credit, plans, renewal, expiry
proxy -p webshare ip                         # a specific provider
```

The default User-Agent is `curl/8.5.0 (+@profullstack/proxy)`: some APIs (ESPN)
allowlist curl-like agents and refuse browser and runtime defaults whatever the
exit IP. `-A` overrides it. Exit codes: 0 ok, 22 non-2xx response (curl's
`--fail`), 3 no credentials, 2 bad arguments, 1 anything else.

## `proxy serve`

```sh
proxy serve --port 8888                      # binds 127.0.0.1
HTTPS_PROXY=http://127.0.0.1:8888 curl https://ipinfo.io/json
curl http://127.0.0.1:8888/status            # JSON API: /status /ip /url /fetch?url=
```

Clients send no credentials; the server adds them on the hop upstream, so a
tool with no proxy-auth support (or one you would rather not hand a password)
still gets the paid exit. Per-connection overrides: `X-Proxy-Country`,
`X-Proxy-Session`. Bind beyond localhost only with `--token T`, which clients
then send as `Proxy-Authorization: Bearer T` (proxy traffic) or
`Authorization: Bearer T` (API).

## MCP

```json
{ "mcpServers": { "proxy": { "command": "proxy", "args": ["mcp"] } } }
```

`proxy_fetch` (url, method, headers, body, max_bytes, provider, country,
session), `proxy_ip`, `proxy_url` (masked unless `reveal`), `proxy_status`.

## Library

```js
import { proxyFetch, proxyUrl, exitIp, status } from '@profullstack/proxy';

const res = await proxyFetch('https://example.com', { headers: { accept: 'text/html' } }, { country: 'us' });
const url = await proxyUrl({ provider: 'proxiware', session: 'worker-3', ttl: 30 });
const { ip, org } = await exitIp({ country: 'gb' });
```

Under Node this is undici's `fetch` with a `ProxyAgent` (credentials sent as a
Basic token, not URL userinfo, which some undici versions drop). Under Bun it is
the built-in `fetch` with its per-request `proxy` option. `createDispatcher()`
returns the undici agent for code that wants to keep one around.

## Development

```sh
npm test                 # offline: fake upstream proxy, no credentials needed
scripts/smoke.sh --vault # live, against the real accounts (shared vault keys)
```

Releases publish to npm from GitHub when a release is published
(`.github/workflows/publish.yml`).

MIT © Profullstack, Inc.