Documentation

Apps & CLI

The com.proxymanager.control XPC service for other applications.

The XPC service needs a Developer-ID signed build installed in /Applications. An ad-hoc build reports the agent as unavailable and keeps the local socket for development.

How it fits together

your app  ──Mach com.proxymanager.control──▶  control agent  ──▶  Proxy Manager
                                              (approved per app)     (router)

The agent is an unprivileged per-user LaunchAgent. It proves who is calling from the code signature, relays the request to the running app, and starts the app if it is not running. It owns no state.

Requirements

  • Proxy Manager Allow external control and Apps and CLI (XPC) are on.
  • A Developer-ID signed app in /Applications — SMAppService.agent refuses an ad-hoc build.
  • Your app must be allowed to look up the Mach service. Sandboxed apps need com.apple.security.temporary-exception.mach-lookup.global-name = com.proxymanager.control. Unsandboxed apps and command-line tools need nothing.

Connect

@objc protocol ControlAgentProtocol {
    func ping(withReply reply: @escaping (Bool) -> Void)
    func perform(_ request: Data, withReply reply: @escaping (Data) -> Void)
}

let connection = NSXPCConnection(machServiceName: "com.proxymanager.control")
connection.remoteObjectInterface = NSXPCInterface(with: ControlAgentProtocol.self)
connection.resume()

let agent = connection.remoteObjectProxyWithErrorHandler { error in
    // service unavailable, or the app could not be launched
} as! ControlAgentProtocol

agent.perform(try JSONEncoder().encode(request)) { data in
    let result = try JSONDecoder().decode(IntegrationResult.self, from: data)
}

The request

{
  "v": 1,
  "id": "8F2C…",
  "action": "targets.add",
  "args": { "pattern": "api.example.com" }
}

The agent adds the caller's identity and a per-launch token; the app decides consent and executes. Responses are the same envelope as the URL and Shortcuts adapters: ok, changed, data, error, auditId, durationMs.

First call

An unknown app triggers the approval sheet in Proxy Manager. The call blocks until the user answers, so give it a generous timeout — 30 seconds rather than the usual two. Approvals are keyed by designated requirement and can be revoked in Settings → Integration. A Read Only grant can list and explain; Control can mutate.

Protocol version

capabilities returns every action with its arguments, the protocol version and which adapters may reach it. A request whose v is newer than the app's is refused with unsupported. Actions are additive; an id is never repurposed.

Development

Without a Developer ID, use the local socket directly. It lives at ~/Library/Application Support/ProxyManager/control.sock, is 0600, accepts the same user only, and requires the per-launch token from control.token in the same folder.

{ "v": 1, "token": "…",
  "identity": { "pid": 4711, "bundleID": "com.acme.tool", "teamID": "ABCDE12345",
                "requirement": "identifier \"com.acme.tool\"",
                "displayName": "Acme Tool", "path": "/Applications/Acme Tool.app" },
  "request": { "v": 1, "id": "…", "action": "status", "args": {} } }

Frames are a 4-byte big-endian length followed by UTF-8 JSON, capped at 256 KB. The app answers with { "v": 1, "result": { … } }.

Rules

  • No administrator prompts. An external call is never a present user.
  • Only routing, targets, per-app rules, status and window navigation — never the tunnel, proxy internals or system settings.
  • Rate limits: 240 reads and 30 mutations per minute per caller, with a tighter limit on routing toggles.
  • Every request is audited with redacted arguments. A write failure degrades the audit but does not block the request.