Skip to main content

proxy

Manage reverse proxies with traffic logging and frontend instrumentation. The proxy sits between the browser and your dev server so the agent can capture frontend errors, inspect the live DOM, run JavaScript in the page, and inject chaos — without touching application code.

Synopsis

proxy {action: "<action>", ...params}

Actions

ActionDescription
startCreate and start a reverse proxy
stopStop a running proxy
restartRestart a running proxy (preserves config)
statusGet proxy status and statistics
listList all running proxies (accepts global: true for all directories)
execExecute JavaScript in connected browsers
chaosConfigure chaos engineering (network failures, latency)
toastDisplay toast notifications in the browser

start

Create and start a reverse proxy.

proxy {
action: "start",
id: "app",
target_url: "http://localhost:3000"
}

Parameters:

ParameterTypeRequiredDefaultDescription
idstringYes-Unique proxy identifier
target_urlstringYes-Backend server URL
portintegerNohash-basedListen port. Only specify if you need a specific port.
max_log_sizeintegerNo1000Maximum log entries
bind_addressstringNo127.0.0.1Bind address: 127.0.0.1 (localhost only) or 0.0.0.0 (all interfaces for tunnel/mobile testing)
allow_externalbooleanNofalseRequired to bind to a non-localhost address (0.0.0.0 or ::). Acknowledges network exposure risk.
skip_tls_verifybooleanNofalseSkip TLS certificate verification (for self-signed/expired certs in dev).
public_urlstringNo-Public URL for tunnel services (e.g., https://abc123.trycloudflare.com). Used for URL rewriting.
tunnelstringNo-Tunnel provider to launch alongside the proxy: ngrok, cloudflared, tailscale, or custom.
tunnel_argsstring[]No-Additional arguments for the tunnel command.
tunnel_tokenstringNo-Authentication token for the tunnel (e.g., ngrok authtoken).
tunnel_regionstringNo-Tunnel region.
tunnel_commandstringNo-Custom tunnel command (when tunnel: "custom"). Use {{PORT}} as placeholder.

Response:

{
"id": "app",
"status": "running",
"target_url": "http://localhost:3000",
"listen_addr": "127.0.0.1:45849",
"message": "Proxy started"
}

Port Selection

By default, the proxy assigns a stable port based on a hash of the target URL. This ensures:

  • The same target URL always gets the same port (consistent across restarts)
  • Different URLs get different ports (avoids conflicts)
  • Ports are in the range 10000-60000 (avoids well-known and ephemeral ports)

Recommended: Let the proxy choose the port automatically. Only specify port if you need a specific port number.

Request a specific port:

proxy {action: "start", id: "app", target_url: "http://localhost:3000", port: 9000}
{listen_addr: ":9000"}

If the requested port is busy, the proxy finds an available one:

proxy {action: "start", id: "app", target_url: "http://localhost:3000", port: 9000}
{
"listen_addr": ":45123",
"message": "Port 9000 was busy, using 45123"
}

stop

Stop a running proxy.

proxy {action: "stop", id: "app"}

Response:

{
"id": "app",
"message": "Proxy stopped"
}

restart

Restart a running proxy, preserving its configuration (target URL, port, bind address, tunnel).

proxy {action: "restart", id: "app"}

Response:

{
"id": "app",
"success": true,
"message": "Proxy restarted"
}

status

Get proxy status and statistics.

proxy {action: "status", id: "app"}

Response:

{
"id": "app",
"status": "running",
"target_url": "http://localhost:3000",
"listen_addr": ":8080",
"uptime": "15m32s",
"total_requests": 1542,
"log_stats": {
"http_entries": 1000,
"error_entries": 3,
"performance_entries": 45,
"dropped": 542
},
"restart_count": 0,
"last_error": null
}

list

List all running proxies.

proxy {action: "list"}

Response:

{
"proxies": [
{
"id": "frontend",
"status": "running",
"target_url": "http://localhost:3000",
"listen_addr": ":8080"
},
{
"id": "api",
"status": "running",
"target_url": "http://localhost:4000",
"listen_addr": ":8081"
}
],
"active_count": 2
}

exec

Execute JavaScript in connected browser clients.

proxy {action: "exec", id: "app", code: "document.title"}

Parameters:

ParameterTypeRequiredDescription
idstringYesProxy ID
codestringYesJavaScript code to execute

Response:

{
"result": "My App - Dashboard",
"clients": 1
}

Examples

// Get page title
proxy {action: "exec", id: "app", code: "document.title"}

// Get current URL
proxy {action: "exec", id: "app", code: "window.location.href"}

// Access __devtool API
proxy {action: "exec", id: "app", code: "window.__devtool.inspect('#header')"}

// Take screenshot
proxy {action: "exec", id: "app", code: "window.__devtool.screenshot('debug')"}

// Highlight element
proxy {action: "exec", id: "app", code: "window.__devtool.highlight('.button')"}

Timeout

Execution has a 30-second timeout. For async operations:

// Interactive element selection (waits for user click)
proxy {action: "exec", id: "app", code: "window.__devtool.selectElement()"}

// Ask user a question
proxy {action: "exec", id: "app", code: "window.__devtool.ask('OK?', ['Yes', 'No'])"}

chaos

Configure chaos engineering to simulate network failures, latency, and API errors.

proxy {action: "chaos", id: "app", chaos_operation: "preset", chaos_preset: "flaky-api"}

Parameters:

ParameterTypeRequiredDescription
idstringYesProxy ID
chaos_operationstringYesOne of enable, disable, status, set, preset, add_rule, remove_rule, list_rules, stats, clear
chaos_presetstringFor presetBuilt-in preset name (see below)
chaos_rulesarray-Legacy field; for set, pass rules inside chaos_config.rules
chaos_ruleobjectFor add_ruleA single rule to add
chaos_rule_idstringFor remove_ruleID of the rule to remove
chaos_configobjectFor setFull chaos configuration (enabled, rules, global_odds, seed, logging_mode)

Operations

chaos_operationEffect
enable / disableTurn chaos on or off for the proxy
presetApply a built-in preset (chaos_preset)
setReplace the full rule set — pass rules inside chaos_config
add_ruleAdd a single rule (chaos_rule)
remove_ruleRemove a rule by id (chaos_rule_id)
list_rulesList the active rules
statusGet current chaos state
statsGet injection statistics
clearRemove all rules

Built-in Presets

PresetDescription
mobile-3g200-2000ms latency, 2% packet loss
mobile-4g50-500ms latency, 0.5% packet loss
flaky-apiRandom 500s, timeouts, variable latency
race-conditionOut-of-order responses, high variance delays
stale-tab3-hour delays (test token expiry)
slow-connection5KB/s bandwidth throttling
connection-drops10% mid-response disconnects
rate-limited20% 429 errors

Examples

// Apply preset
proxy {action: "chaos", id: "app", chaos_operation: "preset", chaos_preset: "mobile-3g"}

// Custom rules (set replaces the full rule set; rules go inside chaos_config)
proxy {
action: "chaos",
id: "app",
chaos_operation: "set",
chaos_config: {
"enabled": true,
"rules": [
{
"id": "api-latency",
"type": "latency",
"enabled": true,
"url_pattern": "/api/.*",
"min_latency_ms": 500,
"max_latency_ms": 2000,
"probability": 0.3
}
]
}
}

// Check status
proxy {action: "chaos", id: "app", chaos_operation: "status"}
{enabled: true, preset: "flaky-api", stats: {affected_count: 38, errors_injected: 7}}

// Disable chaos
proxy {action: "chaos", id: "app", chaos_operation: "disable"}

// Clear all rules
proxy {action: "chaos", id: "app", chaos_operation: "clear"}

See Chaos Engineering for complete documentation.

toast

Display toast notifications in connected browsers.

proxy {action: "toast", id: "app", toast_message: "Build complete!"}

Parameters:

ParameterTypeRequiredDescription
idstringYesProxy ID
toast_messagestringYesNotification message
toast_typestringNosuccess, error, warning, info (default: info)
toast_titlestringNoOptional title
toast_durationintegerNoDuration in milliseconds (default: 5000)

Examples

// Simple notification
proxy {action: "toast", id: "app", toast_message: "Saved!"}

// Success with title
proxy {action: "toast", id: "app", toast_message: "All tests passed", toast_type: "success", toast_title: "Tests"}

// Error that stays longer
proxy {action: "toast", id: "app", toast_message: "Build failed", toast_type: "error", toast_duration: 10000}

// Warning
proxy {action: "toast", id: "app", toast_message: "Slow response detected", toast_type: "warning"}

Features

What the Proxy Does

  1. Forwards HTTP Traffic - Transparent to the application
  2. Logs Requests/Responses - Captured in circular buffer
  3. Injects JavaScript - Adds window.__devtool to HTML pages
  4. Captures Errors - Frontend JavaScript errors with stack traces
  5. Collects Metrics - Page load timing, paint metrics
  6. Tracks Pages - Groups requests by page session

WebSocket Support

WebSocket connections (e.g., HMR) are proxied transparently:

Browser ←→ Proxy (:8080) ←→ Dev Server (:3000)

└── /__devtool_metrics (reserved for metrics WebSocket)

Auto-Restart

Proxies auto-restart on crash (max 5 restarts per minute):

proxy {action: "status", id: "app"}
{
"restart_count": 2,
"last_error": "bind: address already in use"
}

Error Responses

Proxy Not Found

{
"error": "proxy not found",
"id": "nonexistent"
}

ID Already Exists

{
"error": "proxy already exists",
"id": "app"
}

Invalid Target URL

{
"error": "invalid target URL",
"target_url": "not-a-url"
}

No Connected Clients

{
"error": "no connected clients",
"id": "app"
}

Real-World Patterns

Development Setup

// Start dev server
run {script_name: "dev"}

// Wait for it to be ready
proc {action: "output", process_id: "dev", grep: "ready", tail: 5}

// Start proxy (port auto-assigned based on target URL)
proxy {action: "start", id: "app", target_url: "http://localhost:3000"}

// Check the listen_addr in the response, then browse to that address

Multiple Environments

// Local dev (each gets a unique port based on URL hash)
proxy {action: "start", id: "local", target_url: "http://localhost:3000"}

// Staging
proxy {action: "start", id: "staging", target_url: "https://staging.example.com"}

// Compare traffic
proxylog {proxy_id: "local", types: ["http"], url_pattern: "/api"}
proxylog {proxy_id: "staging", types: ["http"], url_pattern: "/api"}

Debugging Session

// Start proxy
proxy {action: "start", id: "debug", target_url: "http://localhost:3000"}

// User navigates to problem page...

// Check for errors
proxylog {proxy_id: "debug", types: ["error"]}

// Inspect problem element
proxy {action: "exec", id: "debug", code: "window.__devtool.inspect('.broken-component')"}

// Take screenshot
proxy {action: "exec", id: "debug", code: "window.__devtool.screenshot('bug')"}

Mobile Testing with Tunnels

For testing on real mobile devices, you need to expose your proxy publicly. agnt supports this via the bind_address and public_url options, combined with the tunnel tool.

Quick Setup

// 1. Start proxy on all interfaces
proxy {
action: "start",
id: "app",
target_url: "http://localhost:3000",
bind_address: "0.0.0.0"
}

// 2. Start a Cloudflare tunnel pointing to the proxy
tunnel {
action: "start",
id: "app",
provider: "cloudflare",
local_port: 45849,
proxy_id: "app"
}

The tunnel automatically configures the proxy's public_url, enabling proper URL rewriting for HTTPS.

Manual Configuration

If you're using an external tunnel service:

proxy {
action: "start",
id: "app",
target_url: "http://localhost:3000",
bind_address: "0.0.0.0",
public_url: "https://your-tunnel-url.trycloudflare.com"
}

Security Note

By default, proxies bind to 127.0.0.1 (localhost only) for security. Only use 0.0.0.0 when you need external access (tunnels, mobile testing).

See Also