agentinbox-cli 0.0.0 → 0.1.0-nightly.10

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.
Files changed (176) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +337 -0
  3. package/dist/admin-CXkmdTYo.js +196 -0
  4. package/dist/admin-CXkmdTYo.js.map +1 -0
  5. package/dist/audit-bItbJYFP.js +65 -0
  6. package/dist/audit-bItbJYFP.js.map +1 -0
  7. package/dist/audit-gVdDzkAE.js +2 -0
  8. package/dist/auth-0NrMvHdE.js +550 -0
  9. package/dist/auth-0NrMvHdE.js.map +1 -0
  10. package/dist/auth-CoHvbWgO.js +2 -0
  11. package/dist/bus-Cz0flS37.js +12 -0
  12. package/dist/bus-Cz0flS37.js.map +1 -0
  13. package/dist/bus-OKIGut72.js +2 -0
  14. package/dist/cli.js +220 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/commands-DZ60j8_y.js +103 -0
  17. package/dist/commands-DZ60j8_y.js.map +1 -0
  18. package/dist/config-CIvgMqLx.js +2 -0
  19. package/dist/config-v975gxv4.js +44 -0
  20. package/dist/config-v975gxv4.js.map +1 -0
  21. package/dist/connect-BHC2trBi.js +292 -0
  22. package/dist/connect-BHC2trBi.js.map +1 -0
  23. package/dist/content-BXblC_Qs.js +1162 -0
  24. package/dist/content-BXblC_Qs.js.map +1 -0
  25. package/dist/content-worker.js +28 -0
  26. package/dist/content-worker.js.map +1 -0
  27. package/dist/crypto-DYZ4R7c2.js +74 -0
  28. package/dist/crypto-DYZ4R7c2.js.map +1 -0
  29. package/dist/db-B6gLQGBK.js +2 -0
  30. package/dist/db-D0gvshCc.js +282 -0
  31. package/dist/db-D0gvshCc.js.map +1 -0
  32. package/dist/errors-FbMS3QBo.js +39 -0
  33. package/dist/errors-FbMS3QBo.js.map +1 -0
  34. package/dist/filter-DGVz6-P1.js +140 -0
  35. package/dist/filter-DGVz6-P1.js.map +1 -0
  36. package/dist/instance-BBzEsYZO.js +175 -0
  37. package/dist/instance-BBzEsYZO.js.map +1 -0
  38. package/dist/instance-DLNTj9nm.js +2 -0
  39. package/dist/listen-D8DkEhqM.js +1724 -0
  40. package/dist/listen-D8DkEhqM.js.map +1 -0
  41. package/dist/listen-DT-omyD6.js +2 -0
  42. package/dist/mcpheader-By2dIyZq.js +2 -0
  43. package/dist/mcpheader-D9rrI-aa.js +65 -0
  44. package/dist/mcpheader-D9rrI-aa.js.map +1 -0
  45. package/dist/mcpsetup-7KxroOCp.js +2 -0
  46. package/dist/mcpsetup-DJ7ebFGJ.js +266 -0
  47. package/dist/mcpsetup-DJ7ebFGJ.js.map +1 -0
  48. package/dist/paths-BCBp8vrG.js +2 -0
  49. package/dist/paths-TLp8hiMt.js +40 -0
  50. package/dist/paths-TLp8hiMt.js.map +1 -0
  51. package/dist/profile-CgGgJ4Dm.js +181 -0
  52. package/dist/profile-CgGgJ4Dm.js.map +1 -0
  53. package/dist/profile-RbN2FRz7.js +2 -0
  54. package/dist/recipes-7d_Xws4M.js +2 -0
  55. package/dist/recipes-UalOc_LB.js +100 -0
  56. package/dist/recipes-UalOc_LB.js.map +1 -0
  57. package/dist/send-Dk-t8ia-.js +108 -0
  58. package/dist/send-Dk-t8ia-.js.map +1 -0
  59. package/dist/server-Hunjpt8n.js +8103 -0
  60. package/dist/server-Hunjpt8n.js.map +1 -0
  61. package/dist/service-BAgZDh22.js +2 -0
  62. package/dist/service-DYdD92CC.js +2 -0
  63. package/dist/service-D_ZEs3Zk.js +508 -0
  64. package/dist/service-D_ZEs3Zk.js.map +1 -0
  65. package/dist/service-whbugeQp.js +295 -0
  66. package/dist/service-whbugeQp.js.map +1 -0
  67. package/dist/settings-CeEsv4CE.js +2 -0
  68. package/dist/settings-DLUStIN7.js +109 -0
  69. package/dist/settings-DLUStIN7.js.map +1 -0
  70. package/dist/setup-token-BO89xnpb.js +48 -0
  71. package/dist/setup-token-BO89xnpb.js.map +1 -0
  72. package/dist/skill/SKILL.md +277 -0
  73. package/dist/skill-BJFn2bHK.js +2 -0
  74. package/dist/skill-WKbeyNct.js +43 -0
  75. package/dist/skill-WKbeyNct.js.map +1 -0
  76. package/dist/types-3eEsBFVj.js +37 -0
  77. package/dist/types-3eEsBFVj.js.map +1 -0
  78. package/dist/ui-B8Sd4Y7Y.js +81 -0
  79. package/dist/ui-B8Sd4Y7Y.js.map +1 -0
  80. package/dist/urls-Dp8pBGMU.js +2 -0
  81. package/dist/urls-U_LfYV61.js +27 -0
  82. package/dist/urls-U_LfYV61.js.map +1 -0
  83. package/dist/web/assets/_agentId-DAj08NDq.js +1 -0
  84. package/dist/web/assets/_app-B6q_HS9y.js +1 -0
  85. package/dist/web/assets/_app-CAQ6Mu1s.js +63 -0
  86. package/dist/web/assets/_webhookId-CMgBUN2Z.js +1 -0
  87. package/dist/web/assets/account-status-omoUlgBI.js +1 -0
  88. package/dist/web/assets/accounts-DyLfACj_.js +1 -0
  89. package/dist/web/assets/accounts-list-DiXACW0-.js +1 -0
  90. package/dist/web/assets/activity-zrckFhPk.js +1 -0
  91. package/dist/web/assets/agent-create-form-DKwaY1Dg.js +1 -0
  92. package/dist/web/assets/agents-Drvjccgt.js +1 -0
  93. package/dist/web/assets/approvals-4kd0_J8X.js +2 -0
  94. package/dist/web/assets/arrow-left-DVJV4Zx7.js +1 -0
  95. package/dist/web/assets/arrow-right-4PFX6MSS.js +1 -0
  96. package/dist/web/assets/audit-76H45orR.js +1 -0
  97. package/dist/web/assets/audit-bits-B1We-fQ3.js +1 -0
  98. package/dist/web/assets/bot-DVw15lBn.js +1 -0
  99. package/dist/web/assets/card-EQTpqntn.js +1 -0
  100. package/dist/web/assets/checkbox-DDZZurgK.js +1 -0
  101. package/dist/web/assets/chevron-down-892ylB4R.js +1 -0
  102. package/dist/web/assets/chevron-right-CbBKS6-T.js +1 -0
  103. package/dist/web/assets/chips-input--xgMZpg2.js +1 -0
  104. package/dist/web/assets/circle-x-Bi4IGwky.js +1 -0
  105. package/dist/web/assets/common-PG6weTqi.js +1 -0
  106. package/dist/web/assets/connect-host-DdkOJnMu.js +1 -0
  107. package/dist/web/assets/data-table-Ca9aQZKR.js +1 -0
  108. package/dist/web/assets/dialog-CxNRCZdE.js +1 -0
  109. package/dist/web/assets/dist-B0e3Ju3r.js +1 -0
  110. package/dist/web/assets/empty-CE_WZGEC.js +1 -0
  111. package/dist/web/assets/es2015-DPaldoxG.js +41 -0
  112. package/dist/web/assets/event-row-2xoyzgbP.js +1 -0
  113. package/dist/web/assets/events-Bizq3DPC.js +1 -0
  114. package/dist/web/assets/eye-off-DjpaihHf.js +1 -0
  115. package/dist/web/assets/field-CcyMn3ey.js +1 -0
  116. package/dist/web/assets/geist-cyrillic-ext-wght-normal-DjL33-gN.woff2 +0 -0
  117. package/dist/web/assets/geist-cyrillic-wght-normal-BEAKL7Jp.woff2 +0 -0
  118. package/dist/web/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  119. package/dist/web/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  120. package/dist/web/assets/geist-mono-cyrillic-ext-wght-normal-X_5orZeX.woff2 +0 -0
  121. package/dist/web/assets/geist-mono-cyrillic-wght-normal-DiZS0aHC.woff2 +0 -0
  122. package/dist/web/assets/geist-mono-latin-ext-wght-normal-Bwz-egvJ.woff2 +0 -0
  123. package/dist/web/assets/geist-mono-latin-wght-normal-XN7g48iV.woff2 +0 -0
  124. package/dist/web/assets/geist-mono-symbols2-wght-normal-CO5SzqOn.woff2 +0 -0
  125. package/dist/web/assets/geist-mono-vietnamese-wght-normal-DadHysG0.woff2 +0 -0
  126. package/dist/web/assets/geist-vietnamese-wght-normal-6IgcOCM7.woff2 +0 -0
  127. package/dist/web/assets/get-started-DXUVmyNX.js +1 -0
  128. package/dist/web/assets/grants-BhXj1Pew.js +1 -0
  129. package/dist/web/assets/index-Bd4Y5wSp.js +10 -0
  130. package/dist/web/assets/index-akvt_1UI.css +2 -0
  131. package/dist/web/assets/input-CMhmmHiH.js +1 -0
  132. package/dist/web/assets/input-group-DRs3TNaT.js +1 -0
  133. package/dist/web/assets/key-round-CVIZRCLM.js +1 -0
  134. package/dist/web/assets/labels-C_Pwium9.js +1 -0
  135. package/dist/web/assets/login-aCr-cPqJ.js +1 -0
  136. package/dist/web/assets/mail-CAVmuESo.js +1 -0
  137. package/dist/web/assets/mutations-DVlbDx0M.js +1 -0
  138. package/dist/web/assets/paperclip-QFjM4y6n.js +1 -0
  139. package/dist/web/assets/password-input-_m9Ycphv.js +1 -0
  140. package/dist/web/assets/pencil-Bf1JhsG0.js +1 -0
  141. package/dist/web/assets/play-DiPk49JH.js +1 -0
  142. package/dist/web/assets/plus-CETmK0SB.js +1 -0
  143. package/dist/web/assets/preload-helper-BFuRUfdt.js +1 -0
  144. package/dist/web/assets/progress-3ZOsbLSK.js +6 -0
  145. package/dist/web/assets/queries-a3M5_loa.js +2 -0
  146. package/dist/web/assets/refresh-cw-DOn9kD7g.js +1 -0
  147. package/dist/web/assets/search-gcYjitzV.js +1 -0
  148. package/dist/web/assets/select-C8tJBHb5.js +1 -0
  149. package/dist/web/assets/send-BhQwhuJo.js +1 -0
  150. package/dist/web/assets/settings-H7ExHD9L.js +1 -0
  151. package/dist/web/assets/settings-form-CNUionsI.js +1 -0
  152. package/dist/web/assets/setup-DLbH5Dvl.js +1 -0
  153. package/dist/web/assets/sheet-BQKpA2xa.js +1 -0
  154. package/dist/web/assets/shield-alert-CbFgd06X.js +1 -0
  155. package/dist/web/assets/stamp-CH2OvjH2.js +1 -0
  156. package/dist/web/assets/stream-C2Tr2d5O.js +1 -0
  157. package/dist/web/assets/tabs-ySiz7Mni.js +1 -0
  158. package/dist/web/assets/tag-CP72Kk6Y.js +1 -0
  159. package/dist/web/assets/test-connection-Bd2Gac0O.js +1 -0
  160. package/dist/web/assets/textarea-CCokBZIw.js +1 -0
  161. package/dist/web/assets/toggle-group-ne0pVDg6.js +1 -0
  162. package/dist/web/assets/trash-DxdMNg8B.js +1 -0
  163. package/dist/web/assets/use-sign-out-BuzlTb0Q.js +1 -0
  164. package/dist/web/assets/useInfiniteQuery-DE6WChlb.js +1 -0
  165. package/dist/web/assets/useMatch-BKUnm54_.js +1 -0
  166. package/dist/web/assets/useMutation-DeWvLw1a.js +1 -0
  167. package/dist/web/assets/useQuery-DAhf1YDH.js +1 -0
  168. package/dist/web/assets/useSelector-D70eKNYs.js +1 -0
  169. package/dist/web/assets/webhook-BWT75M78.js +1 -0
  170. package/dist/web/assets/webhook-sheet-Cy_jHIqG.js +34 -0
  171. package/dist/web/assets/webhooks-CTAj5Ovp.js +1 -0
  172. package/dist/web/assets/with-selector-CkNyXYMW.js +1 -0
  173. package/dist/web/assets/x-BHjDLXNO.js +1 -0
  174. package/dist/web/favicon.svg +1 -0
  175. package/dist/web/index.html +31 -0
  176. package/package.json +95 -1
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Unlabored Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,337 @@
1
+ # agentinbox
2
+
3
+ [![CI](https://github.com/unlaboredlabs/agentinbox/actions/workflows/ci.yml/badge.svg)](https://github.com/unlaboredlabs/agentinbox/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/agentinbox-cli)](https://www.npmjs.com/package/agentinbox-cli)
5
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
6
+
7
+ A self-hosted Gmail broker for AI agents. One small daemon holds your Gmail credentials; every agent gets its own
8
+ scoped API key and reaches mail through **MCP tools** (read, search, draft, send, organize) and **signed webhooks** or an
9
+ MCP event feed for new mail. Guardrails decide what each agent may do per mailbox, every action lands in an audit log,
10
+ and a web console makes setup and management painless. It runs anywhere Node does, against your own Google Cloud
11
+ project, with no cloud service in between.
12
+
13
+ ## Features
14
+
15
+ - **One place for credentials.** Connect Gmail and Google Workspace accounts once. Refresh tokens are encrypted at rest.
16
+ Agents only ever see an `aib_…` key.
17
+ - **MCP for every agent.** Streamable HTTP at `/mcp`: `search_threads`, `get_thread`, `reply`, `send_message`,
18
+ `modify_labels`, `wait_for_events` and more. Each agent sees only the tools its grants allow.
19
+ - **Real-time events.** New mail, replies to threads you sent into, and label changes arrive within seconds through
20
+ Gmail push via a Pub/Sub *pull* subscription (no public URL needed), or by polling with zero extra setup.
21
+ - **Webhooks.** Filter by account, label, sender, recipient, subject or attachments. Payloads are signed per
22
+ [Standard Webhooks](https://www.standardwebhooks.com), retried with backoff, logged, and replayable.
23
+ - **Guardrails that don't degrade content.** Per agent and mailbox: capabilities, label scope, send mode (direct,
24
+ draft-only or human approval), recipient allowlists, reply-only and send caps. Email content is cleaned, not censored.
25
+ - **A console you'll actually enjoy.** Guided Google setup, connect snippets for Claude Code, Codex and Cursor, an
26
+ approvals inbox, live events, delivery logs and the audit trail. Dark mode included.
27
+ - **A ready-made agent skill.** `SKILL.md` teaches agents to use the tools well and to treat email as data.
28
+
29
+ <!--
30
+ ## Preview
31
+
32
+ ![Overview](docs/screenshots/overview.png)
33
+
34
+ <table>
35
+ <tr>
36
+ <td width="50%"><img src="docs/screenshots/setup.png" alt="Setup wizard"><br>Guided Google Cloud setup.</td>
37
+ <td width="50%"><img src="docs/screenshots/agents.png" alt="Agents"><br>Agents, grants and connect snippets.</td>
38
+ </tr>
39
+ <tr>
40
+ <td><img src="docs/screenshots/approvals.png" alt="Approvals"><br>Approve or reject sends.</td>
41
+ <td><img src="docs/screenshots/webhooks.png" alt="Webhooks"><br>Webhooks with delivery log and replay.</td>
42
+ </tr>
43
+ </table>
44
+ -->
45
+
46
+ ## Quick start
47
+
48
+ Requires Node.js 22.13 or newer.
49
+
50
+ ```sh
51
+ npx agentinbox-cli
52
+ ```
53
+
54
+ The npm package is `agentinbox-cli`; the command it provides is `agentinbox`. Use `npx agentinbox-cli …` for one-off
55
+ runs, or `npm i -g agentinbox-cli` and then `agentinbox …`.
56
+
57
+ agentinbox starts on port 8425 and prints a one-time setup link (and opens it if it can):
58
+
59
+ ```
60
+ agentinbox v0.1.0 · data in /home/you/.agentinbox
61
+
62
+ First run: open this link to claim the instance and finish setup:
63
+
64
+ http://localhost:8425/setup?token=…
65
+ ```
66
+
67
+ Open it, choose an admin password, and the setup wizard walks you through the rest in about ten minutes.
68
+
69
+ ## Setting up Google
70
+
71
+ agentinbox uses **your own** Google Cloud project and OAuth client. Gmail's read scopes are restricted, so a shared
72
+ client isn't an option, and this way your mail never passes through anyone else's app. The wizard shows each step with
73
+ the exact values to paste; in short:
74
+
75
+ 1. **Create a Google Cloud project** and enable the **Gmail API** (and the **Cloud Pub/Sub API** if you want push).
76
+ 2. **Configure the OAuth consent screen** (Google Auth Platform → Audience):
77
+ - **Internal** if every mailbox is in your Google Workspace organization. No review, no expiry, nothing else to do.
78
+ - **External** for personal `@gmail.com` accounts (or mailboxes outside your org). Then **publish the app to
79
+ "In production"**. Left in *Testing*, Google expires refresh tokens after **7 days** and your accounts silently
80
+ disconnect. You don't need Google's verification for your own use: on consent, click *Advanced → Go to
81
+ (app name)* past the "unverified app" warning.
82
+ 3. **Create an OAuth client** of type *Web application*, add the redirect URI the console shows
83
+ (`<public URL>/oauth/callback`), and paste the client ID, client secret and project ID into the console.
84
+ 4. **Connect accounts.** Each one goes through Google's consent screen. If the console is on plain HTTP at a non-localhost
85
+ address, Google can't redirect back to it; the console then asks you to paste the URL you land on instead.
86
+ 5. **Choose how new mail arrives:**
87
+ - **Pub/Sub (recommended):** one click creates the topic, grants Gmail's push service
88
+ (`gmail-api-push@system.gserviceaccount.com`) permission to publish, and creates a pull subscription, using a
89
+ connected account that can manage Pub/Sub in the project. Events arrive in seconds, with no inbound connections.
90
+ If a Workspace organization policy (domain-restricted sharing) blocks that grant, the console says so; use polling.
91
+ - **Polling:** no extra setup; checks each mailbox every 30 seconds by default.
92
+
93
+ Then create an agent and give it a grant on a mailbox.
94
+
95
+ ## Connecting agents
96
+
97
+ Create an agent in the console (**Agents → New agent**) to get its API key; the key is shown once. The console has
98
+ copy-paste snippets with your URL and key filled in, and a **Test connection** button. By hand:
99
+
100
+ **Claude Code**
101
+
102
+ ```sh
103
+ claude mcp add --transport http agentinbox http://your-host:8425/mcp --header "Authorization: Bearer aib_…"
104
+ ```
105
+
106
+ **Codex** (`~/.codex/config.toml`)
107
+
108
+ ```toml
109
+ [mcp_servers.agentinbox]
110
+ url = "http://your-host:8425/mcp"
111
+ bearer_token_env_var = "AGENTINBOX_KEY"
112
+ ```
113
+
114
+ **Cursor** (`~/.cursor/mcp.json`) and most other MCP clients
115
+
116
+ ```json
117
+ {
118
+ "mcpServers": {
119
+ "agentinbox": {
120
+ "url": "http://your-host:8425/mcp",
121
+ "headers": { "Authorization": "Bearer aib_…" }
122
+ }
123
+ }
124
+ }
125
+ ```
126
+
127
+ Any MCP client that speaks streamable HTTP works: endpoint `/mcp`, header `Authorization: Bearer <key>`.
128
+
129
+ ### The agent skill
130
+
131
+ `SKILL.md` teaches an agent the tools, Gmail search syntax, reply etiquette, how sends and denials work, the event loop,
132
+ and that email content is data, never instructions. Install it on the machine where the agent runs (no running
133
+ agentinbox needed there):
134
+
135
+ ```sh
136
+ npx agentinbox-cli skill install --url https://mail.example.com # ~/.claude/skills/agentinbox/SKILL.md
137
+ npx agentinbox-cli skill install --target codex --url https://mail.example.com
138
+ npx agentinbox-cli skill install --target ./skills --url https://mail.example.com
139
+ npx agentinbox-cli skill print --url https://mail.example.com # to stdout
140
+ ```
141
+
142
+ Every instance also serves it at `/skill.md` with its public URL filled in.
143
+
144
+ ## Events and webhooks
145
+
146
+ Event types: `message.received`, `message.sent`, `thread.reply_received` (a reply in a thread the account sent into),
147
+ `label.added`, `label.removed`, `account.auth_failed` and `sync.gap`. Agents only receive events for mailboxes and
148
+ labels their grants let them read.
149
+
150
+ - **Agents without an HTTP endpoint** (most coding agents) use the MCP tools `wait_for_events` (long poll),
151
+ `list_events` and `ack_events`. The server keeps each agent's position.
152
+ - **Agents with an endpoint** get webhooks: create one under **Webhooks**, pick event types and filters, and optionally
153
+ include the cleaned message body. Failed deliveries retry with exponential backoff before they're marked dead, and
154
+ any delivery can be replayed from the console.
155
+
156
+ Each webhook request carries `webhook-id`, `webhook-timestamp` and `webhook-signature` headers. Verify them with any
157
+ [Standard Webhooks library](https://github.com/standard-webhooks/standard-webhooks) and the `whsec_…` secret shown when
158
+ you create the webhook:
159
+
160
+ ```js
161
+ import { Webhook } from "standardwebhooks";
162
+
163
+ const wh = new Webhook(process.env.AGENTINBOX_WEBHOOK_SECRET);
164
+ const payload = wh.verify(rawBody, request.headers); // throws if the signature or timestamp is bad
165
+ ```
166
+
167
+ A dependency-free Node version is in the [skill](server/skill/SKILL.md#webhooks).
168
+
169
+ By default webhooks may only target public addresses and loopback; other private addresses (for example a LAN host or
170
+ `n8n.internal`) must be added to the webhook allowlist in **Settings**.
171
+
172
+ ## Security model
173
+
174
+ agentinbox assumes agents can be manipulated by the mail they read. So it keeps content intact and puts the hard limits
175
+ on actions.
176
+
177
+ **Content is cleaned, not censored.** Agents get full-fidelity mail converted to tidy Markdown, with links and tables.
178
+ Only what a human reader couldn't see is removed: hidden CSS (`display:none`, zero-size, transparent or
179
+ same-colour text), HTML comments, scripts and styles, zero-width characters, Unicode tag characters and bidi overrides.
180
+ Each body is wrapped in one `<email_content … untrusted>` delimiter and comes with signals: SPF/DKIM/DMARC results, an
181
+ external-sender flag, whether hidden content was removed, and phrases typical of prompt injection. Signals are
182
+ informational; nothing is withheld because of them.
183
+
184
+ **Actions are strictly controlled.** A new agent can do nothing. Each grant (agent × mailbox) sets:
185
+
186
+ | Control | |
187
+ |---|---|
188
+ | Capabilities | `read` (search, read, attachments, events), `draft`, `send`, `organize` (labels, archive, read state), `trash` |
189
+ | Label scope | Confine the agent to threads carrying certain labels. Searches are rewritten and fetches by id are checked. |
190
+ | Send mode | `draft_only` (default: a Gmail draft for a human to send), `approval` (queued in the console; approving sends it) or `direct` |
191
+ | Allowed recipients | Exact addresses or `*@domain.com` globs |
192
+ | Reply only | Sending only inside threads the agent can already see |
193
+ | Send caps | Per hour and per day |
194
+
195
+ **Everything is audited.** Every tool call and every console change is recorded with its outcome, and denials name the
196
+ guardrail that blocked them.
197
+
198
+ **The console** is protected by an admin password (set through the one-time setup link), HttpOnly session cookies
199
+ that expire after 30 days of inactivity (90 at most), a CSRF header on every change, request size limits, a strict
200
+ Content Security Policy, and a 15-minute lockout after five wrong passwords (or 30 attempts) from one address. When
201
+ failures pile up across many addresses, every sign-in attempt is slowed down instead. It never allows being framed.
202
+ The data directory and database are readable only by their owner.
203
+
204
+ Recommendations: split agents that read untrusted mail from agents that can send; prefer `draft_only` or `approval`;
205
+ use allowlists for anything that sends directly.
206
+
207
+ ## Running it
208
+
209
+ ### As a service
210
+
211
+ ```sh
212
+ npx agentinbox-cli service install
213
+ ```
214
+
215
+ Installs a background service that starts at boot: systemd on Linux (a user service with lingering enabled, or a system
216
+ service when run as root) and launchd on macOS. The service runs its own copy of agentinbox from the data directory,
217
+ so it never depends on the npx cache; a failed install leaves the running service as it was. Re-run `service install`
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
+ `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
+
223
+ Only one agentinbox runs per data directory; a second one refuses to start.
224
+
225
+ When a newer version is published, the console shows **Update available** with the exact command to run.
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
+
248
+ ### Behind a reverse proxy
249
+
250
+ agentinbox serves plain HTTP. To expose it beyond your network, put it behind a TLS-terminating proxy (Caddy, nginx,
251
+ Tailscale Funnel) and:
252
+
253
+ - bind it to localhost: `--host 127.0.0.1`
254
+ - set `TRUST_PROXY=1`, so lockouts apply per client and cookies are marked `Secure`. The client address is taken from
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
259
+ - set **Settings → Public URL** to the https address, so MCP snippets, the skill and the OAuth redirect use it
260
+
261
+ `GET /health` (or `/api/health`) needs no auth and suits uptime monitors: `200` when healthy, `503` when degraded, for
262
+ example when an account needs to be reconnected or ingest is failing. It reports only a status, the version and
263
+ whether setup is complete.
264
+
265
+ ### Options
266
+
267
+ With a global install (`npm i -g agentinbox-cli`); otherwise prefix `npx agentinbox-cli` instead of `agentinbox`.
268
+
269
+ ```
270
+ agentinbox [start] [--port 8425] [--host 0.0.0.0] [--data ~/.agentinbox] [--no-open] [--dev]
271
+ agentinbox service install|uninstall|status [--port …] [--host …] [--data …]
272
+ agentinbox skill install [--target claude|codex|agents|<dir>] [--url <public url>]
273
+ agentinbox skill print [--url <public url>]
274
+ agentinbox reset-password [--data …]
275
+ ```
276
+
277
+ | Flag | Env | Default | |
278
+ |---|---|---|---|
279
+ | `--port` | `PORT` | `8425` | Port to listen on |
280
+ | `--host` | `HOST` | `0.0.0.0` | Bind address. Use `127.0.0.1` behind a reverse proxy or SSH tunnel |
281
+ | `--data` | `AGENTINBOX_DATA` | `~/.agentinbox` | Data directory |
282
+ | | `AGENTINBOX_MASTER_KEY` | | Base64 32-byte key for encrypting secrets, instead of `<data>/master.key` |
283
+ | | `TRUST_PROXY` | | `1` behind a reverse proxy: client IPs from `X-Forwarded-For`, https from `X-Forwarded-Proto` |
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) |
286
+ | | `AGENTINBOX_LOG_LEVEL` | `info` | `debug`, `info`, `warn` or `error` |
287
+
288
+ Forgot the admin password? Run `npx agentinbox-cli reset-password` on the server, as the user agentinbox runs as (`sudo` for a
289
+ system service) and with the same `--data`. It signs out all sessions and prints a new setup link; a running instance
290
+ picks it up without a restart.
291
+
292
+ ## Data and backups
293
+
294
+ Everything lives in the data directory (`~/.agentinbox` by default):
295
+
296
+ | File | |
297
+ |---|---|
298
+ | `agentinbox.db` | SQLite database: accounts, encrypted tokens, agents, grants, events, deliveries, approvals, audit log |
299
+ | `master.key` | Encrypts refresh tokens, webhook secrets and the OAuth client secret (mode 0600) |
300
+ | `setup-token` | The one-time setup token, until the instance is claimed |
301
+ | `agentinbox.lock` | Marks the running instance (pid and port) |
302
+ | `service.env` | Environment the service was installed with, possibly including `AGENTINBOX_MASTER_KEY` (mode 0600; `service install` only) |
303
+ | `agentinbox.log` | Service output on macOS (launchd); on Linux use `journalctl --user -u agentinbox` (or without `--user` for a system service) |
304
+ | `app/` | The service's own install (`service install` only) |
305
+
306
+ Back up `agentinbox.db` and `master.key` together: without the key, stored secrets can't be decrypted, and agentinbox
307
+ refuses to start rather than create a new one. Keep the key somewhere safer than the database backup, or supply it via
308
+ `AGENTINBOX_MASTER_KEY`. The database runs in WAL mode, so don't just copy the file while agentinbox is running: stop
309
+ it first (`systemctl --user stop agentinbox`), or take a consistent copy with
310
+ `sqlite3 ~/.agentinbox/agentinbox.db ".backup /path/to/backup.db"`. Mail
311
+ bodies are never stored; agentinbox keeps metadata (ids, headers, labels, snippets) and fetches content from Gmail on
312
+ demand. Events, deliveries and audit entries are pruned after the retention period set in **Settings** (30 days by
313
+ default).
314
+
315
+ ## Development
316
+
317
+ ```sh
318
+ pnpm install
319
+ AGENTINBOX_DATA=/tmp/agentinbox-dev pnpm dev
320
+ ```
321
+
322
+ `pnpm dev` runs the server on `:8425` in watch mode (API, MCP and OAuth only) and the console on Vite at `:5173`, which
323
+ proxies to it. Open the Vite URL; the printed setup link already points there.
324
+
325
+ ```sh
326
+ pnpm typecheck && pnpm lint && pnpm test
327
+ pnpm build # console → dist/web, server → dist/cli.js
328
+ node dist/cli.js # run the build
329
+ ```
330
+
331
+ Stack: TypeScript on Node 22+, Hono on `@hono/node-server`, `node:sqlite`, the MCP TypeScript SDK, and thin fetch-based
332
+ Google clients (no `googleapis`, so `npx` stays fast). The console is Vite, React 19, TanStack Router / Query / Table /
333
+ Form and shadcn/ui on Tailwind v4.
334
+
335
+ ## License
336
+
337
+ [MIT](LICENSE)
@@ -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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"}