Client SDK
Discovery
Discover Surf-enabled websites
Client SDK — Discovery#
The @surfjs/client package provides a full-featured SDK for interacting with any Surf-enabled website.
import { SurfClient } from '@surfjs/client' // Discover a Surf-enabled site (fetches manifest automatically)const client = await SurfClient.discover('https://shop.example.com') // Or with optionsconst client = await SurfClient.discover('https://shop.example.com', { auth: 'sk-my-token', discoverTimeout: 5000,}) // Inspect the manifestconsole.log(client.manifest.name) // 'My Store'console.log(client.manifest.version) // '1.0.0'console.log(client.manifest.checksum) // 'a1b2c3...' // List all commandsconst cmds = client.commands// => { search: { description: '...', params: {...} }, 'cart.add': {...} } // Get a specific commandconst cmd = client.command('search')// => { description: '...', params: {...}, hints: {...} } // Create from existing manifest (skip discovery)const client2 = SurfClient.fromManifest(manifest, { baseUrl: 'https://...' })SurfClientOptions
| Option | Type | Default | Description |
|--------|------|---------|-------------|
| auth | string? | — | Bearer token sent with every request |
| discoverTimeout | number? | 5000 | Timeout (ms) for the manifest fetch during discovery |
| retry | RetryConfig? | — | Retry configuration for transient failures |
| cache | CacheConfig? | — | Cache configuration for idempotent commands |
| basePath | string? | /surf/execute | Custom execute path (for @surfjs/next or custom routers) |
| fetch | typeof fetch? | globalThis.fetch | Custom fetch implementation |
discoverManifest — Standalone Utility
Fetch and parse a manifest from a URL without creating a full SurfClient. Useful for inspection, validation, or building custom tooling:
import { discoverManifest } from '@surfjs/client' const manifest = await discoverManifest('https://shop.example.com')// Fetches /.well-known/surf.json and parses it console.log(manifest.name) // 'My Store'console.log(manifest.commands) // { search: {...}, 'cart.add': {...} }console.log(manifest.surf) // '0.2' (protocol version)discoverManifest probes /.well-known/surf.json and validates the response. It throws SurfClientError with code INVALID_MANIFEST if the URL is not Surf-enabled or the manifest is malformed.
import { discoverManifest, SurfClientError } from '@surfjs/client' try { const manifest = await discoverManifest('https://example.com')} catch (err) { if (err instanceof SurfClientError && err.code === 'INVALID_MANIFEST') { console.log('Not a Surf-enabled site') }}Transports#
SurfClient uses a transport layer internally. By default, SurfClient.discover uses HttpTransport. You can construct a client with a different transport explicitly:
HttpTransport
Standard HTTP transport — one request per execute() call. Used internally by SurfClient.discover():
import { HttpTransport } from '@surfjs/client' const transport = new HttpTransport({ baseUrl: 'https://shop.example.com', auth: 'sk-token', fetch: globalThis.fetch, basePath: '/surf/execute',})WebSocketTransport
Persistent WebSocket transport — lower latency for frequent calls. Supports auto-reconnect, configurable backoff, and ping keepalive:
import { WebSocketTransport } from '@surfjs/client' const transport = new WebSocketTransport({ reconnect: true, maxReconnectAttempts: Infinity, reconnectDelay: 1000, maxReconnectDelay: 16000, pingInterval: 30000, onStateChange: (state) => console.log('WS state:', state), onDisconnect: () => console.log('Disconnected'), onReconnect: () => console.log('Reconnected'),}) await transport.connect('wss://shop.example.com/surf/ws', 'sk-token') // Execute commands over WebSocketconst result = await transport.execute('search', { query: 'laptop' }) // Check connection stateconsole.log(transport.state) // 'connected' | 'connecting' | 'disconnected' | 'reconnecting' // Close when done (stops reconnection)transport.close()Or use the client's built-in connect():
const client = await SurfClient.discover('https://shop.example.com')const ws = await client.connect()const result = await ws.execute('search', { query: 'laptop' })client.disconnect()WindowTransport
Browser-only transport that uses window.__surf__ — for agent scripts running inside a Surf-enabled web app:
import { WindowTransport } from '@surfjs/client' const transport = new WindowTransport()await transport.connect() // Waits for window.__surf__ or 'surf:ready' event const result = await transport.execute('search', { query: 'laptop' })WindowTransport throws SurfClientError with code NOT_SUPPORTED when used outside a browser context.