diff --git a/Flowlight/Inspection/InspectionController.swift b/Flowlight/Inspection/InspectionController.swift
index 835360a..e7a5213 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 0000000..e0ee605
--- /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 53ada31..73dbe8c 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 0000000..f3a7f02
--- /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 0000000..bf28c02
--- /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 a4cf886..f871b1f 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 f3d86c1..7bd1646 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 232501c..f231b8e 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 bdda1dd..435ac39 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 20a5406..9aca252 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 c0a785f..822d7b2 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 760802e..b01b8ab 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 d672fe3..c71c6e0 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 2b82374..d689032 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 eb9f599..0bf4de8 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 b2c7521..ac26d22 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 6b04367..8ebc5f0 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 5be27b9..6968170 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 e4819a7..32bdea2 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 87e1535..fb2bb29 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 641c61e..339245e 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 5a7f9b8..611c048 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 e48f9d3..7276be5 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 d151de1..53dd98b 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 a2aac2d..a4d95f2 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 a8d3b8e..bc81260 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 0cf14e7..81876b2 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 1bcb9bf..e87a59d 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 93a01c5..5a581a6 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 3c1bef7..bc4b631 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 28d432c..7f2e472 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 b024b04..39177ec 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 00be2b5..a5e9af2 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 ac5e780..23a38ab 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 aa21453..0ec3545 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 48e97da..c0f4ccb 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 bf59205..b62e2f1 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 2992c1c..3127e95 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 e319b56..4eb0ce1 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 7f3a4f0..c5a5d29 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 d816b8a..106f436 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 d466c59..d057e20 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 811e669..53ae95f 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 c7ea1a7..9ab3862 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 3e7ffb8..beb2c2b 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 2b288b5..8fd8b11 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 381a09d..8766507 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 bac6941..7ccfb5b 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 23b47e0..dae7536 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 e45d453..937520b 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 0d7d860..0d1f191 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 8ed0020..ecfa416 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 f2f6de4..5cbeb77 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 68436f1..8b3ae14 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 aab50f5..7567a16 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 357a694..ae95046 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 1c425c5..ff1f7dc 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 b5d07a5..47574b3 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 a07830e..ed46a27 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 51ccfaa..7b1cfac 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 68c3fb4..a57e66a 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 13005da..272b14c 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