novada-proxy-core 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/build/adapters/brightdata.d.ts +24 -0
  2. package/build/adapters/brightdata.js +56 -0
  3. package/build/adapters/generic.d.ts +32 -0
  4. package/build/adapters/generic.js +63 -0
  5. package/build/adapters/index.d.ts +16 -0
  6. package/build/adapters/index.js +42 -0
  7. package/build/adapters/novada.d.ts +23 -0
  8. package/build/adapters/novada.js +61 -0
  9. package/build/adapters/oxylabs.d.ts +22 -0
  10. package/build/adapters/oxylabs.js +54 -0
  11. package/build/adapters/smartproxy.d.ts +22 -0
  12. package/build/adapters/smartproxy.js +54 -0
  13. package/build/adapters/types.d.ts +58 -0
  14. package/build/adapters/types.js +7 -0
  15. package/build/config.d.ts +4 -0
  16. package/build/config.js +7 -0
  17. package/build/errors.d.ts +2 -0
  18. package/build/errors.js +58 -0
  19. package/build/index.d.ts +28 -0
  20. package/build/index.js +22 -0
  21. package/build/redact.d.ts +2 -0
  22. package/build/redact.js +24 -0
  23. package/build/tools/batch.d.ts +24 -0
  24. package/build/tools/batch.js +156 -0
  25. package/build/tools/crawl.d.ts +33 -0
  26. package/build/tools/crawl.js +604 -0
  27. package/build/tools/extract.d.ts +22 -0
  28. package/build/tools/extract.js +454 -0
  29. package/build/tools/fetch.d.ts +17 -0
  30. package/build/tools/fetch.js +243 -0
  31. package/build/tools/index.d.ts +19 -0
  32. package/build/tools/index.js +10 -0
  33. package/build/tools/map.d.ts +19 -0
  34. package/build/tools/map.js +131 -0
  35. package/build/tools/render.d.ts +8 -0
  36. package/build/tools/render.js +98 -0
  37. package/build/tools/research.d.ts +9 -0
  38. package/build/tools/research.js +126 -0
  39. package/build/tools/search.d.ts +9 -0
  40. package/build/tools/search.js +104 -0
  41. package/build/tools/session.d.ts +12 -0
  42. package/build/tools/session.js +108 -0
  43. package/build/tools/status.d.ts +2 -0
  44. package/build/tools/status.js +66 -0
  45. package/build/types.d.ts +34 -0
  46. package/build/types.js +1 -0
  47. package/build/utils.d.ts +18 -0
  48. package/build/utils.js +151 -0
  49. package/build/validation.d.ts +4 -0
  50. package/build/validation.js +6 -0
  51. package/package.json +50 -0
@@ -0,0 +1,24 @@
1
+ import type { ProxyAdapter } from "./types.js";
2
+ /**
3
+ * BrightData residential proxy adapter.
4
+ *
5
+ * BrightData (formerly Luminati) is the largest residential proxy network.
6
+ * This adapter encodes country, city, and session targeting automatically
7
+ * using BrightData's username-suffix format.
8
+ *
9
+ * Auth format:
10
+ * BASE_USERNAME[-country-XX][-city-CITY][-session-ID]:PASS@HOST:PORT
11
+ *
12
+ * Where BASE_USERNAME is your full BrightData username including zone:
13
+ * e.g. brd-customer-abc123-zone-residential
14
+ *
15
+ * Get credentials:
16
+ * brightdata.com → Proxies & Scraping → Residential → Access Parameters
17
+ *
18
+ * Env vars:
19
+ * BRIGHTDATA_USER — required (full username, e.g. brd-customer-abc123-zone-residential)
20
+ * BRIGHTDATA_PASS — required
21
+ * BRIGHTDATA_HOST — optional (default: zproxy.lum-superproxy.io)
22
+ * BRIGHTDATA_PORT — optional (default: 22225)
23
+ */
24
+ export declare const BrightDataAdapter: ProxyAdapter;
@@ -0,0 +1,56 @@
1
+ /**
2
+ * BrightData residential proxy adapter.
3
+ *
4
+ * BrightData (formerly Luminati) is the largest residential proxy network.
5
+ * This adapter encodes country, city, and session targeting automatically
6
+ * using BrightData's username-suffix format.
7
+ *
8
+ * Auth format:
9
+ * BASE_USERNAME[-country-XX][-city-CITY][-session-ID]:PASS@HOST:PORT
10
+ *
11
+ * Where BASE_USERNAME is your full BrightData username including zone:
12
+ * e.g. brd-customer-abc123-zone-residential
13
+ *
14
+ * Get credentials:
15
+ * brightdata.com → Proxies & Scraping → Residential → Access Parameters
16
+ *
17
+ * Env vars:
18
+ * BRIGHTDATA_USER — required (full username, e.g. brd-customer-abc123-zone-residential)
19
+ * BRIGHTDATA_PASS — required
20
+ * BRIGHTDATA_HOST — optional (default: zproxy.lum-superproxy.io)
21
+ * BRIGHTDATA_PORT — optional (default: 22225)
22
+ */
23
+ export const BrightDataAdapter = {
24
+ name: "brightdata",
25
+ displayName: "BrightData",
26
+ lastVerified: "2026-04-09",
27
+ capabilities: { country: true, city: true, sticky: true },
28
+ credentialDocs: "brightdata.com → Proxies & Scraping → Residential → Access Parameters",
29
+ sensitiveFields: ["pass"],
30
+ loadCredentials(env) {
31
+ const user = env.BRIGHTDATA_USER;
32
+ const pass = env.BRIGHTDATA_PASS;
33
+ if (!user || !pass)
34
+ return null;
35
+ const rawPort = Number(env.BRIGHTDATA_PORT);
36
+ const port = Number.isInteger(rawPort) && rawPort > 0 && rawPort < 65536
37
+ ? rawPort : 22225;
38
+ return {
39
+ user,
40
+ pass,
41
+ host: env.BRIGHTDATA_HOST || "zproxy.lum-superproxy.io",
42
+ port: String(port),
43
+ };
44
+ },
45
+ buildProxyUrl(credentials, params) {
46
+ // BrightData appends targeting params to the username with `-` delimiter
47
+ let username = credentials.user;
48
+ if (params.country)
49
+ username += `-country-${params.country.toLowerCase()}`;
50
+ if (params.city)
51
+ username += `-city-${params.city.toLowerCase()}`;
52
+ if (params.session_id)
53
+ username += `-sid-${params.session_id}`;
54
+ return `http://${encodeURIComponent(username)}:${encodeURIComponent(credentials.pass)}@${credentials.host}:${credentials.port}`;
55
+ },
56
+ };
@@ -0,0 +1,32 @@
1
+ import type { ProxyAdapter } from "./types.js";
2
+ /**
3
+ * Generic HTTP Proxy adapter.
4
+ *
5
+ * Set PROXY_URL=http://user:pass@host:port to use any standard HTTP proxy.
6
+ *
7
+ * Compatible with (among others):
8
+ * BrightData — https://brightdata.com (zproxy.lum-superproxy.io:22225)
9
+ * Smartproxy — https://smartproxy.com (gate.smartproxy.com:10001)
10
+ * Oxylabs — https://oxylabs.io (pr.oxylabs.io:7777)
11
+ * IPRoyal — https://iproyal.com (geo.iproyal.com:12321)
12
+ * Any HTTP CONNECT-capable proxy
13
+ *
14
+ * ⚠️ Country, city, and session_id targeting parameters are NOT automatically
15
+ * applied — encode them directly in PROXY_URL per your provider's format.
16
+ * For full targeting support with automatic parameter encoding, use a
17
+ * provider-specific adapter (e.g. Novada via NOVADA_PROXY_USER/PASS).
18
+ *
19
+ * Examples:
20
+ * # BrightData with US targeting (encoded in username)
21
+ * PROXY_URL=http://user-country-us:pass@zproxy.lum-superproxy.io:22225
22
+ *
23
+ * # Smartproxy with DE targeting
24
+ * PROXY_URL=http://user-country-DE:pass@gate.smartproxy.com:10001
25
+ *
26
+ * # Plain proxy (rotating, no targeting)
27
+ * PROXY_URL=http://user:pass@proxy.example.com:8080
28
+ *
29
+ * Env vars:
30
+ * PROXY_URL — required. Must start with http:// or https://
31
+ */
32
+ export declare const GenericHttpAdapter: ProxyAdapter;
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Generic HTTP Proxy adapter.
3
+ *
4
+ * Set PROXY_URL=http://user:pass@host:port to use any standard HTTP proxy.
5
+ *
6
+ * Compatible with (among others):
7
+ * BrightData — https://brightdata.com (zproxy.lum-superproxy.io:22225)
8
+ * Smartproxy — https://smartproxy.com (gate.smartproxy.com:10001)
9
+ * Oxylabs — https://oxylabs.io (pr.oxylabs.io:7777)
10
+ * IPRoyal — https://iproyal.com (geo.iproyal.com:12321)
11
+ * Any HTTP CONNECT-capable proxy
12
+ *
13
+ * ⚠️ Country, city, and session_id targeting parameters are NOT automatically
14
+ * applied — encode them directly in PROXY_URL per your provider's format.
15
+ * For full targeting support with automatic parameter encoding, use a
16
+ * provider-specific adapter (e.g. Novada via NOVADA_PROXY_USER/PASS).
17
+ *
18
+ * Examples:
19
+ * # BrightData with US targeting (encoded in username)
20
+ * PROXY_URL=http://user-country-us:pass@zproxy.lum-superproxy.io:22225
21
+ *
22
+ * # Smartproxy with DE targeting
23
+ * PROXY_URL=http://user-country-DE:pass@gate.smartproxy.com:10001
24
+ *
25
+ * # Plain proxy (rotating, no targeting)
26
+ * PROXY_URL=http://user:pass@proxy.example.com:8080
27
+ *
28
+ * Env vars:
29
+ * PROXY_URL — required. Must start with http:// or https://
30
+ */
31
+ export const GenericHttpAdapter = {
32
+ name: "generic",
33
+ displayName: "Generic HTTP Proxy",
34
+ lastVerified: "2026-04-09",
35
+ capabilities: { country: false, city: false, sticky: false },
36
+ credentialDocs: "Set PROXY_URL=http://user:pass@host:port",
37
+ sensitiveFields: ["pass", "proxyUrl"],
38
+ loadCredentials(env) {
39
+ const raw = env.PROXY_URL;
40
+ if (!raw)
41
+ return null;
42
+ let parsed;
43
+ try {
44
+ parsed = new URL(raw);
45
+ }
46
+ catch {
47
+ // Malformed URL — do not throw, just decline so the error surfaces cleanly
48
+ return null;
49
+ }
50
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
51
+ return null;
52
+ return {
53
+ proxyUrl: raw,
54
+ user: parsed.username || "",
55
+ pass: parsed.password || "",
56
+ };
57
+ },
58
+ buildProxyUrl(credentials, _params) {
59
+ // Return the URL as-is. Country/city/session_id must be encoded
60
+ // in the URL by the user per their provider's specific format.
61
+ return credentials.proxyUrl;
62
+ },
63
+ };
@@ -0,0 +1,16 @@
1
+ import type { ProxyAdapter, ProxyCredentials } from "./types.js";
2
+ export type { ProxyAdapter, ProxyCredentials, ProxyRequestParams, AdapterCapabilities } from "./types.js";
3
+ export interface ResolvedAdapter {
4
+ adapter: ProxyAdapter;
5
+ credentials: ProxyCredentials;
6
+ }
7
+ /**
8
+ * Resolve which proxy adapter to use based on available environment variables.
9
+ * Returns the first configured adapter (Novada wins if multiple are set).
10
+ * Returns null if no proxy provider is configured.
11
+ */
12
+ export declare function resolveAdapter(env: NodeJS.ProcessEnv): ResolvedAdapter | null;
13
+ /**
14
+ * List all registered adapters (for --help and status output).
15
+ */
16
+ export declare function listAdapters(): ProxyAdapter[];
@@ -0,0 +1,42 @@
1
+ import { NovadaAdapter } from "./novada.js";
2
+ import { GenericHttpAdapter } from "./generic.js";
3
+ import { BrightDataAdapter } from "./brightdata.js";
4
+ import { SmartproxyAdapter } from "./smartproxy.js";
5
+ import { OxylabsAdapter } from "./oxylabs.js";
6
+ /**
7
+ * Registered proxy adapters in priority order.
8
+ *
9
+ * Resolution: the first adapter whose loadCredentials() returns non-null wins.
10
+ * Novada is always first — it's our default and priority provider.
11
+ *
12
+ * To add a provider:
13
+ * 1. Create src/adapters/<provider>.ts implementing ProxyAdapter
14
+ * 2. Import it here and add it to the array below
15
+ * 3. Nothing else changes
16
+ */
17
+ const ADAPTERS = [
18
+ NovadaAdapter, // Always first — default, deepest integration
19
+ BrightDataAdapter, // BRIGHTDATA_USER + BRIGHTDATA_PASS
20
+ SmartproxyAdapter, // SMARTPROXY_USER + SMARTPROXY_PASS
21
+ OxylabsAdapter, // OXYLABS_USER + OXYLABS_PASS
22
+ GenericHttpAdapter, // Always last — PROXY_URL fallback (no auto-targeting)
23
+ ];
24
+ /**
25
+ * Resolve which proxy adapter to use based on available environment variables.
26
+ * Returns the first configured adapter (Novada wins if multiple are set).
27
+ * Returns null if no proxy provider is configured.
28
+ */
29
+ export function resolveAdapter(env) {
30
+ for (const adapter of ADAPTERS) {
31
+ const credentials = adapter.loadCredentials(env);
32
+ if (credentials)
33
+ return { adapter, credentials };
34
+ }
35
+ return null;
36
+ }
37
+ /**
38
+ * List all registered adapters (for --help and status output).
39
+ */
40
+ export function listAdapters() {
41
+ return [...ADAPTERS];
42
+ }
@@ -0,0 +1,23 @@
1
+ import type { ProxyAdapter } from "./types.js";
2
+ /**
3
+ * Novada residential proxy adapter.
4
+ *
5
+ * Auth format: USERNAME-zone-ZONE[-region-XX][-city-CITY][-session-ID-sessTime-N]:PASS@HOST:PORT
6
+ *
7
+ * Rules enforced here (not in tool validators — they handle user-facing input):
8
+ * - Hyphen `-` is Novada's segment delimiter → never appears in country/city/session_id
9
+ * (enforced upstream in validateFetchParams / validateSessionParams)
10
+ * - session_id → `-session-ID-sessTime-N` suffix (sessTime required for sticky IP)
11
+ * - country → `-region-XX` suffix (lowercased)
12
+ * - city → `-city-CITY` suffix (lowercased)
13
+ *
14
+ * Env vars:
15
+ * NOVADA_PROXY_USER — required
16
+ * NOVADA_PROXY_PASS — required
17
+ * NOVADA_PROXY_HOST — optional; defaults to super.novada.pro (shared load balancer)
18
+ * Set to your account-specific host for reliable sticky sessions.
19
+ * NOVADA_PROXY_PORT — optional; defaults to 7777
20
+ * NOVADA_PROXY_ZONE — optional; defaults to "res" (residential).
21
+ * Other zones: "isp" (rotating ISP), "dcp" (rotating datacenter)
22
+ */
23
+ export declare const NovadaAdapter: ProxyAdapter;
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Novada residential proxy adapter.
3
+ *
4
+ * Auth format: USERNAME-zone-ZONE[-region-XX][-city-CITY][-session-ID-sessTime-N]:PASS@HOST:PORT
5
+ *
6
+ * Rules enforced here (not in tool validators — they handle user-facing input):
7
+ * - Hyphen `-` is Novada's segment delimiter → never appears in country/city/session_id
8
+ * (enforced upstream in validateFetchParams / validateSessionParams)
9
+ * - session_id → `-session-ID-sessTime-N` suffix (sessTime required for sticky IP)
10
+ * - country → `-region-XX` suffix (lowercased)
11
+ * - city → `-city-CITY` suffix (lowercased)
12
+ *
13
+ * Env vars:
14
+ * NOVADA_PROXY_USER — required
15
+ * NOVADA_PROXY_PASS — required
16
+ * NOVADA_PROXY_HOST — optional; defaults to super.novada.pro (shared load balancer)
17
+ * Set to your account-specific host for reliable sticky sessions.
18
+ * NOVADA_PROXY_PORT — optional; defaults to 7777
19
+ * NOVADA_PROXY_ZONE — optional; defaults to "res" (residential).
20
+ * Other zones: "isp" (rotating ISP), "dcp" (rotating datacenter)
21
+ */
22
+ export const NovadaAdapter = {
23
+ name: "novada",
24
+ displayName: "Novada",
25
+ lastVerified: "2026-04-09",
26
+ capabilities: { country: true, city: true, sticky: true },
27
+ credentialDocs: "novada.com → Dashboard → Residential Proxies → Endpoint Generator",
28
+ sensitiveFields: ["pass"],
29
+ loadCredentials(env) {
30
+ const user = env.NOVADA_PROXY_USER;
31
+ const pass = env.NOVADA_PROXY_PASS;
32
+ if (!user || !pass)
33
+ return null;
34
+ const rawPort = Number(env.NOVADA_PROXY_PORT);
35
+ const port = Number.isInteger(rawPort) && rawPort > 0 && rawPort < 65536
36
+ ? rawPort : 7777;
37
+ const zone = env.NOVADA_PROXY_ZONE || "res";
38
+ return {
39
+ user,
40
+ pass,
41
+ host: env.NOVADA_PROXY_HOST || "super.novada.pro",
42
+ port: String(port),
43
+ zone,
44
+ };
45
+ },
46
+ buildProxyUrl(credentials, params) {
47
+ const zone = credentials.zone || "res";
48
+ let username = `${credentials.user}-zone-${zone}`;
49
+ if (params.country)
50
+ username += `-region-${params.country.toLowerCase()}`;
51
+ if (params.city)
52
+ username += `-city-${params.city.toLowerCase()}`;
53
+ if (params.session_id) {
54
+ // sessTime is required for sticky IP on all Novada zones.
55
+ // Default: 5 min for res/dcp, 120 min for isp. Max: 120 min res, 360 min isp, 30 min dcp.
56
+ const defaultSessTime = zone === "isp" ? 120 : 5;
57
+ username += `-session-${params.session_id}-sessTime-${defaultSessTime}`;
58
+ }
59
+ return `http://${encodeURIComponent(username)}:${encodeURIComponent(credentials.pass)}@${credentials.host}:${credentials.port}`;
60
+ },
61
+ };
@@ -0,0 +1,22 @@
1
+ import type { ProxyAdapter } from "./types.js";
2
+ /**
3
+ * Oxylabs residential proxy adapter.
4
+ *
5
+ * Oxylabs uses a different format: country and city are encoded as
6
+ * `-cc-XX` and `-city-CITY` suffixes. Session stickiness uses `-sessid-ID`.
7
+ *
8
+ * Auth format:
9
+ * USER[-cc-XX][-city-CITY][-sessid-ID]:PASS@pr.oxylabs.io:PORT
10
+ *
11
+ * Default port: 7777.
12
+ *
13
+ * Get credentials:
14
+ * oxylabs.io → Dashboard → Residential Proxies → Access Details
15
+ *
16
+ * Env vars:
17
+ * OXYLABS_USER — required
18
+ * OXYLABS_PASS — required
19
+ * OXYLABS_HOST — optional (default: pr.oxylabs.io)
20
+ * OXYLABS_PORT — optional (default: 7777)
21
+ */
22
+ export declare const OxylabsAdapter: ProxyAdapter;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Oxylabs residential proxy adapter.
3
+ *
4
+ * Oxylabs uses a different format: country and city are encoded as
5
+ * `-cc-XX` and `-city-CITY` suffixes. Session stickiness uses `-sessid-ID`.
6
+ *
7
+ * Auth format:
8
+ * USER[-cc-XX][-city-CITY][-sessid-ID]:PASS@pr.oxylabs.io:PORT
9
+ *
10
+ * Default port: 7777.
11
+ *
12
+ * Get credentials:
13
+ * oxylabs.io → Dashboard → Residential Proxies → Access Details
14
+ *
15
+ * Env vars:
16
+ * OXYLABS_USER — required
17
+ * OXYLABS_PASS — required
18
+ * OXYLABS_HOST — optional (default: pr.oxylabs.io)
19
+ * OXYLABS_PORT — optional (default: 7777)
20
+ */
21
+ export const OxylabsAdapter = {
22
+ name: "oxylabs",
23
+ displayName: "Oxylabs",
24
+ lastVerified: "2026-04-09",
25
+ capabilities: { country: true, city: true, sticky: true },
26
+ credentialDocs: "oxylabs.io → Dashboard → Residential Proxies → Access Details",
27
+ sensitiveFields: ["pass"],
28
+ loadCredentials(env) {
29
+ const user = env.OXYLABS_USER;
30
+ const pass = env.OXYLABS_PASS;
31
+ if (!user || !pass)
32
+ return null;
33
+ const rawPort = Number(env.OXYLABS_PORT);
34
+ const port = Number.isInteger(rawPort) && rawPort > 0 && rawPort < 65536
35
+ ? rawPort : 7777;
36
+ return {
37
+ user,
38
+ pass,
39
+ host: env.OXYLABS_HOST || "pr.oxylabs.io",
40
+ port: String(port),
41
+ };
42
+ },
43
+ buildProxyUrl(credentials, params) {
44
+ // Oxylabs uses -cc-XX for country, -city-CITY for city, -sessid-ID for sticky
45
+ let username = credentials.user;
46
+ if (params.country)
47
+ username += `-cc-${params.country.toUpperCase()}`;
48
+ if (params.city)
49
+ username += `-city-${params.city.toLowerCase()}`;
50
+ if (params.session_id)
51
+ username += `-sessid-${params.session_id}`;
52
+ return `http://${encodeURIComponent(username)}:${encodeURIComponent(credentials.pass)}@${credentials.host}:${credentials.port}`;
53
+ },
54
+ };
@@ -0,0 +1,22 @@
1
+ import type { ProxyAdapter } from "./types.js";
2
+ /**
3
+ * Smartproxy residential proxy adapter.
4
+ *
5
+ * Smartproxy uses a username-suffix format similar to BrightData but with
6
+ * different segment names. Country is encoded as `-country-XX` (uppercase).
7
+ *
8
+ * Auth format:
9
+ * USER[-country-XX][-city-CITY][-session-SESSIONID]:PASS@gate.smartproxy.com:PORT
10
+ *
11
+ * Default port: 10001 (rotating). Use 10000 for US-only pool.
12
+ *
13
+ * Get credentials:
14
+ * smartproxy.com → Dashboard → Residential → Endpoint Generator
15
+ *
16
+ * Env vars:
17
+ * SMARTPROXY_USER — required
18
+ * SMARTPROXY_PASS — required
19
+ * SMARTPROXY_HOST — optional (default: gate.smartproxy.com)
20
+ * SMARTPROXY_PORT — optional (default: 10001)
21
+ */
22
+ export declare const SmartproxyAdapter: ProxyAdapter;
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Smartproxy residential proxy adapter.
3
+ *
4
+ * Smartproxy uses a username-suffix format similar to BrightData but with
5
+ * different segment names. Country is encoded as `-country-XX` (uppercase).
6
+ *
7
+ * Auth format:
8
+ * USER[-country-XX][-city-CITY][-session-SESSIONID]:PASS@gate.smartproxy.com:PORT
9
+ *
10
+ * Default port: 10001 (rotating). Use 10000 for US-only pool.
11
+ *
12
+ * Get credentials:
13
+ * smartproxy.com → Dashboard → Residential → Endpoint Generator
14
+ *
15
+ * Env vars:
16
+ * SMARTPROXY_USER — required
17
+ * SMARTPROXY_PASS — required
18
+ * SMARTPROXY_HOST — optional (default: gate.smartproxy.com)
19
+ * SMARTPROXY_PORT — optional (default: 10001)
20
+ */
21
+ export const SmartproxyAdapter = {
22
+ name: "smartproxy",
23
+ displayName: "Smartproxy",
24
+ lastVerified: "2026-04-09",
25
+ capabilities: { country: true, city: true, sticky: true },
26
+ credentialDocs: "smartproxy.com → Dashboard → Residential → Endpoint Generator",
27
+ sensitiveFields: ["pass"],
28
+ loadCredentials(env) {
29
+ const user = env.SMARTPROXY_USER;
30
+ const pass = env.SMARTPROXY_PASS;
31
+ if (!user || !pass)
32
+ return null;
33
+ const rawPort = Number(env.SMARTPROXY_PORT);
34
+ const port = Number.isInteger(rawPort) && rawPort > 0 && rawPort < 65536
35
+ ? rawPort : 10001;
36
+ return {
37
+ user,
38
+ pass,
39
+ host: env.SMARTPROXY_HOST || "gate.smartproxy.com",
40
+ port: String(port),
41
+ };
42
+ },
43
+ buildProxyUrl(credentials, params) {
44
+ // Smartproxy appends targeting params to the username with `-` delimiter
45
+ let username = credentials.user;
46
+ if (params.country)
47
+ username += `-country-${params.country.toUpperCase()}`;
48
+ if (params.city)
49
+ username += `-city-${params.city.toLowerCase()}`;
50
+ if (params.session_id)
51
+ username += `-session-${params.session_id}`;
52
+ return `http://${encodeURIComponent(username)}:${encodeURIComponent(credentials.pass)}@${credentials.host}:${credentials.port}`;
53
+ },
54
+ };
@@ -0,0 +1,58 @@
1
+ /**
2
+ * ProxyAdapter — the contract every proxy provider must satisfy.
3
+ *
4
+ * Adding a new provider = implement this interface in a new file,
5
+ * register it in index.ts. Nothing else changes.
6
+ */
7
+ export interface ProxyCredentials {
8
+ /** Provider-specific key/value pairs (e.g. user, pass, host, port). */
9
+ [key: string]: string;
10
+ }
11
+ export interface AdapterCapabilities {
12
+ /** Supports 2-letter country-level geo-targeting. */
13
+ country: boolean;
14
+ /** Supports city-level geo-targeting. */
15
+ city: boolean;
16
+ /** Supports sticky sessions (same IP across requests with same session_id). */
17
+ sticky: boolean;
18
+ }
19
+ export interface ProxyRequestParams {
20
+ country?: string;
21
+ city?: string;
22
+ session_id?: string;
23
+ }
24
+ export interface ProxyAdapter {
25
+ /** Internal identifier. Snake-case, lowercase. e.g. "novada", "brightdata" */
26
+ readonly name: string;
27
+ /** Human-readable name shown in error messages and status output. */
28
+ readonly displayName: string;
29
+ /**
30
+ * ISO date when this adapter was last tested against live credentials.
31
+ * Shown to users so they know how fresh the integration is.
32
+ */
33
+ readonly lastVerified: string;
34
+ /** Which targeting features this provider supports. */
35
+ readonly capabilities: AdapterCapabilities;
36
+ /** URL or description of where users get credentials for this provider. */
37
+ readonly credentialDocs: string;
38
+ /**
39
+ * Keys in ProxyCredentials that contain secrets.
40
+ * Used to redact sensitive values from error messages before surfacing to agents.
41
+ */
42
+ readonly sensitiveFields: ReadonlyArray<string>;
43
+ /**
44
+ * Reads environment variables and returns a credentials object,
45
+ * or null if this provider is not configured (required env vars missing).
46
+ *
47
+ * Implementations should not throw — return null for missing/incomplete config.
48
+ */
49
+ loadCredentials(env: NodeJS.ProcessEnv): ProxyCredentials | null;
50
+ /**
51
+ * Builds the full HTTP proxy URL for a given request.
52
+ * Called once per request (or per retry batch).
53
+ *
54
+ * Must return a URL in the form:
55
+ * http://[encoded-user]:[encoded-pass]@[host]:[port]
56
+ */
57
+ buildProxyUrl(credentials: ProxyCredentials, params: ProxyRequestParams): string;
58
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * ProxyAdapter — the contract every proxy provider must satisfy.
3
+ *
4
+ * Adding a new provider = implement this interface in a new file,
5
+ * register it in index.ts. Nothing else changes.
6
+ */
7
+ export {};
@@ -0,0 +1,4 @@
1
+ export declare const VERSION = "1.8.2";
2
+ export declare const NPM_PACKAGE = "novada-proxy-mcp";
3
+ export declare const NOVADA_SEARCH_URL = "https://scraperapi.novada.com/search";
4
+ export declare const DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
@@ -0,0 +1,7 @@
1
+ export const VERSION = "1.8.2";
2
+ // npm package name — used in CLI help and error messages
3
+ export const NPM_PACKAGE = "novada-proxy-mcp";
4
+ // Novada Scraper API — web search
5
+ export const NOVADA_SEARCH_URL = "https://scraperapi.novada.com/search";
6
+ // Default User-Agent for all HTTP requests through the proxy
7
+ export const DEFAULT_USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
@@ -0,0 +1,2 @@
1
+ import type { ProxyErrorResponse } from "./types.js";
2
+ export declare function classifyError(err: unknown): ProxyErrorResponse;
@@ -0,0 +1,58 @@
1
+ import axios from "axios";
2
+ export function classifyError(err) {
3
+ const ax = axios.isAxiosError(err);
4
+ const status = ax ? err.response?.status : undefined;
5
+ const msg = err instanceof Error ? err.message : String(err);
6
+ if (ax && status === 429)
7
+ return { ok: false, error: {
8
+ code: "RATE_LIMITED", message: "HTTP 429 — rate limited",
9
+ recoverable: true,
10
+ agent_instruction: "Wait 5 seconds and retry. Consider reducing request frequency.",
11
+ retry_after_seconds: 5
12
+ } };
13
+ if (ax && status && status >= 400 && status < 500)
14
+ return { ok: false, error: {
15
+ code: "BOT_DETECTION_SUSPECTED",
16
+ message: `HTTP ${status} — request blocked by target`,
17
+ recoverable: true,
18
+ agent_instruction: "Try novada_proxy_render (real browser). Or retry with a different country/session_id. If render also returns 4xx, the page may genuinely not exist — stop retrying."
19
+ } };
20
+ if (msg.includes("timeout") || msg.includes("ECONNABORTED"))
21
+ return { ok: false, error: {
22
+ code: "TIMEOUT", message: "Request timed out",
23
+ recoverable: true,
24
+ agent_instruction: "Increase the timeout parameter or retry. For JS-heavy pages, use novada_proxy_render.",
25
+ retry_after_seconds: 2
26
+ } };
27
+ if (msg.includes("ENOTFOUND") || msg.includes("EAI_AGAIN") || msg.includes("getaddrinfo"))
28
+ return { ok: false, error: {
29
+ code: "NETWORK_ERROR", message: "DNS resolution failed — hostname not found",
30
+ recoverable: false,
31
+ agent_instruction: "The hostname could not be resolved. Verify the URL is correct and the domain exists.",
32
+ } };
33
+ if (msg.includes("TLS") || msg.includes("SSL") || msg.includes("socket disconnect") || msg.includes("secure TLS") || msg.includes("certificate") || msg.includes("issuer cert"))
34
+ return { ok: false, error: {
35
+ code: "TLS_ERROR", message: "TLS/SSL connection failed",
36
+ recoverable: true,
37
+ agent_instruction: "The target rejected the proxy connection. Retry with a different country parameter or use novada_proxy_render. If this is an unrecognized domain, verify it exists — proxy-side DNS failures may appear as TLS errors.",
38
+ retry_after_seconds: 2
39
+ } };
40
+ if (msg.includes("No proxy provider") || msg.includes("not configured"))
41
+ return { ok: false, error: {
42
+ code: "PROVIDER_NOT_CONFIGURED", message: msg,
43
+ recoverable: false,
44
+ agent_instruction: "Set NOVADA_PROXY_USER and NOVADA_PROXY_PASS env vars and restart the MCP server."
45
+ } };
46
+ const INPUT_ERROR_PHRASES = ["is required", "must be", "must start with", "must contain", "letters, numbers", "max 64", "max 50", "between 1 and"];
47
+ if (INPUT_ERROR_PHRASES.some(p => msg.includes(p)))
48
+ return { ok: false, error: {
49
+ code: "INVALID_INPUT", message: msg,
50
+ recoverable: false,
51
+ agent_instruction: "Fix the input parameters and retry. Check the tool's inputSchema for valid values."
52
+ } };
53
+ return { ok: false, error: {
54
+ code: "UNKNOWN_ERROR", message: msg,
55
+ recoverable: true,
56
+ agent_instruction: "Retry the request. Check novada_proxy_status for network health."
57
+ } };
58
+ }
@@ -0,0 +1,28 @@
1
+ export type { ProxyErrorCode, QuotaMeta, ProxySuccessResponse, ProxyErrorResponse, ProxyResponse, } from "./types.js";
2
+ export { VERSION, NPM_PACKAGE } from "./config.js";
3
+ export { classifyError } from "./errors.js";
4
+ export type { ProxyAdapter, ProxyCredentials, ProxyRequestParams, AdapterCapabilities, } from "./adapters/index.js";
5
+ export { resolveAdapter, listAdapters } from "./adapters/index.js";
6
+ export type { ResolvedAdapter } from "./adapters/index.js";
7
+ export { novadaProxyFetch, validateFetchParams, getCacheTtl, makeCacheKey, clearResponseCache, } from "./tools/fetch.js";
8
+ export type { FetchParams } from "./tools/fetch.js";
9
+ export { novadaProxyBatchFetch, validateBatchFetchParams } from "./tools/batch.js";
10
+ export type { BatchFetchParams, BatchFetchResult } from "./tools/batch.js";
11
+ export { novadaProxySearch, validateSearchParams } from "./tools/search.js";
12
+ export type { SearchParams } from "./tools/search.js";
13
+ export { novadaProxySession, validateSessionParams } from "./tools/session.js";
14
+ export type { SessionParams } from "./tools/session.js";
15
+ export { novadaProxyStatus } from "./tools/status.js";
16
+ export { novadaProxyRender, validateRenderParams } from "./tools/render.js";
17
+ export type { RenderParams } from "./tools/render.js";
18
+ export { novadaProxyExtract, validateExtractParams, extractField, deepFind, shouldEscalateToRender, } from "./tools/extract.js";
19
+ export type { ExtractParams } from "./tools/extract.js";
20
+ export { novadaProxyMap, validateMapParams } from "./tools/map.js";
21
+ export type { MapParams } from "./tools/map.js";
22
+ export { novadaProxyCrawl, validateCrawlParams } from "./tools/crawl.js";
23
+ export type { CrawlParams, CrawlPageResult } from "./tools/crawl.js";
24
+ export { novadaProxyResearch, validateResearchParams } from "./tools/research.js";
25
+ export type { ResearchParams } from "./tools/research.js";
26
+ export { unicodeSafeTruncate, decodeHtmlEntities, htmlToMarkdown, htmlToText, stripNoiseElements, countHtmlTags, contentDensity, } from "./utils.js";
27
+ export { SAFE_COUNTRY, SAFE_CITY, SAFE_SESSION_ID, QUOTA_NOTE } from "./validation.js";
28
+ export { redactCredentials } from "./redact.js";