dowafu 0.4.0 → 0.5.0

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/README.md CHANGED
@@ -8,20 +8,26 @@ returned.
8
8
 
9
9
  You write a ticket. `dowafu` calls each provider's API. Each reviewer — a *spoke* —
10
10
  reads only the files you whitelisted, and returns observations with the evidence it
11
- read them from. Everything lands on disk for you to check.
11
+ read them from. Everything it did is recorded for you to check.
12
12
 
13
13
  **Spokes produce observations, not verdicts.** What to do about them stays with you.
14
14
 
15
15
  > ### English and Traditional Chinese are both fully supported.
16
16
  >
17
- > One flag decides the language for the whole run: `--lang en` or `--lang zh-tw`. Without
18
- > it, `DISPATCH_LANG` applies; without that either, the default is **English**. The flag
19
- > wins over the environment variable, and an unrecognized value in either is rejected
20
- > rather than guessed at.
17
+ > Two settings decide the language, and they cover different things.
21
18
  >
22
- > The language reaches everything: the reviewer's prompt and report template, the audit
23
- > that checks the report against it, and the CLI's own output — `--help`, error messages,
24
- > the dry-run report, `summary.md`. The dry run prints the resolved language per reviewer,
19
+ > `--lang en` / `--lang zh-tw` sets the language of **a dispatch**: the reviewer's prompt
20
+ > and report template, the audit that checks the report against it, the dry-run report,
21
+ > and the run summary stored with the results.
22
+ >
23
+ > `DISPATCH_LANG` sets the language of **the CLI's own interface** — `--help`, `--doctor`,
24
+ > parse errors, and the subcommands (`key`, `providers`, `token`, `approve`, `serve`).
25
+ > **`--lang` does not reach these.** They are printed before that flag has been validated,
26
+ > and `--doctor` in particular may be the very tool you reach for when the language
27
+ > settings are themselves what is broken.
28
+ >
29
+ > Without either, the default is **English**, and an unrecognized value in either is
30
+ > rejected rather than guessed at. The dry run prints the resolved language per reviewer,
25
31
  > so you can see it before anything is sent.
26
32
  >
27
33
  > A ticket's section headings may be written in either language regardless — they are
@@ -46,41 +52,43 @@ Five steps, in this order. The first three cost nothing.
46
52
  1. **Install the CLI** — see above. `dowafu --version` should print a version number.
47
53
  2. **Copy one language pack into your project** — the commands are under "Driving it from an agent" below. Pick `publish/en/` or `publish/zh-tw/`; never mix the two.
48
54
  3. **Ask your agent to run `preflight`** — "run the preflight skill". It reports whether the wiring took, whether the lens definitions are where the CLI expects them, and whether `tmp/` is ignored by git. It reads and reports; it changes nothing.
49
- 4. **Put your keys in place** — see "API keys" below. Only the providers you actually dispatch to need one. Your agent can create the directory and an empty template for you, but **do not paste a key into the conversation**: it stays in that conversation's history. Type the value into the file yourself.
50
- 5. **Check the setup** — `dowafu --doctor`. It prints where the config directory resolved to, whether `.env` is there, which providers have a key (presence only, never the value), the bundled model whitelist, and the lens definitions it found. No API call, no cost, and no ticket required.
55
+ 4. **Put your keys in place** — run `dowafu key` and paste each one you need. Only the providers you actually dispatch to need a key. **Do not paste a key into the agent conversation**: it stays in that conversation's history. Type it into `dowafu key` in your own terminal.
56
+ 5. **Check the setup** — `dowafu --doctor`. It prints where the config directory and the database resolved to, which providers have a key (presence only, never the value), the model whitelist and which models are enabled, and the lens definitions it found. No API call, no cost, and no ticket required.
51
57
 
52
- Then write a ticket (below) and dry-run it — `dowafu <ticket-dir> --dry-run` is still free. The first thing that costs money is `--yes`.
58
+ Then write a ticket (below) and dry-run it — `dowafu <ticket-id> --dry-run` is still free. The first thing that costs money is `--yes`.
53
59
 
54
60
  ## API keys
55
61
 
56
- Keys are read from `$DISPATCH_HOME/.env`, which defaults to `~/.config/dowafu/.env`
57
- (`DISPATCH_HOME` or `XDG_CONFIG_HOME` override it). Variables already present in the
58
- environment win over the file, so CI and one-off overrides need no file at all.
62
+ Keys live in the database. Set them from your own terminal:
59
63
 
60
64
  ```bash
61
- mkdir -p ~/.config/dowafu
62
- cat > ~/.config/dowafu/.env <<'EOF'
63
- DEEPSEEK_API_KEY=
64
- GEMINI_API_KEY=
65
- OPENAI_API_KEY=
66
- ANTHROPIC_API_KEY=
67
- EOF
68
- chmod 600 ~/.config/dowafu/.env
65
+ dowafu key
69
66
  ```
70
67
 
71
- Only the providers you actually dispatch to need a key. The file is plain text it is
72
- protected by nothing but its file permissions.
68
+ It lists the providers, takes the key you paste without echoing it, stores it, prints back
69
+ only the last four characters, and offers to verify it against the provider right away.
70
+ `dowafu key list` shows which providers have one — presence only, never the value —
71
+ `dowafu key rm <provider>` removes one, and `dowafu key test <provider>` verifies one
72
+ against the real API (that call costs money, and it asks first). `dowafu key` needs an
73
+ interactive terminal: with stdin piped it refuses rather than reading the pipe.
74
+
75
+ Only the providers you actually dispatch to need a key.
76
+
77
+ **This version reads no `.env` file and no `*_API_KEY` environment variable.** There is one
78
+ source, so there is no precedence to work out and nothing left in your environment that can
79
+ quietly win over the key you just set. Coming from 0.4.x: delete `~/.config/dowafu/.env`
80
+ and set the keys again with `dowafu key`.
73
81
 
74
- **The current directory's `.env` is never read.** That is where you ran the command, and
75
- usually the project under review; its secrets have no business in a process that is
76
- talking to external APIs.
82
+ The database is `$DISPATCH_HOME/dowafu.db`, which defaults to `~/.config/dowafu/dowafu.db`
83
+ (`DISPATCH_HOME` or `XDG_CONFIG_HOME` override it). **The keys are stored in it as plain
84
+ text** like the file before it, it is protected by nothing but its permissions.
77
85
 
78
86
  ## Usage
79
87
 
80
88
  ```bash
81
- dowafu <ticket-dir> --dry-run # parse, validate, estimate. No API call, no cost.
82
- dowafu <ticket-dir> --yes # run it. This is what costs money.
83
- dowafu --help # every flag
89
+ dowafu <ticket-id> --dry-run # parse, validate, estimate. No API call, no cost.
90
+ dowafu <ticket-id> --yes # run it. This is what costs money.
91
+ dowafu --help # every flag
84
92
  ```
85
93
 
86
94
  Without `--yes`, the command asks for confirmation. When stdin is not a TTY — which is
@@ -89,53 +97,72 @@ before calling anything.
89
97
 
90
98
  ## The ticket
91
99
 
92
- A ticket is a directory with three kinds of file. The headings are **literal markers the
93
- parser matches** — use one of the two sets below, exactly as written.
100
+ A ticket lives in the database, not on disk. Build one with the CLI:
94
101
 
95
- | English | 中文 |
96
- | --- | --- |
97
- | `# Questions` | `# 具體問題` |
98
- | `# Allowed reads` | `# 允許讀取` |
99
- | `# Under review` | `# 待審段落` |
100
- | `# Premises` | `# 前提(不受審)` |
101
-
102
- | File | Contents |
103
- | --- | --- |
104
- | `_dispatch.md` | which reviewers to run, and with which provider and model |
105
- | `_shared.md` | the premises, and the section under review, pasted in verbatim |
106
- | `<agent>.md` | one per reviewer: its questions, and the files it may read |
107
-
108
- ```markdown
109
- <!-- _dispatch.md -->
110
- <!-- format: v1 -->
111
- # dispatch auth-review
112
-
113
- | agent | provider | model | effort |
114
- | --- | --- | --- | --- |
115
- | hole-finder-safety | deepseek | deepseek-v4-flash | |
116
- | hole-finder-feasibility | openai | gpt-5.6-luna | |
102
+ ```bash
103
+ dowafu ticket create <ticket-id> <shared-file>
104
+ dowafu ticket add-spoke <ticket-id> <agent> <provider> <model> <body-file> [effort]
105
+ dowafu ticket add-allow <ticket-id> <agent> <repo-relative-path> --repo-root .
117
106
  ```
118
107
 
119
- ```markdown
120
- <!-- hole-finder-safety.md -->
108
+ The body argument of `create` and `add-spoke` takes a path — or `-`, which reads it from
109
+ stdin, so a heredoc writes the whole thing without staging a file first:
110
+
111
+ ```bash
112
+ dowafu ticket create auth-review - <<'EOF'
113
+ # Premises
114
+ - none
115
+
116
+ # Under review
117
+ ...
118
+ EOF
119
+
120
+ dowafu ticket add-spoke auth-review hole-finder-safety deepseek deepseek-v4-flash - <<'EOF'
121
121
  # Questions
122
122
  1. Does the permission check described here hold under concurrent requests?
123
123
 
124
124
  # Allowed reads
125
125
  - lib/auth-guard.ts
126
126
  - prisma/schema.prisma
127
+ EOF
127
128
  ```
128
129
 
130
+ `add-allow` validates the path immediately, refuses `_docs/`, and stores the file's
131
+ content as it stands at that moment — dispatch never re-reads it, so what a reviewer sees
132
+ is that snapshot. `dowafu ticket show <ticket-id>` prints the models, the allowlists and
133
+ the estimates.
134
+
135
+ ### Section headings
136
+
137
+ The headings inside the bodies above are **literal markers the parser matches** — use one
138
+ of the two sets below, exactly as written.
139
+
140
+ | English | 中文 |
141
+ | --- | --- |
142
+ | `# Questions` | `# 具體問題` |
143
+ | `# Allowed reads` | `# 允許讀取` |
144
+ | `# Under review` | `# 待審段落` |
145
+ | `# Premises` | `# 前提(不受審)` |
146
+
129
147
  Either set is accepted, and the choice does **not** decide the reviewer's language —
130
148
  that comes from `--lang` / `DISPATCH_LANG` (see above). The two sets are aliases for the
131
149
  same fields, so an English ticket can run in Chinese and vice versa. Mixing both sets
132
- inside one reviewer's file is not supported — the first heading that matches wins.
150
+ inside one reviewer's body is not supported — the first heading that matches wins.
151
+
152
+ ### Importing an older ticket directory
153
+
154
+ Before 0.5.0 a ticket was a directory of three files — `_dispatch.md`, `_shared.md`, and
155
+ one `<agent>.md` per reviewer. That form still loads, as a one-off migration:
156
+
157
+ ```bash
158
+ dowafu ticket import <dir> [ticket-id] --repo-root .
159
+ ```
133
160
 
134
161
  Reviewer definitions live in `.claude/agents/<agent>.md` under the repo root — they are
135
- the source of each spoke's system prompt, and the CLI reads them directly. Results are
136
- written to `tmp/spoke/<ticket-id>/`: each spoke's report, a `summary.md` with the audit
137
- table and estimated cost, `run.jsonl` with one line per event, and `raw/` with the exact
138
- requests and responses.
162
+ the source of each spoke's system prompt, and the CLI reads them directly. Results are not written to
163
+ disk. They go into the SQLite database (`$DISPATCH_HOME/dowafu.db` by default), which holds
164
+ each spoke's report, the audit table with the actual cost, and the exact request and
165
+ response for every call. Read a run back with `dowafu result <id>`.
139
166
 
140
167
  ## What the tool guarantees
141
168
 
@@ -143,8 +170,8 @@ requests and responses.
143
170
  refused, and the refusal is recorded.
144
171
  - **`_docs/` is off limits**, whatever the whitelist says.
145
172
  - **Nothing is billed before you confirm.** The dry run prints the resolved repo root,
146
- each reviewer's model and language, token estimates and the output path, and calls no API.
147
- - **Secrets are masked** in `run.jsonl`, `raw/*.json` and stdout.
173
+ each reviewer's model and language, token estimates and the hard-cap dollar amount, and calls no API.
174
+ - **Secrets are masked** in everything stored in the database and in stdout.
148
175
  - **Failures stop the run.** A missing key, an unknown model, a file that does not
149
176
  exist — each aborts with the path or name that caused it, before any spend.
150
177
 
@@ -157,8 +184,17 @@ requests and responses.
157
184
  | `gemini` | `gemini-3.1-flash-lite`, `gemini-3.5-flash-lite`, `gemini-3.6-flash` |
158
185
  | `anthropic` | `claude-opus-5`, `claude-sonnet-5` |
159
186
 
160
- The list ships with the package as `providers.json`. Point `--providers` at your own copy
161
- to use anything else.
187
+ The list ships with the package as `providers.json` and is seeded into the database the
188
+ first time one is opened. From then on **the database is the source of truth**:
189
+
190
+ ```bash
191
+ dowafu providers list # the whitelist, and which models are enabled
192
+ dowafu providers enable|disable <model> # flip one on or off
193
+ dowafu providers import <path> # load your own file
194
+ ```
195
+
196
+ There is no `--providers` flag — it was removed in 0.5.0, when the whitelist moved into
197
+ the database. Passing it is rejected as an unknown option.
162
198
 
163
199
  ## Driving it from an agent
164
200
 
@@ -172,7 +208,7 @@ not work: a reviewer's fixed closing line has to match the template the audit ch
172
208
  against.
173
209
 
174
210
  ```bash
175
- npx degit eyesofkids/dowafu/publish/en#v0.4.0 .claude-pack # or publish/zh-tw
211
+ npx degit eyesofkids/dowafu/publish/en#v0.5.0 .claude-pack # or publish/zh-tw
176
212
 
177
213
  cd .claude-pack
178
214
  TARGET=<your project>
@@ -190,6 +226,140 @@ Once the files are in place, have the agent run `preflight` before anything else
190
226
  See `publish/en/README.md` — or `publish/zh-tw/README.md` — for the details, each written
191
227
  in its own language.
192
228
 
229
+ ## Running it as an MCP server
230
+
231
+ An agent can drive `dowafu` over MCP instead of through the shell. Five tools are
232
+ exposed: `dispatch_tickets`, `dispatch_submit`, `dispatch_status`, `dispatch_result`
233
+ and `dispatch_approve`.
234
+
235
+ There are two transports, and the difference that matters is who starts what.
236
+
237
+ | | stdio | HTTP |
238
+ | --- | --- | --- |
239
+ | Command | `dowafu mcp` | `dowafu serve` |
240
+ | Who starts it | the client spawns it | you, in your own terminal |
241
+ | Token | none, and none needed | required (`x-api-key`) |
242
+ | Runs the reviewers | **no** | yes |
243
+
244
+ ### Both transports need `dowafu serve`
245
+
246
+ `dowafu mcp` only answers tool calls — **it never runs a reviewer**. The worker that
247
+ actually spends money lives in `dowafu serve`. Without one running, an approved job sits
248
+ in the queue forever and nothing tells the agent why.
249
+
250
+ ```
251
+ stdio: your client ──spawn──→ dowafu mcp ──┐
252
+ ├─ the same SQLite database
253
+ your terminal ────────→ dowafu serve ┘ (the worker is here)
254
+
255
+ HTTP: your client ──x-api-key──→ dowafu serve (endpoint and worker, one process)
256
+ ```
257
+
258
+ So on stdio you keep `dowafu serve` running in a terminal next to the client. The order
259
+ is forgiving there — start it before or after approving, and the daemon picks the job up
260
+ on its next tick.
261
+
262
+ ### stdio
263
+
264
+ Point the client at the command. For Claude Code:
265
+
266
+ ```bash
267
+ claude mcp add dowafu -- dowafu mcp
268
+ ```
269
+
270
+ No token is involved: the client spawns the process itself, the trust boundary is the
271
+ operating system, and there is no network in between to intercept.
272
+
273
+ ### HTTP
274
+
275
+ `dowafu serve` binds `127.0.0.1:7391/mcp` — **loopback only; there is no `--host` flag.**
276
+ Exposing it is a tunnel's job, not the server's. `--http-port <port>` or the
277
+ `DOWAFU_HTTP_PORT` environment variable changes the port.
278
+
279
+ Every request needs a token:
280
+
281
+ ```bash
282
+ dowafu token issue --label laptop # the plaintext prints once and is never stored
283
+ ```
284
+
285
+ The database keeps only a SHA-256 hash of it, compared in constant time. Put the
286
+ plaintext into your client's MCP configuration as the `x-api-key` header.
287
+ `dowafu token list` shows which tokens exist (never their value) and
288
+ `dowafu token revoke <id>` kills one. Rate limiting is **per token, not per IP** —
289
+ behind a tunnel every request arrives from the same source address.
290
+
291
+ > **Start the daemon before the client.** If the client starts first it marks the server
292
+ > as failed and that entire conversation has no tools — a symptom indistinguishable from
293
+ > a misconfigured entry.
294
+
295
+ ### Keeping the daemon running
296
+
297
+ Nothing wakes `dowafu serve` on demand: a client connects to a socket that is already
298
+ listening, so with no process listening there is nothing to wake. Let the OS keep it up
299
+ instead.
300
+
301
+ **macOS** — `~/Library/LaunchAgents/dowafu.plist`:
302
+
303
+ ```xml
304
+ <?xml version="1.0" encoding="UTF-8"?>
305
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
306
+ "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
307
+ <plist version="1.0">
308
+ <dict>
309
+ <key>Label</key><string>dowafu</string>
310
+ <key>ProgramArguments</key>
311
+ <array>
312
+ <string>/absolute/path/to/dowafu</string>
313
+ <string>serve</string>
314
+ </array>
315
+ <key>RunAtLoad</key><true/>
316
+ <key>KeepAlive</key><true/>
317
+ <key>StandardOutPath</key><string>/Users/you/Library/Logs/dowafu.log</string>
318
+ <key>StandardErrorPath</key><string>/Users/you/Library/Logs/dowafu.err</string>
319
+ </dict>
320
+ </plist>
321
+ ```
322
+
323
+ Load it with `launchctl load -w ~/Library/LaunchAgents/dowafu.plist`.
324
+
325
+ **Linux** — `~/.config/systemd/user/dowafu.service`:
326
+
327
+ ```ini
328
+ [Unit]
329
+ Description=dowafu dispatch daemon
330
+
331
+ [Service]
332
+ ExecStart=/absolute/path/to/dowafu serve
333
+ Restart=always
334
+
335
+ [Install]
336
+ WantedBy=default.target
337
+ ```
338
+
339
+ Enable it with `systemctl --user enable --now dowafu`.
340
+
341
+ Three things that bite:
342
+
343
+ - **Use an absolute path.** Neither launchd nor systemd sees your shell's `PATH`. Run
344
+ `which dowafu` and paste exactly what it prints.
345
+ - **Environment variables are not inherited either.** If your shell sets `DISPATCH_HOME`
346
+ or `XDG_CONFIG_HOME`, the daemon will not see them and will resolve a *different*
347
+ database — one with none of your keys or tickets in it. Declare them in the unit.
348
+ - **Send the output somewhere.** As a background service there is no terminal left to
349
+ watch, and that is where a port already in use, or a reviewer that died, gets
350
+ explained. Point stdout and stderr at files you can actually find.
351
+
352
+ With the daemon always up the stdio path needs nothing extra — `dowafu mcp` finds a
353
+ worker already running. Typing `dowafu serve` yourself will then refuse with
354
+ `daemon already running (pid N)`: that is the guard working, not a fault.
355
+
356
+ ### Approval is still a separate step
357
+
358
+ `dispatch_submit` queues a job and stops; nothing is billed until it is approved. Two
359
+ ways to approve: `dowafu approve <id>` in your own terminal, or the `dispatch_approve`
360
+ tool — which requires the job's hard-cap dollar amount to match what the database
361
+ computes, so an agent cannot approve a job whose cost it never showed you.
362
+
193
363
  ## License
194
364
 
195
365
  MIT