agentinbox-cli 0.1.0-nightly.1 → 0.1.0-nightly.11
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 +29 -4
- package/dist/admin-CXkmdTYo.js +196 -0
- package/dist/admin-CXkmdTYo.js.map +1 -0
- package/dist/{audit-DETqygzG.js → audit-bItbJYFP.js} +2 -2
- package/dist/{audit-DETqygzG.js.map → audit-bItbJYFP.js.map} +1 -1
- package/dist/audit-gVdDzkAE.js +2 -0
- package/dist/{auth-BniddcRD.js → auth-0NrMvHdE.js} +121 -18
- package/dist/auth-0NrMvHdE.js.map +1 -0
- package/dist/auth-CoHvbWgO.js +2 -0
- package/dist/bus-Cz0flS37.js.map +1 -1
- package/dist/cli.js +106 -27
- package/dist/cli.js.map +1 -1
- package/dist/commands-DZ60j8_y.js +103 -0
- package/dist/commands-DZ60j8_y.js.map +1 -0
- package/dist/connect-BHC2trBi.js +292 -0
- package/dist/connect-BHC2trBi.js.map +1 -0
- package/dist/content-BXblC_Qs.js +1162 -0
- package/dist/content-BXblC_Qs.js.map +1 -0
- package/dist/content-worker.js +28 -0
- package/dist/content-worker.js.map +1 -0
- package/dist/db-B6gLQGBK.js +2 -0
- package/dist/{db-DdagFmUY.js → db-D0gvshCc.js} +37 -2
- package/dist/db-D0gvshCc.js.map +1 -0
- package/dist/{errors-CJLUPllS.js → errors-FbMS3QBo.js} +3 -2
- package/dist/{errors-CJLUPllS.js.map → errors-FbMS3QBo.js.map} +1 -1
- package/dist/filter-DGVz6-P1.js +140 -0
- package/dist/filter-DGVz6-P1.js.map +1 -0
- package/dist/{instance-Dy2jeyZc.js → instance-BBzEsYZO.js} +9 -8
- package/dist/instance-BBzEsYZO.js.map +1 -0
- package/dist/instance-DLNTj9nm.js +2 -0
- package/dist/listen-D8DkEhqM.js +1724 -0
- package/dist/listen-D8DkEhqM.js.map +1 -0
- package/dist/listen-DT-omyD6.js +2 -0
- package/dist/mcpheader-By2dIyZq.js +2 -0
- package/dist/mcpheader-D9rrI-aa.js +65 -0
- package/dist/mcpheader-D9rrI-aa.js.map +1 -0
- package/dist/mcpsetup-7KxroOCp.js +2 -0
- package/dist/mcpsetup-DJ7ebFGJ.js +266 -0
- package/dist/mcpsetup-DJ7ebFGJ.js.map +1 -0
- package/dist/paths-BCBp8vrG.js +2 -0
- package/dist/paths-TLp8hiMt.js +40 -0
- package/dist/paths-TLp8hiMt.js.map +1 -0
- package/dist/profile-CgGgJ4Dm.js +181 -0
- package/dist/profile-CgGgJ4Dm.js.map +1 -0
- package/dist/profile-RbN2FRz7.js +2 -0
- package/dist/recipes-7d_Xws4M.js +2 -0
- package/dist/recipes-UalOc_LB.js +100 -0
- package/dist/recipes-UalOc_LB.js.map +1 -0
- package/dist/send-Dk-t8ia-.js +108 -0
- package/dist/send-Dk-t8ia-.js.map +1 -0
- package/dist/server-Hunjpt8n.js +8103 -0
- package/dist/server-Hunjpt8n.js.map +1 -0
- package/dist/service-BAgZDh22.js +2 -0
- package/dist/service-DYdD92CC.js +2 -0
- package/dist/service-D_ZEs3Zk.js +508 -0
- package/dist/service-D_ZEs3Zk.js.map +1 -0
- package/dist/service-whbugeQp.js +295 -0
- package/dist/service-whbugeQp.js.map +1 -0
- package/dist/settings-CeEsv4CE.js +2 -0
- package/dist/{settings-Dq2ETFsd.js → settings-DLUStIN7.js} +18 -7
- package/dist/settings-DLUStIN7.js.map +1 -0
- package/dist/{setup-token-Bkwk84l7.js → setup-token-BO89xnpb.js} +3 -2
- package/dist/setup-token-BO89xnpb.js.map +1 -0
- package/dist/skill/SKILL.md +51 -0
- package/dist/skill-BJFn2bHK.js +2 -0
- package/dist/types-3eEsBFVj.js +37 -0
- package/dist/types-3eEsBFVj.js.map +1 -0
- package/dist/ui-B8Sd4Y7Y.js +81 -0
- package/dist/ui-B8Sd4Y7Y.js.map +1 -0
- package/dist/urls-Dp8pBGMU.js +2 -0
- package/dist/{urls-DmnonkJo.js → urls-U_LfYV61.js} +7 -2
- package/dist/urls-U_LfYV61.js.map +1 -0
- package/dist/web/assets/_agentId-B84nfSSl.js +1 -0
- package/dist/web/assets/_app-D3hyZhYk.js +65 -0
- package/dist/web/assets/_app-DKKA242O.js +1 -0
- package/dist/web/assets/_webhookId-BGQyNNxg.js +1 -0
- package/dist/web/assets/account-status-BFOGX5SV.js +1 -0
- package/dist/web/assets/accounts-D8MBeCtW.js +1 -0
- package/dist/web/assets/accounts-list-CYYEQ0ne.js +1 -0
- package/dist/web/assets/{activity-zrckFhPk.js → activity-BLUuahAs.js} +1 -1
- package/dist/web/assets/agent-create-form-DwVChMkA.js +1 -0
- package/dist/web/assets/agents-5UjVypcM.js +1 -0
- package/dist/web/assets/approvals-CBuFnwI5.js +2 -0
- package/dist/web/assets/{arrow-left-DVJV4Zx7.js → arrow-left-D8xucc1r.js} +1 -1
- package/dist/web/assets/{arrow-right-4PFX6MSS.js → arrow-right-DhRNrsgs.js} +1 -1
- package/dist/web/assets/attention-tZT6tzQf.js +1 -0
- package/dist/web/assets/audit-2ewiHPFi.js +1 -0
- package/dist/web/assets/{audit-bits-DQHo74i6.js → audit-bits-BelfoqL1.js} +1 -1
- package/dist/web/assets/{bot-DVw15lBn.js → bot-OiZnURMn.js} +1 -1
- package/dist/web/assets/card-BDO1I64n.js +1 -0
- package/dist/web/assets/checkbox-GmPrkoUw.js +1 -0
- package/dist/web/assets/chevron-down-NXJmXy-r.js +1 -0
- package/dist/web/assets/chevron-right-B4h8pY71.js +1 -0
- package/dist/web/assets/chips-input-D6bmBebn.js +1 -0
- package/dist/web/assets/choice-cards-CuRsVy_m.js +1 -0
- package/dist/web/assets/{circle-x-Bi4IGwky.js → circle-x-p7eHgV-5.js} +1 -1
- package/dist/web/assets/common-DrM1Mwzz.js +1 -0
- package/dist/web/assets/connect-agent-guide-B52Vmv9K.js +1 -0
- package/dist/web/assets/connect-host-BzdVLkBw.js +1 -0
- package/dist/web/assets/data-table-L10nqo-E.js +1 -0
- package/dist/web/assets/dialog-BxP6Nd7_.js +1 -0
- package/dist/web/assets/dist-D0t-wYZ4.js +1 -0
- package/dist/web/assets/{empty-CE_WZGEC.js → empty-BtPVZxG1.js} +1 -1
- package/dist/web/assets/event-row-BqN0LkGQ.js +1 -0
- package/dist/web/assets/events-BnyFdDJd.js +1 -0
- package/dist/web/assets/{eye-off-DjpaihHf.js → eye-off-CZyueSTe.js} +1 -1
- package/dist/web/assets/{field-CcyMn3ey.js → field-CQ8p4jWV.js} +1 -1
- package/dist/web/assets/get-started-B5p4d45-.js +1 -0
- package/dist/web/assets/globe-T3y1BcXN.js +1 -0
- package/dist/web/assets/grant-editor-xJIdXRXz.js +1 -0
- package/dist/web/assets/grants-65cf2tpC.js +1 -0
- package/dist/web/assets/index-Bgtxd2kT.js +10 -0
- package/dist/web/assets/index-DqlComjO.css +2 -0
- package/dist/web/assets/{input-CMhmmHiH.js → input-D7gX3AnN.js} +1 -1
- package/dist/web/assets/{input-group-DRs3TNaT.js → input-group--cocoRRt.js} +1 -1
- package/dist/web/assets/{key-round-CVIZRCLM.js → key-round-BOdOCZ-v.js} +1 -1
- package/dist/web/assets/label-multi-select-YGmajxiS.js +1 -0
- package/dist/web/assets/{labels-C_Pwium9.js → labels-NC5qakj0.js} +1 -1
- package/dist/web/assets/login-B189BgE9.js +1 -0
- package/dist/web/assets/{mail-CAVmuESo.js → mail-S8ynVqel.js} +1 -1
- package/dist/web/assets/mutations-CrK_dHjN.js +1 -0
- package/dist/web/assets/{paperclip-QFjM4y6n.js → paperclip-CtB5KhFw.js} +1 -1
- package/dist/web/assets/{password-input-_m9Ycphv.js → password-input-CTohT-yQ.js} +1 -1
- package/dist/web/assets/{pencil-Bf1JhsG0.js → pencil-_Bqubp2c.js} +1 -1
- package/dist/web/assets/{play-DiPk49JH.js → play-D8flktq1.js} +1 -1
- package/dist/web/assets/plus-CWffTpRJ.js +1 -0
- package/dist/web/assets/{preload-helper-BFuRUfdt.js → preload-helper-D5-cnlPa.js} +1 -1
- package/dist/web/assets/{progress-3ZOsbLSK.js → progress-Bpra1fcb.js} +2 -2
- package/dist/web/assets/queries-BxLg0RZo.js +42 -0
- package/dist/web/assets/{refresh-cw-DOn9kD7g.js → refresh-cw-CIvW5Kqj.js} +1 -1
- package/dist/web/assets/revealed-keys-Bv4iviE0.js +1 -0
- package/dist/web/assets/{search-gcYjitzV.js → search-QEpoTp6V.js} +1 -1
- package/dist/web/assets/select-CULZqVpx.js +1 -0
- package/dist/web/assets/{send-BhQwhuJo.js → send-hXPeGSRm.js} +1 -1
- package/dist/web/assets/settings-DvT2H98U.js +1 -0
- package/dist/web/assets/settings-form-Npxmo-48.js +1 -0
- package/dist/web/assets/setup-Dv2Jwg6C.js +1 -0
- package/dist/web/assets/sheet-DVsI6wIy.js +1 -0
- package/dist/web/assets/{shield-alert-CbFgd06X.js → shield-alert-CxOKxuwK.js} +1 -1
- package/dist/web/assets/sparkles-dOOQuzbv.js +1 -0
- package/dist/web/assets/{stamp-CH2OvjH2.js → stamp-nrknJt3U.js} +1 -1
- package/dist/web/assets/stream-BslYFQRu.js +1 -0
- package/dist/web/assets/{tag-CP72Kk6Y.js → tag-BkSuXFCL.js} +1 -1
- package/dist/web/assets/terminal-DFMaTA9M.js +1 -0
- package/dist/web/assets/test-connection-CJfCVOun.js +1 -0
- package/dist/web/assets/{textarea-CCokBZIw.js → textarea-bv6G7Gyi.js} +1 -1
- package/dist/web/assets/toggle-group-BITCutvh.js +1 -0
- package/dist/web/assets/{trash-DxdMNg8B.js → trash-C7fJDkrt.js} +1 -1
- package/dist/web/assets/use-label-names-Cu1DP8da.js +1 -0
- package/dist/web/assets/use-sign-out-B17XhNuw.js +1 -0
- package/dist/web/assets/{useInfiniteQuery-DE6WChlb.js → useInfiniteQuery-Boz7XmVv.js} +1 -1
- package/dist/web/assets/useMatch-IXjbdY_b.js +1 -0
- package/dist/web/assets/useQuery-DRAomrvU.js +1 -0
- package/dist/web/assets/{webhook-BWT75M78.js → webhook-CpBtx11Y.js} +1 -1
- package/dist/web/assets/webhook-bits-DsxZ4Q_e.js +1 -0
- package/dist/web/assets/webhook-sheet-BgBsnR5G.js +34 -0
- package/dist/web/assets/webhooks-BXdMFkZM.js +1 -0
- package/dist/web/index.html +7 -10
- package/package.json +2 -1
- package/dist/audit-CQe-_dPQ.js +0 -2
- package/dist/auth-BniddcRD.js.map +0 -1
- package/dist/auth-C2v7bEmQ.js +0 -2
- package/dist/db-CfoMO5zW.js +0 -2
- package/dist/db-DdagFmUY.js.map +0 -1
- package/dist/instance-DZMV78rI.js +0 -2
- package/dist/instance-Dy2jeyZc.js.map +0 -1
- package/dist/server-CWqKeCGn.js +0 -981
- package/dist/server-CWqKeCGn.js.map +0 -1
- package/dist/service-63pdSmhO.js +0 -332
- package/dist/service-63pdSmhO.js.map +0 -1
- package/dist/settings-Dq2ETFsd.js.map +0 -1
- package/dist/settings-qtn5WTBM.js +0 -2
- package/dist/setup-token-Bkwk84l7.js.map +0 -1
- package/dist/skill-BWWtinsE.js +0 -2
- package/dist/urls-CNYhbeUQ.js +0 -2
- package/dist/urls-DmnonkJo.js.map +0 -1
- package/dist/web/assets/_agentId-il4y7OCG.js +0 -1
- package/dist/web/assets/_app-BIO8WIYB.js +0 -1
- package/dist/web/assets/_app-wlLL4MrD.js +0 -63
- package/dist/web/assets/_webhookId-CiwDrkY4.js +0 -1
- package/dist/web/assets/account-status-at9Bv3_T.js +0 -1
- package/dist/web/assets/accounts-B4rfPwCV.js +0 -1
- package/dist/web/assets/accounts-list-e--JWxHH.js +0 -1
- package/dist/web/assets/agent-create-form-DaWPR6kx.js +0 -1
- package/dist/web/assets/agents-D4gPq4dt.js +0 -1
- package/dist/web/assets/approvals-DaoZ6wuf.js +0 -2
- package/dist/web/assets/audit-bAdLe-px.js +0 -1
- package/dist/web/assets/card-EQTpqntn.js +0 -1
- package/dist/web/assets/checkbox-DDZZurgK.js +0 -1
- package/dist/web/assets/chevron-down-892ylB4R.js +0 -1
- package/dist/web/assets/chevron-right-CbBKS6-T.js +0 -1
- package/dist/web/assets/chips-input-Btdc_f7b.js +0 -1
- package/dist/web/assets/common-h6596gEC.js +0 -1
- package/dist/web/assets/connect-host-Co31qIYr.js +0 -1
- package/dist/web/assets/data-table-DsKrM62X.js +0 -1
- package/dist/web/assets/dialog-CgZr27yw.js +0 -1
- package/dist/web/assets/dist-B0e3Ju3r.js +0 -1
- package/dist/web/assets/es2015-DPaldoxG.js +0 -41
- package/dist/web/assets/event-row-C0qRfWuV.js +0 -1
- package/dist/web/assets/events-Svyij7mp.js +0 -1
- package/dist/web/assets/get-started-Cxfw97sJ.js +0 -1
- package/dist/web/assets/grants-BilXCDps.js +0 -1
- package/dist/web/assets/index-DgIgw66p.css +0 -2
- package/dist/web/assets/index-TbxC5ZgP.js +0 -10
- package/dist/web/assets/login-DUJia7lg.js +0 -1
- package/dist/web/assets/mutations-D_GluJ0j.js +0 -1
- package/dist/web/assets/plus-CETmK0SB.js +0 -1
- package/dist/web/assets/queries-a3M5_loa.js +0 -2
- package/dist/web/assets/select-_tULk-vs.js +0 -1
- package/dist/web/assets/settings-Dl4wb4f0.js +0 -1
- package/dist/web/assets/settings-form-D21eKBh-.js +0 -1
- package/dist/web/assets/setup-DYnNhnfH.js +0 -1
- package/dist/web/assets/sheet--2lKU0jN.js +0 -1
- package/dist/web/assets/stream-6OwrgVZw.js +0 -1
- package/dist/web/assets/tabs-CiAUEj_y.js +0 -1
- package/dist/web/assets/test-connection-F2tMowbv.js +0 -1
- package/dist/web/assets/toggle-group-D1pC7sJr.js +0 -1
- package/dist/web/assets/use-sign-out-BuzlTb0Q.js +0 -1
- package/dist/web/assets/useMatch-BKUnm54_.js +0 -1
- package/dist/web/assets/useMutation-DGl4PaZ1.js +0 -1
- package/dist/web/assets/useQuery-DAhf1YDH.js +0 -1
- package/dist/web/assets/useSelector-D70eKNYs.js +0 -1
- package/dist/web/assets/webhook-sheet-4U_6Rw8I.js +0 -34
- package/dist/web/assets/webhooks-C1JvTI88.js +0 -1
- package/dist/web/assets/with-selector-CkNyXYMW.js +0 -1
- package/dist/web/assets/x-BHjDLXNO.js +0 -1
package/README.md
CHANGED
|
@@ -216,14 +216,35 @@ Installs a background service that starts at boot: systemd on Linux (a user serv
|
|
|
216
216
|
service when run as root) and launchd on macOS. The service runs its own copy of agentinbox from the data directory,
|
|
217
217
|
so it never depends on the npx cache; a failed install leaves the running service as it was. Re-run `service install`
|
|
218
218
|
to update; `service status` and `service uninstall` do what you'd expect, and uninstalling keeps your data. Pass the same
|
|
219
|
-
`--port`, `--host` and `--data` options each time. `AGENTINBOX_MASTER_KEY`, `TRUST_PROXY`, `TRUST_PROXY_HOPS
|
|
220
|
-
`AGENTINBOX_LOG_LEVEL` are taken from your shell and remembered in `<data>/service.env`, so later
|
|
221
|
-
(set one to an empty value to remove it).
|
|
219
|
+
`--port`, `--host` and `--data` options each time. `AGENTINBOX_MASTER_KEY`, `TRUST_PROXY`, `TRUST_PROXY_HOPS`,
|
|
220
|
+
`TRUST_PROXY_FROM` and `AGENTINBOX_LOG_LEVEL` are taken from your shell and remembered in `<data>/service.env`, so later
|
|
221
|
+
installs keep them (set one to an empty value to remove it).
|
|
222
222
|
|
|
223
223
|
Only one agentinbox runs per data directory; a second one refuses to start.
|
|
224
224
|
|
|
225
225
|
When a newer version is published, the console shows **Update available** with the exact command to run.
|
|
226
226
|
|
|
227
|
+
The unit runs the Node.js binary you installed it with. If you remove or replace that Node, run `service install` again.
|
|
228
|
+
If the service doesn't come up, `service install` rolls back and prints where to look (`systemctl --user status
|
|
229
|
+
agentinbox`, `journalctl --user -u agentinbox`). Over `su` or `sudo -u` there is often no systemd user session: log
|
|
230
|
+
in as that user directly, or enable lingering and set `XDG_RUNTIME_DIR=/run/user/$(id -u)` first.
|
|
231
|
+
|
|
232
|
+
A service that hasn't been claimed yet doesn't print its setup token to the journal. Get the link with
|
|
233
|
+
`npx agentinbox-cli reset-password --data <dir>`, or read the token from `<data>/setup-token`.
|
|
234
|
+
|
|
235
|
+
### With your own supervisor
|
|
236
|
+
|
|
237
|
+
On other systems, or to manage it yourself (Docker, runit, supervisord, pm2), install the command and run it in the
|
|
238
|
+
foreground:
|
|
239
|
+
|
|
240
|
+
```sh
|
|
241
|
+
npm i -g agentinbox-cli
|
|
242
|
+
agentinbox start --no-open --data /var/lib/agentinbox
|
|
243
|
+
```
|
|
244
|
+
|
|
245
|
+
Use the installed `agentinbox` binary rather than `npx`: npx doesn't pass stop signals on, so agentinbox wouldn't shut
|
|
246
|
+
down cleanly.
|
|
247
|
+
|
|
227
248
|
### Behind a reverse proxy
|
|
228
249
|
|
|
229
250
|
agentinbox serves plain HTTP. To expose it beyond your network, put it behind a TLS-terminating proxy (Caddy, nginx,
|
|
@@ -231,7 +252,10 @@ Tailscale Funnel) and:
|
|
|
231
252
|
|
|
232
253
|
- bind it to localhost: `--host 127.0.0.1`
|
|
233
254
|
- set `TRUST_PROXY=1`, so lockouts apply per client and cookies are marked `Secure`. The client address is taken from
|
|
234
|
-
the right end of `X-Forwarded-For`; with more than one proxy in front, set `TRUST_PROXY_HOPS` to their number
|
|
255
|
+
the right end of `X-Forwarded-For`; with more than one proxy in front, set `TRUST_PROXY_HOPS` to their number.
|
|
256
|
+
Forwarding headers are believed only from a proxy on the same host (loopback); if the proxy runs on another machine,
|
|
257
|
+
list its address in `TRUST_PROXY_FROM` (for example `TRUST_PROXY_FROM=10.0.0.5` or a CIDR like `10.0.0.0/24`; `/0` is refused). The proxy must append to (or overwrite)
|
|
258
|
+
`X-Forwarded-For`; a tunnel or proxy that passes the header through unchanged lets clients choose their own address
|
|
235
259
|
- set **Settings → Public URL** to the https address, so MCP snippets, the skill and the OAuth redirect use it
|
|
236
260
|
|
|
237
261
|
`GET /health` (or `/api/health`) needs no auth and suits uptime monitors: `200` when healthy, `503` when degraded, for
|
|
@@ -258,6 +282,7 @@ agentinbox reset-password [--data …]
|
|
|
258
282
|
| | `AGENTINBOX_MASTER_KEY` | | Base64 32-byte key for encrypting secrets, instead of `<data>/master.key` |
|
|
259
283
|
| | `TRUST_PROXY` | | `1` behind a reverse proxy: client IPs from `X-Forwarded-For`, https from `X-Forwarded-Proto` |
|
|
260
284
|
| | `TRUST_PROXY_HOPS` | `1` | Number of proxies in front of agentinbox (which `X-Forwarded-For` entry, from the right, is the client) |
|
|
285
|
+
| | `TRUST_PROXY_FROM` | loopback | Other proxy addresses whose forwarding headers are believed: comma-separated IPs or CIDRs (IPv4 or IPv6) |
|
|
261
286
|
| | `AGENTINBOX_LOG_LEVEL` | `info` | `debug`, `info`, `warn` or `error` |
|
|
262
287
|
|
|
263
288
|
Forgot the admin password? Run `npx agentinbox-cli reset-password` on the server, as the user agentinbox runs as (`sudo` for a
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { i as safeText, r as formatDuration } from "./ui-B8Sd4Y7Y.js";
|
|
3
|
+
import { d as NPX, i as saveProfile, n as loadProfile, s as ensurePrivateDir } from "./profile-CgGgJ4Dm.js";
|
|
4
|
+
import { _ as runCommand, a as readDead, f as forwardEvent, i as pidAlive, l as forwardSecret, m as checkTransport, o as readStatus, p as TransportError, s as writeDead, u as resolveListen, v as Dispatcher, x as getSelf, y as ApiError } from "./listen-D8DkEhqM.js";
|
|
5
|
+
import { f as validProfileName, i as deadPath, o as logPath, s as profilePath } from "./paths-TLp8hiMt.js";
|
|
6
|
+
import { d as unitName, n as isServiceInstalled, r as launchdLabel } from "./service-whbugeQp.js";
|
|
7
|
+
import pc from "picocolors";
|
|
8
|
+
//#region server/listen/admin.ts
|
|
9
|
+
const out = (s = "") => process.stdout.write(`${s}\n`);
|
|
10
|
+
const flagFor = (profile) => profile === "default" ? "" : ` --profile ${profile}`;
|
|
11
|
+
const ago = (iso) => iso ? `${formatDuration(Date.now() - Date.parse(iso))} ago` : "never";
|
|
12
|
+
/** Prints the forward-mode signing secret (generating and saving it on first use). */
|
|
13
|
+
function printSecret(profileName, opts) {
|
|
14
|
+
const name = validProfileName(profileName);
|
|
15
|
+
const profile = loadProfile(name);
|
|
16
|
+
let { secret, created } = forwardSecret(profile);
|
|
17
|
+
if (opts.rotate) {
|
|
18
|
+
secret = forwardSecret({
|
|
19
|
+
...profile,
|
|
20
|
+
forward: void 0,
|
|
21
|
+
forwardSecret: void 0
|
|
22
|
+
}).secret;
|
|
23
|
+
created = true;
|
|
24
|
+
}
|
|
25
|
+
if (created) {
|
|
26
|
+
if (profile.forward) profile.forward = {
|
|
27
|
+
...profile.forward,
|
|
28
|
+
secret
|
|
29
|
+
};
|
|
30
|
+
else profile.forwardSecret = secret;
|
|
31
|
+
saveProfile(name, profile);
|
|
32
|
+
process.stderr.write(pc.gray(`${opts.rotate ? "Rotated" : "Generated"} the forward signing secret for profile "${name}"${opts.rotate ? "; restart the listener" : ""}.\n`));
|
|
33
|
+
}
|
|
34
|
+
out(secret);
|
|
35
|
+
}
|
|
36
|
+
async function printStatus(profileName) {
|
|
37
|
+
const name = validProfileName(profileName);
|
|
38
|
+
const profile = loadProfile(name);
|
|
39
|
+
const st = readStatus(name);
|
|
40
|
+
const alive = st ? pidAlive(st.pid) && !st.stoppedAt : false;
|
|
41
|
+
const dead = readDead(name);
|
|
42
|
+
out(`${pc.bold("Profile")} ${name} ${pc.gray(profilePath(name))}`);
|
|
43
|
+
out(`${pc.bold("Agent")} ${safeText(profile.agentName)} ${pc.gray(`(${profile.agentId})`)}`);
|
|
44
|
+
out(`${pc.bold("Daemon")} ${profile.url}`);
|
|
45
|
+
out(`${pc.bold("Consumer")} ${st?.consumer ?? profile.consumer}`);
|
|
46
|
+
const exec = st && st.exec !== void 0 ? st.exec : profile.exec;
|
|
47
|
+
const forward = st && st.forward !== void 0 ? st.forward : profile.forward?.url;
|
|
48
|
+
const targets = [exec ? `exec ${safeText(exec, 120)}` : null, forward ? `forward ${forward}` : null].filter(Boolean);
|
|
49
|
+
out(`${pc.bold("Runs")} ${targets.length ? targets.join(" · ") : pc.gray("nothing saved (pass --exec/--forward with --save)")}`);
|
|
50
|
+
let svc = "not installed";
|
|
51
|
+
if (process.platform === "linux" || process.platform === "darwin") {
|
|
52
|
+
if (isServiceInstalled(name)) svc = `installed (${process.platform === "linux" ? unitName(name) : launchdLabel(name)}; details: ${NPX} listen service status${flagFor(name)})`;
|
|
53
|
+
}
|
|
54
|
+
out(`${pc.bold("Service")} ${svc}`);
|
|
55
|
+
try {
|
|
56
|
+
await checkTransport(profile.url, { insecure: profile.insecure });
|
|
57
|
+
const self = await getSelf(profile.url, profile.apiKey, 5e3);
|
|
58
|
+
out(`${pc.bold("Key")} ${pc.green("valid")} · ${self.grants.length ? self.grants.map((g) => `${g.accountEmail} (${g.capabilities.join(", ")}; ${g.sendMode})`).join(", ") : pc.yellow("no mailbox grants")}`);
|
|
59
|
+
} catch (err) {
|
|
60
|
+
const revoked = err instanceof ApiError && (err.status === 401 || err.status === 403);
|
|
61
|
+
const why = err instanceof ApiError && err.status === 0 ? err.cause : err instanceof Error ? err.message.split("\n")[0] : String(err);
|
|
62
|
+
if (err instanceof TransportError) out(`${pc.bold("Key")} ${pc.red(`not checked: ${why}`)}`);
|
|
63
|
+
else out(`${pc.bold("Key")} ${revoked ? pc.red(`rejected: ${err.message}`) : pc.yellow(`not checked (${why})`)}`);
|
|
64
|
+
}
|
|
65
|
+
if (!st) out(`${pc.bold("Listener")} ${pc.gray("has not run yet")}`);
|
|
66
|
+
else {
|
|
67
|
+
const state = !alive ? pc.gray(`not running${st.stoppedAt ? ` (stopped ${ago(st.stoppedAt)}${st.exitReason ? `: ${safeText(st.exitReason, 160)}` : ""})` : " (exited unexpectedly)"}`) : st.connected ? pc.green(`running (pid ${st.pid}), connected ${ago(st.connectedSince)}`) : pc.yellow(`running (pid ${st.pid}), disconnected${st.lastDisconnect ? ` ${ago(st.lastDisconnect.at)}: ${safeText(st.lastDisconnect.reason, 160)}` : ""}`);
|
|
68
|
+
out(`${pc.bold("Listener")} ${state}`);
|
|
69
|
+
out(`${pc.bold("Cursor")} acked #${st.ackedSeq} · received #${st.receivedSeq}${alive && st.running ? ` · ${st.running} in progress` : ""} · last event ${ago(st.lastEventAt)}`);
|
|
70
|
+
if (st.recent.length) {
|
|
71
|
+
out(pc.bold("Recent"));
|
|
72
|
+
for (const r of st.recent.slice(0, 8)) {
|
|
73
|
+
const icon = r.outcome === "succeeded" ? pc.green("✓") : r.outcome === "dead" ? pc.red("✖") : r.outcome === "retrying" ? pc.yellow("↻") : pc.gray("·");
|
|
74
|
+
out(` ${icon} #${r.seq} ${r.type} ${pc.gray(`${ago(r.at)}${r.attempts > 1 ? `, ${r.attempts} attempts` : ""}${r.error && r.outcome !== "succeeded" ? `, ${safeText(r.error, 100)}` : ""}`)}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
out(`${pc.bold("Dead")} ${dead.length ? pc.red(`${dead.length} event${dead.length > 1 ? "s" : ""}`) + pc.gray(` (inspect: ${NPX} listen dead${flagFor(name)})`) : "0"}`);
|
|
79
|
+
out(`${pc.bold("Log")} ${logPath(name)}`);
|
|
80
|
+
}
|
|
81
|
+
function printDead(profileName, opts) {
|
|
82
|
+
const name = validProfileName(profileName);
|
|
83
|
+
const entries = readDead(name);
|
|
84
|
+
if (opts.json) {
|
|
85
|
+
for (const e of entries) out(JSON.stringify(e));
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
if (!entries.length) {
|
|
89
|
+
out(`No dead events for profile "${name}".`);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
for (const e of entries) {
|
|
93
|
+
const m = e.payload.data.message;
|
|
94
|
+
out(`${pc.red("✖")} #${e.seq} ${e.type} ${pc.gray(e.id)} ${pc.gray(`died ${ago(e.deadAt)} after ${e.attempts} attempts`)}`);
|
|
95
|
+
if (m) out(` from ${safeText(m.from?.email ?? "?", 80)} "${safeText(m.subject, 80)}"`);
|
|
96
|
+
out(pc.gray(` ${safeText(e.error, 300)}`));
|
|
97
|
+
}
|
|
98
|
+
out();
|
|
99
|
+
out(pc.gray(`${entries.length} dead event${entries.length > 1 ? "s" : ""} in ${deadPath(name)}.`));
|
|
100
|
+
out(pc.gray(`Replay with: ${NPX} listen dead --replay${flagFor(name)} (one: --id <evt_…>; drop all: --clear)`));
|
|
101
|
+
out(pc.gray(`Replay uses the profile's saved --exec/--forward; if you ran them as flags only, pass them again, e.g. --replay --exec '<command>'.`));
|
|
102
|
+
}
|
|
103
|
+
function clearDead(profileName, id) {
|
|
104
|
+
const name = validProfileName(profileName);
|
|
105
|
+
const entries = readDead(name);
|
|
106
|
+
const keep = id ? entries.filter((e) => e.id !== id) : [];
|
|
107
|
+
writeDead(name, keep);
|
|
108
|
+
out(`Removed ${entries.length - keep.length} dead event${entries.length - keep.length === 1 ? "" : "s"}.`);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Runs dead events again through the profile's targets (or the ones passed as flags). Successes are
|
|
112
|
+
* removed from dead.jsonl; failures stay. Returns how many still failed.
|
|
113
|
+
*/
|
|
114
|
+
async function replayDead(flags, opts = {}) {
|
|
115
|
+
const name = validProfileName(flags.profile ?? "default");
|
|
116
|
+
const profile = loadProfile(name);
|
|
117
|
+
const { config } = resolveListen(name, profile, {
|
|
118
|
+
...flags,
|
|
119
|
+
save: false
|
|
120
|
+
});
|
|
121
|
+
const chosen = readDead(name).filter((e) => !flags.id || e.id === flags.id);
|
|
122
|
+
if (!chosen.length) {
|
|
123
|
+
out(flags.id ? `No dead event with id ${flags.id}.` : `No dead events for profile "${name}".`);
|
|
124
|
+
return 0;
|
|
125
|
+
}
|
|
126
|
+
const available = [];
|
|
127
|
+
if (config.exec) available.push("exec");
|
|
128
|
+
if (config.forward) available.push("forward");
|
|
129
|
+
if (!available.length) throw new Error(`Nothing to replay to: pass --exec '<command>' and/or --forward <url> (or save them in the profile with \`${NPX} listen --save …\`).`);
|
|
130
|
+
if (config.exec && process.platform === "win32") throw new Error("--exec isn't supported on Windows; replay with --forward instead.");
|
|
131
|
+
const timeoutMs = config.timeoutSeconds * 1e3;
|
|
132
|
+
if (config.cwdIsDefault) ensurePrivateDir(config.cwd);
|
|
133
|
+
const failed = /* @__PURE__ */ new Map();
|
|
134
|
+
const bySeq = new Map(chosen.map((e) => [e.seq, e]));
|
|
135
|
+
const dispatcher = new Dispatcher({
|
|
136
|
+
targets: available,
|
|
137
|
+
concurrency: config.concurrency,
|
|
138
|
+
maxAttempts: config.maxAttempts,
|
|
139
|
+
retryDelayMs: opts.retryDelayMs,
|
|
140
|
+
run: async (job, signal) => {
|
|
141
|
+
const errors = [];
|
|
142
|
+
if (job.remaining.has("exec") && config.exec) {
|
|
143
|
+
const r = await runCommand(config.exec, job.payload, {
|
|
144
|
+
timeoutMs,
|
|
145
|
+
signal,
|
|
146
|
+
cwd: config.cwd
|
|
147
|
+
});
|
|
148
|
+
if (r.ok) job.remaining.delete("exec");
|
|
149
|
+
else errors.push(`command: ${r.error}`);
|
|
150
|
+
}
|
|
151
|
+
if (job.remaining.has("forward") && config.forward) {
|
|
152
|
+
const r = await forwardEvent(config.forward.url, config.forward.secret, job.payload, {
|
|
153
|
+
timeoutMs: Math.min(timeoutMs, 3e4),
|
|
154
|
+
signal
|
|
155
|
+
});
|
|
156
|
+
if (r.ok) job.remaining.delete("forward");
|
|
157
|
+
else errors.push(`forward: ${r.error}`);
|
|
158
|
+
}
|
|
159
|
+
return errors;
|
|
160
|
+
},
|
|
161
|
+
onAdvance: () => {},
|
|
162
|
+
onDead: (job) => {
|
|
163
|
+
const e = bySeq.get(job.seq);
|
|
164
|
+
failed.set(e.id, {
|
|
165
|
+
...e,
|
|
166
|
+
deadAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
167
|
+
attempts: e.attempts + job.attempts,
|
|
168
|
+
error: job.lastError ?? "failed"
|
|
169
|
+
});
|
|
170
|
+
out(`${pc.red("✖")} #${job.seq} still failing: ${safeText(job.lastError ?? "failed", 200)}`);
|
|
171
|
+
},
|
|
172
|
+
onFinished: (job) => {
|
|
173
|
+
if (job.outcome === "succeeded") out(`${pc.green("✓")} #${job.seq} ${job.payload.type} replayed`);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
out(`Replaying ${chosen.length} dead event${chosen.length > 1 ? "s" : ""} (up to ${config.maxAttempts} attempts each)…`);
|
|
177
|
+
for (const e of [...chosen].sort((a, b) => a.seq - b.seq)) {
|
|
178
|
+
const targets = e.targets?.length ? e.targets.filter((t) => available.includes(t)) : available;
|
|
179
|
+
if (!targets.length) {
|
|
180
|
+
out(`${pc.yellow("!")} #${e.seq} needs ${e.targets.join(" and ")}, which isn't configured now; pass --${e.targets[0]} to replay it.`);
|
|
181
|
+
failed.set(e.id, e);
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
dispatcher.push(e.payload, { targets });
|
|
185
|
+
}
|
|
186
|
+
await dispatcher.idle();
|
|
187
|
+
const replayed = new Set(chosen.map((e) => e.id));
|
|
188
|
+
const current = readDead(name);
|
|
189
|
+
writeDead(name, [...current.filter((e) => !replayed.has(e.id)), ...failed.values()]);
|
|
190
|
+
out(`${chosen.length - failed.size} replayed, ${failed.size} still dead.`);
|
|
191
|
+
return failed.size;
|
|
192
|
+
}
|
|
193
|
+
//#endregion
|
|
194
|
+
export { clearDead, printDead, printSecret, printStatus, replayDead };
|
|
195
|
+
|
|
196
|
+
//# sourceMappingURL=admin-CXkmdTYo.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"admin-CXkmdTYo.js","names":[],"sources":["../server/listen/admin.ts"],"sourcesContent":["import pc from \"picocolors\";\nimport { ApiError, getSelf } from \"./api.ts\";\nimport { Dispatcher, type Job, type Target } from \"./dispatcher.ts\";\nimport { runCommand } from \"./exec.ts\";\nimport { ensurePrivateDir } from \"./fsutil.ts\";\nimport { forwardEvent } from \"./forward.ts\";\nimport { type ListenFlags, resolveListen } from \"./options.ts\";\nimport { DEFAULT_PROFILE, deadPath, logPath, profilePath, validProfileName } from \"./paths.ts\";\nimport { loadProfile, saveProfile } from \"./profile.ts\";\nimport { forwardSecret } from \"./options.ts\";\nimport { isServiceInstalled, launchdLabel, unitName } from \"./service.ts\";\nimport { type DeadEntry, pidAlive, readDead, readStatus, writeDead } from \"./state.ts\";\nimport { checkTransport, TransportError } from \"./transport.ts\";\nimport { formatDuration, safeText } from \"./ui.ts\";\nimport { NPX } from \"./version.ts\";\n\nconst out = (s = \"\") => process.stdout.write(`${s}\\n`);\nconst flagFor = (profile: string) => (profile === DEFAULT_PROFILE ? \"\" : ` --profile ${profile}`);\nconst ago = (iso: string | null | undefined) => (iso ? `${formatDuration(Date.now() - Date.parse(iso))} ago` : \"never\");\n\n// ───────────── listen secret ─────────────\n\n/** Prints the forward-mode signing secret (generating and saving it on first use). */\nexport function printSecret(profileName: string, opts: { rotate?: boolean }): void {\n const name = validProfileName(profileName);\n const profile = loadProfile(name);\n let { secret, created } = forwardSecret(profile);\n if (opts.rotate) {\n secret = forwardSecret({ ...profile, forward: undefined, forwardSecret: undefined }).secret;\n created = true;\n }\n if (created) {\n if (profile.forward) profile.forward = { ...profile.forward, secret };\n else profile.forwardSecret = secret;\n saveProfile(name, profile);\n process.stderr.write(pc.gray(`${opts.rotate ? \"Rotated\" : \"Generated\"} the forward signing secret for profile \"${name}\"${opts.rotate ? \"; restart the listener\" : \"\"}.\\n`));\n }\n // The secret alone on stdout, so `export AGENTINBOX_WEBHOOK_SECRET=$(agentinbox listen secret)` works.\n out(secret);\n}\n\n// ───────────── listen status ─────────────\n\nexport async function printStatus(profileName: string): Promise<void> {\n const name = validProfileName(profileName);\n const profile = loadProfile(name);\n const st = readStatus(name);\n const alive = st ? pidAlive(st.pid) && !st.stoppedAt : false;\n const dead = readDead(name);\n\n out(`${pc.bold(\"Profile\")} ${name} ${pc.gray(profilePath(name))}`);\n out(`${pc.bold(\"Agent\")} ${safeText(profile.agentName)} ${pc.gray(`(${profile.agentId})`)}`);\n out(`${pc.bold(\"Daemon\")} ${profile.url}`);\n out(`${pc.bold(\"Consumer\")} ${st?.consumer ?? profile.consumer}`);\n // What the last run used (flags included), else what the profile has saved.\n const exec = st && st.exec !== undefined ? st.exec : profile.exec;\n const forward = st && st.forward !== undefined ? st.forward : profile.forward?.url;\n const targets = [exec ? `exec ${safeText(exec, 120)}` : null, forward ? `forward ${forward}` : null].filter(Boolean);\n out(`${pc.bold(\"Runs\")} ${targets.length ? targets.join(\" · \") : pc.gray(\"nothing saved (pass --exec/--forward with --save)\")}`);\n\n let svc = \"not installed\";\n if (process.platform === \"linux\" || process.platform === \"darwin\") {\n if (isServiceInstalled(name)) svc = `installed (${process.platform === \"linux\" ? unitName(name) : launchdLabel(name)}; details: ${NPX} listen service status${flagFor(name)})`;\n }\n out(`${pc.bold(\"Service\")} ${svc}`);\n\n try {\n // Never send the key over a connection `listen` itself would refuse.\n await checkTransport(profile.url, { insecure: profile.insecure });\n const self = await getSelf(profile.url, profile.apiKey, 5000);\n out(`${pc.bold(\"Key\")} ${pc.green(\"valid\")} · ${self.grants.length ? self.grants.map((g) => `${g.accountEmail} (${g.capabilities.join(\", \")}; ${g.sendMode})`).join(\", \") : pc.yellow(\"no mailbox grants\")}`);\n } catch (err) {\n const revoked = err instanceof ApiError && (err.status === 401 || err.status === 403);\n const why = err instanceof ApiError && err.status === 0 ? (err.cause as string) : err instanceof Error ? err.message.split(\"\\n\")[0] : String(err);\n if (err instanceof TransportError) out(`${pc.bold(\"Key\")} ${pc.red(`not checked: ${why}`)}`);\n else out(`${pc.bold(\"Key\")} ${revoked ? pc.red(`rejected: ${(err as Error).message}`) : pc.yellow(`not checked (${why})`)}`);\n }\n\n if (!st) {\n out(`${pc.bold(\"Listener\")} ${pc.gray(\"has not run yet\")}`);\n } else {\n const state = !alive\n ? pc.gray(`not running${st.stoppedAt ? ` (stopped ${ago(st.stoppedAt)}${st.exitReason ? `: ${safeText(st.exitReason, 160)}` : \"\"})` : \" (exited unexpectedly)\"}`)\n : st.connected\n ? pc.green(`running (pid ${st.pid}), connected ${ago(st.connectedSince)}`)\n : pc.yellow(`running (pid ${st.pid}), disconnected${st.lastDisconnect ? ` ${ago(st.lastDisconnect.at)}: ${safeText(st.lastDisconnect.reason, 160)}` : \"\"}`);\n out(`${pc.bold(\"Listener\")} ${state}`);\n out(`${pc.bold(\"Cursor\")} acked #${st.ackedSeq} · received #${st.receivedSeq}${alive && st.running ? ` · ${st.running} in progress` : \"\"} · last event ${ago(st.lastEventAt)}`);\n if (st.recent.length) {\n out(pc.bold(\"Recent\"));\n for (const r of st.recent.slice(0, 8)) {\n const icon = r.outcome === \"succeeded\" ? pc.green(\"✓\") : r.outcome === \"dead\" ? pc.red(\"✖\") : r.outcome === \"retrying\" ? pc.yellow(\"↻\") : pc.gray(\"·\");\n out(` ${icon} #${r.seq} ${r.type} ${pc.gray(`${ago(r.at)}${r.attempts > 1 ? `, ${r.attempts} attempts` : \"\"}${r.error && r.outcome !== \"succeeded\" ? `, ${safeText(r.error, 100)}` : \"\"}`)}`);\n }\n }\n }\n out(`${pc.bold(\"Dead\")} ${dead.length ? pc.red(`${dead.length} event${dead.length > 1 ? \"s\" : \"\"}`) + pc.gray(` (inspect: ${NPX} listen dead${flagFor(name)})`) : \"0\"}`);\n out(`${pc.bold(\"Log\")} ${logPath(name)}`);\n}\n\n// ───────────── listen dead ─────────────\n\nexport function printDead(profileName: string, opts: { json?: boolean }): void {\n const name = validProfileName(profileName);\n const entries = readDead(name);\n if (opts.json) {\n for (const e of entries) out(JSON.stringify(e));\n return;\n }\n if (!entries.length) {\n out(`No dead events for profile \"${name}\".`);\n return;\n }\n for (const e of entries) {\n const m = e.payload.data.message;\n out(`${pc.red(\"✖\")} #${e.seq} ${e.type} ${pc.gray(e.id)} ${pc.gray(`died ${ago(e.deadAt)} after ${e.attempts} attempts`)}`);\n if (m) out(` from ${safeText(m.from?.email ?? \"?\", 80)} \"${safeText(m.subject, 80)}\"`);\n out(pc.gray(` ${safeText(e.error, 300)}`));\n }\n out();\n out(pc.gray(`${entries.length} dead event${entries.length > 1 ? \"s\" : \"\"} in ${deadPath(name)}.`));\n out(pc.gray(`Replay with: ${NPX} listen dead --replay${flagFor(name)} (one: --id <evt_…>; drop all: --clear)`));\n out(pc.gray(`Replay uses the profile's saved --exec/--forward; if you ran them as flags only, pass them again, e.g. --replay --exec '<command>'.`));\n}\n\nexport function clearDead(profileName: string, id?: string): void {\n const name = validProfileName(profileName);\n const entries = readDead(name);\n const keep = id ? entries.filter((e) => e.id !== id) : [];\n writeDead(name, keep);\n out(`Removed ${entries.length - keep.length} dead event${entries.length - keep.length === 1 ? \"\" : \"s\"}.`);\n}\n\n/**\n * Runs dead events again through the profile's targets (or the ones passed as flags). Successes are\n * removed from dead.jsonl; failures stay. Returns how many still failed.\n */\nexport async function replayDead(flags: ListenFlags & { id?: string }, opts: { retryDelayMs?: (n: number) => number } = {}): Promise<number> {\n const name = validProfileName(flags.profile ?? DEFAULT_PROFILE);\n const profile = loadProfile(name);\n const { config } = resolveListen(name, profile, { ...flags, save: false });\n const all = readDead(name);\n const chosen = all.filter((e) => !flags.id || e.id === flags.id);\n if (!chosen.length) {\n out(flags.id ? `No dead event with id ${flags.id}.` : `No dead events for profile \"${name}\".`);\n return 0;\n }\n const available: Target[] = [];\n if (config.exec) available.push(\"exec\");\n if (config.forward) available.push(\"forward\");\n if (!available.length) throw new Error(`Nothing to replay to: pass --exec '<command>' and/or --forward <url> (or save them in the profile with \\`${NPX} listen --save …\\`).`);\n if (config.exec && process.platform === \"win32\") throw new Error(\"--exec isn't supported on Windows; replay with --forward instead.\");\n\n const timeoutMs = config.timeoutSeconds * 1000;\n if (config.cwdIsDefault) ensurePrivateDir(config.cwd);\n const failed = new Map<string, DeadEntry>();\n const bySeq = new Map(chosen.map((e) => [e.seq, e]));\n const dispatcher = new Dispatcher({\n targets: available,\n concurrency: config.concurrency,\n maxAttempts: config.maxAttempts,\n retryDelayMs: opts.retryDelayMs,\n run: async (job: Job, signal) => {\n const errors: string[] = [];\n if (job.remaining.has(\"exec\") && config.exec) {\n const r = await runCommand(config.exec, job.payload, { timeoutMs, signal, cwd: config.cwd });\n if (r.ok) job.remaining.delete(\"exec\");\n else errors.push(`command: ${r.error}`);\n }\n if (job.remaining.has(\"forward\") && config.forward) {\n const r = await forwardEvent(config.forward.url, config.forward.secret, job.payload, { timeoutMs: Math.min(timeoutMs, 30_000), signal });\n if (r.ok) job.remaining.delete(\"forward\");\n else errors.push(`forward: ${r.error}`);\n }\n return errors;\n },\n onAdvance: () => {},\n onDead: (job) => {\n const e = bySeq.get(job.seq)!;\n failed.set(e.id, { ...e, deadAt: new Date().toISOString(), attempts: e.attempts + job.attempts, error: job.lastError ?? \"failed\" });\n out(`${pc.red(\"✖\")} #${job.seq} still failing: ${safeText(job.lastError ?? \"failed\", 200)}`);\n },\n onFinished: (job) => {\n if (job.outcome === \"succeeded\") out(`${pc.green(\"✓\")} #${job.seq} ${job.payload.type} replayed`);\n },\n });\n out(`Replaying ${chosen.length} dead event${chosen.length > 1 ? \"s\" : \"\"} (up to ${config.maxAttempts} attempts each)…`);\n // Only the targets that never succeeded (a forward that went through isn't POSTed again).\n for (const e of [...chosen].sort((a, b) => a.seq - b.seq)) {\n const targets = e.targets?.length ? e.targets.filter((t) => available.includes(t)) : available;\n if (!targets.length) {\n out(`${pc.yellow(\"!\")} #${e.seq} needs ${e.targets.join(\" and \")}, which isn't configured now; pass --${e.targets[0]} to replay it.`);\n failed.set(e.id, e);\n continue;\n }\n dispatcher.push(e.payload, { targets });\n }\n await dispatcher.idle();\n\n const replayed = new Set(chosen.map((e) => e.id));\n // Re-read so entries added by a running listener meanwhile are kept.\n const current = readDead(name);\n writeDead(name, [...current.filter((e) => !replayed.has(e.id)), ...failed.values()]);\n out(`${chosen.length - failed.size} replayed, ${failed.size} still dead.`);\n return failed.size;\n}\n"],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { i as now, r as json } from "./db-D0gvshCc.js";
|
|
3
3
|
//#region server/audit.ts
|
|
4
4
|
const SELECT = `SELECT a.*, CASE a.actor_type WHEN 'agent' THEN ag.name WHEN 'admin' THEN 'admin' ELSE NULL END AS actor_name
|
|
5
5
|
FROM audit_log a LEFT JOIN agents ag ON a.actor_type = 'agent' AND ag.id = a.actor_id`;
|
|
@@ -62,4 +62,4 @@ var AuditLog = class {
|
|
|
62
62
|
//#endregion
|
|
63
63
|
export { AuditLog as t };
|
|
64
64
|
|
|
65
|
-
//# sourceMappingURL=audit-
|
|
65
|
+
//# sourceMappingURL=audit-bItbJYFP.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"audit-
|
|
1
|
+
{"version":3,"file":"audit-bItbJYFP.js","names":[],"sources":["../server/audit.ts"],"sourcesContent":["import type { ActorType, AuditEntry, AuditOutcome, Page } from \"../shared/types.ts\";\nimport type { Bus } from \"./bus.ts\";\nimport { type Db, json, now } from \"./db/index.ts\";\n\nexport interface AuditInput {\n actorType: ActorType;\n actorId?: string | null;\n /** MCP tool name or console action, e.g. \"send_message\", \"agent.create\", \"account.connect\". */\n action: string;\n accountId?: string | null;\n target?: string | null;\n outcome: AuditOutcome;\n /** Required for denials: the guardrail that blocked it (\"allow_to\", \"label_scope\", \"rate_limit\", …). */\n reason?: string | null;\n durationMs?: number | null;\n /** Small, non-sensitive context. Never message bodies, keys or tokens. */\n detail?: Record<string, unknown> | null;\n}\n\nexport interface AuditQuery {\n agentId?: string;\n accountId?: string;\n outcome?: AuditOutcome;\n action?: string;\n /** Opaque cursor from a previous page. */\n cursor?: string | null;\n limit?: number;\n}\n\ninterface AuditRow {\n id: number;\n ts: string;\n actor_type: ActorType;\n actor_id: string | null;\n actor_name: string | null;\n action: string;\n account_id: string | null;\n target: string | null;\n outcome: AuditOutcome;\n reason: string | null;\n duration_ms: number | null;\n detail: string | null;\n}\n\nconst SELECT = `SELECT a.*, CASE a.actor_type WHEN 'agent' THEN ag.name WHEN 'admin' THEN 'admin' ELSE NULL END AS actor_name\n FROM audit_log a LEFT JOIN agents ag ON a.actor_type = 'agent' AND ag.id = a.actor_id`;\n\nconst toEntry = (r: AuditRow): AuditEntry => ({\n id: r.id,\n ts: r.ts,\n actorType: r.actor_type,\n actorId: r.actor_id,\n actorName: r.actor_name,\n action: r.action,\n accountId: r.account_id,\n target: r.target,\n outcome: r.outcome,\n reason: r.reason,\n durationMs: r.duration_ms,\n detail: json.parse<Record<string, unknown> | null>(r.detail, null),\n});\n\n/** Append-only audit log. Every agent tool call and every admin mutation is recorded. */\nexport class AuditLog {\n constructor(\n private readonly db: Db,\n private readonly bus: Bus,\n ) {}\n\n record(input: AuditInput): AuditEntry {\n const { lastInsertRowid } = this.db.run(\n `INSERT INTO audit_log (ts, actor_type, actor_id, action, account_id, target, outcome, reason, duration_ms, detail)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n now(),\n input.actorType,\n input.actorId ?? null,\n input.action,\n input.accountId ?? null,\n input.target ?? null,\n input.outcome,\n input.reason ?? null,\n input.durationMs ?? null,\n input.detail ? json.str(input.detail) : null,\n );\n const entry = toEntry(this.db.get<AuditRow>(`${SELECT} WHERE a.id = ?`, lastInsertRowid)!);\n this.bus.emit(\"audit\", entry);\n return entry;\n }\n\n /** Newest first. */\n list(q: AuditQuery = {}): Page<AuditEntry> {\n const where: string[] = [];\n const params: (string | number)[] = [];\n const add = (clause: string, value: string | number) => {\n where.push(clause);\n params.push(value);\n };\n if (q.agentId) add(\"a.actor_id = ?\", q.agentId);\n if (q.accountId) add(\"a.account_id = ?\", q.accountId);\n if (q.outcome) add(\"a.outcome = ?\", q.outcome);\n if (q.action) add(\"a.action = ?\", q.action);\n const before = Number(q.cursor);\n if (q.cursor && Number.isInteger(before)) add(\"a.id < ?\", before);\n const limit = Math.min(Math.max(q.limit ?? 50, 1), 200);\n const rows = this.db.all<AuditRow>(\n `${SELECT} ${where.length ? \"WHERE \" + where.join(\" AND \") : \"\"} ORDER BY a.id DESC LIMIT ?`,\n ...params,\n limit + 1,\n );\n const items = rows.slice(0, limit).map(toEntry);\n return { items, nextCursor: rows.length > limit ? String(items.at(-1)!.id) : null };\n }\n\n /** Deletes entries older than `beforeIso`. Returns the number removed. */\n prune(beforeIso: string): number {\n return this.db.run(\"DELETE FROM audit_log WHERE ts < ?\", beforeIso).changes;\n }\n}\n"],"mappings":";;;AA4CA,MAAM,SAAS;;AAGf,MAAM,WAAW,OAA6B;CAC5C,IAAI,EAAE;CACN,IAAI,EAAE;CACN,WAAW,EAAE;CACb,SAAS,EAAE;CACX,WAAW,EAAE;CACb,QAAQ,EAAE;CACV,WAAW,EAAE;CACb,QAAQ,EAAE;CACV,SAAS,EAAE;CACX,QAAQ,EAAE;CACV,YAAY,EAAE;CACd,QAAQ,KAAK,MAAsC,EAAE,QAAQ,IAAI;AACnE;;AAGA,IAAa,WAAb,MAAsB;CAED;CACA;CAFnB,YACE,IACA,KACA;EAFiB,KAAA,KAAA;EACA,KAAA,MAAA;CAChB;CAEH,OAAO,OAA+B;EACpC,MAAM,EAAE,oBAAoB,KAAK,GAAG,IAClC;+CAEA,IAAI,GACJ,MAAM,WACN,MAAM,WAAW,MACjB,MAAM,QACN,MAAM,aAAa,MACnB,MAAM,UAAU,MAChB,MAAM,SACN,MAAM,UAAU,MAChB,MAAM,cAAc,MACpB,MAAM,SAAS,KAAK,IAAI,MAAM,MAAM,IAAI,IAC1C;EACA,MAAM,QAAQ,QAAQ,KAAK,GAAG,IAAc,GAAG,OAAO,kBAAkB,eAAe,CAAE;EACzF,KAAK,IAAI,KAAK,SAAS,KAAK;EAC5B,OAAO;CACT;;CAGA,KAAK,IAAgB,CAAC,GAAqB;EACzC,MAAM,QAAkB,CAAC;EACzB,MAAM,SAA8B,CAAC;EACrC,MAAM,OAAO,QAAgB,UAA2B;GACtD,MAAM,KAAK,MAAM;GACjB,OAAO,KAAK,KAAK;EACnB;EACA,IAAI,EAAE,SAAS,IAAI,kBAAkB,EAAE,OAAO;EAC9C,IAAI,EAAE,WAAW,IAAI,oBAAoB,EAAE,SAAS;EACpD,IAAI,EAAE,SAAS,IAAI,iBAAiB,EAAE,OAAO;EAC7C,IAAI,EAAE,QAAQ,IAAI,gBAAgB,EAAE,MAAM;EAC1C,MAAM,SAAS,OAAO,EAAE,MAAM;EAC9B,IAAI,EAAE,UAAU,OAAO,UAAU,MAAM,GAAG,IAAI,YAAY,MAAM;EAChE,MAAM,QAAQ,KAAK,IAAI,KAAK,IAAI,EAAE,SAAS,IAAI,CAAC,GAAG,GAAG;EACtD,MAAM,OAAO,KAAK,GAAG,IACnB,GAAG,OAAO,GAAG,MAAM,SAAS,WAAW,MAAM,KAAK,OAAO,IAAI,GAAG,8BAChE,GAAG,QACH,QAAQ,CACV;EACA,MAAM,QAAQ,KAAK,MAAM,GAAG,KAAK,CAAC,CAAC,IAAI,OAAO;EAC9C,OAAO;GAAE;GAAO,YAAY,KAAK,SAAS,QAAQ,OAAO,MAAM,GAAG,EAAE,CAAC,CAAE,EAAE,IAAI;EAAK;CACpF;;CAGA,MAAM,WAA2B;EAC/B,OAAO,KAAK,GAAG,IAAI,sCAAsC,SAAS,CAAC,CAAC;CACtE;AACF"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { t as HttpError } from "./errors-
|
|
2
|
+
import { t as HttpError } from "./errors-FbMS3QBo.js";
|
|
3
3
|
import { i as sha256 } from "./crypto-DYZ4R7c2.js";
|
|
4
|
-
import { i as regenerateSetupToken, n as ensureSetupToken } from "./setup-token-
|
|
4
|
+
import { i as regenerateSetupToken, n as ensureSetupToken } from "./setup-token-BO89xnpb.js";
|
|
5
5
|
import { isIP, isIPv4, isIPv6 } from "node:net";
|
|
6
6
|
import { getConnInfo } from "@hono/node-server/conninfo";
|
|
7
7
|
import { deleteCookie, getCookie, setCookie } from "hono/cookie";
|
|
@@ -129,6 +129,104 @@ function clientKey(ip) {
|
|
|
129
129
|
]).slice(0, 4).map((g) => g.replace(/^0+(?=.)/, "")).join(":")}::/64`;
|
|
130
130
|
}
|
|
131
131
|
//#endregion
|
|
132
|
+
//#region server/auth/proxy.ts
|
|
133
|
+
function parseV4(ip) {
|
|
134
|
+
return ip.split(".").reduce((acc, part) => acc << 8n | BigInt(Number(part)), 0n);
|
|
135
|
+
}
|
|
136
|
+
function parseAddress(raw) {
|
|
137
|
+
const ip = raw.trim().replace(/^\[|\]$/g, "").replace(/%.*$/, "").toLowerCase();
|
|
138
|
+
if (isIPv4(ip)) return {
|
|
139
|
+
v6: false,
|
|
140
|
+
n: parseV4(ip)
|
|
141
|
+
};
|
|
142
|
+
if (!isIPv6(ip)) return null;
|
|
143
|
+
const mapped = /^(?:0{0,4}:){0,5}:?ffff:(\d+\.\d+\.\d+\.\d+)$/.exec(ip);
|
|
144
|
+
if (mapped && isIPv4(mapped[1])) return {
|
|
145
|
+
v6: false,
|
|
146
|
+
n: parseV4(mapped[1])
|
|
147
|
+
};
|
|
148
|
+
let text = ip;
|
|
149
|
+
const v4Tail = /(\d+\.\d+\.\d+\.\d+)$/.exec(text);
|
|
150
|
+
if (v4Tail) {
|
|
151
|
+
const n = parseV4(v4Tail[1]);
|
|
152
|
+
text = text.slice(0, -v4Tail[1].length) + `${(n >> 16n).toString(16)}:${(n & 65535n).toString(16)}`;
|
|
153
|
+
}
|
|
154
|
+
const [head = "", tail] = text.split("::");
|
|
155
|
+
const left = head ? head.split(":") : [];
|
|
156
|
+
const right = tail ? tail.split(":") : [];
|
|
157
|
+
const groups = tail === void 0 ? left : [
|
|
158
|
+
...left,
|
|
159
|
+
...Array(8 - left.length - right.length).fill("0"),
|
|
160
|
+
...right
|
|
161
|
+
];
|
|
162
|
+
if (groups.length !== 8) return null;
|
|
163
|
+
return {
|
|
164
|
+
v6: true,
|
|
165
|
+
n: groups.reduce((acc, g) => acc << 16n | BigInt(Number.parseInt(g || "0", 16)), 0n)
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Parses "10.0.0.5", "10.0.0.0/8", "fd00::/8", "::ffff:10.0.0.0/104"; null when invalid. A /0
|
|
170
|
+
* (trust everyone) is refused: it would let any client pick its own address.
|
|
171
|
+
*/
|
|
172
|
+
function parseRange(entry) {
|
|
173
|
+
const parts = entry.trim().split("/");
|
|
174
|
+
if (parts.length > 2) return null;
|
|
175
|
+
const [ip = "", prefix] = parts;
|
|
176
|
+
const addr = parseAddress(ip);
|
|
177
|
+
if (!addr) return null;
|
|
178
|
+
let bits = addr.v6 ? 128 : 32;
|
|
179
|
+
if (prefix !== void 0) {
|
|
180
|
+
if (!/^\d{1,3}$/.test(prefix)) return null;
|
|
181
|
+
bits = Number(prefix);
|
|
182
|
+
if (!addr.v6 && ip.includes(":")) bits -= 96;
|
|
183
|
+
if (bits <= 0 || bits > (addr.v6 ? 128 : 32)) return null;
|
|
184
|
+
}
|
|
185
|
+
const width = BigInt(addr.v6 ? 128 : 32);
|
|
186
|
+
const mask = bits === 0 ? 0n : (1n << BigInt(bits)) - 1n << width - BigInt(bits);
|
|
187
|
+
return {
|
|
188
|
+
v6: addr.v6,
|
|
189
|
+
base: addr.n & mask,
|
|
190
|
+
bits
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
function inRange(addr, r) {
|
|
194
|
+
if (addr.v6 !== r.v6) return false;
|
|
195
|
+
const width = BigInt(r.v6 ? 128 : 32);
|
|
196
|
+
const mask = r.bits === 0 ? 0n : (1n << BigInt(r.bits)) - 1n << width - BigInt(r.bits);
|
|
197
|
+
return (addr.n & mask) === r.base;
|
|
198
|
+
}
|
|
199
|
+
const LOOPBACK = [parseRange("127.0.0.0/8"), parseRange("::1/128")];
|
|
200
|
+
let cache;
|
|
201
|
+
/** TRUST_PROXY_FROM as ranges. Invalid entries are reported once and ignored. */
|
|
202
|
+
function trustedRanges(raw) {
|
|
203
|
+
if (cache?.raw === raw) return cache.ranges;
|
|
204
|
+
const ranges = [];
|
|
205
|
+
const invalid = [];
|
|
206
|
+
for (const entry of raw.split(/[,\s]+/).filter(Boolean)) {
|
|
207
|
+
const r = parseRange(entry);
|
|
208
|
+
if (r) ranges.push(r);
|
|
209
|
+
else invalid.push(entry);
|
|
210
|
+
}
|
|
211
|
+
if (invalid.length) process.stderr.write(`agentinbox: ignoring invalid TRUST_PROXY_FROM entries: ${invalid.join(", ")}\n`);
|
|
212
|
+
cache = {
|
|
213
|
+
raw,
|
|
214
|
+
ranges
|
|
215
|
+
};
|
|
216
|
+
return ranges;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Whether forwarding headers from this socket peer may be believed: loopback always (a proxy on
|
|
220
|
+
* the same host), anything else only when listed in TRUST_PROXY_FROM (IPs or CIDRs, comma-separated).
|
|
221
|
+
* The daemon usually binds 0.0.0.0, so trusting whole private ranges would let any LAN or tailnet
|
|
222
|
+
* peer forge its address.
|
|
223
|
+
*/
|
|
224
|
+
function isTrustedProxy(peer, trustFrom = process.env.TRUST_PROXY_FROM ?? "") {
|
|
225
|
+
const addr = parseAddress(peer);
|
|
226
|
+
if (!addr) return false;
|
|
227
|
+
return [...LOOPBACK, ...trustedRanges(trustFrom)].some((r) => inRange(addr, r));
|
|
228
|
+
}
|
|
229
|
+
//#endregion
|
|
132
230
|
//#region server/auth/sessions.ts
|
|
133
231
|
const SESSION_TTL_MS = 2592e6;
|
|
134
232
|
/** Sliding refresh never extends a session past this age: sign in again after 90 days. */
|
|
@@ -257,16 +355,6 @@ function resetAdminPassword(ctx) {
|
|
|
257
355
|
});
|
|
258
356
|
return regenerateSetupToken(ctx.config.dataDir);
|
|
259
357
|
}
|
|
260
|
-
/** Loopback, RFC 1918, CGNAT (100.64/10) and IPv6 ULA (fc00::/7): where a reverse proxy can be. */
|
|
261
|
-
function isPrivateAddress(ip) {
|
|
262
|
-
const addr = ip.replace(/%.*$/, "").toLowerCase().replace(/^::ffff:(?=\d+\.\d+\.\d+\.\d+$)/, "");
|
|
263
|
-
if (isIPv4(addr)) {
|
|
264
|
-
const [a = 0, b = 0] = addr.split(".").map(Number);
|
|
265
|
-
return a === 127 || a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 100 && b >= 64 && b <= 127;
|
|
266
|
-
}
|
|
267
|
-
if (isIPv6(addr)) return addr === "::1" || /^f[cd][0-9a-f]{0,2}:/.test(addr);
|
|
268
|
-
return false;
|
|
269
|
-
}
|
|
270
358
|
function socketAddress(c) {
|
|
271
359
|
try {
|
|
272
360
|
return getConnInfo(c).remote.address ?? null;
|
|
@@ -276,15 +364,30 @@ function socketAddress(c) {
|
|
|
276
364
|
}
|
|
277
365
|
/**
|
|
278
366
|
* The client address. X-Forwarded-For is honored only with TRUST_PROXY and only when the
|
|
279
|
-
* connection comes from a
|
|
367
|
+
* connection comes from a trusted proxy (loopback, or TRUST_PROXY_FROM); it is read from the
|
|
280
368
|
* right, since proxies append the address they saw and entries further left are client-controlled.
|
|
281
369
|
* TRUST_PROXY_HOPS (default 1) is the number of proxies in front of agentinbox.
|
|
282
370
|
*/
|
|
371
|
+
/** Some proxies (Azure, IIS) append ":port" to X-Forwarded-For entries. */
|
|
372
|
+
function stripPort(entry) {
|
|
373
|
+
const v6 = /^\[([^\]]+)\](?::\d+)?$/.exec(entry);
|
|
374
|
+
if (v6) return v6[1];
|
|
375
|
+
const v4 = /^(\d{1,3}(?:\.\d{1,3}){3}):\d+$/.exec(entry);
|
|
376
|
+
return v4 ? v4[1] : entry;
|
|
377
|
+
}
|
|
378
|
+
let warnedUntrustedProxy = false;
|
|
379
|
+
/** Upgrades from the old private-range rule silently lost proxy trust; say so once. */
|
|
380
|
+
function warnUntrustedProxy(peer) {
|
|
381
|
+
if (warnedUntrustedProxy) return;
|
|
382
|
+
warnedUntrustedProxy = true;
|
|
383
|
+
process.stderr.write(`agentinbox: ignoring X-Forwarded-For from ${peer}: TRUST_PROXY only trusts loopback proxies unless TRUST_PROXY_FROM lists them (e.g. TRUST_PROXY_FROM=${peer}).\n`);
|
|
384
|
+
}
|
|
283
385
|
function clientIp(c, trustProxy) {
|
|
284
386
|
const peer = socketAddress(c);
|
|
285
|
-
if (trustProxy && peer &&
|
|
387
|
+
if (trustProxy && peer && !isTrustedProxy(peer) && c.req.header("x-forwarded-for")) warnUntrustedProxy(peer);
|
|
388
|
+
if (trustProxy && peer && isTrustedProxy(peer)) {
|
|
286
389
|
const hops = Math.max(1, Number.parseInt(process.env.TRUST_PROXY_HOPS ?? "1", 10) || 1);
|
|
287
|
-
const chain = (c.req.header("x-forwarded-for") ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
390
|
+
const chain = (c.req.header("x-forwarded-for") ?? "").split(",").map((s) => stripPort(s.trim())).filter(Boolean);
|
|
288
391
|
const forwarded = chain[chain.length - hops];
|
|
289
392
|
if (forwarded && isIP(forwarded)) return forwarded;
|
|
290
393
|
}
|
|
@@ -293,7 +396,7 @@ function clientIp(c, trustProxy) {
|
|
|
293
396
|
function isSecure(c, trustProxy) {
|
|
294
397
|
if (new URL(c.req.url).protocol === "https:") return true;
|
|
295
398
|
const peer = socketAddress(c);
|
|
296
|
-
return trustProxy && !!peer &&
|
|
399
|
+
return trustProxy && !!peer && isTrustedProxy(peer) && c.req.header("x-forwarded-proto")?.split(",")[0]?.trim().toLowerCase() === "https";
|
|
297
400
|
}
|
|
298
401
|
function sessionToken(c) {
|
|
299
402
|
return getCookie(c, "aib_session") || void 0;
|
|
@@ -442,6 +545,6 @@ function rejection(c, check) {
|
|
|
442
545
|
return check.lockedMinutes ? lockedOut(c, check.lockedMinutes) : null;
|
|
443
546
|
}
|
|
444
547
|
//#endregion
|
|
445
|
-
export {
|
|
548
|
+
export { sessionToken as _, adminAuth as a, LoginLimiter as b, csrfGuard as c, hasSession as d, isClaimed as f, resetAdminPassword as g, requireAdmin as h, Semaphore as i, currentSetupToken as l, rejection as m, MAX_PASSWORD_LENGTH as n, checkSecret as o, lockedOut as p, SESSION_COOKIE as r, clientIp as s, CSRF_HEADER as t, endSession as u, startSession as v, clientKey as x, BAN_MS as y };
|
|
446
549
|
|
|
447
|
-
//# sourceMappingURL=auth-
|
|
550
|
+
//# sourceMappingURL=auth-0NrMvHdE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth-0NrMvHdE.js","names":[],"sources":["../server/auth/lockout.ts","../server/auth/proxy.ts","../server/auth/sessions.ts","../server/auth/index.ts"],"sourcesContent":["import { isIPv6 } from \"node:net\";\nimport { sha256 } from \"../crypto.ts\";\n\n/** Distinct wrong secrets per client within the window before a ban. */\nexport const MAX_FAILURES = 5;\n/** Raw failed attempts per client within the window (repeats of the same wrong secret included). */\nexport const MAX_ATTEMPTS = 30;\n/** Distinct wrong secrets across all clients within the window before every attempt is slowed down. */\nexport const GLOBAL_MAX_FAILURES = 100;\n/** Delay per attempt while the global budget is exceeded. */\nexport const GLOBAL_SLOWDOWN_MS = 2000;\nexport const WINDOW_MS = 15 * 60_000;\nexport const BAN_MS = 15 * 60_000;\n/** Tracked clients; the least recently seen is dropped beyond this. */\nexport const MAX_ENTRIES = 10_000;\n\ninterface Entry {\n tried: Set<string>;\n attempts: number;\n since: number;\n bannedUntil: number;\n}\n\n/**\n * Failed sign-in limiter. Per client (IPv4 address or IPv6 /64): 5 distinct wrong secrets or 30\n * attempts within 15 minutes ban it for 15 minutes. A secret already known to be wrong is\n * rejected without hashing (`knownBad`) and counts only as an attempt, so a client retrying a stale\n * password can't spend every attempt, and can't make the server hash it again either. Past a global\n * budget of distinct failures, every attempt is slowed down (never refused, so many clients can't\n * lock the owner out).\n */\nexport class LoginLimiter {\n private readonly entries = new Map<string, Entry>();\n private global = { failures: 0, since: 0 };\n\n constructor(private readonly clock: () => number = Date.now) {}\n\n /** Minutes until this client may try again, or 0. */\n bannedMinutes(ip: string): number {\n const t = this.clock();\n const entry = this.entries.get(clientKey(ip));\n const until = entry && entry.bannedUntil > t ? entry.bannedUntil : 0;\n return until > t ? Math.ceil((until - t) / 60_000) : 0;\n }\n\n /** The global failure budget is exhausted: slow every attempt down. */\n overGlobalBudget(): boolean {\n return this.global.failures >= GLOBAL_MAX_FAILURES && this.clock() - this.global.since < WINDOW_MS;\n }\n\n /** The client already failed with exactly this secret in the current window. */\n knownBad(ip: string, secret: string): boolean {\n const entry = this.current(clientKey(ip), this.clock());\n return !!entry?.tried.has(sha256(secret));\n }\n\n /** Records a failure; returns true when it started a ban for this client. `global: false` keeps it out of the global budget. */\n fail(ip: string, secret: string, opts: { global?: boolean } = {}): boolean {\n return this.record(ip, secret, opts).banned;\n }\n\n /**\n * Counts an attempt with this secret now, before it is verified, so concurrent requests can't\n * all pass the ban check first. `countedGlobally` says whether it used global budget (refund it\n * with `refundGlobal` when the secret turns out to be right).\n */\n record(ip: string, secret: string, opts: { global?: boolean } = {}): { banned: boolean; countedGlobally: boolean } {\n const t = this.clock();\n const key = clientKey(ip);\n const entry = this.current(key, t) ?? { tried: new Set<string>(), attempts: 0, since: t, bannedUntil: 0 };\n const hash = sha256(secret);\n let countedGlobally = false;\n if (!entry.tried.has(hash)) {\n entry.tried.add(hash);\n if (opts.global !== false) {\n if (t - this.global.since >= WINDOW_MS) this.global = { failures: 0, since: t };\n this.global.failures++;\n countedGlobally = true;\n }\n }\n entry.attempts++;\n const banned = entry.tried.size >= MAX_FAILURES || entry.attempts >= MAX_ATTEMPTS;\n if (banned) entry.bannedUntil = t + BAN_MS;\n this.touch(key, entry);\n return { banned, countedGlobally };\n }\n\n refundGlobal(): void {\n if (this.global.failures > 0) this.global.failures--;\n }\n\n clear(ip: string): void {\n this.entries.delete(clientKey(ip));\n }\n\n /** After the password changes, previously wrong secrets may be right. */\n reset(): void {\n this.entries.clear();\n this.global = { failures: 0, since: 0 };\n }\n\n get size(): number {\n return this.entries.size;\n }\n\n /** The client's entry if its window (or ban) is still running. */\n private current(key: string, t: number): Entry | undefined {\n const entry = this.entries.get(key);\n if (!entry) return undefined;\n if (entry.bannedUntil > t || (!entry.bannedUntil && t - entry.since < WINDOW_MS)) return entry;\n this.entries.delete(key);\n return undefined;\n }\n\n /** Map order doubles as LRU order: re-insert on use, evict from the front. */\n private touch(key: string, entry: Entry): void {\n this.entries.delete(key);\n this.entries.set(key, entry);\n while (this.entries.size > MAX_ENTRIES) this.entries.delete(this.entries.keys().next().value!);\n }\n}\n\n/** IPv4 addresses as is (also when IPv4-mapped); IPv6 by /64, since one client usually owns the whole prefix. */\nexport function clientKey(ip: string): string {\n const addr = ip.replace(/%.*$/, \"\").toLowerCase();\n const mapped = /^::ffff:(\\d+\\.\\d+\\.\\d+\\.\\d+)$/.exec(addr);\n if (mapped) return mapped[1]!;\n if (!isIPv6(addr)) return addr;\n const [head = \"\", tail] = addr.split(\"::\");\n const left = head ? head.split(\":\") : [];\n const right = tail ? tail.split(\":\") : [];\n const groups = tail === undefined ? left : [...left, ...Array(8 - left.length - right.length).fill(\"0\"), ...right];\n return `${groups\n .slice(0, 4)\n .map((g) => g.replace(/^0+(?=.)/, \"\"))\n .join(\":\")}::/64`;\n}\n","import { isIPv4, isIPv6 } from \"node:net\";\n\n/** An address as a family and a big integer, with IPv4-mapped IPv6 (::ffff:a.b.c.d) treated as IPv4. */\ninterface Addr {\n v6: boolean;\n n: bigint;\n}\n\nfunction parseV4(ip: string): bigint {\n return ip.split(\".\").reduce((acc, part) => (acc << 8n) | BigInt(Number(part)), 0n);\n}\n\nexport function parseAddress(raw: string): Addr | null {\n const ip = raw.trim().replace(/^\\[|\\]$/g, \"\").replace(/%.*$/, \"\").toLowerCase();\n if (isIPv4(ip)) return { v6: false, n: parseV4(ip) };\n if (!isIPv6(ip)) return null;\n const mapped = /^(?:0{0,4}:){0,5}:?ffff:(\\d+\\.\\d+\\.\\d+\\.\\d+)$/.exec(ip);\n if (mapped && isIPv4(mapped[1]!)) return { v6: false, n: parseV4(mapped[1]!) };\n // Expand an embedded IPv4 tail into two hextets, then \"::\".\n let text = ip;\n const v4Tail = /(\\d+\\.\\d+\\.\\d+\\.\\d+)$/.exec(text);\n if (v4Tail) {\n const n = parseV4(v4Tail[1]!);\n text = text.slice(0, -v4Tail[1]!.length) + `${(n >> 16n).toString(16)}:${(n & 0xffffn).toString(16)}`;\n }\n const [head = \"\", tail] = text.split(\"::\");\n const left = head ? head.split(\":\") : [];\n const right = tail ? tail.split(\":\") : [];\n const groups = tail === undefined ? left : [...left, ...Array<string>(8 - left.length - right.length).fill(\"0\"), ...right];\n if (groups.length !== 8) return null;\n return { v6: true, n: groups.reduce((acc, g) => (acc << 16n) | BigInt(Number.parseInt(g || \"0\", 16)), 0n) };\n}\n\ninterface Range {\n v6: boolean;\n base: bigint;\n bits: number;\n}\n\n/**\n * Parses \"10.0.0.5\", \"10.0.0.0/8\", \"fd00::/8\", \"::ffff:10.0.0.0/104\"; null when invalid. A /0\n * (trust everyone) is refused: it would let any client pick its own address.\n */\nexport function parseRange(entry: string): Range | null {\n const parts = entry.trim().split(\"/\");\n if (parts.length > 2) return null;\n const [ip = \"\", prefix] = parts;\n const addr = parseAddress(ip);\n if (!addr) return null;\n let bits = addr.v6 ? 128 : 32;\n if (prefix !== undefined) {\n if (!/^\\d{1,3}$/.test(prefix)) return null;\n bits = Number(prefix);\n // A mapped IPv4 range written in IPv6 form: drop the 96-bit ::ffff: prefix.\n if (!addr.v6 && ip.includes(\":\")) bits -= 96;\n if (bits <= 0 || bits > (addr.v6 ? 128 : 32)) return null;\n }\n const width = BigInt(addr.v6 ? 128 : 32);\n const mask = bits === 0 ? 0n : ((1n << BigInt(bits)) - 1n) << (width - BigInt(bits));\n return { v6: addr.v6, base: addr.n & mask, bits };\n}\n\nfunction inRange(addr: Addr, r: Range): boolean {\n if (addr.v6 !== r.v6) return false;\n const width = BigInt(r.v6 ? 128 : 32);\n const mask = r.bits === 0 ? 0n : ((1n << BigInt(r.bits)) - 1n) << (width - BigInt(r.bits));\n return (addr.n & mask) === r.base;\n}\n\nconst LOOPBACK = [parseRange(\"127.0.0.0/8\")!, parseRange(\"::1/128\")!];\n\nlet cache: { raw: string; ranges: Range[] } | undefined;\n\n/** TRUST_PROXY_FROM as ranges. Invalid entries are reported once and ignored. */\nfunction trustedRanges(raw: string): Range[] {\n if (cache?.raw === raw) return cache.ranges;\n const ranges: Range[] = [];\n const invalid: string[] = [];\n for (const entry of raw.split(/[,\\s]+/).filter(Boolean)) {\n const r = parseRange(entry);\n if (r) ranges.push(r);\n else invalid.push(entry);\n }\n if (invalid.length) process.stderr.write(`agentinbox: ignoring invalid TRUST_PROXY_FROM entries: ${invalid.join(\", \")}\\n`);\n cache = { raw, ranges };\n return ranges;\n}\n\n/**\n * Whether forwarding headers from this socket peer may be believed: loopback always (a proxy on\n * the same host), anything else only when listed in TRUST_PROXY_FROM (IPs or CIDRs, comma-separated).\n * The daemon usually binds 0.0.0.0, so trusting whole private ranges would let any LAN or tailnet\n * peer forge its address.\n */\nexport function isTrustedProxy(peer: string, trustFrom = process.env.TRUST_PROXY_FROM ?? \"\"): boolean {\n const addr = parseAddress(peer);\n if (!addr) return false;\n return [...LOOPBACK, ...trustedRanges(trustFrom)].some((r) => inRange(addr, r));\n}\n","import { randomBytes } from \"node:crypto\";\nimport { sha256 } from \"../crypto.ts\";\nimport type { Db } from \"../db/index.ts\";\n\nexport const SESSION_TTL_MS = 30 * 24 * 3600_000;\n/** Sliding refresh never extends a session past this age: sign in again after 90 days. */\nexport const SESSION_MAX_AGE_MS = 90 * 24 * 3600_000;\n/** Sliding expiry is pushed forward at most once per this interval, to avoid a write per request. */\nconst REFRESH_AFTER_MS = 24 * 3600_000;\n\nexport interface SessionCheck {\n valid: boolean;\n /** The expiry was extended: re-send the cookie so the browser keeps it as long. */\n refreshed: boolean;\n}\n\n/** Admin console sessions. Only sha256(token) is stored. */\nexport class SessionStore {\n constructor(\n private readonly db: Db,\n private readonly clock: () => number = Date.now,\n ) {}\n\n create(meta: { ip?: string | null; userAgent?: string | null } = {}): string {\n const token = randomBytes(32).toString(\"base64url\");\n const t = this.clock();\n this.db.run(\n \"INSERT INTO admin_sessions (token_hash, created_at, expires_at, ip, user_agent) VALUES (?, ?, ?, ?, ?)\",\n sha256(token),\n new Date(t).toISOString(),\n new Date(t + SESSION_TTL_MS).toISOString(),\n meta.ip ?? null,\n meta.userAgent?.slice(0, 300) ?? null,\n );\n return token;\n }\n\n check(token: string | undefined | null): SessionCheck {\n if (!token) return { valid: false, refreshed: false };\n const hash = sha256(token);\n const row = this.db.get<{ created_at: string; expires_at: string }>(\n \"SELECT created_at, expires_at FROM admin_sessions WHERE token_hash = ?\",\n hash,\n );\n if (!row) return { valid: false, refreshed: false };\n const t = this.clock();\n const expires = Date.parse(row.expires_at);\n const hardLimit = Date.parse(row.created_at) + SESSION_MAX_AGE_MS;\n if (!(expires > t) || !(hardLimit > t)) {\n this.db.run(\"DELETE FROM admin_sessions WHERE token_hash = ?\", hash);\n return { valid: false, refreshed: false };\n }\n const next = Math.min(t + SESSION_TTL_MS, hardLimit);\n if (next - expires < REFRESH_AFTER_MS) return { valid: true, refreshed: false };\n this.db.run(\"UPDATE admin_sessions SET expires_at = ? WHERE token_hash = ?\", new Date(next).toISOString(), hash);\n return { valid: true, refreshed: true };\n }\n\n delete(token: string): void {\n this.db.run(\"DELETE FROM admin_sessions WHERE token_hash = ?\", sha256(token));\n }\n\n /** Revokes every session, optionally keeping one (the caller's own). */\n deleteAll(exceptToken?: string | null): number {\n if (exceptToken) return this.db.run(\"DELETE FROM admin_sessions WHERE token_hash != ?\", sha256(exceptToken)).changes;\n return this.db.run(\"DELETE FROM admin_sessions\").changes;\n }\n\n pruneExpired(): number {\n return this.db.run(\"DELETE FROM admin_sessions WHERE expires_at <= ?\", new Date(this.clock()).toISOString()).changes;\n }\n}\n","import { isIP } from \"node:net\";\nimport { getConnInfo } from \"@hono/node-server/conninfo\";\nimport type { Context, MiddlewareHandler } from \"hono\";\nimport { deleteCookie, getCookie, setCookie } from \"hono/cookie\";\nimport type { AppEnv } from \"../api/util.ts\";\nimport type { CoreContext } from \"../context.ts\";\nimport { HttpError } from \"../util/errors.ts\";\nimport { clientKey, GLOBAL_SLOWDOWN_MS, LoginLimiter } from \"./lockout.ts\";\nimport { isTrustedProxy } from \"./proxy.ts\";\nimport { SESSION_TTL_MS, SessionStore } from \"./sessions.ts\";\nimport { ensureSetupToken, regenerateSetupToken } from \"./setup-token.ts\";\n\nexport const SESSION_COOKIE = \"aib_session\";\nexport const CSRF_HEADER = \"x-agentinbox-csrf\";\nexport const MIN_PASSWORD_LENGTH = 10;\nexport const MAX_PASSWORD_LENGTH = 1024;\n\n/** Secret checks (scrypt) allowed at once: libuv's default thread pool size. */\nexport const MAX_CONCURRENT_VERIFICATIONS = 4;\n/** Requests from one client that may wait behind its in-flight check; more get 429. */\nexport const MAX_QUEUED_PER_CLIENT = 4;\n\n/** Hands out at most `max` slots; waiters are served in order. */\nexport class Semaphore {\n private active = 0;\n private readonly waiters: (() => void)[] = [];\n\n constructor(private readonly max: number) {}\n\n get inUse(): number {\n return this.active;\n }\n\n async acquire(): Promise<() => void> {\n if (this.active < this.max) this.active++;\n else await new Promise<void>((resolve) => this.waiters.push(resolve));\n let released = false;\n return () => {\n if (released) return;\n released = true;\n const next = this.waiters.shift();\n if (next) next();\n else this.active--;\n };\n }\n}\n\n/** Per-context admin auth state: the session store, the failed-login limiter and verification queues. */\nexport interface AdminAuth {\n sessions: SessionStore;\n limiter: LoginLimiter;\n /** One secret check per client at a time: the tail of each client's queue. */\n lanes: Map<string, { tail: Promise<void>; queued: number }>;\n verifySlots: Semaphore;\n}\n\n/** What admin auth needs from the context (the CLI builds this without loading the master key). */\nexport type AuthCore = Pick<CoreContext, \"db\" | \"settings\" | \"audit\" | \"config\">;\n\nconst instances = new WeakMap<AuthCore, AdminAuth>();\n\nexport function adminAuth(ctx: AuthCore): AdminAuth {\n let auth = instances.get(ctx);\n if (!auth) {\n auth = { sessions: new SessionStore(ctx.db), limiter: new LoginLimiter(), lanes: new Map(), verifySlots: new Semaphore(MAX_CONCURRENT_VERIFICATIONS) };\n instances.set(ctx, auth);\n }\n return auth;\n}\n\n/** Claimed = an admin password exists. Before that only the setup-token claim works. */\nexport const isClaimed = (ctx: AuthCore): boolean => !!ctx.settings.getState(\"admin.passwordHash\");\n\n/** The current setup token while unclaimed (created on demand), otherwise null. */\nexport function currentSetupToken(ctx: AuthCore): string | null {\n return isClaimed(ctx) ? null : ensureSetupToken(ctx.config.dataDir);\n}\n\n/** `agentinbox reset-password`: back to the unclaimed state with a fresh setup link. */\nexport function resetAdminPassword(ctx: AuthCore): string {\n const auth = adminAuth(ctx);\n ctx.db.tx(() => {\n ctx.settings.deleteState(\"admin.passwordHash\");\n auth.sessions.deleteAll();\n });\n auth.limiter.reset();\n ctx.audit.record({ actorType: \"system\", action: \"admin.password_reset\", outcome: \"ok\" });\n return regenerateSetupToken(ctx.config.dataDir);\n}\n\nfunction socketAddress(c: Context): string | null {\n try {\n return getConnInfo(c).remote.address ?? null;\n } catch {\n // No Node socket (e.g. app.request() without bindings).\n return null;\n }\n}\n\n/**\n * The client address. X-Forwarded-For is honored only with TRUST_PROXY and only when the\n * connection comes from a trusted proxy (loopback, or TRUST_PROXY_FROM); it is read from the\n * right, since proxies append the address they saw and entries further left are client-controlled.\n * TRUST_PROXY_HOPS (default 1) is the number of proxies in front of agentinbox.\n */\n/** Some proxies (Azure, IIS) append \":port\" to X-Forwarded-For entries. */\nfunction stripPort(entry: string): string {\n const v6 = /^\\[([^\\]]+)\\](?::\\d+)?$/.exec(entry);\n if (v6) return v6[1]!;\n const v4 = /^(\\d{1,3}(?:\\.\\d{1,3}){3}):\\d+$/.exec(entry);\n return v4 ? v4[1]! : entry;\n}\n\nlet warnedUntrustedProxy = false;\n\n/** Upgrades from the old private-range rule silently lost proxy trust; say so once. */\nfunction warnUntrustedProxy(peer: string): void {\n if (warnedUntrustedProxy) return;\n warnedUntrustedProxy = true;\n process.stderr.write(\n `agentinbox: ignoring X-Forwarded-For from ${peer}: TRUST_PROXY only trusts loopback proxies unless TRUST_PROXY_FROM lists them (e.g. TRUST_PROXY_FROM=${peer}).\\n`,\n );\n}\n\nexport function clientIp(c: Context, trustProxy: boolean): string {\n const peer = socketAddress(c);\n if (trustProxy && peer && !isTrustedProxy(peer) && c.req.header(\"x-forwarded-for\")) warnUntrustedProxy(peer);\n if (trustProxy && peer && isTrustedProxy(peer)) {\n const hops = Math.max(1, Number.parseInt(process.env.TRUST_PROXY_HOPS ?? \"1\", 10) || 1);\n const chain = (c.req.header(\"x-forwarded-for\") ?? \"\")\n .split(\",\")\n .map((s) => stripPort(s.trim()))\n .filter(Boolean);\n const forwarded = chain[chain.length - hops];\n if (forwarded && isIP(forwarded)) return forwarded;\n }\n return peer ?? \"unknown\";\n}\n\nfunction isSecure(c: Context, trustProxy: boolean): boolean {\n if (new URL(c.req.url).protocol === \"https:\") return true;\n const peer = socketAddress(c);\n return trustProxy && !!peer && isTrustedProxy(peer) && c.req.header(\"x-forwarded-proto\")?.split(\",\")[0]?.trim().toLowerCase() === \"https\";\n}\n\nexport function sessionToken(c: Context): string | undefined {\n return getCookie(c, SESSION_COOKIE) || undefined;\n}\n\nfunction writeSessionCookie(c: Context, token: string, trustProxy: boolean): void {\n setCookie(c, SESSION_COOKIE, token, {\n httpOnly: true,\n sameSite: \"Lax\",\n path: \"/\",\n secure: isSecure(c, trustProxy),\n maxAge: Math.floor(SESSION_TTL_MS / 1000),\n });\n}\n\n/** Creates a session for the requester and sets the cookie. */\nexport function startSession(c: Context, ctx: AuthCore): void {\n const token = adminAuth(ctx).sessions.create({ ip: clientIp(c, ctx.config.trustProxy), userAgent: c.req.header(\"user-agent\") });\n writeSessionCookie(c, token, ctx.config.trustProxy);\n}\n\nexport function endSession(c: Context, ctx: AuthCore): void {\n const token = sessionToken(c);\n if (token) adminAuth(ctx).sessions.delete(token);\n deleteCookie(c, SESSION_COOKIE, { path: \"/\" });\n}\n\n/** Whether the request carries a valid session; refreshes the cookie when the expiry slid forward. */\nexport function hasSession(c: Context, ctx: AuthCore): boolean {\n const token = sessionToken(c);\n const check = adminAuth(ctx).sessions.check(token);\n if (check.refreshed && token) writeSessionCookie(c, token, ctx.config.trustProxy);\n return check.valid;\n}\n\n/** Requires an admin session on every route it guards. */\nexport function requireAdmin(ctx: AuthCore): MiddlewareHandler<AppEnv> {\n return async (c, next) => {\n if (!hasSession(c, ctx)) {\n if (!isClaimed(ctx)) {\n throw new HttpError(401, \"setup_required\", \"This instance has not been claimed yet\", \"Open the setup link printed by agentinbox on startup.\");\n }\n throw new HttpError(401, \"unauthorized\", \"Sign in to continue\");\n }\n c.set(\"admin\", true);\n await next();\n };\n}\n\nconst MUTATING = new Set([\"POST\", \"PUT\", \"PATCH\", \"DELETE\"]);\n\n/**\n * CSRF guard: a custom header can't be sent cross-site without a CORS preflight (which this server\n * never approves), and SameSite=Lax already keeps the cookie off cross-site POSTs.\n */\nexport function csrfGuard(): MiddlewareHandler<AppEnv> {\n return async (c, next) => {\n if (MUTATING.has(c.req.method) && c.req.header(CSRF_HEADER) !== \"1\") {\n throw new HttpError(403, \"csrf\", `Missing ${CSRF_HEADER} header`, `Send \"${CSRF_HEADER}: 1\" with every mutating /api request.`);\n }\n await next();\n };\n}\n\n/** 429 response for a locked-out address. */\nexport function lockedOut(c: Context, minutes: number): Response {\n c.header(\"Retry-After\", String(minutes * 60));\n const message = `Too many failed attempts. Try again in ${minutes} minute${minutes === 1 ? \"\" : \"s\"}.`;\n return c.json({ error: { code: \"locked_out\", message } }, 429);\n}\n\nexport type CheckResult =\n | { ok: true }\n | { ok: false; lockedMinutes: number; nowBanned: boolean; /** Too many concurrent attempts from this client. */ busy?: boolean };\n\nexport interface CheckOptions {\n /**\n * Count toward (and be slowed down by) the instance-wide failure budget. Off for the setup claim:\n * it's only open while unclaimed and needs the token.\n */\n global?: boolean;\n /** Test hook: how long to wait while over the global budget. */\n slowdownMs?: number;\n}\n\n/**\n * Verifies a secret under the failed-login limiter.\n * - Each client gets one check at a time (a few more may queue, the rest get `busy`), and at most\n * MAX_CONCURRENT_VERIFICATIONS run at once overall.\n * - The attempt is recorded before `verify` (scrypt) runs and refunded if it succeeds, so\n * concurrent requests can't all slip past the ban.\n * - Locked-out clients and secrets this client already failed with never reach `verify`.\n * - While the instance-wide budget is exhausted, each attempt waits a little first; it is never refused.\n */\nexport async function checkSecret(\n ctx: AuthCore,\n ip: string,\n secret: string,\n verify: () => boolean | Promise<boolean>,\n opts: CheckOptions = {},\n): Promise<CheckResult> {\n const auth = adminAuth(ctx);\n const key = clientKey(ip);\n const lane = auth.lanes.get(key) ?? { tail: Promise.resolve(), queued: 0 };\n if (lane.queued > MAX_QUEUED_PER_CLIENT) return { ok: false, lockedMinutes: 0, nowBanned: false, busy: true };\n lane.queued++;\n auth.lanes.set(key, lane);\n const previous = lane.tail;\n let done!: () => void;\n lane.tail = new Promise<void>((resolve) => (done = resolve));\n try {\n await previous;\n return await checkOnce(auth, ip, secret, verify, opts);\n } finally {\n done();\n if (--lane.queued === 0) auth.lanes.delete(key);\n }\n}\n\nasync function checkOnce(auth: AdminAuth, ip: string, secret: string, verify: () => boolean | Promise<boolean>, opts: CheckOptions): Promise<CheckResult> {\n const { limiter } = auth;\n const global = opts.global !== false;\n if (global && limiter.overGlobalBudget() && !limiter.bannedMinutes(ip)) {\n await new Promise((r) => setTimeout(r, opts.slowdownMs ?? GLOBAL_SLOWDOWN_MS));\n }\n // From here to the reservation everything is synchronous.\n const locked = limiter.bannedMinutes(ip);\n if (locked) return { ok: false, lockedMinutes: locked, nowBanned: false };\n if (limiter.knownBad(ip, secret)) return { ok: false, lockedMinutes: 0, nowBanned: limiter.fail(ip, secret, { global }) };\n const reservation = limiter.record(ip, secret, { global });\n const release = await auth.verifySlots.acquire();\n let ok = false;\n try {\n ok = await verify();\n } finally {\n release();\n }\n if (!ok) return { ok: false, lockedMinutes: 0, nowBanned: reservation.banned };\n limiter.clear(ip);\n if (reservation.countedGlobally) limiter.refundGlobal();\n return { ok: true };\n}\n\n/** The 429 for a rejected check (locked out or too many at once), or null to continue. */\nexport function rejection(c: Context, check: CheckResult): Response | null {\n if (check.ok) return null;\n if (check.busy) {\n c.header(\"Retry-After\", \"2\");\n return c.json({ error: { code: \"too_many_requests\", message: \"Another sign-in attempt from your address is still being checked. Try again in a moment.\" } }, 429);\n }\n return check.lockedMinutes ? lockedOut(c, check.lockedMinutes) : null;\n}\n"],"mappings":";;;;;;;;AAYA,MAAa,SAAS;;AAEtB,MAAa,cAAc;;;;;;;;;AAiB3B,IAAa,eAAb,MAA0B;CAIK;CAH7B,0BAA2B,IAAI,IAAmB;CAClD,SAAiB;EAAE,UAAU;EAAG,OAAO;CAAE;CAEzC,YAAY,QAAuC,KAAK,KAAK;EAAhC,KAAA,QAAA;CAAiC;;CAG9D,cAAc,IAAoB;EAChC,MAAM,IAAI,KAAK,MAAM;EACrB,MAAM,QAAQ,KAAK,QAAQ,IAAI,UAAU,EAAE,CAAC;EAC5C,MAAM,QAAQ,SAAS,MAAM,cAAc,IAAI,MAAM,cAAc;EACnE,OAAO,QAAQ,IAAI,KAAK,MAAM,QAAQ,KAAK,GAAM,IAAI;CACvD;;CAGA,mBAA4B;EAC1B,OAAO,KAAK,OAAO,YAAA,OAAmC,KAAK,MAAM,IAAI,KAAK,OAAO,QAAA;CACnF;;CAGA,SAAS,IAAY,QAAyB;EAE5C,OAAO,CAAC,CADM,KAAK,QAAQ,UAAU,EAAE,GAAG,KAAK,MAAM,CACxC,CAAC,EAAE,MAAM,IAAI,OAAO,MAAM,CAAC;CAC1C;;CAGA,KAAK,IAAY,QAAgB,OAA6B,CAAC,GAAY;EACzE,OAAO,KAAK,OAAO,IAAI,QAAQ,IAAI,CAAC,CAAC;CACvC;;;;;;CAOA,OAAO,IAAY,QAAgB,OAA6B,CAAC,GAAkD;EACjH,MAAM,IAAI,KAAK,MAAM;EACrB,MAAM,MAAM,UAAU,EAAE;EACxB,MAAM,QAAQ,KAAK,QAAQ,KAAK,CAAC,KAAK;GAAE,uBAAO,IAAI,IAAY;GAAG,UAAU;GAAG,OAAO;GAAG,aAAa;EAAE;EACxG,MAAM,OAAO,OAAO,MAAM;EAC1B,IAAI,kBAAkB;EACtB,IAAI,CAAC,MAAM,MAAM,IAAI,IAAI,GAAG;GAC1B,MAAM,MAAM,IAAI,IAAI;GACpB,IAAI,KAAK,WAAW,OAAO;IACzB,IAAI,IAAI,KAAK,OAAO,SAAA,KAAoB,KAAK,SAAS;KAAE,UAAU;KAAG,OAAO;IAAE;IAC9E,KAAK,OAAO;IACZ,kBAAkB;GACpB;EACF;EACA,MAAM;EACN,MAAM,SAAS,MAAM,MAAM,QAAA,KAAwB,MAAM,YAAA;EACzD,IAAI,QAAQ,MAAM,cAAc,IAAI;EACpC,KAAK,MAAM,KAAK,KAAK;EACrB,OAAO;GAAE;GAAQ;EAAgB;CACnC;CAEA,eAAqB;EACnB,IAAI,KAAK,OAAO,WAAW,GAAG,KAAK,OAAO;CAC5C;CAEA,MAAM,IAAkB;EACtB,KAAK,QAAQ,OAAO,UAAU,EAAE,CAAC;CACnC;;CAGA,QAAc;EACZ,KAAK,QAAQ,MAAM;EACnB,KAAK,SAAS;GAAE,UAAU;GAAG,OAAO;EAAE;CACxC;CAEA,IAAI,OAAe;EACjB,OAAO,KAAK,QAAQ;CACtB;;CAGA,QAAgB,KAAa,GAA8B;EACzD,MAAM,QAAQ,KAAK,QAAQ,IAAI,GAAG;EAClC,IAAI,CAAC,OAAO,OAAO,KAAA;EACnB,IAAI,MAAM,cAAc,KAAM,CAAC,MAAM,eAAe,IAAI,MAAM,QAAA,KAAoB,OAAO;EACzF,KAAK,QAAQ,OAAO,GAAG;CAEzB;;CAGA,MAAc,KAAa,OAAoB;EAC7C,KAAK,QAAQ,OAAO,GAAG;EACvB,KAAK,QAAQ,IAAI,KAAK,KAAK;EAC3B,OAAO,KAAK,QAAQ,OAAO,aAAa,KAAK,QAAQ,OAAO,KAAK,QAAQ,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,KAAM;CAC/F;AACF;;AAGA,SAAgB,UAAU,IAAoB;CAC5C,MAAM,OAAO,GAAG,QAAQ,QAAQ,EAAE,CAAC,CAAC,YAAY;CAChD,MAAM,SAAS,gCAAgC,KAAK,IAAI;CACxD,IAAI,QAAQ,OAAO,OAAO;CAC1B,IAAI,CAAC,OAAO,IAAI,GAAG,OAAO;CAC1B,MAAM,CAAC,OAAO,IAAI,QAAQ,KAAK,MAAM,IAAI;CACzC,MAAM,OAAO,OAAO,KAAK,MAAM,GAAG,IAAI,CAAC;CACvC,MAAM,QAAQ,OAAO,KAAK,MAAM,GAAG,IAAI,CAAC;CAExC,OAAO,IADQ,SAAS,KAAA,IAAY,OAAO;EAAC,GAAG;EAAM,GAAG,MAAM,IAAI,KAAK,SAAS,MAAM,MAAM,CAAC,CAAC,KAAK,GAAG;EAAG,GAAG;CAAK,EAAA,CAE9G,MAAM,GAAG,CAAC,CAAC,CACX,KAAK,MAAM,EAAE,QAAQ,YAAY,EAAE,CAAC,CAAC,CACrC,KAAK,GAAG,EAAE;AACf;;;AChIA,SAAS,QAAQ,IAAoB;CACnC,OAAO,GAAG,MAAM,GAAG,CAAC,CAAC,QAAQ,KAAK,SAAU,OAAO,KAAM,OAAO,OAAO,IAAI,CAAC,GAAG,EAAE;AACnF;AAEA,SAAgB,aAAa,KAA0B;CACrD,MAAM,KAAK,IAAI,KAAK,CAAC,CAAC,QAAQ,YAAY,EAAE,CAAC,CAAC,QAAQ,QAAQ,EAAE,CAAC,CAAC,YAAY;CAC9E,IAAI,OAAO,EAAE,GAAG,OAAO;EAAE,IAAI;EAAO,GAAG,QAAQ,EAAE;CAAE;CACnD,IAAI,CAAC,OAAO,EAAE,GAAG,OAAO;CACxB,MAAM,SAAS,gDAAgD,KAAK,EAAE;CACtE,IAAI,UAAU,OAAO,OAAO,EAAG,GAAG,OAAO;EAAE,IAAI;EAAO,GAAG,QAAQ,OAAO,EAAG;CAAE;CAE7E,IAAI,OAAO;CACX,MAAM,SAAS,wBAAwB,KAAK,IAAI;CAChD,IAAI,QAAQ;EACV,MAAM,IAAI,QAAQ,OAAO,EAAG;EAC5B,OAAO,KAAK,MAAM,GAAG,CAAC,OAAO,EAAE,CAAE,MAAM,IAAI,IAAI,KAAK,IAAA,CAAK,SAAS,EAAE,EAAE,IAAI,IAAI,OAAA,CAAS,SAAS,EAAE;CACpG;CACA,MAAM,CAAC,OAAO,IAAI,QAAQ,KAAK,MAAM,IAAI;CACzC,MAAM,OAAO,OAAO,KAAK,MAAM,GAAG,IAAI,CAAC;CACvC,MAAM,QAAQ,OAAO,KAAK,MAAM,GAAG,IAAI,CAAC;CACxC,MAAM,SAAS,SAAS,KAAA,IAAY,OAAO;EAAC,GAAG;EAAM,GAAG,MAAc,IAAI,KAAK,SAAS,MAAM,MAAM,CAAC,CAAC,KAAK,GAAG;EAAG,GAAG;CAAK;CACzH,IAAI,OAAO,WAAW,GAAG,OAAO;CAChC,OAAO;EAAE,IAAI;EAAM,GAAG,OAAO,QAAQ,KAAK,MAAO,OAAO,MAAO,OAAO,OAAO,SAAS,KAAK,KAAK,EAAE,CAAC,GAAG,EAAE;CAAE;AAC5G;;;;;AAYA,SAAgB,WAAW,OAA6B;CACtD,MAAM,QAAQ,MAAM,KAAK,CAAC,CAAC,MAAM,GAAG;CACpC,IAAI,MAAM,SAAS,GAAG,OAAO;CAC7B,MAAM,CAAC,KAAK,IAAI,UAAU;CAC1B,MAAM,OAAO,aAAa,EAAE;CAC5B,IAAI,CAAC,MAAM,OAAO;CAClB,IAAI,OAAO,KAAK,KAAK,MAAM;CAC3B,IAAI,WAAW,KAAA,GAAW;EACxB,IAAI,CAAC,YAAY,KAAK,MAAM,GAAG,OAAO;EACtC,OAAO,OAAO,MAAM;EAEpB,IAAI,CAAC,KAAK,MAAM,GAAG,SAAS,GAAG,GAAG,QAAQ;EAC1C,IAAI,QAAQ,KAAK,QAAQ,KAAK,KAAK,MAAM,KAAK,OAAO;CACvD;CACA,MAAM,QAAQ,OAAO,KAAK,KAAK,MAAM,EAAE;CACvC,MAAM,OAAO,SAAS,IAAI,MAAO,MAAM,OAAO,IAAI,KAAK,MAAQ,QAAQ,OAAO,IAAI;CAClF,OAAO;EAAE,IAAI,KAAK;EAAI,MAAM,KAAK,IAAI;EAAM;CAAK;AAClD;AAEA,SAAS,QAAQ,MAAY,GAAmB;CAC9C,IAAI,KAAK,OAAO,EAAE,IAAI,OAAO;CAC7B,MAAM,QAAQ,OAAO,EAAE,KAAK,MAAM,EAAE;CACpC,MAAM,OAAO,EAAE,SAAS,IAAI,MAAO,MAAM,OAAO,EAAE,IAAI,KAAK,MAAQ,QAAQ,OAAO,EAAE,IAAI;CACxF,QAAQ,KAAK,IAAI,UAAU,EAAE;AAC/B;AAEA,MAAM,WAAW,CAAC,WAAW,aAAa,GAAI,WAAW,SAAS,CAAE;AAEpE,IAAI;;AAGJ,SAAS,cAAc,KAAsB;CAC3C,IAAI,OAAO,QAAQ,KAAK,OAAO,MAAM;CACrC,MAAM,SAAkB,CAAC;CACzB,MAAM,UAAoB,CAAC;CAC3B,KAAK,MAAM,SAAS,IAAI,MAAM,QAAQ,CAAC,CAAC,OAAO,OAAO,GAAG;EACvD,MAAM,IAAI,WAAW,KAAK;EAC1B,IAAI,GAAG,OAAO,KAAK,CAAC;OACf,QAAQ,KAAK,KAAK;CACzB;CACA,IAAI,QAAQ,QAAQ,QAAQ,OAAO,MAAM,0DAA0D,QAAQ,KAAK,IAAI,EAAE,GAAG;CACzH,QAAQ;EAAE;EAAK;CAAO;CACtB,OAAO;AACT;;;;;;;AAQA,SAAgB,eAAe,MAAc,YAAY,QAAQ,IAAI,oBAAoB,IAAa;CACpG,MAAM,OAAO,aAAa,IAAI;CAC9B,IAAI,CAAC,MAAM,OAAO;CAClB,OAAO,CAAC,GAAG,UAAU,GAAG,cAAc,SAAS,CAAC,CAAC,CAAC,MAAM,MAAM,QAAQ,MAAM,CAAC,CAAC;AAChF;;;AC9FA,MAAa,iBAAiB;;AAE9B,MAAa,qBAAqB;;AAElC,MAAM,mBAAmB;;AASzB,IAAa,eAAb,MAA0B;CAEL;CACA;CAFnB,YACE,IACA,QAAuC,KAAK,KAC5C;EAFiB,KAAA,KAAA;EACA,KAAA,QAAA;CAChB;CAEH,OAAO,OAA0D,CAAC,GAAW;EAC3E,MAAM,QAAQ,YAAY,EAAE,CAAC,CAAC,SAAS,WAAW;EAClD,MAAM,IAAI,KAAK,MAAM;EACrB,KAAK,GAAG,IACN,0GACA,OAAO,KAAK,GACZ,IAAI,KAAK,CAAC,CAAC,CAAC,YAAY,GACxB,IAAI,KAAK,IAAI,cAAc,CAAC,CAAC,YAAY,GACzC,KAAK,MAAM,MACX,KAAK,WAAW,MAAM,GAAG,GAAG,KAAK,IACnC;EACA,OAAO;CACT;CAEA,MAAM,OAAgD;EACpD,IAAI,CAAC,OAAO,OAAO;GAAE,OAAO;GAAO,WAAW;EAAM;EACpD,MAAM,OAAO,OAAO,KAAK;EACzB,MAAM,MAAM,KAAK,GAAG,IAClB,0EACA,IACF;EACA,IAAI,CAAC,KAAK,OAAO;GAAE,OAAO;GAAO,WAAW;EAAM;EAClD,MAAM,IAAI,KAAK,MAAM;EACrB,MAAM,UAAU,KAAK,MAAM,IAAI,UAAU;EACzC,MAAM,YAAY,KAAK,MAAM,IAAI,UAAU,IAAI;EAC/C,IAAI,EAAE,UAAU,MAAM,EAAE,YAAY,IAAI;GACtC,KAAK,GAAG,IAAI,mDAAmD,IAAI;GACnE,OAAO;IAAE,OAAO;IAAO,WAAW;GAAM;EAC1C;EACA,MAAM,OAAO,KAAK,IAAI,IAAI,gBAAgB,SAAS;EACnD,IAAI,OAAO,UAAU,kBAAkB,OAAO;GAAE,OAAO;GAAM,WAAW;EAAM;EAC9E,KAAK,GAAG,IAAI,iEAAiE,IAAI,KAAK,IAAI,CAAC,CAAC,YAAY,GAAG,IAAI;EAC/G,OAAO;GAAE,OAAO;GAAM,WAAW;EAAK;CACxC;CAEA,OAAO,OAAqB;EAC1B,KAAK,GAAG,IAAI,mDAAmD,OAAO,KAAK,CAAC;CAC9E;;CAGA,UAAU,aAAqC;EAC7C,IAAI,aAAa,OAAO,KAAK,GAAG,IAAI,oDAAoD,OAAO,WAAW,CAAC,CAAC,CAAC;EAC7G,OAAO,KAAK,GAAG,IAAI,4BAA4B,CAAC,CAAC;CACnD;CAEA,eAAuB;EACrB,OAAO,KAAK,GAAG,IAAI,oDAAoD,IAAI,KAAK,KAAK,MAAM,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC;CAC/G;AACF;;;AC3DA,MAAa,iBAAiB;AAC9B,MAAa,cAAc;AAE3B,MAAa,sBAAsB;;AAQnC,IAAa,YAAb,MAAuB;CAIQ;CAH7B,SAAiB;CACjB,UAA2C,CAAC;CAE5C,YAAY,KAA8B;EAAb,KAAA,MAAA;CAAc;CAE3C,IAAI,QAAgB;EAClB,OAAO,KAAK;CACd;CAEA,MAAM,UAA+B;EACnC,IAAI,KAAK,SAAS,KAAK,KAAK,KAAK;OAC5B,MAAM,IAAI,SAAe,YAAY,KAAK,QAAQ,KAAK,OAAO,CAAC;EACpE,IAAI,WAAW;EACf,aAAa;GACX,IAAI,UAAU;GACd,WAAW;GACX,MAAM,OAAO,KAAK,QAAQ,MAAM;GAChC,IAAI,MAAM,KAAK;QACV,KAAK;EACZ;CACF;AACF;AAcA,MAAM,4BAAY,IAAI,QAA6B;AAEnD,SAAgB,UAAU,KAA0B;CAClD,IAAI,OAAO,UAAU,IAAI,GAAG;CAC5B,IAAI,CAAC,MAAM;EACT,OAAO;GAAE,UAAU,IAAI,aAAa,IAAI,EAAE;GAAG,SAAS,IAAI,aAAa;GAAG,uBAAO,IAAI,IAAI;GAAG,aAAa,IAAI,UAAA,CAAsC;EAAE;EACrJ,UAAU,IAAI,KAAK,IAAI;CACzB;CACA,OAAO;AACT;;AAGA,MAAa,aAAa,QAA2B,CAAC,CAAC,IAAI,SAAS,SAAS,oBAAoB;;AAGjG,SAAgB,kBAAkB,KAA8B;CAC9D,OAAO,UAAU,GAAG,IAAI,OAAO,iBAAiB,IAAI,OAAO,OAAO;AACpE;;AAGA,SAAgB,mBAAmB,KAAuB;CACxD,MAAM,OAAO,UAAU,GAAG;CAC1B,IAAI,GAAG,SAAS;EACd,IAAI,SAAS,YAAY,oBAAoB;EAC7C,KAAK,SAAS,UAAU;CAC1B,CAAC;CACD,KAAK,QAAQ,MAAM;CACnB,IAAI,MAAM,OAAO;EAAE,WAAW;EAAU,QAAQ;EAAwB,SAAS;CAAK,CAAC;CACvF,OAAO,qBAAqB,IAAI,OAAO,OAAO;AAChD;AAEA,SAAS,cAAc,GAA2B;CAChD,IAAI;EACF,OAAO,YAAY,CAAC,CAAC,CAAC,OAAO,WAAW;CAC1C,QAAQ;EAEN,OAAO;CACT;AACF;;;;;;;;AASA,SAAS,UAAU,OAAuB;CACxC,MAAM,KAAK,0BAA0B,KAAK,KAAK;CAC/C,IAAI,IAAI,OAAO,GAAG;CAClB,MAAM,KAAK,kCAAkC,KAAK,KAAK;CACvD,OAAO,KAAK,GAAG,KAAM;AACvB;AAEA,IAAI,uBAAuB;;AAG3B,SAAS,mBAAmB,MAAoB;CAC9C,IAAI,sBAAsB;CAC1B,uBAAuB;CACvB,QAAQ,OAAO,MACb,6CAA6C,KAAK,uGAAuG,KAAK,KAChK;AACF;AAEA,SAAgB,SAAS,GAAY,YAA6B;CAChE,MAAM,OAAO,cAAc,CAAC;CAC5B,IAAI,cAAc,QAAQ,CAAC,eAAe,IAAI,KAAK,EAAE,IAAI,OAAO,iBAAiB,GAAG,mBAAmB,IAAI;CAC3G,IAAI,cAAc,QAAQ,eAAe,IAAI,GAAG;EAC9C,MAAM,OAAO,KAAK,IAAI,GAAG,OAAO,SAAS,QAAQ,IAAI,oBAAoB,KAAK,EAAE,KAAK,CAAC;EACtF,MAAM,SAAS,EAAE,IAAI,OAAO,iBAAiB,KAAK,GAAA,CAC/C,MAAM,GAAG,CAAC,CACV,KAAK,MAAM,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC,CAC/B,OAAO,OAAO;EACjB,MAAM,YAAY,MAAM,MAAM,SAAS;EACvC,IAAI,aAAa,KAAK,SAAS,GAAG,OAAO;CAC3C;CACA,OAAO,QAAQ;AACjB;AAEA,SAAS,SAAS,GAAY,YAA8B;CAC1D,IAAI,IAAI,IAAI,EAAE,IAAI,GAAG,CAAC,CAAC,aAAa,UAAU,OAAO;CACrD,MAAM,OAAO,cAAc,CAAC;CAC5B,OAAO,cAAc,CAAC,CAAC,QAAQ,eAAe,IAAI,KAAK,EAAE,IAAI,OAAO,mBAAmB,CAAC,EAAE,MAAM,GAAG,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,YAAY,MAAM;AACpI;AAEA,SAAgB,aAAa,GAAgC;CAC3D,OAAO,UAAU,GAAA,aAAiB,KAAK,KAAA;AACzC;AAEA,SAAS,mBAAmB,GAAY,OAAe,YAA2B;CAChF,UAAU,GAAG,gBAAgB,OAAO;EAClC,UAAU;EACV,UAAU;EACV,MAAM;EACN,QAAQ,SAAS,GAAG,UAAU;EAC9B,QAAQ,KAAK,MAAM,iBAAiB,GAAI;CAC1C,CAAC;AACH;;AAGA,SAAgB,aAAa,GAAY,KAAqB;CAE5D,mBAAmB,GADL,UAAU,GAAG,CAAC,CAAC,SAAS,OAAO;EAAE,IAAI,SAAS,GAAG,IAAI,OAAO,UAAU;EAAG,WAAW,EAAE,IAAI,OAAO,YAAY;CAAE,CACnG,GAAG,IAAI,OAAO,UAAU;AACpD;AAEA,SAAgB,WAAW,GAAY,KAAqB;CAC1D,MAAM,QAAQ,aAAa,CAAC;CAC5B,IAAI,OAAO,UAAU,GAAG,CAAC,CAAC,SAAS,OAAO,KAAK;CAC/C,aAAa,GAAG,gBAAgB,EAAE,MAAM,IAAI,CAAC;AAC/C;;AAGA,SAAgB,WAAW,GAAY,KAAwB;CAC7D,MAAM,QAAQ,aAAa,CAAC;CAC5B,MAAM,QAAQ,UAAU,GAAG,CAAC,CAAC,SAAS,MAAM,KAAK;CACjD,IAAI,MAAM,aAAa,OAAO,mBAAmB,GAAG,OAAO,IAAI,OAAO,UAAU;CAChF,OAAO,MAAM;AACf;;AAGA,SAAgB,aAAa,KAA0C;CACrE,OAAO,OAAO,GAAG,SAAS;EACxB,IAAI,CAAC,WAAW,GAAG,GAAG,GAAG;GACvB,IAAI,CAAC,UAAU,GAAG,GAChB,MAAM,IAAI,UAAU,KAAK,kBAAkB,0CAA0C,uDAAuD;GAE9I,MAAM,IAAI,UAAU,KAAK,gBAAgB,qBAAqB;EAChE;EACA,EAAE,IAAI,SAAS,IAAI;EACnB,MAAM,KAAK;CACb;AACF;AAEA,MAAM,2BAAW,IAAI,IAAI;CAAC;CAAQ;CAAO;CAAS;AAAQ,CAAC;;;;;AAM3D,SAAgB,YAAuC;CACrD,OAAO,OAAO,GAAG,SAAS;EACxB,IAAI,SAAS,IAAI,EAAE,IAAI,MAAM,KAAK,EAAE,IAAI,OAAA,mBAAkB,MAAM,KAC9D,MAAM,IAAI,UAAU,KAAK,QAAQ,WAAW,YAAY,UAAU,SAAS,YAAY,uCAAuC;EAEhI,MAAM,KAAK;CACb;AACF;;AAGA,SAAgB,UAAU,GAAY,SAA2B;CAC/D,EAAE,OAAO,eAAe,OAAO,UAAU,EAAE,CAAC;CAC5C,MAAM,UAAU,0CAA0C,QAAQ,SAAS,YAAY,IAAI,KAAK,IAAI;CACpG,OAAO,EAAE,KAAK,EAAE,OAAO;EAAE,MAAM;EAAc;CAAQ,EAAE,GAAG,GAAG;AAC/D;;;;;;;;;;AAyBA,eAAsB,YACpB,KACA,IACA,QACA,QACA,OAAqB,CAAC,GACA;CACtB,MAAM,OAAO,UAAU,GAAG;CAC1B,MAAM,MAAM,UAAU,EAAE;CACxB,MAAM,OAAO,KAAK,MAAM,IAAI,GAAG,KAAK;EAAE,MAAM,QAAQ,QAAQ;EAAG,QAAQ;CAAE;CACzE,IAAI,KAAK,SAAA,GAAgC,OAAO;EAAE,IAAI;EAAO,eAAe;EAAG,WAAW;EAAO,MAAM;CAAK;CAC5G,KAAK;CACL,KAAK,MAAM,IAAI,KAAK,IAAI;CACxB,MAAM,WAAW,KAAK;CACtB,IAAI;CACJ,KAAK,OAAO,IAAI,SAAe,YAAa,OAAO,OAAQ;CAC3D,IAAI;EACF,MAAM;EACN,OAAO,MAAM,UAAU,MAAM,IAAI,QAAQ,QAAQ,IAAI;CACvD,UAAU;EACR,KAAK;EACL,IAAI,EAAE,KAAK,WAAW,GAAG,KAAK,MAAM,OAAO,GAAG;CAChD;AACF;AAEA,eAAe,UAAU,MAAiB,IAAY,QAAgB,QAA0C,MAA0C;CACxJ,MAAM,EAAE,YAAY;CACpB,MAAM,SAAS,KAAK,WAAW;CAC/B,IAAI,UAAU,QAAQ,iBAAiB,KAAK,CAAC,QAAQ,cAAc,EAAE,GACnE,MAAM,IAAI,SAAS,MAAM,WAAW,GAAG,KAAK,cAAA,GAAgC,CAAC;CAG/E,MAAM,SAAS,QAAQ,cAAc,EAAE;CACvC,IAAI,QAAQ,OAAO;EAAE,IAAI;EAAO,eAAe;EAAQ,WAAW;CAAM;CACxE,IAAI,QAAQ,SAAS,IAAI,MAAM,GAAG,OAAO;EAAE,IAAI;EAAO,eAAe;EAAG,WAAW,QAAQ,KAAK,IAAI,QAAQ,EAAE,OAAO,CAAC;CAAE;CACxH,MAAM,cAAc,QAAQ,OAAO,IAAI,QAAQ,EAAE,OAAO,CAAC;CACzD,MAAM,UAAU,MAAM,KAAK,YAAY,QAAQ;CAC/C,IAAI,KAAK;CACT,IAAI;EACF,KAAK,MAAM,OAAO;CACpB,UAAU;EACR,QAAQ;CACV;CACA,IAAI,CAAC,IAAI,OAAO;EAAE,IAAI;EAAO,eAAe;EAAG,WAAW,YAAY;CAAO;CAC7E,QAAQ,MAAM,EAAE;CAChB,IAAI,YAAY,iBAAiB,QAAQ,aAAa;CACtD,OAAO,EAAE,IAAI,KAAK;AACpB;;AAGA,SAAgB,UAAU,GAAY,OAAqC;CACzE,IAAI,MAAM,IAAI,OAAO;CACrB,IAAI,MAAM,MAAM;EACd,EAAE,OAAO,eAAe,GAAG;EAC3B,OAAO,EAAE,KAAK,EAAE,OAAO;GAAE,MAAM;GAAqB,SAAS;EAA2F,EAAE,GAAG,GAAG;CAClK;CACA,OAAO,MAAM,gBAAgB,UAAU,GAAG,MAAM,aAAa,IAAI;AACnE"}
|