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 +236 -66
- package/README_zh-tw.md +216 -66
- package/dist/adapters/anthropic-messages.js +5 -3
- package/dist/adapters/gemini-native.js +13 -5
- package/dist/adapters/responses.js +5 -1
- package/dist/api-token.js +54 -0
- package/dist/cli-args.js +45 -3
- package/dist/cli.js +680 -101
- package/dist/cost.js +24 -0
- package/dist/daemon.js +209 -0
- package/dist/db.js +219 -0
- package/dist/dispatch-home.js +8 -19
- package/dist/doctor.js +105 -45
- package/dist/gate.js +9 -4
- package/dist/job.js +97 -0
- package/dist/liveness.js +39 -0
- package/dist/mcp/http.js +178 -0
- package/dist/mcp/protocol.js +308 -0
- package/dist/mcp/stdio.js +36 -0
- package/dist/messages.js +338 -52
- package/dist/output.js +91 -104
- package/dist/progress.js +36 -0
- package/dist/provider-key.js +84 -0
- package/dist/providers-store.js +165 -0
- package/dist/providers.js +18 -6
- package/dist/report.js +38 -14
- package/dist/runner.js +18 -0
- package/dist/ticket-store.js +195 -0
- package/dist/ticket.js +22 -1
- package/dist/validate.js +58 -21
- package/package.json +1 -2
- package/providers.json +12 -10
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
|
|
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
|
-
>
|
|
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
|
-
>
|
|
23
|
-
> that checks the report against it,
|
|
24
|
-
> the
|
|
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** —
|
|
50
|
-
5. **Check the setup** — `dowafu --doctor`. It prints where the config directory
|
|
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-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
72
|
-
|
|
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
|
-
|
|
75
|
-
|
|
76
|
-
|
|
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-
|
|
82
|
-
dowafu <ticket-
|
|
83
|
-
dowafu --help
|
|
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
|
|
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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
-
|
|
120
|
-
|
|
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
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
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
|
|
147
|
-
- **Secrets are masked** in
|
|
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
|
|
161
|
-
|
|
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.
|
|
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
|