diff --git a/Flowlight/Inspection/InspectionController.swift b/Flowlight/Inspection/InspectionController.swift
index 835360ad..e7a52137 100644
--- a/Flowlight/Inspection/InspectionController.swift
+++ b/Flowlight/Inspection/InspectionController.swift
@@ -18,6 +18,8 @@ final class InspectionController: ObservableObject {
static let neverInspect = "inspection.neverInspect"
static let systemProxy = "inspection.systemProxy"
static let mockRules = "inspection.mockRules"
+ /// Rules that rewrite an outgoing request's headers or JSON body before it is forwarded (see `RewriteRule`).
+ static let rewriteRules = "inspection.rewriteRules"
/// When the running session was switched on, so its end survives a relaunch.
static let sessionStarted = "inspection.sessionStarted"
/// Names of the agents the user asked Flowlight to keep routed through the proxy by editing their own
@@ -93,6 +95,7 @@ final class InspectionController: ObservableObject {
UserDefaults.standard.stringArray(forKey: Keys.neverInspect) ?? Self.defaultNeverInspect)
}
let mockRules = { Self.decodeMockRules(UserDefaults.standard.data(forKey: Keys.mockRules)) }
+ let rewriteRules = { Self.decodeRewriteRules(UserDefaults.standard.data(forKey: Keys.rewriteRules)) }
// A rule refusing a request is answered by the same machinery that gives a mock its canned response, and
// it goes first: a block someone wrote has to outrank a mock they left switched on.
let answersFor = { [weak self, recorder, proxy] (host: String, clientPort: UInt16) -> [MockRule] in
@@ -116,30 +119,44 @@ final class InspectionController: ObservableObject {
proxy.interventions = { [weak self, recorder, proxy] host, clientPort in
guard let self else { return nil }
let guardrails = self.guardrails()
- guard !guardrails.isEmpty else { return nil }
+ let rewrites = RewriteRules.matching(rewriteRules(), host: host)
let owner = recorder.owner(clientPort: clientPort, proxyPort: proxy.port)
let agent = owner.agent ?? owner.bundleID
- guard GuardrailBook.any(guardrails, agent: agent) else { return nil }
+ let guardsApply = !guardrails.isEmpty && GuardrailBook.any(guardrails, agent: agent)
+ // Hold the request only when something would act on it: a guardrail for this agent, or a rewrite rule
+ // for this host. Everything else streams untouched.
+ guard guardsApply || !rewrites.isEmpty else { return nil }
return { [weak self] head, bytes in
- guard let self, let body = Self.body(of: bytes) else { return nil }
+ guard let self else { return nil }
let server = owner.mcpServer
- // A call to an MCP server over HTTP, answered here rather than forwarded.
- if let refusal = GuardrailEngine.refuse(jsonrpc: body, guardrails: guardrails, agent: agent, server: server) {
+ // A guardrail that answers an MCP call locally short-circuits — nothing goes upstream to rewrite.
+ if guardsApply, let body = Self.body(of: bytes),
+ let refusal = GuardrailEngine.refuse(jsonrpc: body, guardrails: guardrails, agent: agent, server: server) {
self.report(refusal.guardrail, subject: refusal.subject, owner: owner, host: host,
port: UInt16(clamping: 443), method: head.method, engine: .request)
- let answer = MockRule(id: refusal.guardrail.id, name: refusal.guardrail.title, host: host,
- path: "*", status: 200, body: refusal.body, blocked: true)
- return .answer(answer)
+ return .answer(MockRule(id: refusal.guardrail.id, name: refusal.guardrail.title, host: host,
+ path: "*", status: 200, body: refusal.body, blocked: true))
}
- // The declaration, which is the lever that means the model is never offered the tool at all.
- guard let filtered = GuardrailEngine.filter(request: body, guardrails: guardrails, agent: agent) else {
- return nil
+ var current = bytes
+ var notes: [String] = []
+ // Guardrails first — strip refused tools from the declaration — so a rewrite acts on the filtered body.
+ if guardsApply, let body = Self.body(of: current),
+ let filtered = GuardrailEngine.filter(request: body, guardrails: guardrails, agent: agent) {
+ current = Self.reframe(current, body: filtered.body)
+ self.report(guardrails.first { g in filtered.removed.contains { g.refuses(agent: agent, server: server, tool: $0) } },
+ subject: filtered.removed.joined(separator: ", "), owner: owner, host: host,
+ port: UInt16(clamping: 443), method: head.method, engine: .request)
+ notes.append(L("Removed %@", filtered.removed.joined(separator: ", ")))
+ }
+ // Then the user's rewrite rules — header and JSON-body edits.
+ let path = head.target.split(separator: "?").first.map(String.init) ?? "/"
+ if !rewrites.isEmpty,
+ let edited = RewriteRules.apply(rewrites, to: current, host: host, method: head.method, path: path) {
+ current = edited.data
+ notes.append(edited.note)
}
- self.report(guardrails.first { g in filtered.removed.contains { g.refuses(agent: agent, server: server, tool: $0) } },
- subject: filtered.removed.joined(separator: ", "), owner: owner, host: host,
- port: UInt16(clamping: 443), method: head.method, engine: .request)
- return .replace(Self.reframe(bytes, body: filtered.body),
- note: L("Removed %@", filtered.removed.joined(separator: ", ")))
+ guard !notes.isEmpty else { return nil }
+ return .replace(current, note: notes.joined(separator: " · "))
}
}
proxy.onAnswered = { [weak self, recorder, proxy] rule, flow, head in
@@ -160,6 +177,8 @@ final class InspectionController: ObservableObject {
// A host someone wrote a mock rule for is decrypted whatever the scope says: a rule can only answer a
// request Flowlight can read, and "my mock didn't fire" is a bad afternoon.
guard answersFor(host, clientPort).isEmpty else { answer(true); return }
+ // Same for a host with a rewrite rule: it can only edit a request Flowlight can read.
+ guard RewriteRules.matching(rewriteRules(), host: host).isEmpty else { answer(true); return }
guard scope == .agents else { answer(true); return }
decide.async {
answer(recorder.owner(clientPort: clientPort, proxyPort: proxy.port).agent != nil)
@@ -267,6 +286,24 @@ final class InspectionController: ObservableObject {
return (try? JSONDecoder().decode([MockRule].self, from: data)) ?? []
}
+ /// Rules that rewrite outgoing requests. Stored like mocks: settings, not history, so "remove everything
+ /// Flowlight recorded" leaves them alone.
+ var rewriteRules: [RewriteRule] {
+ get { Self.decodeRewriteRules(UserDefaults.standard.data(forKey: Keys.rewriteRules)) }
+ set {
+ UserDefaults.standard.set(try? JSONEncoder().encode(newValue), forKey: Keys.rewriteRules)
+ objectWillChange.send()
+ }
+ }
+
+ /// How many rewrite rules are live, so a request quietly being changed isn't mistaken for the server's own reply.
+ var activeRewriteRules: Int { rewriteRules.filter(\.enabled).count }
+
+ nonisolated static func decodeRewriteRules(_ data: Data?) -> [RewriteRule] {
+ guard let data else { return [] }
+ return (try? JSONDecoder().decode([RewriteRule].self, from: data)) ?? []
+ }
+
var configuredPort: UInt16 { UInt16(clamping: max(1024, UserDefaults.standard.integer(forKey: Keys.port))) }
func attach(db: TrafficDatabase) {
diff --git a/Flowlight/Inspection/RewriteRule.swift b/Flowlight/Inspection/RewriteRule.swift
new file mode 100644
index 00000000..e0ee6055
--- /dev/null
+++ b/Flowlight/Inspection/RewriteRule.swift
@@ -0,0 +1,192 @@
+import Foundation
+
+/// An edit to one outgoing header: replace it (`set`), append another copy (`add`), or drop it (`remove`).
+struct HeaderEdit: Codable, Equatable, Identifiable, Sendable {
+ enum Op: String, Codable, Sendable, CaseIterable { case set, add, remove }
+ var id = UUID()
+ var op: Op = .set
+ var name = ""
+ var value = ""
+
+ init(id: UUID = UUID(), op: Op = .set, name: String = "", value: String = "") {
+ self.id = id; self.op = op; self.name = name; self.value = value
+ }
+
+ enum CodingKeys: String, CodingKey { case id, op, name, value }
+ init(from decoder: Decoder) throws {
+ let c = try decoder.container(keyedBy: CodingKeys.self)
+ id = try c.decodeIfPresent(UUID.self, forKey: .id) ?? UUID()
+ op = try c.decodeIfPresent(Op.self, forKey: .op) ?? .set
+ name = try c.decodeIfPresent(String.self, forKey: .name) ?? ""
+ value = try c.decodeIfPresent(String.self, forKey: .value) ?? ""
+ }
+}
+
+/// An edit to the request's JSON body, addressed by a dotted key path into objects (`metadata.user`). `set` creates
+/// the path if needed; `remove` deletes the leaf. The value is parsed as JSON when it can be (`0.7`, `true`,
+/// `{"a":1}`) and taken as a plain string otherwise.
+struct BodyEdit: Codable, Equatable, Identifiable, Sendable {
+ enum Op: String, Codable, Sendable, CaseIterable { case set, remove }
+ var id = UUID()
+ var op: Op = .set
+ var path = ""
+ var value = ""
+
+ init(id: UUID = UUID(), op: Op = .set, path: String = "", value: String = "") {
+ self.id = id; self.op = op; self.path = path; self.value = value
+ }
+
+ enum CodingKeys: String, CodingKey { case id, op, path, value }
+ init(from decoder: Decoder) throws {
+ let c = try decoder.container(keyedBy: CodingKeys.self)
+ id = try c.decodeIfPresent(UUID.self, forKey: .id) ?? UUID()
+ op = try c.decodeIfPresent(Op.self, forKey: .op) ?? .set
+ path = try c.decodeIfPresent(String.self, forKey: .path) ?? ""
+ value = try c.decodeIfPresent(String.self, forKey: .value) ?? ""
+ }
+}
+
+/// A rule that rewrites a matching outgoing request before it is forwarded upstream — changing headers or the JSON
+/// body. Like a mock, but it edits the request and lets it through rather than answering it: add an `Authorization`
+/// header, pin `model`, strip a tracking field. Matched like a mock (host / path glob / method), and applied by the
+/// same intervention path the guardrails use. Only requests Flowlight decrypts and can buffer (bodies up to a few MB,
+/// not chunked or streamed) can be rewritten.
+struct RewriteRule: Codable, Equatable, Identifiable, Sendable {
+ var id = UUID()
+ var enabled = true
+ var name = ""
+ /// `api.example.com` matches that host alone; `*.example.com` matches the domain and its subdomains.
+ var host = ""
+ /// A glob where `*` stands for any run of characters.
+ var path = "*"
+ /// Empty (or `ANY`) matches any method.
+ var method = ""
+ var headers: [HeaderEdit] = []
+ var body: [BodyEdit] = []
+
+ static let methods = MockRule.methods
+
+ var title: String {
+ name.isEmpty ? "\(method.isEmpty ? "ANY" : method.uppercased()) \(host)\(path)" : name
+ }
+
+ init(id: UUID = UUID(), enabled: Bool = true, name: String = "", host: String = "", path: String = "*",
+ method: String = "", headers: [HeaderEdit] = [], body: [BodyEdit] = []) {
+ self.id = id; self.enabled = enabled; self.name = name; self.host = host; self.path = path
+ self.method = method; self.headers = headers; self.body = body
+ }
+
+ enum CodingKeys: String, CodingKey { case id, enabled, name, host, path, method, headers, body }
+ init(from decoder: Decoder) throws {
+ let c = try decoder.container(keyedBy: CodingKeys.self)
+ id = try c.decodeIfPresent(UUID.self, forKey: .id) ?? UUID()
+ enabled = try c.decodeIfPresent(Bool.self, forKey: .enabled) ?? true
+ name = try c.decodeIfPresent(String.self, forKey: .name) ?? ""
+ host = try c.decodeIfPresent(String.self, forKey: .host) ?? ""
+ path = try c.decodeIfPresent(String.self, forKey: .path) ?? "*"
+ method = try c.decodeIfPresent(String.self, forKey: .method) ?? ""
+ headers = try c.decodeIfPresent([HeaderEdit].self, forKey: .headers) ?? []
+ body = try c.decodeIfPresent([BodyEdit].self, forKey: .body) ?? []
+ }
+}
+
+// MARK: Matching & applying
+
+/// Pure functions over plain data — no sockets — so the whole rewrite is testable without a proxy.
+enum RewriteRules {
+ /// The enabled rules that could touch this host. Asked once per connection, so a host no rule names never pays.
+ static func matching(_ rules: [RewriteRule], host: String) -> [RewriteRule] {
+ rules.filter { $0.enabled && GlobMatch.host($0.host, host) }
+ }
+
+ /// Apply every rule that matches this request to the framed bytes (full head + body). Returns the rewritten
+ /// request and a short note of what changed, or nil when nothing matched or nothing changed.
+ static func apply(_ rules: [RewriteRule], to request: Data, host: String, method: String, path: String)
+ -> (data: Data, note: String)? {
+ let applicable = rules.filter {
+ $0.enabled && GlobMatch.host($0.host, host) && GlobMatch.method($0.method, method) && GlobMatch.path($0.path, path)
+ }
+ guard !applicable.isEmpty else { return nil }
+ guard let sep = request.range(of: Data("\r\n\r\n".utf8)) else { return nil }
+
+ let headText = String(decoding: request[request.startIndex.. Any {
+ if let data = "[\(s)]".data(using: .utf8),
+ let array = try? JSONSerialization.jsonObject(with: data) as? [Any], let first = array.first {
+ return first
+ }
+ return s
+ }
+
+ private static func setJSON(_ object: inout [String: Any], path: [String], value: Any) {
+ guard let key = path.first else { return }
+ if path.count == 1 { object[key] = value; return }
+ var child = object[key] as? [String: Any] ?? [:]
+ setJSON(&child, path: Array(path.dropFirst()), value: value)
+ object[key] = child
+ }
+
+ private static func removeJSON(_ object: inout [String: Any], path: [String]) {
+ guard let key = path.first else { return }
+ if path.count == 1 { object.removeValue(forKey: key); return }
+ guard var child = object[key] as? [String: Any] else { return }
+ removeJSON(&child, path: Array(path.dropFirst()))
+ object[key] = child
+ }
+}
diff --git a/Flowlight/UI/InspectView.swift b/Flowlight/UI/InspectView.swift
index 53ada31c..73dbe8cd 100644
--- a/Flowlight/UI/InspectView.swift
+++ b/Flowlight/UI/InspectView.swift
@@ -277,6 +277,7 @@ private struct InspectionSetup: View {
@State private var confirmRemove = false
@State private var showAdvanced = false
@State private var showMocks = false
+ @State private var showRewrites = false
@State private var confirmTurnOn = false
@Environment(\.openURL) private var openURL
@@ -402,6 +403,19 @@ private struct InspectionSetup: View {
}
}
}
+
+ DisclosureGroup(isExpanded: $showRewrites) {
+ RewriteRulesSection(inspection: inspection).padding(.top, 10)
+ } label: {
+ HStack(spacing: 8) {
+ Text(L("Modify requests")).font(.headline)
+ if inspection.activeRewriteRules > 0 {
+ Label(inspection.activeRewriteRules == 1 ? L("1 on") : L("%lld on", inspection.activeRewriteRules),
+ systemImage: "slider.horizontal.3")
+ .font(.caption.bold()).foregroundStyle(FL.tool)
+ }
+ }
+ }
}
.measured(Measure.prose)
.confirmationDialog(L("macOS will ask you twice"), isPresented: $confirmTurnOn) {
diff --git a/Flowlight/UI/RewriteRulesView.swift b/Flowlight/UI/RewriteRulesView.swift
new file mode 100644
index 00000000..f3a7f02f
--- /dev/null
+++ b/Flowlight/UI/RewriteRulesView.swift
@@ -0,0 +1,202 @@
+import SwiftUI
+
+/// Modify requests: rules that edit an outgoing request's headers or JSON body before it's forwarded. Sits with the
+/// rest of inspection setup — a rule can only change a request Flowlight decrypts.
+struct RewriteRulesSection: View {
+ @ObservedObject var inspection: InspectionController
+ @State private var editing: RewriteRule?
+ @State private var isNew = false
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 10) {
+ Text(L("Change a request on its way out — add or replace a header, pin a field in the JSON body, or strip one — and let it continue to the server. Like a mock, but it edits the request instead of answering it."))
+ .font(.caption).foregroundStyle(.secondary).fixedSize(horizontal: false, vertical: true)
+ Label(L("Only requests Flowlight decrypts can be rewritten, and only buffered ones — bodies up to a few megabytes. Tunnelled, pinned, chunked or streamed uploads pass through untouched."),
+ systemImage: "info.circle")
+ .font(.caption).foregroundStyle(.secondary).fixedSize(horizontal: false, vertical: true)
+ if !inspection.enabled {
+ Label(L("HTTPS inspection is off, so nothing is decrypted and no rule can change anything yet."),
+ systemImage: "exclamationmark.triangle")
+ .font(.caption).foregroundStyle(FL.warning).fixedSize(horizontal: false, vertical: true)
+ }
+
+ if inspection.rewriteRules.isEmpty {
+ Text(L("No request rules.")).font(.caption).foregroundStyle(.tertiary)
+ } else {
+ VStack(spacing: 0) {
+ ForEach(Array(inspection.rewriteRules.enumerated()), id: \.element.id) { index, rule in
+ row(rule, index: index)
+ if index < inspection.rewriteRules.count - 1 { Divider() }
+ }
+ }
+ .padding(.vertical, 2)
+ .background(.quaternary.opacity(0.3), in: RoundedRectangle(cornerRadius: 6))
+ Text(L("Every matching rule is applied, in order."))
+ .font(.caption).foregroundStyle(.secondary)
+ }
+
+ HStack {
+ Button(L("Add Rule…")) {
+ editing = RewriteRule(host: "", path: "/*")
+ isNew = true
+ }
+ Spacer()
+ if inspection.activeRewriteRules > 0 {
+ Button(L("Turn All Off")) {
+ inspection.rewriteRules = inspection.rewriteRules.map { var r = $0; r.enabled = false; return r }
+ }
+ }
+ }
+ }
+ .sheet(item: $editing) { rule in
+ RewriteRuleEditor(rule: rule, isNew: isNew) { saved in
+ if let at = inspection.rewriteRules.firstIndex(where: { $0.id == saved.id }) {
+ inspection.rewriteRules[at] = saved
+ } else {
+ inspection.rewriteRules.append(saved)
+ }
+ }
+ }
+ }
+
+ private func row(_ rule: RewriteRule, index: Int) -> some View {
+ HStack(spacing: 8) {
+ Toggle("", isOn: Binding(get: { rule.enabled }, set: { on in
+ var copy = rule; copy.enabled = on
+ inspection.rewriteRules[index] = copy
+ }))
+ .toggleStyle(.switch).controlSize(.mini).labelsHidden()
+ .accessibilityLabel(L("Enable %@", rule.title))
+ VStack(alignment: .leading, spacing: 1) {
+ Text(rule.title).font(.callout).lineLimit(1)
+ Text(summary(rule)).font(.caption.monospaced()).foregroundStyle(.secondary).lineLimit(1)
+ }
+ .opacity(rule.enabled ? 1 : 0.5)
+ Spacer()
+ Button { editing = rule; isNew = false } label: { Image(systemName: "pencil") }
+ .buttonStyle(.borderless).accessibilityLabel(L("Edit %@", rule.title))
+ Button(role: .destructive) {
+ inspection.rewriteRules.removeAll { $0.id == rule.id }
+ } label: {
+ Image(systemName: "trash")
+ }
+ .buttonStyle(.borderless).accessibilityLabel(L("Remove %@", rule.title))
+ }
+ .padding(.horizontal, 8).padding(.vertical, 5)
+ }
+
+ private func summary(_ rule: RewriteRule) -> String {
+ let scope = "\(rule.method.isEmpty ? "ANY" : rule.method.uppercased()) \(rule.host)\(rule.path)"
+ let edits = rule.headers.count + rule.body.count
+ return "\(scope) · \(L("%lld edit(s)", edits))"
+ }
+}
+
+/// One rewrite rule, edited in a sheet: where it matches, and the header and body edits it makes.
+struct RewriteRuleEditor: View {
+ @State var rule: RewriteRule
+ var isNew: Bool
+ var save: (RewriteRule) -> Void
+ @Environment(\.dismiss) private var dismiss
+
+ var body: some View {
+ VStack(alignment: .leading, spacing: 14) {
+ Text(isNew ? L("New request rule") : L("Edit request rule")).font(.title3.bold())
+
+ Grid(alignment: .leadingFirstTextBaseline, horizontalSpacing: 10, verticalSpacing: 8) {
+ GridRow {
+ Text(L("Name")).gridColumnAlignment(.trailing).foregroundStyle(.secondary)
+ TextField(L("Optional, e.g. “Force temperature”"), text: $rule.name)
+ }
+ GridRow {
+ Text(L("Host")).gridColumnAlignment(.trailing).foregroundStyle(.secondary)
+ VStack(alignment: .leading, spacing: 2) {
+ TextField(L("api.example.com"), text: $rule.host)
+ Text(L("Exactly that host. Write *.example.com to cover the domain and its subdomains."))
+ .font(.caption).foregroundStyle(.secondary)
+ }
+ }
+ GridRow {
+ Text(L("Path")).gridColumnAlignment(.trailing).foregroundStyle(.secondary)
+ TextField(L("/v1/*"), text: $rule.path)
+ }
+ GridRow {
+ Text(L("Method")).gridColumnAlignment(.trailing).foregroundStyle(.secondary)
+ Picker("", selection: Binding(get: { rule.method.isEmpty ? "ANY" : rule.method.uppercased() },
+ set: { rule.method = $0 == "ANY" ? "" : $0 })) {
+ ForEach(RewriteRule.methods, id: \.self) { Text($0).tag($0) }
+ }
+ .labelsHidden().frame(width: 130)
+ }
+ }
+
+ Divider()
+
+ // Header edits
+ VStack(alignment: .leading, spacing: 6) {
+ Text(L("Headers")).font(.caption.bold()).foregroundStyle(.secondary)
+ ForEach($rule.headers) { $edit in
+ HStack(spacing: 6) {
+ Picker("", selection: $edit.op) {
+ Text(L("Set")).tag(HeaderEdit.Op.set)
+ Text(L("Add")).tag(HeaderEdit.Op.add)
+ Text(L("Remove")).tag(HeaderEdit.Op.remove)
+ }.labelsHidden().frame(width: 92)
+ TextField(L("Header name"), text: $edit.name).frame(width: 150)
+ TextField(L("Value"), text: $edit.value).disabled(edit.op == .remove)
+ .opacity(edit.op == .remove ? 0.4 : 1)
+ Button(role: .destructive) { rule.headers.removeAll { $0.id == edit.id } } label: {
+ Image(systemName: "minus.circle")
+ }.buttonStyle(.borderless)
+ }
+ }
+ Button(L("Add header edit")) { rule.headers.append(HeaderEdit()) }.controlSize(.small)
+ }
+
+ // Body edits
+ VStack(alignment: .leading, spacing: 6) {
+ Text(L("JSON body")).font(.caption.bold()).foregroundStyle(.secondary)
+ ForEach($rule.body) { $edit in
+ HStack(spacing: 6) {
+ Picker("", selection: $edit.op) {
+ Text(L("Set")).tag(BodyEdit.Op.set)
+ Text(L("Remove")).tag(BodyEdit.Op.remove)
+ }.labelsHidden().frame(width: 92)
+ TextField(L("key.path"), text: $edit.path).font(.caption.monospaced()).frame(width: 150)
+ TextField(L("value (JSON or text)"), text: $edit.value).font(.caption.monospaced())
+ .disabled(edit.op == .remove).opacity(edit.op == .remove ? 0.4 : 1)
+ Button(role: .destructive) { rule.body.removeAll { $0.id == edit.id } } label: {
+ Image(systemName: "minus.circle")
+ }.buttonStyle(.borderless)
+ }
+ }
+ Button(L("Add body edit")) { rule.body.append(BodyEdit()) }.controlSize(.small)
+ Text(L("Dotted path into the JSON object (metadata.user). A value that is valid JSON (0.7, true, {\"a\":1}) is used as-is; anything else is a string. Only requests with a JSON body are changed."))
+ .font(.caption).foregroundStyle(.secondary).fixedSize(horizontal: false, vertical: true)
+ }
+
+ HStack {
+ Button(L("Cancel"), role: .cancel) { dismiss() }.keyboardShortcut(.cancelAction)
+ Spacer()
+ Button(isNew ? L("Add Rule") : L("Save")) { save(cleaned()); dismiss() }
+ .keyboardShortcut(.defaultAction)
+ .disabled(rule.host.trimmingCharacters(in: .whitespaces).isEmpty)
+ }
+ }
+ .padding(20)
+ .frame(width: 600)
+ }
+
+ private func cleaned() -> RewriteRule {
+ var copy = rule
+ copy.name = copy.name.trimmingCharacters(in: .whitespaces)
+ copy.host = copy.host.trimmingCharacters(in: .whitespaces).lowercased()
+ copy.path = copy.path.trimmingCharacters(in: .whitespaces)
+ if copy.path.isEmpty { copy.path = "*" }
+ copy.method = copy.method.trimmingCharacters(in: .whitespaces).uppercased()
+ // Drop blank edits a half-filled row would leave behind.
+ copy.headers = copy.headers.filter { !$0.name.trimmingCharacters(in: .whitespaces).isEmpty }
+ copy.body = copy.body.filter { !$0.path.trimmingCharacters(in: .whitespaces).isEmpty }
+ return copy
+ }
+}
diff --git a/FlowlightTests/RewriteRuleTests.swift b/FlowlightTests/RewriteRuleTests.swift
new file mode 100644
index 00000000..bf28c02e
--- /dev/null
+++ b/FlowlightTests/RewriteRuleTests.swift
@@ -0,0 +1,135 @@
+import XCTest
+@testable import Flowlight
+
+/// Rewriting an outgoing request is pure data work — these exercise it without a proxy. The framing has to stay
+/// correct (Content-Length must agree with the body) or the connection would hang, so several checks assert on it.
+final class RewriteRuleTests: XCTestCase {
+ private func request(_ method: String, _ target: String, headers: [String] = [], body: String = "") -> Data {
+ var s = "\(method) \(target) HTTP/1.1\r\n"
+ for h in headers { s += h + "\r\n" }
+ if !body.isEmpty { s += "Content-Length: \(body.utf8.count)\r\n" }
+ s += "\r\n" + body
+ return Data(s.utf8)
+ }
+
+ private func parts(_ data: Data) -> (head: [String], body: String) {
+ let sep = data.range(of: Data("\r\n\r\n".utf8))!
+ let head = String(decoding: data[.. [String: Any] {
+ let sep = data.range(of: Data("\r\n\r\n".utf8))!
+ return (try? JSONSerialization.jsonObject(with: Data(data[sep.upperBound...]))) as? [String: Any] ?? [:]
+ }
+
+ private func apply(_ rule: RewriteRule, _ data: Data, host: String = "api.example.com", method: String = "POST", path: String = "/v1/messages") -> Data? {
+ RewriteRules.apply([rule], to: data, host: host, method: method, path: path)?.data
+ }
+
+ // MARK: Headers
+
+ /// `set` replaces an existing header rather than duplicating it.
+ func testHeaderSetReplaces() throws {
+ let rule = RewriteRule(host: "api.example.com", headers: [HeaderEdit(op: .set, name: "Authorization", value: "Bearer new")])
+ let out = try XCTUnwrap(apply(rule, request("POST", "/v1/messages", headers: ["Authorization: Bearer old"])))
+ let auth = parts(out).head.filter { $0.lowercased().hasPrefix("authorization:") }
+ XCTAssertEqual(auth, ["Authorization: Bearer new"])
+ }
+
+ /// `add` appends and keeps what was there.
+ func testHeaderAddAppends() throws {
+ let rule = RewriteRule(host: "api.example.com", headers: [HeaderEdit(op: .add, name: "X-Trace", value: "1")])
+ let out = try XCTUnwrap(apply(rule, request("POST", "/v1/messages", headers: ["X-Trace: 0"])))
+ XCTAssertEqual(parts(out).head.filter { $0.hasPrefix("X-Trace:") }, ["X-Trace: 0", "X-Trace: 1"])
+ }
+
+ /// `remove` drops the header.
+ func testHeaderRemove() throws {
+ let rule = RewriteRule(host: "api.example.com", headers: [HeaderEdit(op: .remove, name: "Cookie")])
+ let out = try XCTUnwrap(apply(rule, request("POST", "/v1/messages", headers: ["Cookie: a=b"])))
+ XCTAssertFalse(parts(out).head.contains { $0.lowercased().hasPrefix("cookie:") })
+ }
+
+ /// A newline in a header value can't forge a second header or request.
+ func testHeaderValueIsSanitised() throws {
+ let rule = RewriteRule(host: "api.example.com", headers: [HeaderEdit(op: .set, name: "X-Evil", value: "ok\r\nInjected: yes")])
+ let out = try XCTUnwrap(apply(rule, request("POST", "/v1/messages")))
+ XCTAssertFalse(parts(out).head.contains { $0.lowercased().hasPrefix("injected:") })
+ }
+
+ // MARK: Body
+
+ /// Setting a scalar parses it as JSON (a number stays a number), and Content-Length is recomputed.
+ func testBodySetNumberAndReframes() throws {
+ let rule = RewriteRule(host: "api.example.com", body: [BodyEdit(op: .set, path: "temperature", value: "0.2")])
+ let out = try XCTUnwrap(apply(rule, request("POST", "/v1/messages", body: #"{"model":"x","temperature":0.9}"#)))
+ XCTAssertEqual(bodyJSON(out)["temperature"] as? Double, 0.2)
+ let cl = parts(out).head.first { $0.lowercased().hasPrefix("content-length:") }!
+ let declared = Int(cl.split(separator: ":")[1].trimmingCharacters(in: .whitespaces))!
+ let actual = String(decoding: out[out.range(of: Data("\r\n\r\n".utf8))!.upperBound...], as: UTF8.self).utf8.count
+ XCTAssertEqual(declared, actual, "Content-Length must agree with the rewritten body")
+ }
+
+ /// An unquoted value that isn't valid JSON is taken as a plain string.
+ func testBodySetStringFallback() throws {
+ let rule = RewriteRule(host: "api.example.com", body: [BodyEdit(op: .set, path: "model", value: "claude-opus")])
+ let out = try XCTUnwrap(apply(rule, request("POST", "/v1/messages", body: #"{"model":"x"}"#)))
+ XCTAssertEqual(bodyJSON(out)["model"] as? String, "claude-opus")
+ }
+
+ /// A nested path is created if it isn't there.
+ func testBodySetNestedCreatesPath() throws {
+ let rule = RewriteRule(host: "api.example.com", body: [BodyEdit(op: .set, path: "metadata.user", value: #""alice""#)])
+ let out = try XCTUnwrap(apply(rule, request("POST", "/v1/messages", body: #"{"model":"x"}"#)))
+ XCTAssertEqual((bodyJSON(out)["metadata"] as? [String: Any])?["user"] as? String, "alice")
+ }
+
+ /// `remove` deletes the leaf.
+ func testBodyRemove() throws {
+ let rule = RewriteRule(host: "api.example.com", body: [BodyEdit(op: .remove, path: "stream")])
+ let out = try XCTUnwrap(apply(rule, request("POST", "/v1/messages", body: #"{"model":"x","stream":true}"#)))
+ XCTAssertNil(bodyJSON(out)["stream"])
+ XCTAssertEqual(bodyJSON(out)["model"] as? String, "x")
+ }
+
+ /// A non-JSON body is left alone while header edits still apply.
+ func testNonJSONBodyKeepsBodyButEditsHeaders() throws {
+ let rule = RewriteRule(host: "api.example.com",
+ headers: [HeaderEdit(op: .set, name: "X-Tag", value: "1")],
+ body: [BodyEdit(op: .set, path: "model", value: "y")])
+ let out = try XCTUnwrap(apply(rule, request("POST", "/form", headers: [], body: "a=1&b=2"), path: "/form"))
+ XCTAssertEqual(parts(out).body, "a=1&b=2")
+ XCTAssertTrue(parts(out).head.contains("X-Tag: 1"))
+ }
+
+ // MARK: Matching
+
+ /// A rule for another host, method or path doesn't touch the request.
+ func testNonMatchIsNil() {
+ let rule = RewriteRule(host: "other.example.com", headers: [HeaderEdit(op: .set, name: "X", value: "1")])
+ XCTAssertNil(apply(rule, request("POST", "/v1/messages")))
+ let m = RewriteRule(host: "api.example.com", method: "GET", headers: [HeaderEdit(op: .set, name: "X", value: "1")])
+ XCTAssertNil(apply(m, request("POST", "/v1/messages")))
+ let p = RewriteRule(host: "api.example.com", path: "/other/*", headers: [HeaderEdit(op: .set, name: "X", value: "1")])
+ XCTAssertNil(apply(p, request("POST", "/v1/messages")))
+ }
+
+ /// A disabled rule, or one with no effective edits, changes nothing.
+ func testNoChangeIsNil() {
+ let disabled = RewriteRule(enabled: false, host: "api.example.com", headers: [HeaderEdit(op: .set, name: "X", value: "1")])
+ XCTAssertNil(apply(disabled, request("POST", "/v1/messages")))
+ let empty = RewriteRule(host: "api.example.com")
+ XCTAssertNil(apply(empty, request("POST", "/v1/messages")))
+ }
+
+ /// Old stored rules with fields missing still decode.
+ func testDecodesWithMissingFields() throws {
+ let json = Data(#"{"host":"api.example.com"}"#.utf8)
+ let rule = try JSONDecoder().decode(RewriteRule.self, from: json)
+ XCTAssertEqual(rule.host, "api.example.com")
+ XCTAssertTrue(rule.enabled)
+ XCTAssertEqual(rule.path, "*")
+ XCTAssertTrue(rule.headers.isEmpty)
+ }
+}
diff --git a/docs/404.html b/docs/404.html
index a4cf886c..f871b1fc 100644
--- a/docs/404.html
+++ b/docs/404.html
@@ -80,7 +80,7 @@ That page isn't here Try the home page ,
© 2026 The Flowlight contributors. Flowlight is free software, released under the
GNU General Public License v3.0 .
-
Version 0.12.0 · Not affiliated with Apple or any AI provider named on this site.
+
Version 0.13.0 · Not affiliated with Apple or any AI provider named on this site.
diff --git a/docs/about/index.html b/docs/about/index.html
index f3d86c1c..7bd16466 100644
--- a/docs/about/index.html
+++ b/docs/about/index.html
@@ -138,7 +138,7 @@ Thanks
© 2026 The Flowlight contributors. Flowlight is free software, released under the
GNU General Public License v3.0 .
-
Version 0.12.0 · Not affiliated with Apple or any AI provider named on this site.
+
Version 0.13.0 · Not affiliated with Apple or any AI provider named on this site.
diff --git a/docs/de/about/index.html b/docs/de/about/index.html
index 232501c2..f231b8e5 100644
--- a/docs/de/about/index.html
+++ b/docs/de/about/index.html
@@ -137,7 +137,7 @@ Dank
© 2026 Die Flowlight-Mitwirkenden. Flowlight ist freie Software, veröffentlicht unter der GNU General Public License v3.0 .
-
Version 0.12.0 · Nicht verbunden mit Apple oder einem der hier genannten KI-Anbieter.
+
Version 0.13.0 · Nicht verbunden mit Apple oder einem der hier genannten KI-Anbieter.
diff --git a/docs/de/docs/index.html b/docs/de/docs/index.html
index bdda1dd6..435ac392 100644
--- a/docs/de/docs/index.html
+++ b/docs/de/docs/index.html
@@ -61,7 +61,7 @@
Dokumentation
Flowlight benutzen
-
Alles vom ersten Start bis zum Feinschliff an den Agentenregeln. Flowlight 0.12.0, macOS 15 oder neuer.
+
Alles vom ersten Start bis zum Feinschliff an den Agentenregeln. Flowlight 0.13.0, macOS 15 oder neuer.
@@ -738,7 +738,7 @@ Grenzen
© 2026 Die Flowlight-Mitwirkenden. Flowlight ist freie Software, veröffentlicht unter der GNU General Public License v3.0 .
-
Version 0.12.0 · Nicht verbunden mit Apple oder einem der hier genannten KI-Anbieter.
+
Version 0.13.0 · Nicht verbunden mit Apple oder einem der hier genannten KI-Anbieter.
diff --git a/docs/de/index.html b/docs/de/index.html
index 20a54061..9aca252a 100644
--- a/docs/de/index.html
+++ b/docs/de/index.html
@@ -34,7 +34,7 @@
-
+
Zum Inhalt springen
@@ -75,7 +75,7 @@ Sieh, was deine Apps im Netz tun.brew install --cask xinbetween/tap/flowlightKopieren
- v0.12.0 macOS 15+ Universal GPL-3.0 Keine Telemetrie
+ v0.13.0 macOS 15+ Universal GPL-3.0 Keine Telemetrie
@@ -616,7 +616,7 @@ Wisse, was deinen Mac verlässt.
© 2026 Die Flowlight-Mitwirkenden. Flowlight ist freie Software, veröffentlicht unter der GNU General Public License v3.0 .
-
Version 0.12.0 · Nicht verbunden mit Apple oder einem der hier genannten KI-Anbieter.
+
Version 0.13.0 · Nicht verbunden mit Apple oder einem der hier genannten KI-Anbieter.
diff --git a/docs/de/privacy/index.html b/docs/de/privacy/index.html
index c0a785fd..822d7b21 100644
--- a/docs/de/privacy/index.html
+++ b/docs/de/privacy/index.html
@@ -173,7 +173,7 @@
© 2026 Die Flowlight-Mitwirkenden. Flowlight ist freie Software, veröffentlicht unter der GNU General Public License v3.0 .
-
Version 0.12.0 · Nicht verbunden mit Apple oder einem der hier genannten KI-Anbieter.
+
Version 0.13.0 · Nicht verbunden mit Apple oder einem der hier genannten KI-Anbieter.
diff --git a/docs/de/threat-model/index.html b/docs/de/threat-model/index.html
index 760802e0..b01b8ab0 100644
--- a/docs/de/threat-model/index.html
+++ b/docs/de/threat-model/index.html
@@ -213,7 +213,7 @@ Eine Schwachstelle melden
© 2026 Die Flowlight-Mitwirkenden. Flowlight ist freie Software, veröffentlicht unter der GNU General Public License v3.0 .
-
Version 0.12.0 · Nicht verbunden mit Apple oder einem der hier genannten KI-Anbieter.
+
Version 0.13.0 · Nicht verbunden mit Apple oder einem der hier genannten KI-Anbieter.
diff --git a/docs/docs/index.html b/docs/docs/index.html
index d672fe35..c71c6e0f 100644
--- a/docs/docs/index.html
+++ b/docs/docs/index.html
@@ -61,7 +61,7 @@
Documentation
Using Flowlight
-
Everything from the first launch to tuning the agent rules. Flowlight 0.12.0, macOS 15 or later.
+
Everything from the first launch to tuning the agent rules. Flowlight 0.13.0, macOS 15 or later.
@@ -732,7 +732,7 @@ Limitations
© 2026 The Flowlight contributors. Flowlight is free software, released under the
GNU General Public License v3.0 .
-
Version 0.12.0 · Not affiliated with Apple or any AI provider named on this site.
+
Version 0.13.0 · Not affiliated with Apple or any AI provider named on this site.
diff --git a/docs/es/about/index.html b/docs/es/about/index.html
index 2b82374d..d689032f 100644
--- a/docs/es/about/index.html
+++ b/docs/es/about/index.html
@@ -137,7 +137,7 @@ Agradecimientos
© 2026 Quienes contribuyen a Flowlight. Flowlight es software libre, publicado bajo la GNU General Public License v3.0 .
-
Versión 0.12.0 · Sin relación con Apple ni con ningún proveedor de IA mencionado en este sitio.
+
Versión 0.13.0 · Sin relación con Apple ni con ningún proveedor de IA mencionado en este sitio.
diff --git a/docs/es/docs/index.html b/docs/es/docs/index.html
index eb9f599d..0bf4de8e 100644
--- a/docs/es/docs/index.html
+++ b/docs/es/docs/index.html
@@ -61,7 +61,7 @@
Documentación
Usar Flowlight
-
Todo, desde el primer arranque hasta el ajuste de las reglas de agentes. Flowlight 0.12.0, macOS 15 o posterior.
+
Todo, desde el primer arranque hasta el ajuste de las reglas de agentes. Flowlight 0.13.0, macOS 15 o posterior.
@@ -732,7 +732,7 @@ Limitaciones
© 2026 Quienes contribuyen a Flowlight. Flowlight es software libre, publicado bajo la GNU General Public License v3.0 .
-
Versión 0.12.0 · Sin relación con Apple ni con ningún proveedor de IA mencionado en este sitio.
+
Versión 0.13.0 · Sin relación con Apple ni con ningún proveedor de IA mencionado en este sitio.
diff --git a/docs/es/index.html b/docs/es/index.html
index b2c75210..ac26d224 100644
--- a/docs/es/index.html
+++ b/docs/es/index.html
@@ -34,7 +34,7 @@
-
+
Ir al contenido
@@ -75,7 +75,7 @@ Observa la actividad de red de tus apps.
brew install --cask xinbetween/tap/flowlightCopiar
- v0.12.0 macOS 15+ Universal GPL-3.0 Sin telemetría
+ v0.13.0 macOS 15+ Universal GPL-3.0 Sin telemetría
@@ -616,7 +616,7 @@ Ten claro qué sale de tu Mac.
© 2026 Quienes contribuyen a Flowlight. Flowlight es software libre, publicado bajo la GNU General Public License v3.0 .
-
Versión 0.12.0 · Sin relación con Apple ni con ningún proveedor de IA mencionado en este sitio.
+
Versión 0.13.0 · Sin relación con Apple ni con ningún proveedor de IA mencionado en este sitio.
diff --git a/docs/es/privacy/index.html b/docs/es/privacy/index.html
index 6b043677..8ebc5f07 100644
--- a/docs/es/privacy/index.html
+++ b/docs/es/privacy/index.html
@@ -173,7 +173,7 @@
© 2026 Quienes contribuyen a Flowlight. Flowlight es software libre, publicado bajo la GNU General Public License v3.0 .
-
Versión 0.12.0 · Sin relación con Apple ni con ningún proveedor de IA mencionado en este sitio.
+
Versión 0.13.0 · Sin relación con Apple ni con ningún proveedor de IA mencionado en este sitio.
diff --git a/docs/es/threat-model/index.html b/docs/es/threat-model/index.html
index 5be27b97..69681702 100644
--- a/docs/es/threat-model/index.html
+++ b/docs/es/threat-model/index.html
@@ -208,7 +208,7 @@ Informar de una vulnerabilidad
© 2026 Quienes contribuyen a Flowlight. Flowlight es software libre, publicado bajo la GNU General Public License v3.0 .
-
Versión 0.12.0 · Sin relación con Apple ni con ningún proveedor de IA mencionado en este sitio.
+
Versión 0.13.0 · Sin relación con Apple ni con ningún proveedor de IA mencionado en este sitio.
diff --git a/docs/fr/about/index.html b/docs/fr/about/index.html
index e4819a73..32bdea25 100644
--- a/docs/fr/about/index.html
+++ b/docs/fr/about/index.html
@@ -137,7 +137,7 @@ Remerciements
© 2026 Les contributeurs de Flowlight. Flowlight est un logiciel libre, publié sous la GNU General Public License v3.0 .
-
Version 0.12.0 · Sans lien avec Apple ni avec aucun fournisseur d’IA cité sur ce site.
+
Version 0.13.0 · Sans lien avec Apple ni avec aucun fournisseur d’IA cité sur ce site.
diff --git a/docs/fr/docs/index.html b/docs/fr/docs/index.html
index 87e15356..fb2bb297 100644
--- a/docs/fr/docs/index.html
+++ b/docs/fr/docs/index.html
@@ -61,7 +61,7 @@
Documentation
Utiliser Flowlight
-
Tout, du premier lancement au réglage des règles des agents. Flowlight 0.12.0, macOS 15 ou version ultérieure.
+
Tout, du premier lancement au réglage des règles des agents. Flowlight 0.13.0, macOS 15 ou version ultérieure.
@@ -738,7 +738,7 @@ Limites
© 2026 Les contributeurs de Flowlight. Flowlight est un logiciel libre, publié sous la GNU General Public License v3.0 .
-
Version 0.12.0 · Sans lien avec Apple ni avec aucun fournisseur d’IA cité sur ce site.
+
Version 0.13.0 · Sans lien avec Apple ni avec aucun fournisseur d’IA cité sur ce site.
diff --git a/docs/fr/index.html b/docs/fr/index.html
index 641c61e7..339245ef 100644
--- a/docs/fr/index.html
+++ b/docs/fr/index.html
@@ -34,7 +34,7 @@
-
+
Aller au contenu
@@ -75,7 +75,7 @@ Voyez l’activité réseau de vos apps.
brew install --cask xinbetween/tap/flowlightCopier
- v0.12.0 macOS 15+ Universel GPL-3.0 Sans télémétrie
+ v0.13.0 macOS 15+ Universel GPL-3.0 Sans télémétrie
@@ -616,7 +616,7 @@ Sachez ce qui quitte votre Mac.
© 2026 Les contributeurs de Flowlight. Flowlight est un logiciel libre, publié sous la GNU General Public License v3.0 .
-
Version 0.12.0 · Sans lien avec Apple ni avec aucun fournisseur d’IA cité sur ce site.
+
Version 0.13.0 · Sans lien avec Apple ni avec aucun fournisseur d’IA cité sur ce site.
diff --git a/docs/fr/privacy/index.html b/docs/fr/privacy/index.html
index 5a7f9b82..611c048e 100644
--- a/docs/fr/privacy/index.html
+++ b/docs/fr/privacy/index.html
@@ -173,7 +173,7 @@
© 2026 Les contributeurs de Flowlight. Flowlight est un logiciel libre, publié sous la GNU General Public License v3.0 .
-
Version 0.12.0 · Sans lien avec Apple ni avec aucun fournisseur d’IA cité sur ce site.
+
Version 0.13.0 · Sans lien avec Apple ni avec aucun fournisseur d’IA cité sur ce site.
diff --git a/docs/fr/threat-model/index.html b/docs/fr/threat-model/index.html
index e48f9d3e..7276be5a 100644
--- a/docs/fr/threat-model/index.html
+++ b/docs/fr/threat-model/index.html
@@ -211,7 +211,7 @@ Signaler une vulnérabilité
© 2026 Les contributeurs de Flowlight. Flowlight est un logiciel libre, publié sous la GNU General Public License v3.0 .
-
Version 0.12.0 · Sans lien avec Apple ni avec aucun fournisseur d’IA cité sur ce site.
+
Version 0.13.0 · Sans lien avec Apple ni avec aucun fournisseur d’IA cité sur ce site.
diff --git a/docs/index.html b/docs/index.html
index d151de1d..53dd98b6 100644
--- a/docs/index.html
+++ b/docs/index.html
@@ -34,7 +34,7 @@
-
+
Skip to content
@@ -75,7 +75,7 @@ See your apps' network activity.brew install --cask xinbetween/tap/flowlightCopy
- v0.12.0 macOS 15+ Universal GPL-3.0 No telemetry
+ v0.13.0 macOS 15+ Universal GPL-3.0 No telemetry
@@ -625,7 +625,7 @@ Know what leaves your Mac.
© 2026 The Flowlight contributors. Flowlight is free software, released under the
GNU General Public License v3.0 .
-
Version 0.12.0 · Not affiliated with Apple or any AI provider named on this site.
+
Version 0.13.0 · Not affiliated with Apple or any AI provider named on this site.
diff --git a/docs/it/about/index.html b/docs/it/about/index.html
index a2aac2d7..a4d95f2a 100644
--- a/docs/it/about/index.html
+++ b/docs/it/about/index.html
@@ -137,7 +137,7 @@ Ringraziamenti
© 2026 Chi contribuisce a Flowlight. Flowlight è software libero, distribuito sotto la GNU General Public License v3.0 .
-
Versione 0.12.0 · Non affiliato ad Apple né ad alcun fornitore di IA citato in questo sito.
+
Versione 0.13.0 · Non affiliato ad Apple né ad alcun fornitore di IA citato in questo sito.
diff --git a/docs/it/docs/index.html b/docs/it/docs/index.html
index a8d3b8e2..bc81260d 100644
--- a/docs/it/docs/index.html
+++ b/docs/it/docs/index.html
@@ -61,7 +61,7 @@
Documentazione
Usare Flowlight
-
Tutto, dal primo avvio alla messa a punto delle regole per gli agenti. Flowlight 0.12.0, macOS 15 o successivo.
+
Tutto, dal primo avvio alla messa a punto delle regole per gli agenti. Flowlight 0.13.0, macOS 15 o successivo.
@@ -736,7 +736,7 @@ Limitazioni
© 2026 Chi contribuisce a Flowlight. Flowlight è software libero, distribuito sotto la GNU General Public License v3.0 .
-
Versione 0.12.0 · Non affiliato ad Apple né ad alcun fornitore di IA citato in questo sito.
+
Versione 0.13.0 · Non affiliato ad Apple né ad alcun fornitore di IA citato in questo sito.
diff --git a/docs/it/index.html b/docs/it/index.html
index 0cf14e7d..81876b2a 100644
--- a/docs/it/index.html
+++ b/docs/it/index.html
@@ -34,7 +34,7 @@
-
+
Vai al contenuto
@@ -75,7 +75,7 @@ Vedi la rete delle tue app.Ca
Metti una stella su GitHub
brew install --cask xinbetween/tap/flowlightCopia
- v0.12.0 macOS 15+ Universale GPL-3.0 Nessuna telemetria
+ v0.13.0 macOS 15+ Universale GPL-3.0 Nessuna telemetria
@@ -616,7 +616,7 @@ Sappi che cosa esce dal tuo Mac.
© 2026 Chi contribuisce a Flowlight. Flowlight è software libero, distribuito sotto la GNU General Public License v3.0 .
-
Versione 0.12.0 · Non affiliato ad Apple né ad alcun fornitore di IA citato in questo sito.
+
Versione 0.13.0 · Non affiliato ad Apple né ad alcun fornitore di IA citato in questo sito.
diff --git a/docs/it/privacy/index.html b/docs/it/privacy/index.html
index 1bcb9bff..e87a59d3 100644
--- a/docs/it/privacy/index.html
+++ b/docs/it/privacy/index.html
@@ -173,7 +173,7 @@
© 2026 Chi contribuisce a Flowlight. Flowlight è software libero, distribuito sotto la GNU General Public License v3.0 .
-
Versione 0.12.0 · Non affiliato ad Apple né ad alcun fornitore di IA citato in questo sito.
+
Versione 0.13.0 · Non affiliato ad Apple né ad alcun fornitore di IA citato in questo sito.
diff --git a/docs/it/threat-model/index.html b/docs/it/threat-model/index.html
index 93a01c5c..5a581a6b 100644
--- a/docs/it/threat-model/index.html
+++ b/docs/it/threat-model/index.html
@@ -209,7 +209,7 @@ Segnalare una vulnerabilità
© 2026 Chi contribuisce a Flowlight. Flowlight è software libero, distribuito sotto la GNU General Public License v3.0 .
-
Versione 0.12.0 · Non affiliato ad Apple né ad alcun fornitore di IA citato in questo sito.
+
Versione 0.13.0 · Non affiliato ad Apple né ad alcun fornitore di IA citato in questo sito.
diff --git a/docs/ja/about/index.html b/docs/ja/about/index.html
index 3c1bef77..bc4b631b 100644
--- a/docs/ja/about/index.html
+++ b/docs/ja/about/index.html
@@ -138,7 +138,7 @@ 謝辞
© 2026 Flowlight コントリビューター。Flowlight は GNU General Public License v3.0 のもとで公開されている自由ソフトウェアです。
-
バージョン 0.12.0 · Apple および本サイトに挙げた AI プロバイダとは関係ありません。
+
バージョン 0.13.0 · Apple および本サイトに挙げた AI プロバイダとは関係ありません。
diff --git a/docs/ja/docs/index.html b/docs/ja/docs/index.html
index 28d432cc..7f2e472f 100644
--- a/docs/ja/docs/index.html
+++ b/docs/ja/docs/index.html
@@ -61,7 +61,7 @@
ドキュメント
Flowlight の使い方
-
初回起動からエージェントのルールの調整まで、すべてここに。Flowlight 0.12.0、macOS 15 以降。
+
初回起動からエージェントのルールの調整まで、すべてここに。Flowlight 0.13.0、macOS 15 以降。
@@ -686,7 +686,7 @@ 制限事項
© 2026 Flowlight コントリビューター。Flowlight は GNU General Public License v3.0 のもとで公開されている自由ソフトウェアです。
-
バージョン 0.12.0 · Apple および本サイトに挙げた AI プロバイダとは関係ありません。
+
バージョン 0.13.0 · Apple および本サイトに挙げた AI プロバイダとは関係ありません。
diff --git a/docs/ja/index.html b/docs/ja/index.html
index b024b048..39177eca 100644
--- a/docs/ja/index.html
+++ b/docs/ja/index.html
@@ -34,7 +34,7 @@
-
+
本文へスキップ
@@ -75,7 +75,7 @@ アプリの通信が見える。brew install --cask xinbetween/tap/flowlightコピー
- v0.12.0 macOS 15+ ユニバーサル GPL-3.0 テレメトリなし
+ v0.13.0 macOS 15+ ユニバーサル GPL-3.0 テレメトリなし
@@ -615,7 +615,7 @@ Mac から何が出ていくのかを知る。
© 2026 Flowlight コントリビューター。Flowlight は GNU General Public License v3.0 のもとで公開されている自由ソフトウェアです。
-
バージョン 0.12.0 · Apple および本サイトに挙げた AI プロバイダとは関係ありません。
+
バージョン 0.13.0 · Apple および本サイトに挙げた AI プロバイダとは関係ありません。
diff --git a/docs/ja/privacy/index.html b/docs/ja/privacy/index.html
index 00be2b52..a5e9af2e 100644
--- a/docs/ja/privacy/index.html
+++ b/docs/ja/privacy/index.html
@@ -171,7 +171,7 @@
© 2026 Flowlight コントリビューター。Flowlight は GNU General Public License v3.0 のもとで公開されている自由ソフトウェアです。
-
バージョン 0.12.0 · Apple および本サイトに挙げた AI プロバイダとは関係ありません。
+
バージョン 0.13.0 · Apple および本サイトに挙げた AI プロバイダとは関係ありません。
diff --git a/docs/ja/threat-model/index.html b/docs/ja/threat-model/index.html
index ac5e780f..23a38aba 100644
--- a/docs/ja/threat-model/index.html
+++ b/docs/ja/threat-model/index.html
@@ -210,7 +210,7 @@ 脆弱性を報告する
© 2026 Flowlight コントリビューター。Flowlight は GNU General Public License v3.0 のもとで公開されている自由ソフトウェアです。
-
バージョン 0.12.0 · Apple および本サイトに挙げた AI プロバイダとは関係ありません。
+
バージョン 0.13.0 · Apple および本サイトに挙げた AI プロバイダとは関係ありません。
diff --git a/docs/ko/about/index.html b/docs/ko/about/index.html
index aa21453a..0ec3545f 100644
--- a/docs/ko/about/index.html
+++ b/docs/ko/about/index.html
@@ -138,7 +138,7 @@ 감사
© 2026 Flowlight 기여자들. Flowlight는 GNU General Public License v3.0 아래 배포되는 자유 소프트웨어입니다.
-
버전 0.12.0 · Apple 및 이 사이트에 언급된 어떤 AI 제공업체와도 무관합니다.
+
버전 0.13.0 · Apple 및 이 사이트에 언급된 어떤 AI 제공업체와도 무관합니다.
diff --git a/docs/ko/docs/index.html b/docs/ko/docs/index.html
index 48e97dac..c0f4ccb0 100644
--- a/docs/ko/docs/index.html
+++ b/docs/ko/docs/index.html
@@ -61,7 +61,7 @@
문서
Flowlight 사용하기
-
첫 실행부터 에이전트 규칙 조정까지 전부. Flowlight 0.12.0, macOS 15 이상.
+
첫 실행부터 에이전트 규칙 조정까지 전부. Flowlight 0.13.0, macOS 15 이상.
@@ -728,7 +728,7 @@ 한계
© 2026 Flowlight 기여자들. Flowlight는 GNU General Public License v3.0 아래 배포되는 자유 소프트웨어입니다.
-
버전 0.12.0 · Apple 및 이 사이트에 언급된 어떤 AI 제공업체와도 무관합니다.
+
버전 0.13.0 · Apple 및 이 사이트에 언급된 어떤 AI 제공업체와도 무관합니다.
diff --git a/docs/ko/index.html b/docs/ko/index.html
index bf592058..b62e2f17 100644
--- a/docs/ko/index.html
+++ b/docs/ko/index.html
@@ -34,7 +34,7 @@
-
+
본문으로 이동
@@ -75,7 +75,7 @@ 앱이 무엇을 하는지 봅니다.
brew install --cask xinbetween/tap/flowlight복사
- v0.12.0 macOS 15+ 유니버설 GPL-3.0 텔레메트리 없음
+ v0.13.0 macOS 15+ 유니버설 GPL-3.0 텔레메트리 없음
@@ -616,7 +616,7 @@ 내 Mac에서 무엇이 나가는지 아세요.
© 2026 Flowlight 기여자들. Flowlight는 GNU General Public License v3.0 아래 배포되는 자유 소프트웨어입니다.
-
버전 0.12.0 · Apple 및 이 사이트에 언급된 어떤 AI 제공업체와도 무관합니다.
+
버전 0.13.0 · Apple 및 이 사이트에 언급된 어떤 AI 제공업체와도 무관합니다.
diff --git a/docs/ko/privacy/index.html b/docs/ko/privacy/index.html
index 2992c1ca..3127e950 100644
--- a/docs/ko/privacy/index.html
+++ b/docs/ko/privacy/index.html
@@ -174,7 +174,7 @@
© 2026 Flowlight 기여자들. Flowlight는 GNU General Public License v3.0 아래 배포되는 자유 소프트웨어입니다.
-
버전 0.12.0 · Apple 및 이 사이트에 언급된 어떤 AI 제공업체와도 무관합니다.
+
버전 0.13.0 · Apple 및 이 사이트에 언급된 어떤 AI 제공업체와도 무관합니다.
diff --git a/docs/ko/threat-model/index.html b/docs/ko/threat-model/index.html
index e319b567..4eb0ce1d 100644
--- a/docs/ko/threat-model/index.html
+++ b/docs/ko/threat-model/index.html
@@ -208,7 +208,7 @@ 취약점 신고하기
© 2026 Flowlight 기여자들. Flowlight는 GNU General Public License v3.0 아래 배포되는 자유 소프트웨어입니다.
-
버전 0.12.0 · Apple 및 이 사이트에 언급된 어떤 AI 제공업체와도 무관합니다.
+
버전 0.13.0 · Apple 및 이 사이트에 언급된 어떤 AI 제공업체와도 무관합니다.
diff --git a/docs/llms-full.txt b/docs/llms-full.txt
index 7f3a4f04..c5a5d294 100644
--- a/docs/llms-full.txt
+++ b/docs/llms-full.txt
@@ -62,7 +62,7 @@ Documentation
Using Flowlight
- Everything from the first launch to tuning the agent rules. Flowlight 0.12.0, macOS 15 or later.
+ Everything from the first launch to tuning the agent rules. Flowlight 0.13.0, macOS 15 or later.
On this page
@@ -839,7 +839,7 @@ Understand your AI agents.
brew install --cask xinbetween/tap/flowlightCopy
- v0.12.0macOS 15+UniversalGPL-3.0No telemetry
+ v0.13.0macOS 15+UniversalGPL-3.0No telemetry
connectionslive
@@ -1398,8 +1398,20 @@ Releases
Downloads, checksums and full notes for each version are on GitHub Releases.
+ 0.13.0
+October 1, 2026Latest
+
+ Change a request on its way out. A new kind of rule edits a matching outgoing
+ request before it reaches the server — add or replace a header, pin a field in the JSON body, or strip
+ one — then lets it continue. It's the mock feature's sibling: where a mock answers a request, this one
+ rewrites it and forwards it. Matched the same way, by host, path and method.
+
+ Where it lives. "Modify requests" sits beside "Mock responses" in the inspection
+ setup. Like a mock, it only touches requests Flowlight decrypts, and the change is recorded on the
+ request so it's never mistaken for what the server actually received.
+
0.12.0
-September 30, 2026Latest
+September 30, 2026
A refreshed interface, on one design system. Flowlight now draws from a single set of
tokens — a brand palette that resolves for light and dark, consistent spacing, and shared surfaces — so
diff --git a/docs/llms.txt b/docs/llms.txt
index d816b8ab..106f4362 100644
--- a/docs/llms.txt
+++ b/docs/llms.txt
@@ -1,6 +1,6 @@
# Flowlight
-> Free, open-source (GPL-3.0) application-aware network monitor for macOS, with focused visibility into AI agents. It attributes observed TCP and UDP activity to the application that made it and records the destination, protocol and byte counts, keeping local history from second to year. Recognized AI agents are listed by name along with the tools and MCP servers they start, under per-agent allowlists. It can also refuse, once asked: a rule blocks an application, a destination or a URL for as long as you specify, and a guardrail withholds a tool from an agent before its model is offered it. Runs on macOS 15 or later; capture is by a sampler or a Network Extension. Current version: 0.12.0.
+> Free, open-source (GPL-3.0) application-aware network monitor for macOS, with focused visibility into AI agents. It attributes observed TCP and UDP activity to the application that made it and records the destination, protocol and byte counts, keeping local history from second to year. Recognized AI agents are listed by name along with the tools and MCP servers they start, under per-agent allowlists. It can also refuse, once asked: a rule blocks an application, a destination or a URL for as long as you specify, and a guardrail withholds a tool from an agent before its model is offered it. Runs on macOS 15 or later; capture is by a sampler or a Network Extension. Current version: 0.13.0.
- [Download Flowlight.dmg](https://github.com/xinbetween/flowlight/releases/latest/download/Flowlight.dmg)
- [Source code](https://github.com/xinbetween/flowlight)
diff --git a/docs/privacy/index.html b/docs/privacy/index.html
index d466c591..d057e20a 100644
--- a/docs/privacy/index.html
+++ b/docs/privacy/index.html
@@ -174,7 +174,7 @@
© 2026 The Flowlight contributors. Flowlight is free software, released under the
GNU General Public License v3.0 .
-
Version 0.12.0 · Not affiliated with Apple or any AI provider named on this site.
+
Version 0.13.0 · Not affiliated with Apple or any AI provider named on this site.
diff --git a/docs/pt-PT/about/index.html b/docs/pt-PT/about/index.html
index 811e6696..53ae95f3 100644
--- a/docs/pt-PT/about/index.html
+++ b/docs/pt-PT/about/index.html
@@ -137,7 +137,7 @@ Agradecimentos
© 2026 Quem contribui para o Flowlight. O Flowlight é software livre, publicado sob a GNU General Public License v3.0 .
-
Versão 0.12.0 · Sem qualquer ligação à Apple ou a algum fornecedor de IA mencionado neste site.
+
Versão 0.13.0 · Sem qualquer ligação à Apple ou a algum fornecedor de IA mencionado neste site.
diff --git a/docs/pt-PT/docs/index.html b/docs/pt-PT/docs/index.html
index c7ea1a75..9ab38622 100644
--- a/docs/pt-PT/docs/index.html
+++ b/docs/pt-PT/docs/index.html
@@ -61,7 +61,7 @@
Documentação
Usar o Flowlight
-
Tudo, da primeira abertura ao ajuste das regras dos agentes. Flowlight 0.12.0, macOS 15 ou posterior.
+
Tudo, da primeira abertura ao ajuste das regras dos agentes. Flowlight 0.13.0, macOS 15 ou posterior.
@@ -733,7 +733,7 @@ Limitações
© 2026 Quem contribui para o Flowlight. O Flowlight é software livre, publicado sob a GNU General Public License v3.0 .
-
Versão 0.12.0 · Sem qualquer ligação à Apple ou a algum fornecedor de IA mencionado neste site.
+
Versão 0.13.0 · Sem qualquer ligação à Apple ou a algum fornecedor de IA mencionado neste site.
diff --git a/docs/pt-PT/index.html b/docs/pt-PT/index.html
index 3e7ffb80..beb2c2b6 100644
--- a/docs/pt-PT/index.html
+++ b/docs/pt-PT/index.html
@@ -34,7 +34,7 @@
-
+
Ir para o conteúdo
@@ -75,7 +75,7 @@ Veja a atividade de rede das suas apps.
brew install --cask xinbetween/tap/flowlightCopiar
- v0.12.0 macOS 15+ Universal GPL-3.0 Sem telemetria
+ v0.13.0 macOS 15+ Universal GPL-3.0 Sem telemetria
@@ -616,7 +616,7 @@ Saiba o que sai do seu Mac.
© 2026 Quem contribui para o Flowlight. O Flowlight é software livre, publicado sob a GNU General Public License v3.0 .
-
Versão 0.12.0 · Sem qualquer ligação à Apple ou a algum fornecedor de IA mencionado neste site.
+
Versão 0.13.0 · Sem qualquer ligação à Apple ou a algum fornecedor de IA mencionado neste site.
diff --git a/docs/pt-PT/privacy/index.html b/docs/pt-PT/privacy/index.html
index 2b288b59..8fd8b112 100644
--- a/docs/pt-PT/privacy/index.html
+++ b/docs/pt-PT/privacy/index.html
@@ -173,7 +173,7 @@
© 2026 Quem contribui para o Flowlight. O Flowlight é software livre, publicado sob a GNU General Public License v3.0 .
-
Versão 0.12.0 · Sem qualquer ligação à Apple ou a algum fornecedor de IA mencionado neste site.
+
Versão 0.13.0 · Sem qualquer ligação à Apple ou a algum fornecedor de IA mencionado neste site.
diff --git a/docs/pt-PT/threat-model/index.html b/docs/pt-PT/threat-model/index.html
index 381a09df..87665077 100644
--- a/docs/pt-PT/threat-model/index.html
+++ b/docs/pt-PT/threat-model/index.html
@@ -210,7 +210,7 @@ Comunicar uma vulnerabilidade
© 2026 Quem contribui para o Flowlight. O Flowlight é software livre, publicado sob a GNU General Public License v3.0 .
-
Versão 0.12.0 · Sem qualquer ligação à Apple ou a algum fornecedor de IA mencionado neste site.
+
Versão 0.13.0 · Sem qualquer ligação à Apple ou a algum fornecedor de IA mencionado neste site.
diff --git a/docs/releases/index.html b/docs/releases/index.html
index bac6941a..7ccfb5b0 100644
--- a/docs/releases/index.html
+++ b/docs/releases/index.html
@@ -55,7 +55,20 @@ What's new
- 0.12.0 September 30, 2026 Latest
+ 0.13.0 October 1, 2026 Latest
+
+ Change a request on its way out. A new kind of rule edits a matching outgoing
+ request before it reaches the server — add or replace a header, pin a field in the JSON body, or strip
+ one — then lets it continue. It's the mock feature's sibling: where a mock answers a request, this one
+ rewrites it and forwards it. Matched the same way, by host, path and method.
+ Where it lives. "Modify requests" sits beside "Mock responses" in the inspection
+ setup. Like a mock, it only touches requests Flowlight decrypts, and the change is recorded on the
+ request so it's never mistaken for what the server actually received.
+
+
+
+
+
A refreshed interface, on one design system. Flowlight now draws from a single set of
tokens — a brand palette that resolves for light and dark, consistent spacing, and shared surfaces — so
@@ -1013,7 +1026,7 @@ What's new
© 2026 The Flowlight contributors. Flowlight is free software, released under the
GNU General Public License v3.0 .
-
Version 0.12.0 · Not affiliated with Apple or any AI provider named on this site.
+
Version 0.13.0 · Not affiliated with Apple or any AI provider named on this site.
diff --git a/docs/sitemap.xml b/docs/sitemap.xml
index 23b47e0a..dae7536a 100644
--- a/docs/sitemap.xml
+++ b/docs/sitemap.xml
@@ -4,7 +4,7 @@
https://flowlight.xinbetween.com/docs/ 2026-09-28
https://flowlight.xinbetween.com/ 2026-09-30
https://flowlight.xinbetween.com/privacy/ 2026-09-27
- https://flowlight.xinbetween.com/releases/ 2026-09-30
+ https://flowlight.xinbetween.com/releases/ 2026-10-01
https://flowlight.xinbetween.com/threat-model/ 2026-09-27
https://flowlight.xinbetween.com/de/about/ 2026-09-26
https://flowlight.xinbetween.com/de/docs/ 2026-09-28
diff --git a/docs/threat-model/index.html b/docs/threat-model/index.html
index e45d453d..937520ba 100644
--- a/docs/threat-model/index.html
+++ b/docs/threat-model/index.html
@@ -209,7 +209,7 @@ Reporting a vulnerability
© 2026 The Flowlight contributors. Flowlight is free software, released under the
GNU General Public License v3.0 .
-
Version 0.12.0 · Not affiliated with Apple or any AI provider named on this site.
+
Version 0.13.0 · Not affiliated with Apple or any AI provider named on this site.
diff --git a/docs/zh-Hans/about/index.html b/docs/zh-Hans/about/index.html
index 0d7d860a..0d1f1919 100644
--- a/docs/zh-Hans/about/index.html
+++ b/docs/zh-Hans/about/index.html
@@ -137,7 +137,7 @@ 致谢
© 2026 Flowlight 贡献者。Flowlight 是自由软件,依 GNU General Public License v3.0 发布。
-
版本 0.12.0 · 与 Apple 及本站提及的任何 AI 提供方均无关联。
+
版本 0.13.0 · 与 Apple 及本站提及的任何 AI 提供方均无关联。
diff --git a/docs/zh-Hans/docs/index.html b/docs/zh-Hans/docs/index.html
index 8ed00209..ecfa416d 100644
--- a/docs/zh-Hans/docs/index.html
+++ b/docs/zh-Hans/docs/index.html
@@ -61,7 +61,7 @@
文档
使用 Flowlight
-
从第一次启动到调整代理规则,需要的都在这里。Flowlight 0.12.0,macOS 15 或更高版本。
+
从第一次启动到调整代理规则,需要的都在这里。Flowlight 0.13.0,macOS 15 或更高版本。
@@ -689,7 +689,7 @@ 限制
© 2026 Flowlight 贡献者。Flowlight 是自由软件,依 GNU General Public License v3.0 发布。
-
版本 0.12.0 · 与 Apple 及本站提及的任何 AI 提供方均无关联。
+
版本 0.13.0 · 与 Apple 及本站提及的任何 AI 提供方均无关联。
diff --git a/docs/zh-Hans/index.html b/docs/zh-Hans/index.html
index f2f6de4a..5cbeb776 100644
--- a/docs/zh-Hans/index.html
+++ b/docs/zh-Hans/index.html
@@ -34,7 +34,7 @@
-
+
跳到正文
@@ -75,7 +75,7 @@ 看清应用的网络活动。
brew install --cask xinbetween/tap/flowlight拷贝
- v0.12.0 macOS 15+ 通用架构 GPL-3.0 无遥测
+ v0.13.0 macOS 15+ 通用架构 GPL-3.0 无遥测
@@ -613,7 +613,7 @@ 知道什么离开了你的 Mac。
© 2026 Flowlight 贡献者。Flowlight 是自由软件,依 GNU General Public License v3.0 发布。
-
版本 0.12.0 · 与 Apple 及本站提及的任何 AI 提供方均无关联。
+
版本 0.13.0 · 与 Apple 及本站提及的任何 AI 提供方均无关联。
diff --git a/docs/zh-Hans/privacy/index.html b/docs/zh-Hans/privacy/index.html
index 68436f19..8b3ae140 100644
--- a/docs/zh-Hans/privacy/index.html
+++ b/docs/zh-Hans/privacy/index.html
@@ -171,7 +171,7 @@
© 2026 Flowlight 贡献者。Flowlight 是自由软件,依 GNU General Public License v3.0 发布。
-
版本 0.12.0 · 与 Apple 及本站提及的任何 AI 提供方均无关联。
+
版本 0.13.0 · 与 Apple 及本站提及的任何 AI 提供方均无关联。
diff --git a/docs/zh-Hans/threat-model/index.html b/docs/zh-Hans/threat-model/index.html
index aab50f5c..7567a16e 100644
--- a/docs/zh-Hans/threat-model/index.html
+++ b/docs/zh-Hans/threat-model/index.html
@@ -197,7 +197,7 @@ 报告漏洞
© 2026 Flowlight 贡献者。Flowlight 是自由软件,依 GNU General Public License v3.0 发布。
-
版本 0.12.0 · 与 Apple 及本站提及的任何 AI 提供方均无关联。
+
版本 0.13.0 · 与 Apple 及本站提及的任何 AI 提供方均无关联。
diff --git a/docs/zh-Hant/about/index.html b/docs/zh-Hant/about/index.html
index 357a6943..ae950462 100644
--- a/docs/zh-Hant/about/index.html
+++ b/docs/zh-Hant/about/index.html
@@ -137,7 +137,7 @@ 致謝
© 2026 Flowlight 貢獻者。Flowlight 是自由軟體,依 GNU General Public License v3.0 發布。
-
版本 0.12.0 · 與 Apple 及本站提及的任何 AI 供應商均無關聯。
+
版本 0.13.0 · 與 Apple 及本站提及的任何 AI 供應商均無關聯。
diff --git a/docs/zh-Hant/docs/index.html b/docs/zh-Hant/docs/index.html
index 1c425c59..ff1f7dc8 100644
--- a/docs/zh-Hant/docs/index.html
+++ b/docs/zh-Hant/docs/index.html
@@ -61,7 +61,7 @@
說明文件
使用 Flowlight
-
從第一次啟動到調整代理規則,全都在這裡。Flowlight 0.12.0,macOS 15 或以上版本。
+
從第一次啟動到調整代理規則,全都在這裡。Flowlight 0.13.0,macOS 15 或以上版本。
@@ -695,7 +695,7 @@ 限制
© 2026 Flowlight 貢獻者。Flowlight 是自由軟體,依 GNU General Public License v3.0 發布。
-
版本 0.12.0 · 與 Apple 及本站提及的任何 AI 供應商均無關聯。
+
版本 0.13.0 · 與 Apple 及本站提及的任何 AI 供應商均無關聯。
diff --git a/docs/zh-Hant/index.html b/docs/zh-Hant/index.html
index b5d07a54..47574b34 100644
--- a/docs/zh-Hant/index.html
+++ b/docs/zh-Hant/index.html
@@ -34,7 +34,7 @@
-
+
跳至內容
@@ -75,7 +75,7 @@ 看見每個 App 的網路活動。brew install --cask xinbetween/tap/flowlight拷貝
- v0.12.0 macOS 15+ 通用架構 GPL-3.0 無遙測
+ v0.13.0 macOS 15+ 通用架構 GPL-3.0 無遙測
@@ -614,7 +614,7 @@ 知道有什麼離開了你的 Mac。
© 2026 Flowlight 貢獻者。Flowlight 是自由軟體,依 GNU General Public License v3.0 發布。
-
版本 0.12.0 · 與 Apple 及本站提及的任何 AI 供應商均無關聯。
+
版本 0.13.0 · 與 Apple 及本站提及的任何 AI 供應商均無關聯。
diff --git a/docs/zh-Hant/privacy/index.html b/docs/zh-Hant/privacy/index.html
index a07830ed..ed46a274 100644
--- a/docs/zh-Hant/privacy/index.html
+++ b/docs/zh-Hant/privacy/index.html
@@ -171,7 +171,7 @@
© 2026 Flowlight 貢獻者。Flowlight 是自由軟體,依 GNU General Public License v3.0 發布。
-
版本 0.12.0 · 與 Apple 及本站提及的任何 AI 供應商均無關聯。
+
版本 0.13.0 · 與 Apple 及本站提及的任何 AI 供應商均無關聯。
diff --git a/docs/zh-Hant/threat-model/index.html b/docs/zh-Hant/threat-model/index.html
index 51ccfaa1..7b1cfac5 100644
--- a/docs/zh-Hant/threat-model/index.html
+++ b/docs/zh-Hant/threat-model/index.html
@@ -198,7 +198,7 @@ 回報漏洞
© 2026 Flowlight 貢獻者。Flowlight 是自由軟體,依 GNU General Public License v3.0 發布。
-
版本 0.12.0 · 與 Apple 及本站提及的任何 AI 供應商均無關聯。
+
版本 0.13.0 · 與 Apple 及本站提及的任何 AI 供應商均無關聯。
diff --git a/project.yml b/project.yml
index 68c3fb41..a57e66a7 100644
--- a/project.yml
+++ b/project.yml
@@ -11,7 +11,7 @@ settings:
DEVELOPMENT_TEAM: ""
CODE_SIGN_STYLE: Automatic
ENABLE_HARDENED_RUNTIME: YES
- MARKETING_VERSION: "0.12.0"
+ MARKETING_VERSION: "0.13.0"
CURRENT_PROJECT_VERSION: "22"
targets:
Flowlight:
diff --git a/site/pages/releases.html b/site/pages/releases.html
index 13005dad..272b14c0 100644
--- a/site/pages/releases.html
+++ b/site/pages/releases.html
@@ -13,7 +13,20 @@ What's new
- 0.12.0 September 30, 2026 Latest
+ 0.13.0 October 1, 2026 Latest
+
+ Change a request on its way out. A new kind of rule edits a matching outgoing
+ request before it reaches the server — add or replace a header, pin a field in the JSON body, or strip
+ one — then lets it continue. It's the mock feature's sibling: where a mock answers a request, this one
+ rewrites it and forwards it. Matched the same way, by host, path and method.
+ Where it lives. "Modify requests" sits beside "Mock responses" in the inspection
+ setup. Like a mock, it only touches requests Flowlight decrypts, and the change is recorded on the
+ request so it's never mistaken for what the server actually received.
+
+
+
+
+
A refreshed interface, on one design system. Flowlight now draws from a single set of
tokens — a brand palette that resolves for light and dark, consistent spacing, and shared surfaces — so