Scripting

Run JavaScript on matching requests and responses. Edit headers, query and JSON bodies, answer locally, keep state and call other services.

Mac iPhone and iPad Android

When a fixed rule is not enough, write a script. A script rule has a host and a path, and runs JavaScript in onRequest, onResponse or both for every matching exchange. Scripts run inside the WireLens app.

Add a script#

  • Mac: open Tools ▸ Scripting and click Add Script. Give it a Name, a Host and a Path, and pick Run on: Request, Response or both. Each phase gets its own editor, onRequest and onResponse.
  • iPhone and iPad: open Tools ▸ Scripting and add a script with a name, a Host pattern and a Path pattern, then turn on Run on requests, Run on responses or both.
  • Android: long-press a host or a request and choose Script…, or open Tools ▸ Scripting and tap New script. Pick a template under Start from, then the Match and Run on: Requests, Responses or Both.

A rule with a blank pattern matches nothing. Each script has its own switch: turn it off to park the script without deleting it.

On the Mac and iPhone, only the first enabled script that matches a request runs. On the Mac, Move Up and Move Down in a rule's menu change the order, as does move_rule over MCP. On Android, every matching script runs, top to bottom.

A script that throws leaves the exchange as it was, and so does one that misses its deadline, unless a Mac rule's On timeout says Abort. The error shows in the run log: Runs on the Mac, Recent Runs on iPhone and iPad, Settings ▸ Debug Log on Android.

For HTTPS, the host has to be decrypted before a script can see it. See SSL Proxying.

The two hooks#

This is the API on the Mac, iPhone and iPad:

js
// Before the request leaves for the server.
function onRequest(req) {
  req.headers.upsert({ key: 'X-WireLens', value: 'scripted' });
}

// After the server answers, before the app sees it.
function onResponse(req, res) {
  var data = res.body.json();
  if (data && typeof data === 'object') {
    data.ok = true;
    res.body.text = data;
  }
}

Either hook can be async. WireLens waits for it, including $http calls, up to the rule's deadline.

Reading and changing an exchange#

In a scriptWhat it does
req.methodThe method. Assign to change it: req.method = 'PATCH'.
req.urlThe full URL. Assign a new URL to send the request somewhere else.
req.originalUrlThe URL before any rule changed it.
req.headers.get(name), .all()Read headers.
req.headers.upsert({ key, value })Add a header, or replace it if it is already there.
req.headers.remove(name)Remove a header.
req.query.get(name), .all()Read query parameters.
req.query.set(name, value), .remove(name)Set or remove a query parameter.
req.body.textThe body as text. Assign a string, or an object to write JSON.
req.body.json()The body parsed as JSON.
req.body.multipartThe parts of a multipart form body, by name or index.
req.bodyBytesThe body as a Uint8Array, for binary bodies.
req.comment, req.colorThe flow's comment and colour in the traffic list.
res.statusThe status. Assign to change it: res.status = 201.
res.headers, res.body, res.bodyBytesThe response, the same way as the request's.

Built-in functions#

FunctionWhat it does
mock(status, headers, body)Answers locally, without contacting the server. An object body is sent as JSON.
abort()Closes the connection without contacting the server.
comment(text)Adds a comment to the flow, which shows in the traffic list.
console.log, console.warn, console.errorWrite to the run log.
readFile(path), writeToFile(path, data)Read and write files of up to 1 MB in the script folder.

On the Mac the script folder is ~/Library/Application Support/WireLens/script-sandbox.

js
function onRequest(req) {
  mock(503, { 'Content-Type': 'application/json' }, {
    error: 'service_unavailable',
    message: 'Mocked by WireLens'
  });
}

State, environment and HTTP#

  • $state is a small store shared by every run in this app session: $state.get(key), $state.set(key, value), $state.remove(key) and $state.all(). Values are strings. Use it to count requests, carry a token from one response into the next request, or fail every third call.
  • $env holds values you would rather keep out of the script, such as API keys, with the same four functions. Add them under Environment on the Mac, or Environment Variables on iPhone and iPad. Every $env value is hidden from the run log and from error messages.
  • $http(options) calls another service and returns a promise. Pass url, and optionally method, headers, body and timeout. The result has status, statusText, headers, body, url and durationMs.
js
async function onRequest(req) {
  var count = Number($state.get('checkout') || 0) + 1;
  $state.set('checkout', String(count));
  if (count % 3 === 0) {
    mock(502, { 'Content-Type': 'application/json' }, { error: 'flaky upstream' });
    return;
  }
  var token = await $http({ url: 'https://auth.example.com/token', method: 'POST' });
  req.headers.upsert({ key: 'Authorization', value: 'Bearer ' + token.body });
}

On the Mac#

Traffic
Rules
Setup
Scripting
Change matching requests and responses with JavaScript.
Rules
1 rule · 1 armedCollapse AllDisable All
Free shippingapi.acme.dev /v2/cart*onResponse
Name
Free shipping
Host
api.acme.dev
Path
/v2/cart*
Run on
RequestResponse
Deadline
8seconds. Blank uses 8.
On timeout
Execute unchanged
Conditionsany request
Highlight
Folder
Cart
Test this rule
Source
Insert SnippetCompletionsOpen in Cursor
onResponse
Find
ReplaceReplaceReplace All
1
2
3
4
5
6
7
function onResponse(req, res) {
var cart = res.body.json();
if (cart && cart.items) {
cart.freeShipping = true;
res.body.text = JSON.stringify(cart);
}
}
167 bytesClear
Fired 61× · 2s ago·https://api.acme.dev/v2/cart/items
Add Script
The first matching script runs. Matching traffic waits until it finishes.
Runs
AllWarningsErrorsClear
https://api.acme.dev/v2/cart/items8 sec
Edited the exchange
https://api.acme.dev/v2/cart21 sec
Edited the exchange
https://api.acme.dev/v2/cart/items1 min, 4 sec
console.warn
https://api.acme.dev/v2/cart2 min, 30 sec
TypeError: cart.items is undefined
https://api.acme.dev/v2/cart/items3 min, 12 sec
Environment
API_TOKENacme_test_4f9c1e2b
FREE_SHIPPING_MIN50
Add Variable
Scripts read these through $env.get("name").
Folders
Cart
Folder nameTop levelAdd Folder
Custom Addons
No custom addons yet.
Name
helpers
Source
Find
ReplaceReplaceReplace All
1
module.exports = { hello: function () { return 'hi'; } };
Unchanged
Register Addon
Load an addon with require("name").
npm Packages
Package name, e.g. uuidInstall
Load a package with require("name"). Native modules are not supported.
Tools ▸ Scripting

Each rule card has:

  • Deadline: how long one run may take, in seconds. Blank uses 8.
  • On timeout: Execute unchanged or Abort when a run misses the deadline.
  • Conditions, folders, and Test this rule for sample URLs.
  • Source: Insert Snippet, Completions (a cheatsheet of req., res., $utils. and $addons.), and Open in VS Code or Open in Cursor when either is installed.

Insert Snippet has five working examples: Add request header, Rewrite a JSON field, Mock a 503 JSON body, Strip Cookie and Authorization and Drop with abort().

The Runs log lists every run with its URL, time, and anything it logged or failed on. Filter it to All, Warnings or Errors.

Helpers and packages are Mac only:

  • $utils (also $_) has Base64, hex, MD5, SHA-1, SHA-256, SHA-512, UUID, JSON minify and beautify, and JWT decode. Completions in a rule's Source row lists them. WireLens bundles $_ and $utils only. lodash and crypto-js are not included.
  • require loads built-in addons such as require("uuid"). A // @require uuid line at the top of a script does the same.
  • Custom Addons are .js files in ~/Library/Application Support/WireLens/addons/custom/. Add one in the pane or drop a file there, then load it with require("name") or require("@custom/name"). A blank name or * is refused.
  • Shared libraries go in ~/Library/Application Support/WireLens/addons/libs/. require("@libs/foo") loads addons/libs/foo.js.
  • npm Packages: Install runs npm install <name> into ~/Library/Application Support/WireLens/addons/npm/. require("pkg") then loads the package's main file or index.js. Packages with native modules do not load.

Scripts from Proxyman#

Proxyman scripts run as they are. A handler written as onRequest(context, url, request) or onResponse(context, url, request, response) receives Proxyman's objects: request.headers, request.queries, a parsed JSON body, response.statusCode, sharedState. Return the object, as in Proxyman, and WireLens applies what changed. File ▸ Import ▸ Proxyman Rules… brings your scripts across with their hosts, paths and Mock API setting.

On iPhone and iPad#

Tools ▸ Scripting has your scripts, Environment Variables and Recent Runs. Each script has a Deadline; a run that misses it continues unmodified. Test Run runs a script against a captured exchange and shows what it would change, what it logged and any error.

Scripts run in the WireLens app, not in the capture tunnel. Keep the app running while scripted traffic passes through. Otherwise each matching exchange waits for its deadline and then continues unmodified.

On Android#

Android scripts use the same shape as Proxyman's, which the Mac and iPhone also run. Each hook gets plain objects and returns the one to send on:

js
function onRequest(context, url, request) {
  request.headers["X-Debug"] = "1";
  return request;
}

function onResponse(context, url, request, response) {
  response.statusCode = 200;
  response.headers["Content-Type"] = "application/json";
  response.body = JSON.stringify({ ok: true, items: [] });
  return response;
}
  • request has method, url, headers and body. Changing url sends the request somewhere else.
  • response has statusCode, headers and body. An object assigned to body is sent as JSON.
  • headers is a plain object. Repeated headers arrive joined with , .
  • abort() cancels the exchange. console.log writes to Settings ▸ Debug Log.

The templates under Start from are Add a header, Mock JSON, Change the status, Block, Log the body and Blank. Each run has 8 seconds. $state, $env, $http and mock() are not available on Android.

Free and Pro#

The free plan runs one script on every platform. Pro removes the limit.

Command line and MCP#

The command line and the MCP server run on the Mac. wirelens ships inside the app and is not on your PATH until you link it once.

bash
wirelens rules script --host api.example.com --path '/v2/*' \
  --name 'Debug header' \
  --on-request-file ./debug-header.js

--on-request and --on-response take the source inline instead of from a file.

The MCP tool add_script_rule takes a host and path plus on_request and on_response source. Give it either the whole function or only the statements inside it. Three more tools help an agent get the script right before it runs on live traffic. script_reference returns this API. generate_script builds a script from a list of edits and can replay a captured response as a mock. test_script runs a script against a captured request and reports what it would change, what it logged and any error.

If a fixed answer or a header change is all you need, Map Local and Rewrite are easier to review.

Something here is unclear, or wrong for your setup? Contact support or write to support@wirelens.app.