This file provides guidance to coding agents working in this repository.
FRTMProxy is a native macOS SwiftUI app that acts as a frontend/controller for an embedded
mitmdump (mitmproxy) binary. It lets developers inspect, mock, record/replay, and shape
HTTP/S traffic in real time. The Swift app never speaks HTTP itself — all interception happens
inside mitmproxy, driven by a bundled Python addon.
The Xcode project is generated by XcodeGen from the root project.yml (the source of truth).
FRTMProxy.xcodeproj is still committed, but do not hand-edit it — change project.yml and run
make gen (or xcodegen generate) to regenerate. Dependencies are resolved by Xcode via
XCRemoteSwiftPackageReference: Sparkle (auto-update) and CodeMirror-Swift (the in-app
code/JSON editor).
- Task runner (
Makefile):make gen(regenerate project),make build,make test,make run,make bootstrap(installs xcodegen if missing + generates).make helplists them. - Schemes:
FRTMProxy(development) andFRTMProxy_Release(release builds). - Build (dev):
xcodebuild -scheme FRTMProxy -configuration Debug -destination 'platform=macOS' build - Test:
xcodebuild -scheme FRTMProxy -destination 'platform=macOS' test— theFRTMProxyTeststarget (Swift Testing) covers pure logic. Note:MapRuleKeyBuilderTestsholds golden keys generated frombridge.py; if you change key/signature logic on either side, keep both in sync and regenerate the goldens (the generator script is documented in the test file). - Day-to-day development is done in Xcode (or VS Code via
buildServer.json+ xcode-build-server;buildServer.jsonis machine-specific — regenerate it withxcode-build-server configafter checkout).
./scripts/publish_sparkle_release.sh builds FRTMProxy_Release, zips the app, regenerates
appcast.xml via Sparkle tools, and pushes artifacts to the gh-pages branch. Useful flags:
--no-publish (local only), --skip-build (reuse build output), --notarize (Apple notarization).
The release version comes from MARKETING_VERSION and CURRENT_PROJECT_VERSION in project.yml.
The script may update SUPublicEDKey in the root Info.plist when the Sparkle key changes.
After a GitHub release, update the Homebrew cask metadata with
./scripts/update_homebrew_cask.sh --version <x.y.z> --tag <tag> --tap-dir <path> (--commit/--push optional).
The root Info.plist references the Xcode build settings rather than hard-coding a version.
The system is two processes talking over stdin/stdout, plus a SwiftUI layer on top.
A mitmproxy addon, bundled as a resource and loaded with mitmdump -s bridge.py. It is the
single source of truth for all traffic manipulation. Communication is line-delimited JSON:
- stdout → app: one JSON object per line, one per flow event (
request,response,websocket_start/message/end). Bodies for images are emitted asdata:URLs. - app → stdin: command objects (
mock_response,mock_rule,delete_rule,mock_request,breakpoint_rule,breakpoint_continue,retry_flow,traffic_profile).
Key mechanisms inside the bridge:
- Map Local matching: rules are keyed by
<host><path>, optionally suffixed with#<sha256(signature)>where the signature is canonicalizedmethod + query + body— this lets multiple mocks coexist on the same path, disambiguated by request content. Wildcards (*,?) in the base key are matched viafnmatchwith a specificity score. Round-robin cursors rotate among multiple matching rules. - Breakpoints:
flow.intercept()pauses a flow;breakpoint_continueapplies edits and callsflow.resume(). Flow-lookup tables (FLOW_BY_ID,FLOW_BY_KEY,FLOW_BY_MAP_LOCAL_KEY) are re-keyed when a paused request is edited. - Traffic profiles: latency/jitter/bandwidth/packet-loss/response-delay are simulated with
time.sleepand synthetic 598 responses. - Loopback hosts are ignored so the app's own pairing server traffic isn't captured.
When editing matching/mock logic, the Swift MapRuleKeyBuilder (key generation) and the Python
key logic must stay in sync.
MitmproxyService (conforms to ProxyServiceProtocol, so it can be mocked) owns the Process:
- Launches the bundled
Resources/mitmdump(a ~37 MB Mach-O arm64 binary), prewarms it with--versionbefore first Start, and kills stalemitmdump/mitmproxyprocesses on launch. - Parses stdout line-by-line into
MitmFlow(orWebSocketMessageEvent) and publishes a[String: MitmFlow]dictionary; caps stored flows at 500. - Sends commands by writing JSON +
\nto the process's stdinFileHandle.
ProxyServiceProtocol is the seam between UI and proxy — depend on it, not on MitmproxyService.
ProxyViewModel is the app's brain, split across ProxyViewModel+*.swift extensions by feature
(FlowBinding, Rules, Breakpoints, Collections, Alerts, Scripts, Settings,
Persistence). It subscribes to the service's Combine publishers (flows throttled to 120 ms),
enriches flows with the resolved client app, and drives recording, breakpoints, alerts, and scripts.
Note: the existing code uses
ObservableObject+ Combine +@StateObject, even though AGENTS.md prescribes@Observable. Match the surrounding file's pattern; don't mix paradigms within a type.
FRTMProxy/Features/<Domain>/— one folder per domain:Inspector,Rules,Breakpoints,Collections,Composer,Device,Scripts,Settings,MenuBar,Onboarding.FRTMProxy/Shared/—Models,Services,DesignSystem,Components,Extensions,Filters,Utilities.FRTMProxyApp.swiftwires the scenes (mainWindowGroup,MenuBarExtra,Settings, About window), Sparkle updater, and AppDelegate termination hook.
- Device certificate setup —
SimulatorCertificateInstaller(CA into booted simulators),DevicePairingHTTPServer+MobileConfigBuilder+QRCodeView(physical devices via QR /.mobileconfig),MitmproxyCertificateLoader. MacOSProxyOverrideManager— toggles the system proxy vianetworksetup.ClientAppResolver— maps a loopback connection (client IP/port + proxy port) back to the macOS app that originated it, so flows can be attributed to an app.- Collections — record a session, export to HAR (
HARModels/HARCollectionConverter), persist locally or sync/publish via Git (GitCollections*). Enabling a collection enters Replay mode (recorded responses are served as mocks).
FRTMProxy/AGENTS.md is the authoritative Swift/SwiftUI style guide for this repo — read it before
writing code. Highlights: target macOS 14.1+ / Swift 5; prefer
@Observable + @MainActor for new shared state; no third-party frameworks without asking; avoid
UIKit; deployment-compatible Foundation/SwiftUI APIs (foregroundStyle, clipShape(.rect(...)),
Task.sleep(for:), no DispatchQueue.main.async); one type per file; run SwiftLint clean before
committing.