@openafw/openafw 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 afw contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/PRIVACY.md ADDED
@@ -0,0 +1,377 @@
1
+ # Privacy & data handling
2
+
3
+ afw is local-first. Everything you capture stays on your own
4
+ machine under `~/.afw/`. The published package does **not** collect
5
+ telemetry, does **not** report crashes, and does **not** send any data
6
+ **about you or your usage** anywhere. There is no `openafw.com` /
7
+ afw / Anthropic host wired into the package.
8
+
9
+ From v0.4 the daemon makes one outbound request of its own: a daily
10
+ **version check** against the public npm registry — see the v0.4
11
+ section. It carries no user data, contacts only the registry npm
12
+ itself uses, and can be switched off. From v0.5 an **opt-in** daily
13
+ **pricing-catalog refresh** (off by default) can fetch a public price
14
+ list from models.dev — see the v0.5 section.
15
+
16
+ From v1, when you route an agent onto a managed **subscription**
17
+ provider, afw may refresh that subscription's OAuth token against
18
+ the agent's own identity provider — see the v1 section. It is
19
+ request-driven, stays within the agent's existing upstream, sends only
20
+ the token the agent already holds, and never contacts us.
21
+
22
+ This file is updated **every release** with the exact behaviour of
23
+ the version it ships with. If a future release sends any data **about
24
+ you or your usage** anywhere, it will be:
25
+ 1. Strictly opt-in.
26
+ 2. Documented here under that version's section with what is sent,
27
+ to where, and why.
28
+ 3. Documented in the release notes.
29
+
30
+ (A version check against the public package registry is not in that
31
+ category — it reveals nothing about you — but it is documented in full
32
+ below and is disableable all the same.)
33
+
34
+ Audit the claims yourself by reading the source — `git grep https://`
35
+ finds every URL the package can talk to.
36
+
37
+ ---
38
+
39
+ ## v1
40
+
41
+ v1 adds **model routing & combos** (`afw route` and the Control ·
42
+ Routing pages) — you can reconfigure which model(s) a wired agent's
43
+ traffic reaches. This changes where your agent traffic goes, so here is
44
+ exactly what it does.
45
+
46
+ ### Where your traffic goes
47
+
48
+ By default every route is plain **passthrough**: traffic reaches the
49
+ same upstream your agent already chose, unchanged. Nothing about routing
50
+ takes effect until you explicitly opt a route in.
51
+
52
+ When you do route an agent to a different model or a combo, its requests
53
+ go to the upstream **you configured for that model's provider** — a URL
54
+ you typed in yourself, via the UI or `afw route provider add`. afw
55
+ still opens no outbound surface of its own: it only ever talks to
56
+ upstreams you named. There is no afw/Anthropic host in this path.
57
+
58
+ ### `secrets.json` — your API keys
59
+
60
+ Cross-agent routing and the vision companion may need API keys for
61
+ providers your agent never authenticates to itself. Those keys are
62
+ stored **locally** in `~/.afw/secrets.json`, written with file mode
63
+ `0600` (readable only by your user account).
64
+
65
+ * The keys are sent **only** to the upstream URLs you configured for
66
+ their providers — nowhere else, never to us.
67
+ * The secret store is **write-only** across the CLI and the daemon API:
68
+ you can set and remove keys, but a value can never be read back out.
69
+ The UI shows only which key *refs* exist, never their contents.
70
+ * Removing a provider removes its stored key.
71
+
72
+ ### Captured credentials & subscription token refresh
73
+
74
+ So one agent can use another agent's model, afw manages every
75
+ agent's credentials itself. `afw wire` reads each agent's existing
76
+ credential from its **own config** and handles it one of two ways.
77
+
78
+ **Static API keys** — Claude Code's `ANTHROPIC_API_KEY`, Codex's
79
+ `OPENAI_API_KEY`, Hermes / OpenClaw provider keys — are copied into
80
+ `~/.afw/secrets.json` (mode `0600`, see above) under a
81
+ `provider:<agent>/<provider>` ref. They are read from files already on
82
+ your machine and only ever sent to the same upstream that agent
83
+ already calls.
84
+
85
+ **Subscription logins** — Claude Code's Claude.ai login, Codex's
86
+ ChatGPT login — use OAuth tokens that expire. afw does **not** copy
87
+ these into `secrets.json`. It reads them, only when a managed route
88
+ needs them, from the agent's own credential store: the macOS Keychain
89
+ item `Claude Code-credentials` (fallback `~/.claude/.credentials.json`)
90
+ and `~/.codex/auth.json`.
91
+
92
+ #### The refresh call
93
+
94
+ A subscription access token expires. When a managed subscription route
95
+ is used and the token is within five minutes of expiry, afw
96
+ refreshes it:
97
+
98
+ * **Request:** an HTTPS `POST` to the agent's **own** OAuth provider —
99
+ `https://platform.claude.com/v1/oauth/token` for Claude Code,
100
+ `https://auth.openai.com/oauth/token` for Codex. These are the exact
101
+ endpoints the agent itself calls to refresh the exact same token.
102
+ * **What it sends:** your `refresh_token` and the agent's public
103
+ `client_id`. Nothing else — no machine ID, no usage data, nothing
104
+ identifying you beyond the token the agent already holds.
105
+ * **Where it does NOT go:** not `openafw.com`, not any afw server.
106
+ We never see this request.
107
+ * **When:** only when you have opted a route onto a managed
108
+ subscription provider *and* its token is near expiry. A passthrough
109
+ route never triggers it.
110
+
111
+ #### Rotated tokens are written back
112
+
113
+ OAuth refresh tokens are single-use — a refresh returns a new one.
114
+ afw writes the rotated token back to the agent's own store
115
+ (Keychain / `auth.json`, atomically) so the agent picks it up on its
116
+ next read instead of failing. afw is a **cooperative co-refresher**
117
+ of the same credential, not a competing one.
118
+
119
+ This stays within afw's contract — data about you goes only to the
120
+ upstream your agent already talks to, and an agent's OAuth provider is
121
+ part of that upstream — but it is called out here, and in the release
122
+ notes, all the same.
123
+
124
+ #### Request shape on managed subscription routes
125
+
126
+ Anthropic gates a Claude.ai (subscription) OAuth token behind specific
127
+ request-shape requirements — the token is accepted only when the
128
+ request claims **Claude Code shape**. When afw routes a call onto a
129
+ managed Claude.ai subscription provider, it adjusts the outgoing
130
+ request to satisfy those gates:
131
+
132
+ * Two `anthropic-beta` flags on the request:
133
+ `claude-code-20250219` and `oauth-2025-04-20`.
134
+ * A fixed Claude-Code identity prepended to the request's `system`
135
+ block: *"You are Claude Code, Anthropic's official CLI for Claude."*
136
+ Your agent's own system prompt is preserved after it.
137
+
138
+ This is a **protocol-level concession** to the upstream — the same
139
+ shape Claude Code itself sends on every call. No new data about you
140
+ leaves the machine, and no new outbound surface is opened. Without
141
+ these adjustments, Anthropic rejects subscription tokens used outside
142
+ Claude Code with `4xx` errors or aggressive throttling. Disclosed here
143
+ for the same reason the refresh call is: afw is shaping a request
144
+ on your behalf, and you should know what shape it takes.
145
+
146
+ ### New files on disk
147
+
148
+ | Path | What |
149
+ |---|---|
150
+ | `~/.afw/models.json` | The catalog of providers and models afw can route to. Provider base URLs, wire formats, model metadata. No secrets — keys live in `secrets.json`. Seeded from your wire and extended by you. |
151
+ | `~/.afw/routing.json` | Per-agent routing decisions and combo definitions. No secrets. |
152
+ | `~/.afw/secrets.json` | Provider API keys — typed in by you or captured from an agent's own config by `afw wire`. File mode `0600`. Local-only; see above. |
153
+ | `~/.afw/oauth-<agent>.lock` | Transient lock held only while afw refreshes a subscription OAuth token, deleted immediately after. No contents. |
154
+
155
+ These are local files. Routing uploads nothing; the only outbound call
156
+ this version adds is the subscription token refresh described above.
157
+
158
+ ---
159
+
160
+ ## v0.5
161
+
162
+ v0.5 adds an **opt-in pricing-catalog refresh**. By default afw prices
163
+ calls from the catalog bundled inside the package (and your own
164
+ `~/.afw/pricing.json` overrides) — entirely offline. If you want prices
165
+ to stay fresh without upgrading afw, you can turn on a daily refresh.
166
+
167
+ ### The pricing refresh
168
+
169
+ * **Off by default.** Enable with `autoRefreshPricing: true` in
170
+ `~/.afw/config.json`. With it off (the default) the daemon makes no
171
+ pricing requests at all.
172
+ * **Request:** when on, one plain HTTPS `GET` per day to
173
+ `https://models.dev/api.json` — a public, community-maintained price
174
+ list. The same source the bundled catalog is built from.
175
+ * **What it sends:** nothing about you. No user data, no machine ID, no
176
+ model ids you've used, no usage data — just an anonymous GET for the
177
+ public catalog.
178
+ * **Where it does NOT go:** not `openafw.com`, not any afw or
179
+ Anthropic server, never your captured traffic. We never see this request.
180
+ * The result is cached at `~/.afw/pricing-catalog.json` and read locally;
181
+ nothing leaves your machine.
182
+
183
+ This is the second sanctioned outbound call (after the v0.4 version check),
184
+ and like that one it is opt-in here, hits only a public third-party
185
+ endpoint, and is fully disableable.
186
+
187
+ ### New files on disk
188
+
189
+ | Path | What |
190
+ |---|---|
191
+ | `~/.afw/pricing-catalog.json` | Cached price catalog from the last refresh (only when `autoRefreshPricing` is on). |
192
+
193
+ ### Session correlation (opt-in, local-only)
194
+
195
+ To attribute captured Claude Code calls to the specific window (instance) and
196
+ parallel sub-agent that made them — fleet management — afw can correlate
197
+ captures with Claude Code's own local transcripts.
198
+
199
+ * **Off by default.** Enable with `correlateSessions: true` in
200
+ `~/.afw/config.json`.
201
+ * **What it reads:** `~/.claude/projects/**/*.jsonl` — Claude Code's session
202
+ transcripts, already on your disk. It joins each captured call to its session
203
+ on the upstream `message.id` (which afw already has in the stored
204
+ response) and records the session id + sub-agent id locally.
205
+ * **What it sends:** nothing. No network calls at all. This is a local file
206
+ read; results are written only to your local trace DB. It never contacts
207
+ `openafw.com`, Anthropic, or any host.
208
+ * Reads stop working gracefully when the files are absent (e.g. the daemon and
209
+ agent run on different machines) — calls simply stay unattributed.
210
+
211
+ ---
212
+
213
+ ## v0.4
214
+
215
+ v0.4 adds the **update system** — `afw update`, `afw rollback`,
216
+ the dashboard update banner, and optional auto-update. This is the
217
+ first afw behaviour that contacts a host other than your agent's
218
+ own upstream. Here is exactly what it does.
219
+
220
+ ### The version check
221
+
222
+ The daemon checks once a day whether a newer afw has been
223
+ published:
224
+
225
+ * **Request:** a plain HTTPS `GET` to
226
+ `https://registry.npmjs.org/@openafw%2Fopenafw/latest` — the public
227
+ npm registry, the same one `npm install` used to put afw on your
228
+ machine.
229
+ * **What it sends:** the package name, in the URL. Nothing else. No
230
+ user data, no machine ID, no install ID, no usage data, no header
231
+ that identifies you. The registry sees an anonymous GET, exactly
232
+ like `npm view @openafw/openafw version`.
233
+ * **Where it does NOT go:** not `openafw.com`, not any afw or
234
+ Anthropic server. We never see this request.
235
+ * **On by default**, because it carries nothing about you. Turn it
236
+ off completely with `updateCheck: false` in `~/.afw/config.json`
237
+ — the daemon then makes no version checks at all.
238
+ * The result is cached in `~/.afw/update.json` and never leaves
239
+ your machine.
240
+
241
+ ### Installing an update
242
+
243
+ `afw update`, or "Update now" in the dashboard, runs
244
+ `npm install -g @openafw/openafw@<version>` — exactly what you would
245
+ run by hand. Before installing, afw snapshots your trace database
246
+ into `~/.afw/backups/`. After installing it restarts the daemon
247
+ (waiting for a quiet moment so no in-flight agent call is dropped) and
248
+ health-checks the new version; if the new version does not come up
249
+ healthy it automatically reinstalls the previous one and restores the
250
+ database snapshot. None of this uploads anything.
251
+
252
+ ### Auto-update
253
+
254
+ Off until you turn it on. After your first manual update, afw asks
255
+ once whether to auto-update future releases. If you say yes
256
+ (`autoUpdate: true` in `~/.afw/config.json`), the daily check will
257
+ install a new version on its own — still backed up, still
258
+ health-gated, still auto-rolled-back on failure. Say no and updates
259
+ stay manual.
260
+
261
+ ### New files on disk
262
+
263
+ | Path | What |
264
+ |---|---|
265
+ | `~/.afw/config.json` | Your preferences: `updateCheck`, `autoUpdate`, `autoUpdateAsked`. |
266
+ | `~/.afw/update.json` | Cached result of the last version check. |
267
+ | `~/.afw/update-progress.json` | State of the current / last update, for the CLI and dashboard. |
268
+ | `~/.afw/backups/db-pre-<version>-<ts>.sqlite` | Database snapshots taken before each update so a rollback can restore them. The most recent 3 are kept. |
269
+ | `~/.afw/pending-db-restore` | Transient marker written during a rollback; consumed and deleted at the next daemon boot. |
270
+
271
+ All of these are local files. Nothing in the update system uploads
272
+ anything.
273
+
274
+ ---
275
+
276
+ ## v0.1
277
+
278
+ ### What afw writes to your disk
279
+
280
+ | Path | What |
281
+ |---|---|
282
+ | `~/.afw/wire/routes.json` | Mapping from `<agent>/<provider>` route keys to upstream URLs, written by `afw wire`. |
283
+ | `~/.afw/wire/traces/traces.db` | SQLite database of every captured action — model calls (prompts, responses, tool_use blocks, tokens, cost), MCP frames (server, method, params/result), durations. |
284
+ | `~/.afw/backups/manifest.json` + `~/.afw/backups/<timestamp>/<agent>/<file>` | Byte-exact copies of each agent config afw rewrote, plus a manifest with `sha256` checksums of before and after. `afw unwire` uses these to restore. |
285
+ | `~/.afw/logs/daemon.log`, `daemon.err` | Daemon stdout / stderr — info lines for each captured request, errors for parse failures. Plain text. |
286
+ | `~/.afw/wire/daemon.sock` (planned) | Unix-domain socket for CLI ↔ daemon. Not yet used in v0.1. |
287
+
288
+ ### What afw sends over the network
289
+
290
+ **One pattern only:** the HTTP proxy at `http://localhost:9877/wire/...`
291
+ forwards your agent's outbound request to whatever upstream is
292
+ registered in `routes.json`. Concretely:
293
+
294
+ * If your agent calls `api.anthropic.com`, afw relays the same
295
+ bytes to `api.anthropic.com`.
296
+ * If your agent calls `api.openai.com`, afw relays to `api.openai.com`.
297
+ * If your agent calls some internal/private endpoint, afw relays
298
+ there.
299
+
300
+ The bytes are unchanged — same body, same auth headers. afw
301
+ **also** keeps a normalized copy of the request and response on your
302
+ disk for `afw list / show / report`.
303
+
304
+ **Where afw does NOT send data:**
305
+
306
+ * No `openafw.com` host. No `*.openafw.com` calls anywhere in
307
+ the source. Grep `packages/afw/src/` to confirm.
308
+ * No telemetry endpoint, anonymous or otherwise.
309
+ * No auto-update check (`npm update -g @openafw/openafw` is something
310
+ you run manually).
311
+ * No error / crash reporting.
312
+ * No license-check ping (no license system exists in v0.1).
313
+
314
+ ### What other tools can read
315
+
316
+ * `afw report <run-id>` produces markdown or JSON to stdout or a
317
+ file you choose. Nothing is uploaded; you decide where the output
318
+ goes. Pass `--redact` to mask common credential shapes (API keys,
319
+ bearer tokens, GitHub PATs, AWS keys, Google keys, Slack tokens)
320
+ before sharing.
321
+ * `afw list --json` and the REST API at `/api/runs` are served on
322
+ `localhost:9877` only. Not bound to external interfaces. Anyone
323
+ with access to your user account on this machine can read them.
324
+
325
+ ### What's intentionally not in v0.1
326
+
327
+ * Any form of telemetry, opt-in or otherwise.
328
+ * Crash reporting.
329
+ * Auto-update checks.
330
+ * License validation.
331
+ * Cloud sync.
332
+ * Any cryptographic identifiers (machine IDs, install IDs, etc.).
333
+
334
+ ### Source-code receipts
335
+
336
+ Confirm the above by reading the package source. Two greps cover
337
+ nearly the entire surface:
338
+
339
+ ```bash
340
+ # Every URL constant or template literal that starts with http
341
+ git grep -E "https?://" packages/afw/src/ | grep -v "node:" | grep -v "//---"
342
+
343
+ # Every call to fetch(), which is how outbound HTTP happens in Node
344
+ git grep -n "fetch(" packages/afw/src/
345
+ ```
346
+
347
+ What you should find:
348
+ * The proxy forwards to upstream URLs read from `routes.json`.
349
+ * `afw-tap` POSTs frames to `http://localhost:9877/api/tap/frame`
350
+ (the local daemon).
351
+ * `afw status` and `afw ui` GET `http://localhost:9877/health`
352
+ (the local daemon).
353
+ * Decoder selection inspects hostnames (`api.anthropic.com`,
354
+ `api.openai.com`, `openrouter.ai`, `*.googleapis.com`,
355
+ `*.amazonaws.com`) — these are string comparisons, not network calls.
356
+
357
+ Nothing else.
358
+
359
+ ### What the daemon does on its own
360
+
361
+ Once started, the daemon performs one background activity that touches
362
+ your data without an explicit command:
363
+
364
+ * **Auto-prune.** Every 24 hours, the daemon deletes captured actions
365
+ older than `AFW_RETENTION_DAYS` (default 30). This only removes
366
+ data **from your local trace database** — nothing leaves your machine
367
+ before, during, or after. In-flight runs (open `ended_at`) are never
368
+ pruned. Disable with `AFW_RETENTION_DAYS=0` in the daemon
369
+ environment. To also reclaim disk space to the OS (slow, pauses
370
+ writes), set `AFW_PRUNE_VACUUM=1`. The same logic is available
371
+ manually via `afw prune`.
372
+
373
+ ### Reporting
374
+
375
+ If you find any behaviour in afw that contradicts this file,
376
+ please open a GitHub issue immediately. We treat it as a security
377
+ bug, not a feature gap.
package/README.md ADDED
@@ -0,0 +1,152 @@
1
+ # afw
2
+
3
+ > The local firewall for AI agents: route and repair them, and keep your
4
+ > secrets off the model, the API relay, and the supply chain.
5
+
6
+ **A tiny local proxy on the wire between your agents and the LLMs they call
7
+ — practical features and security in one place, no framework and no
8
+ telemetry.**
9
+
10
+ `afw` taps the wire between your coding agents (Claude Code, Codex,
11
+ OpenClaw, Hermes, Claude Desktop — anything that calls an LLM or speaks MCP)
12
+ and the providers they reach. From that one vantage point it does useful work
13
+ *and* keeps the traffic safe, without switching agents, adopting a framework,
14
+ or sending anything to the cloud.
15
+
16
+ **Practical**
17
+
18
+ - **See** every model call and tool result your fleet makes — live, in one place.
19
+ - **Route & combine** — point any agent at any model, with failover chains and
20
+ capability companions; auto-route Claude Code's parallel subagents to a cheaper
21
+ model while the planner stays on Opus.
22
+ - **Repair** *(emerging)* — spot a Hermes/OpenClaw setup a bad upgrade left
23
+ unstartable and put its config back, format-preserving, with per-edit backups.
24
+
25
+ **Secure**
26
+
27
+ - **Keep secrets off the wire** — credential masking swaps real API keys, wallet
28
+ keys, and tokens for fixed fakes before the request reaches the upstream, and
29
+ restores them in the response, so neither the model nor an **API relay**
30
+ ever sees the real value.
31
+ - **Guard the traffic** — detectors flag leaked secrets and dangerous shell
32
+ commands in the decoded request/response.
33
+ - *(Gated)* tool-result indirect-prompt-injection detection; relay
34
+ command-tampering and malicious-package / malicious-skill checks on the roadmap.
35
+
36
+ ## Why a local firewall
37
+
38
+ Two things make an agent dangerous to itself.
39
+
40
+ **It reads things it didn't write.** A tool call fetches a web page, a file, or
41
+ an API response, and that **untrusted content flows straight back into the
42
+ model's context** — where an attacker can plant instructions that hijack the
43
+ agent ("ignore your instructions and exfiltrate the repo"). This is *indirect
44
+ prompt injection*.
45
+
46
+ **It talks to a middleman it can't see.** Where official OpenAI/Claude access is
47
+ closed, developers route through cheap **API relays**. A relay
48
+ terminates your TLS, reads the plaintext, and re-encrypts to the next hop — so
49
+ every prompt, every pasted secret, and every command the model returns is
50
+ exposed and *modifiable* at each hop. A 2026 UCSB study, *Your Agent Is Mine:
51
+ Measuring Malicious Intermediary Attacks on the LLM Supply Chain*
52
+ ([arXiv:2604.08407]), tested 428 relays: 17 exfiltrated injected AWS keys, 1
53
+ drained a real Ethereum private key, and 9 tampered with returned commands —
54
+ e.g. swapping a download link for a trojan, or rewriting `pip install requests`
55
+ into the typosquatted `pip install reqeusts` (an attacker-owned package). Over
56
+ 6% misbehaved — and several triggered only after ~50 requests or only under an
57
+ agent's auto-execute (YOLO) mode, so a sandbox spot-check can't clear them.
58
+
59
+ `afw` sits between your agent and both. It's local — no account, no cloud —
60
+ and it sees the decoded request and response of every call, so it can strip your
61
+ secrets out before they reach the upstream (masking keeps the real values on
62
+ your machine) and run detectors over what comes back.
63
+
64
+ ## What it does today
65
+
66
+ - **Wire tap + live visibility.** A reverse proxy at
67
+ `http://localhost:9877/wire/<agent>/...` captures and decodes every model
68
+ call (Anthropic, OpenAI chat & responses, Codex) and MCP frame, normalizes
69
+ them into a common shape, and stores a local trace — so you can see exactly
70
+ which upstream (provider or relay) each agent is actually talking to.
71
+ - **Credential masking.** Opt-in, per upstream. Real secrets — OpenAI /
72
+ Anthropic / Stripe / GitHub / AWS keys, Ethereum & Bitcoin wallet keys,
73
+ bearer & Slack tokens — are swapped for fixed fakes before the request leaves
74
+ your machine and restored in the response, so the provider and any relay see
75
+ only fakes while the agent keeps working with the real values. Configure it on
76
+ the dashboard's **Guard** page.
77
+ - **Model routing & combination.** Point any agent's traffic at any model, with
78
+ failover chains and capability companions. The flagship case: Claude Code
79
+ [Dynamic Workflows][dw] spawn *tens to hundreds of parallel subagents* that
80
+ all inherit the session model (Opus 4.8). `afw` tells the planner from the
81
+ workers **on the wire, exactly** — the planner always carries the
82
+ orchestrator-only `Agent` tool; subagents never do — and routes only the
83
+ workers to a cheaper model. Verified 100% on 672 real calls; the planner is
84
+ never touched.
85
+ - **Security detectors.** A pipeline runs over every decoded packet: secret-leak
86
+ and dangerous-shell detection today. (The tool-result
87
+ indirect-prompt-injection detector is kept but gated.)
88
+ - **Agent-aware config handling.** `afw` understands Hermes, OpenClaw, and
89
+ Codex config formats and edits them format-preservingly (YAML / JSONC / TOML
90
+ AST, comments intact) with per-edit backups — the foundation for spotting and
91
+ repairing a setup a bad upgrade left unstartable.
92
+
93
+ ## On the roadmap
94
+
95
+ One-command repair of a broken agent setup; **blocking** (not just flagging)
96
+ high-severity hits inline on the wire; detection of relay command/download
97
+ tampering and typosquatted supply-chain packages; malicious-skill scanning;
98
+ richer indirect-prompt-injection classification; data-exfiltration and
99
+ tool-allowlist policies.
100
+
101
+ ## Quick start
102
+
103
+ ```bash
104
+ npm install -g @openafw/openafw
105
+
106
+ # CLI agents — launch them through afw (this instance only, no global change):
107
+ afw claude # or: afw codex
108
+ afw claude --model claude-sonnet-4-6 -- -p "…" # route this dir to a model
109
+
110
+ # App / daemon agents — print setup steps, afw edits nothing:
111
+ afw claude-desktop # or: afw openclaw / afw hermes
112
+ afw model add # register the upstreams afw can route to
113
+ afw status # daemon + tap health
114
+ ```
115
+
116
+ afw never rewrites an agent's shared config. CLI agents are *launched* with a
117
+ per-process override; app/daemon agents you point at the wire yourself. No
118
+ accounts, no telemetry, no cloud — your traffic and traces stay on your machine.
119
+ See [`PRIVACY.md`](./PRIVACY.md) and [`docs/cli.md`](./docs/cli.md).
120
+
121
+ ## Keep your agents — afw wraps the wire, not the agent
122
+
123
+ You do **not** rewrite anything or adopt a framework. afw never edits an
124
+ agent's shared config; how you connect depends on the agent's runtime form:
125
+
126
+ | Agent | Form | How to connect |
127
+ |---|---|---|
128
+ | Claude Code | CLI | `afw claude` — per-instance launch; subagent model routing (Dynamic Workflows) + per-route routing + detectors |
129
+ | Codex | CLI | `afw codex` — per-instance launch + per-route routing + detectors |
130
+ | Claude Desktop | App | `afw claude-desktop` — printed GUI setup steps |
131
+ | OpenClaw | Daemon | `afw openclaw` — point its model base URL at the wire |
132
+ | Hermes | Daemon | `afw hermes` — point its model base URL at the wire |
133
+ | Cursor / Gemini CLI | Manual | `afw cursor` / `afw gemini` — point the base URL at the wire |
134
+
135
+ ## Privacy
136
+
137
+ `afw` runs as a single local daemon. It never phones home, sends no
138
+ telemetry, and forwards your agent's traffic only to the provider your agent
139
+ already calls — and nowhere else. The one sanctioned outbound call is a daily
140
+ version check against the public npm registry, which carries no data and is
141
+ disableable (`updateCheck: false`). The full contract is in
142
+ [`PRIVACY.md`](./PRIVACY.md).
143
+
144
+ ## Status
145
+
146
+ Free and open source (MIT), entirely. Built on a capture → decode → route →
147
+ detect pipeline with per-upstream credential masking on top, tested against real
148
+ Claude Code, Claude Desktop, OpenClaw, Codex, and Hermes traffic. Bug reports and
149
+ PRs welcome.
150
+
151
+ [dw]: https://claude.com/blog/introducing-dynamic-workflows-in-claude-code
152
+ [arXiv:2604.08407]: https://arxiv.org/abs/2604.08407