@stage-labs/metro 0.1.0-beta.13 → 0.1.0-beta.130

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 (328) hide show
  1. package/dist/api.js +14 -0
  2. package/dist/claude-settings.js +40 -0
  3. package/dist/claude.js +176 -0
  4. package/dist/cli.js +101 -0
  5. package/dist/control.js +74 -0
  6. package/dist/hold.js +211 -0
  7. package/dist/local.js +93 -0
  8. package/dist/mcp-config.js +25 -0
  9. package/dist/node-name.js +71 -0
  10. package/dist/onboarding.js +43 -0
  11. package/dist/plugin.js +120 -0
  12. package/dist/provider-flags.js +9 -0
  13. package/dist/route.js +38 -0
  14. package/dist/runtime-install.js +97 -0
  15. package/dist/runtime.js +74 -0
  16. package/dist/serve.js +150 -0
  17. package/dist/service.js +242 -0
  18. package/dist/tail.js +119 -0
  19. package/dist/update.js +45 -0
  20. package/dist/version.js +97 -0
  21. package/package.json +14 -27
  22. package/runtime/marketplace/.claude-plugin/marketplace.json +12 -0
  23. package/runtime/marketplace/plugin/.claude-plugin/plugin.json +18 -0
  24. package/runtime/marketplace/plugin/.mcp.json +1 -0
  25. package/runtime/marketplace/plugin/bin/guard.mjs +55 -0
  26. package/runtime/marketplace/plugin/bin/metro-plugin.mjs +122 -0
  27. package/runtime/marketplace/plugin/bin/session-start.mjs +31 -0
  28. package/runtime/marketplace/plugin/hooks/hooks.json +25 -0
  29. package/runtime/marketplace/plugin/orchestrator.md +16 -0
  30. package/runtime/marketplace/plugin/skills/refresh/SKILL.md +15 -0
  31. package/runtime/node_modules/@metro-labs/core/package.json +43 -0
  32. package/runtime/node_modules/@metro-labs/core/src/address.ts +6 -0
  33. package/runtime/node_modules/@metro-labs/core/src/endpoints.ts +7 -0
  34. package/runtime/node_modules/@metro-labs/core/src/events.ts +120 -0
  35. package/runtime/node_modules/@metro-labs/core/src/identity.ts +7 -0
  36. package/runtime/node_modules/@metro-labs/core/src/ids.ts +21 -0
  37. package/runtime/node_modules/@metro-labs/core/src/is-record.ts +2 -0
  38. package/runtime/node_modules/@metro-labs/core/src/lines.ts +87 -0
  39. package/runtime/node_modules/@metro-labs/core/src/log.ts +42 -0
  40. package/runtime/node_modules/@metro-labs/core/src/protocol.ts +226 -0
  41. package/runtime/node_modules/@metro-labs/core/src/secure-fs.ts +57 -0
  42. package/runtime/node_modules/@metro-labs/core/src/station-names.ts +13 -0
  43. package/runtime/node_modules/@metro-labs/core/src/stations/account-store.ts +60 -0
  44. package/runtime/node_modules/@metro-labs/core/src/stations/attachments.ts +196 -0
  45. package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +88 -0
  46. package/runtime/node_modules/@metro-labs/core/src/stations/station-runtime.ts +64 -0
  47. package/runtime/node_modules/@metro-labs/core/src/stations/types.ts +108 -0
  48. package/runtime/node_modules/@metro-labs/core/src/str.ts +5 -0
  49. package/runtime/node_modules/@metro-labs/core/src/train-error.ts +53 -0
  50. package/runtime/node_modules/@metro-labs/core/src/version.ts +2 -0
  51. package/runtime/node_modules/@metro-labs/daemon/package.json +48 -0
  52. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-attach.ts +19 -0
  53. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-routes.ts +70 -0
  54. package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +365 -0
  55. package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +43 -0
  56. package/runtime/node_modules/@metro-labs/daemon/src/agents/allowlist.ts +37 -0
  57. package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +300 -0
  58. package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +122 -0
  59. package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +371 -0
  60. package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +155 -0
  61. package/runtime/node_modules/@metro-labs/daemon/src/agents/keys.ts +45 -0
  62. package/runtime/node_modules/@metro-labs/daemon/src/agents/map.ts +95 -0
  63. package/runtime/node_modules/@metro-labs/daemon/src/agents/scope.ts +64 -0
  64. package/runtime/node_modules/@metro-labs/daemon/src/agents/senders.ts +38 -0
  65. package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +207 -0
  66. package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +39 -0
  67. package/runtime/node_modules/@metro-labs/daemon/src/boot/crash-guard.ts +49 -0
  68. package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +14 -0
  69. package/runtime/node_modules/@metro-labs/daemon/src/boot/paths.ts +106 -0
  70. package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +25 -0
  71. package/runtime/node_modules/@metro-labs/daemon/src/channels/dedupe.ts +24 -0
  72. package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +377 -0
  73. package/runtime/node_modules/@metro-labs/daemon/src/channels/media-note.ts +85 -0
  74. package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +75 -0
  75. package/runtime/node_modules/@metro-labs/daemon/src/channels/relay.ts +129 -0
  76. package/runtime/node_modules/@metro-labs/daemon/src/channels/webhook-note.ts +51 -0
  77. package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +309 -0
  78. package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +358 -0
  79. package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +169 -0
  80. package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +56 -0
  81. package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +140 -0
  82. package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +90 -0
  83. package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +253 -0
  84. package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +117 -0
  85. package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +239 -0
  86. package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +154 -0
  87. package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +109 -0
  88. package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +310 -0
  89. package/runtime/node_modules/@metro-labs/daemon/src/connectors/cli-api.ts +63 -0
  90. package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +175 -0
  91. package/runtime/node_modules/@metro-labs/daemon/src/connectors/json.ts +21 -0
  92. package/runtime/node_modules/@metro-labs/daemon/src/connectors/model.ts +77 -0
  93. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-client.ts +166 -0
  94. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-discovery.ts +139 -0
  95. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-pending.ts +65 -0
  96. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-routes.ts +171 -0
  97. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth.ts +155 -0
  98. package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +122 -0
  99. package/runtime/node_modules/@metro-labs/daemon/src/connectors/reach.ts +27 -0
  100. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-target.ts +39 -0
  101. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +296 -0
  102. package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +286 -0
  103. package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +98 -0
  104. package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +70 -0
  105. package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +332 -0
  106. package/runtime/node_modules/@metro-labs/daemon/src/connectors/watch.ts +54 -0
  107. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +99 -0
  108. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-owner.ts +32 -0
  109. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +121 -0
  110. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +294 -0
  111. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-store.ts +181 -0
  112. package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +259 -0
  113. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +231 -0
  114. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
  115. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +288 -0
  116. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +203 -0
  117. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +231 -0
  118. package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +123 -0
  119. package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +187 -0
  120. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +229 -0
  121. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +269 -0
  122. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +207 -0
  123. package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +90 -0
  124. package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +65 -0
  125. package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +17 -0
  126. package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +229 -0
  127. package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +151 -0
  128. package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +216 -0
  129. package/runtime/node_modules/@metro-labs/daemon/src/mcp/channel-owner.ts +29 -0
  130. package/runtime/node_modules/@metro-labs/daemon/src/mcp/ctx.ts +62 -0
  131. package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +91 -0
  132. package/runtime/node_modules/@metro-labs/daemon/src/mcp/frame-scope.ts +21 -0
  133. package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +129 -0
  134. package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +191 -0
  135. package/runtime/node_modules/@metro-labs/daemon/src/mcp/member-tools.ts +72 -0
  136. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +66 -0
  137. package/runtime/node_modules/@metro-labs/daemon/src/mcp/raw-get-stream.ts +207 -0
  138. package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +47 -0
  139. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-registry.ts +172 -0
  140. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-route.ts +43 -0
  141. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +215 -0
  142. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-dispatch.ts +215 -0
  143. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-schemas.ts +400 -0
  144. package/runtime/node_modules/@metro-labs/daemon/src/mcp/upload-tool.ts +66 -0
  145. package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +237 -0
  146. package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +389 -0
  147. package/runtime/node_modules/@metro-labs/daemon/src/routes/body.ts +24 -0
  148. package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +372 -0
  149. package/runtime/node_modules/@metro-labs/daemon/src/routes/identity.ts +54 -0
  150. package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +223 -0
  151. package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +60 -0
  152. package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
  153. package/runtime/node_modules/@metro-labs/daemon/src/routes/threema-callback.ts +103 -0
  154. package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +64 -0
  155. package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +61 -0
  156. package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +116 -0
  157. package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
  158. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-inline.ts +100 -0
  159. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-interactive.ts +161 -0
  160. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +202 -0
  161. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-session.ts +280 -0
  162. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-xmtp.ts +111 -0
  163. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +248 -0
  164. package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +261 -0
  165. package/runtime/node_modules/@metro-labs/daemon/src/stations/registry.ts +37 -0
  166. package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
  167. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor-io.ts +97 -0
  168. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +320 -0
  169. package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
  170. package/runtime/node_modules/@metro-labs/daemon/src/stations/train-call.ts +22 -0
  171. package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +96 -0
  172. package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +123 -0
  173. package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +40 -0
  174. package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
  175. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -0
  176. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
  177. package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
  178. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
  179. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +220 -0
  180. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
  181. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
  182. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
  183. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
  184. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
  185. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
  186. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
  187. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
  188. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
  189. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
  190. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
  191. package/runtime/node_modules/@metro-labs/http/package.json +33 -0
  192. package/runtime/node_modules/@metro-labs/http/src/api-error.ts +8 -0
  193. package/runtime/node_modules/@metro-labs/http/src/api-http.ts +95 -0
  194. package/runtime/node_modules/@metro-labs/http/src/cors.ts +29 -0
  195. package/runtime/node_modules/@metro-labs/http/src/identity-key.ts +33 -0
  196. package/runtime/node_modules/@metro-labs/http/src/identity-registry.ts +13 -0
  197. package/runtime/node_modules/@metro-labs/http/src/mode-api.ts +32 -0
  198. package/runtime/node_modules/@metro-labs/http/src/return-to.ts +21 -0
  199. package/runtime/node_modules/@metro-labs/http/src/signed-identity.ts +36 -0
  200. package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
  201. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
  202. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +254 -0
  203. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
  204. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
  205. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +156 -0
  206. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
  207. package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
  208. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
  209. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
  210. package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
  211. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
  212. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
  213. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
  214. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
  215. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
  216. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
  217. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
  218. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
  219. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
  220. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
  221. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
  222. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +201 -0
  223. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +109 -0
  224. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
  225. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
  226. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
  227. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +58 -0
  228. package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
  229. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
  230. package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
  231. package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +100 -0
  232. package/runtime/node_modules/@metro-labs/threema/src/actions.ts +219 -0
  233. package/runtime/node_modules/@metro-labs/threema/src/api.ts +130 -0
  234. package/runtime/node_modules/@metro-labs/threema/src/crypto.ts +132 -0
  235. package/runtime/node_modules/@metro-labs/threema/src/format.ts +123 -0
  236. package/runtime/node_modules/@metro-labs/threema/src/ids.ts +21 -0
  237. package/runtime/node_modules/@metro-labs/threema/src/index.ts +29 -0
  238. package/runtime/node_modules/@metro-labs/threema/src/station.ts +10 -0
  239. package/runtime/node_modules/@metro-labs/threema/src/verify.ts +117 -0
  240. package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
  241. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
  242. package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
  243. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
  244. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
  245. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +190 -0
  246. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
  247. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
  248. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +347 -0
  249. package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
  250. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +138 -0
  251. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
  252. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
  253. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
  254. package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
  255. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
  256. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
  257. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
  258. package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
  259. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
  260. package/runtime/node_modules/@metro-labs/whatsapp/src/resolve.ts +52 -0
  261. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +18 -0
  262. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
  263. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
  264. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
  265. package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
  266. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +112 -0
  267. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
  268. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
  269. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
  270. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
  271. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
  272. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
  273. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
  274. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
  275. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
  276. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +335 -0
  277. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +80 -0
  278. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
  279. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
  280. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
  281. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
  282. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
  283. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
  284. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
  285. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
  286. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
  287. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
  288. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
  289. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
  290. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +53 -0
  291. package/runtime/runtime.json +3 -0
  292. package/runtime/server.ts +1 -0
  293. package/runtime/stations.json +39 -0
  294. package/runtime/trains/.keep +0 -0
  295. package/LICENSE +0 -21
  296. package/README.md +0 -252
  297. package/dist/broker.js +0 -202
  298. package/dist/cache.js +0 -69
  299. package/dist/cli/actions.js +0 -206
  300. package/dist/cli/config.js +0 -185
  301. package/dist/cli/index.js +0 -188
  302. package/dist/cli/skill.js +0 -62
  303. package/dist/cli/tail.js +0 -164
  304. package/dist/cli/util.js +0 -72
  305. package/dist/cli/webhook.js +0 -81
  306. package/dist/codex-rc.js +0 -245
  307. package/dist/dispatcher.js +0 -128
  308. package/dist/history.js +0 -121
  309. package/dist/ipc.js +0 -72
  310. package/dist/log.js +0 -15
  311. package/dist/monitor.js +0 -194
  312. package/dist/paths.js +0 -68
  313. package/dist/registry.js +0 -48
  314. package/dist/stations/claude.js +0 -45
  315. package/dist/stations/codex.js +0 -68
  316. package/dist/stations/discord.js +0 -216
  317. package/dist/stations/index.js +0 -129
  318. package/dist/stations/telegram-md.js +0 -34
  319. package/dist/stations/telegram-upload.js +0 -113
  320. package/dist/stations/telegram.js +0 -234
  321. package/dist/stations/webhook.js +0 -103
  322. package/dist/tunnel.js +0 -64
  323. package/dist/webhooks.js +0 -41
  324. package/docs/broker.md +0 -214
  325. package/docs/monitor.md +0 -163
  326. package/docs/uri-scheme.md +0 -124
  327. package/docs/users.md +0 -226
  328. package/skills/metro/SKILL.md +0 -256
@@ -0,0 +1,237 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { stationByName } from '../stations/registry.js';
3
+ import { errMsg, log } from '@metro-labs/core/log';
4
+ import { subscribeEvents, type MetroEvent } from '@metro-labs/core/events';
5
+ import { callTargetDenied, eventInScope } from '../agents/scope.js';
6
+ import { hasAnyKey } from '../agents/keys.js';
7
+ import {
8
+ allowedAgents,
9
+ authenticate,
10
+ } from '../mcp/request-identity.js';
11
+ import { METRO_VERSION } from '@metro-labs/core/version';
12
+
13
+ export type MonitorCall = (
14
+ train: string,
15
+ action: string,
16
+ args: Record<string, unknown>,
17
+ ) => Promise<{ result: unknown }>;
18
+
19
+ const KEEPALIVE_MS = 25_000;
20
+ const CALL_BODY_MAX = 256 * 1024;
21
+
22
+ const monitorEnabled = (): boolean => hasAnyKey();
23
+
24
+ function cors(req: IncomingMessage): Record<string, string> {
25
+ return {
26
+ 'access-control-allow-origin': req.headers.origin ?? '*',
27
+ 'access-control-allow-methods': 'GET, POST, OPTIONS',
28
+ 'access-control-allow-headers': 'Authorization, Content-Type',
29
+ 'access-control-max-age': '86400',
30
+ vary: 'Origin',
31
+ };
32
+ }
33
+
34
+ function sendJson(
35
+ res: ServerResponse,
36
+ req: IncomingMessage,
37
+ status: number,
38
+ body: unknown,
39
+ ): void {
40
+ res.writeHead(status, { 'content-type': 'application/json', ...cors(req) });
41
+ res.end(JSON.stringify(body));
42
+ }
43
+
44
+ function parseCallArgs(raw: string): Record<string, unknown> {
45
+ if (!raw) return {};
46
+ const parsed = JSON.parse(raw) as { args?: unknown };
47
+ const value =
48
+ parsed && typeof parsed === 'object' && 'args' in parsed
49
+ ? parsed.args
50
+ : parsed;
51
+ if (!value || typeof value !== 'object' || Array.isArray(value))
52
+ throw new Error('call body must be a JSON object');
53
+ return value as Record<string, unknown>;
54
+ }
55
+
56
+ async function readCallArgs(
57
+ req: IncomingMessage,
58
+ ): Promise<Record<string, unknown>> {
59
+ const chunks: Buffer[] = [];
60
+ let total = 0;
61
+ for await (const chunk of req) {
62
+ const buf = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk as string);
63
+ total += buf.length;
64
+ if (total > CALL_BODY_MAX)
65
+ throw new Error(`request body exceeds ${CALL_BODY_MAX} bytes`);
66
+ chunks.push(buf);
67
+ }
68
+ return parseCallArgs(Buffer.concat(chunks).toString('utf8').trim());
69
+ }
70
+
71
+ function startTailStream(
72
+ req: IncomingMessage,
73
+ res: ServerResponse,
74
+ allowed: Set<string>,
75
+ ): void {
76
+ res.writeHead(200, {
77
+ 'content-type': 'text/event-stream',
78
+ 'cache-control': 'no-cache, no-transform',
79
+ connection: 'keep-alive',
80
+ 'x-accel-buffering': 'no',
81
+ ...cors(req),
82
+ });
83
+ const timers: { keepalive?: ReturnType<typeof setInterval> } = {};
84
+ const subs: { stop?: () => void } = {};
85
+ let done = false;
86
+ const cleanup = (): void => {
87
+ if (done) return;
88
+ done = true;
89
+ subs.stop?.();
90
+ if (timers.keepalive) clearInterval(timers.keepalive);
91
+ try {
92
+ res.end();
93
+ } catch {
94
+ log.debug('monitor: tail cleanup end failed');
95
+ }
96
+ };
97
+ const write = (chunk: string): void => {
98
+ if (done) return;
99
+ if (res.destroyed || res.writableEnded) {
100
+ cleanup();
101
+ return;
102
+ }
103
+ try {
104
+ res.write(chunk);
105
+ } catch (err) {
106
+ log.debug({ err: errMsg(err) }, 'monitor: tail write failed');
107
+ cleanup();
108
+ }
109
+ };
110
+ write(': metro monitor tail (live)\n\n');
111
+ let id = 0;
112
+ subs.stop = subscribeEvents((e: MetroEvent): void => {
113
+ if (!eventInScope(allowed, e.line)) return;
114
+ id += 1;
115
+ write(`id: ${id}\nevent: live\ndata: ${JSON.stringify(e)}\n\n`);
116
+ });
117
+ timers.keepalive = setInterval(() => {
118
+ write(': keepalive\n\n');
119
+ }, KEEPALIVE_MS);
120
+ timers.keepalive.unref?.();
121
+ req.on('close', cleanup);
122
+ req.on('error', cleanup);
123
+ }
124
+
125
+ async function handleCall(
126
+ req: IncomingMessage,
127
+ res: ServerResponse,
128
+ train: string,
129
+ action: string,
130
+ call: MonitorCall,
131
+ allowed: Set<string>,
132
+ ): Promise<void> {
133
+ let args: Record<string, unknown>;
134
+ try {
135
+ args = await readCallArgs(req);
136
+ } catch (err) {
137
+ sendJson(res, req, 400, { error: `bad JSON body: ${errMsg(err)}` });
138
+ return;
139
+ }
140
+ if (callTargetDenied(allowed, train, args)) {
141
+ sendJson(res, req, 403, {
142
+ error: 'metro: this account is outside your authorized scope',
143
+ });
144
+ return;
145
+ }
146
+ if (stationByName(train)?.hasTrain === false) {
147
+ sendJson(res, req, 400, {
148
+ error: `metro: station '${train}' runs in-core and takes no calls`,
149
+ });
150
+ return;
151
+ }
152
+ try {
153
+ const { result } = await call(train, action, args);
154
+ sendJson(res, req, 200, { result });
155
+ } catch (err) {
156
+ sendJson(res, req, 502, { error: errMsg(err) });
157
+ }
158
+ }
159
+
160
+ function routeApi(
161
+ req: IncomingMessage,
162
+ res: ServerResponse,
163
+ path: string,
164
+ call: MonitorCall,
165
+ allowed: Set<string>,
166
+ ): void {
167
+ const callMatch = /^\/api\/call\/([^/]+)\/([^/]+)$/.exec(path);
168
+ if (callMatch) {
169
+ if (req.method !== 'POST') {
170
+ sendJson(res, req, 405, { error: 'method not allowed' });
171
+ return;
172
+ }
173
+ handleCall(
174
+ req,
175
+ res,
176
+ callMatch[1] ?? '',
177
+ callMatch[2] ?? '',
178
+ call,
179
+ allowed,
180
+ ).catch((err: unknown) => {
181
+ log.warn({ err: errMsg(err) }, 'monitor: call handler error');
182
+ if (!res.headersSent) sendJson(res, req, 500, { error: errMsg(err) });
183
+ });
184
+ return;
185
+ }
186
+ if (path === '/api/tail') {
187
+ if (req.method !== 'GET') {
188
+ sendJson(res, req, 405, { error: 'method not allowed' });
189
+ return;
190
+ }
191
+ startTailStream(req, res, allowed);
192
+ return;
193
+ }
194
+ sendJson(res, req, 404, { error: 'not found' });
195
+ }
196
+
197
+ function preflight(
198
+ req: IncomingMessage,
199
+ res: ServerResponse,
200
+ path: string,
201
+ ): boolean {
202
+ if (!monitorEnabled()) {
203
+ sendJson(res, req, 404, { error: 'not found' });
204
+ return true;
205
+ }
206
+ if (req.method === 'OPTIONS') {
207
+ res.writeHead(204, cors(req)).end();
208
+ return true;
209
+ }
210
+ if (req.method === 'GET' && path === '/api/health') {
211
+ sendJson(res, req, 200, {
212
+ ok: true,
213
+ service: 'metro',
214
+ version: METRO_VERSION,
215
+ uptime_s: Math.round(process.uptime()),
216
+ });
217
+ return true;
218
+ }
219
+ return false;
220
+ }
221
+
222
+ export function handleMonitorRequest(
223
+ req: IncomingMessage,
224
+ res: ServerResponse,
225
+ call: MonitorCall,
226
+ ): boolean {
227
+ const path = (req.url ?? '').split('?', 2)[0] ?? '';
228
+ if (!path.startsWith('/api/')) return false;
229
+ if (preflight(req, res, path)) return true;
230
+ const identity = authenticate(req);
231
+ if (!identity) {
232
+ sendJson(res, req, 401, { error: 'unauthorized' });
233
+ return true;
234
+ }
235
+ routeApi(req, res, path, call, allowedAgents(identity));
236
+ return true;
237
+ }
@@ -0,0 +1,389 @@
1
+ import type { Endpoint } from '@metro-labs/core/endpoints';
2
+ import { execFile, spawn, type ChildProcess } from 'node:child_process';
3
+ import { timingSafeEqual } from 'node:crypto';
4
+ import { Resolver } from 'node:dns/promises';
5
+ import { request as httpsRequest } from 'node:https';
6
+ import { homedir } from 'node:os';
7
+ import { join } from 'node:path';
8
+ import { errMsg, log } from '@metro-labs/core/log';
9
+ import { readJson } from '@metro-labs/core/secure-fs';
10
+ import { localOwner } from '../agents/file-admin.js';
11
+
12
+ const RESTART_DELAY_MS = 2_000;
13
+ const RESTART_DELAY_MAX_MS = 30_000;
14
+ const TAKEN_RE = /listener already exists/i;
15
+ const ADOPTED_MISSES = 2;
16
+ const adoptedCheckMs = (): number => Number(process.env.METRO_TUNNEL_WATCH_MS) || 30_000;
17
+ const FUNNEL_ON_RE = /\(Funnel on\)/i;
18
+
19
+ export const tunnelWanted = (): boolean => process.env.METRO_TUNNEL?.trim() === 'tailscale';
20
+
21
+ const FUNNEL_URL_RE = /https:\/\/[a-z0-9-]+(?:\.[a-z0-9-]+)+\.ts\.net(?::\d{2,5})?(?=[\s/]|$)/i;
22
+ const SILENT_MS = 20_000;
23
+
24
+ export const funnelUrlIn = (text: string): string | null =>
25
+ FUNNEL_URL_RE.exec(text)?.[0]?.toLowerCase() ?? null;
26
+
27
+ export interface Adopted {
28
+ url: string | null;
29
+ hint: string;
30
+ alive?: () => Promise<boolean>;
31
+ }
32
+
33
+ export interface TunnelDriver {
34
+ name: string;
35
+ command: string;
36
+ args: string[];
37
+ urlIn: (text: string) => string | null;
38
+ waitsForDns: boolean;
39
+ adopt?: () => Promise<Adopted>;
40
+ }
41
+
42
+ const runText = (command: string, args: string[]): Promise<string> =>
43
+ new Promise((resolve) => {
44
+ execFile(command, args, { encoding: 'utf8', timeout: 10_000 }, (_err, stdout, stderr) => {
45
+ resolve(`${stdout}\n${stderr}`);
46
+ });
47
+ });
48
+
49
+ export type Probe = (url: string, owner: string | null) => Promise<boolean>;
50
+
51
+ const PROBE_MS = 5_000;
52
+
53
+ async function publicAddresses(host: string): Promise<string[]> {
54
+ const found = await Promise.all(
55
+ PUBLIC_RESOLVERS.map(async (server) => {
56
+ const resolver = new Resolver();
57
+ resolver.setServers([server]);
58
+ const [v4, v6] = await Promise.all([resolver.resolve4(host).catch(() => []), resolver.resolve6(host).catch(() => [])]);
59
+ return [...v4, ...v6];
60
+ }),
61
+ );
62
+ return [...new Set(found.flat())];
63
+ }
64
+
65
+ function modeThrough(ip: string, host: string): Promise<{ mode?: unknown; owner?: unknown } | null> {
66
+ return new Promise((resolve) => {
67
+ const req = httpsRequest(
68
+ { host: ip, servername: host, port: 443, path: '/api/mode', method: 'GET', headers: { host }, timeout: PROBE_MS },
69
+ (res) => {
70
+ const chunks: Buffer[] = [];
71
+ res.on('data', (c: Buffer) => chunks.push(c));
72
+ res.on('end', () => {
73
+ try {
74
+ resolve(res.statusCode === 200 ? (JSON.parse(Buffer.concat(chunks).toString('utf8')) as { mode?: unknown }) : null);
75
+ } catch {
76
+ resolve(null);
77
+ }
78
+ });
79
+ },
80
+ );
81
+ req.on('timeout', () => {
82
+ req.destroy();
83
+ });
84
+ req.on('error', () => {
85
+ resolve(null);
86
+ });
87
+ req.end();
88
+ });
89
+ }
90
+
91
+ export async function daemonAnswersAt(url: string, owner: string | null): Promise<boolean> {
92
+ const host = new URL(url).host;
93
+ const addresses = await publicAddresses(host);
94
+ if (addresses.length === 0) return false;
95
+ const body = await modeThrough(addresses[0] ?? '', host);
96
+ return body !== null && body.mode === 'local' && (owner === null || body.owner === owner);
97
+ }
98
+
99
+ export function nodeNameIn(statusJson: string): string | null {
100
+ try {
101
+ const parsed: unknown = JSON.parse(statusJson);
102
+ const self = typeof parsed === 'object' && parsed !== null ? (parsed as { Self?: { DNSName?: unknown } }).Self : undefined;
103
+ const name = typeof self?.DNSName === 'string' ? self.DNSName.replace(/\.$/, '').toLowerCase() : '';
104
+ return name === '' ? null : name;
105
+ } catch {
106
+ return null;
107
+ }
108
+ }
109
+
110
+ async function adoptFunnel(bin: string, port: number, probe: Probe, owner: () => string | null): Promise<Adopted> {
111
+ const name = nodeNameIn(await runText(bin, ['status', '--json']));
112
+ const watched = (url: string): Adopted => ({
113
+ url,
114
+ hint: `a Funnel already publishes this daemon at ${url}; using it`,
115
+ alive: () => probe(url, owner()),
116
+ });
117
+ if (name !== null) {
118
+ const url = `https://${name}`;
119
+ if (await probe(url, owner())) return watched(url);
120
+ }
121
+ const found = funnelAlreadyServing(await runText(bin, ['funnel', 'status']), port);
122
+ return found.url === null ? found : watched(found.url);
123
+ }
124
+
125
+ export function funnelAlreadyServing(status: string, port: number): Adopted {
126
+ const lines = status.split('\n');
127
+ const target = `http://127.0.0.1:${String(port)}`;
128
+ for (const [i, line] of lines.entries()) {
129
+ if (line.trimStart().startsWith('#')) continue;
130
+ const url = funnelUrlIn(line);
131
+ if (url === null) continue;
132
+ const body = lines.slice(i + 1, i + 6).join('\n');
133
+ if (!body.includes(target)) continue;
134
+ if (FUNNEL_ON_RE.test(line)) return { url, hint: `a Funnel already publishes this daemon at ${url}; using it` };
135
+ return {
136
+ url: null,
137
+ hint: `${url} is a tailnet-only serve config on this node, not a Funnel; run: tailscale serve reset`,
138
+ };
139
+ }
140
+ return { url: null, hint: 'port 443 is held by another serve config on this node; run: tailscale serve reset' };
141
+ }
142
+
143
+ function tailscaleBin(): string {
144
+ const configured = process.env.METRO_TAILSCALE_BIN?.trim() ?? '';
145
+ return configured === '' ? 'tailscale' : configured;
146
+ }
147
+
148
+ export const funnelDriver = (
149
+ port: number,
150
+ bin = tailscaleBin(),
151
+ probe: Probe = daemonAnswersAt,
152
+ owner: () => string | null = localOwner,
153
+ ): TunnelDriver => ({
154
+ name: 'tailscale funnel',
155
+ command: bin,
156
+ args: ['funnel', String(port)],
157
+ urlIn: funnelUrlIn,
158
+ waitsForDns: true,
159
+ adopt: () => adoptFunnel(bin, port, probe, owner),
160
+ });
161
+
162
+ let liveUrl: string | null = null;
163
+
164
+ export const currentTunnelUrl = (): string | null => liveUrl;
165
+
166
+ export type Resolves = (host: string) => Promise<boolean>;
167
+
168
+ const PUBLIC_RESOLVERS = ['1.1.1.1', '8.8.8.8'];
169
+ const RESOLVE_EVERY_MS = 3_000;
170
+ const RESOLVE_GIVE_UP_MS = 180_000;
171
+
172
+ async function resolvesAt(server: string, host: string): Promise<boolean> {
173
+ const resolver = new Resolver();
174
+ resolver.setServers([server]);
175
+ const answers = await Promise.all([
176
+ resolver.resolve4(host).catch(() => []),
177
+ resolver.resolve6(host).catch(() => []),
178
+ ]);
179
+ return answers.some((a) => a.length > 0);
180
+ }
181
+
182
+ export async function resolvesPublicly(host: string): Promise<boolean> {
183
+ const found = await Promise.all(PUBLIC_RESOLVERS.map((server) => resolvesAt(server, host)));
184
+ return found.some(Boolean);
185
+ }
186
+
187
+ async function untilResolvable(host: string, resolves: Resolves): Promise<boolean> {
188
+ const deadline = Date.now() + RESOLVE_GIVE_UP_MS;
189
+ while (Date.now() < deadline) {
190
+ if (await resolves(host)) return true;
191
+ await new Promise((r) => setTimeout(r, RESOLVE_EVERY_MS));
192
+ }
193
+ return false;
194
+ }
195
+
196
+ interface AccountRecord {
197
+ id?: unknown;
198
+ webhookId?: unknown;
199
+ label?: unknown;
200
+ secret?: unknown;
201
+ createdAt?: unknown;
202
+ }
203
+
204
+ export const webhookPort = (): number =>
205
+ Number(process.env.METRO_WEBHOOK_PORT) || 8420;
206
+
207
+ export type { Endpoint };
208
+
209
+ const accountsFile = (): string =>
210
+ process.env.WEBHOOK_ACCOUNTS_FILE ??
211
+ join(homedir(), '.metro', 'webhook-accounts.json');
212
+
213
+ const str = (v: unknown): string | undefined =>
214
+ typeof v === 'string' && v !== '' ? v : undefined;
215
+
216
+ function toEndpoint(raw: AccountRecord): Endpoint | null {
217
+ const id = str(raw.id);
218
+ if (id === undefined) return null;
219
+ return {
220
+ id,
221
+ webhookId: str(raw.webhookId),
222
+ label: str(raw.label) ?? id,
223
+ secret: str(raw.secret),
224
+ createdAt: str(raw.createdAt) ?? '',
225
+ };
226
+ }
227
+
228
+ export function listEndpoints(): Endpoint[] {
229
+ const raw = readJson<AccountRecord[]>(accountsFile(), [], {
230
+ warn: 'webhook-accounts.json: malformed, ignoring',
231
+ });
232
+ if (!Array.isArray(raw)) return [];
233
+ return raw.map(toEndpoint).filter((e): e is Endpoint => e !== null);
234
+ }
235
+
236
+ export const findEndpointByWebhookId = (
237
+ webhookId: string,
238
+ ): Endpoint | undefined =>
239
+ listEndpoints().find((e) => e.webhookId === webhookId);
240
+
241
+ export function tokenMatches(secret: string, given: string): boolean {
242
+ const want = Buffer.from(secret);
243
+ const got = Buffer.from(given);
244
+ return want.length === got.length && timingSafeEqual(want, got);
245
+ }
246
+
247
+ export class Tunnel {
248
+ private child: ChildProcess | null = null;
249
+ private closed = false;
250
+ private output: string[] = [];
251
+ private restartDelay = RESTART_DELAY_MS;
252
+ private watch: ReturnType<typeof setInterval> | null = null;
253
+
254
+ constructor(
255
+ private driver: TunnelDriver,
256
+ private onUrl: (url: string) => void = () => undefined,
257
+ private resolves: Resolves = resolvesPublicly,
258
+ ) {}
259
+
260
+ private noticeUrl(text: string): void {
261
+ const url = this.driver.urlIn(text);
262
+ if (url === null || url === liveUrl) return;
263
+ liveUrl = url;
264
+ this.restartDelay = RESTART_DELAY_MS;
265
+ if (!this.driver.waitsForDns) {
266
+ log.info({ url }, `${this.driver.name} up`);
267
+ this.onUrl(url);
268
+ return;
269
+ }
270
+ log.info({ url }, `${this.driver.name} up; waiting for its name to resolve`);
271
+ this.announceWhenResolvable(url).catch((err: unknown) => {
272
+ log.warn({ err: errMsg(err) }, `${this.driver.name}: announce failed`);
273
+ });
274
+ }
275
+
276
+ private async announceWhenResolvable(url: string): Promise<void> {
277
+ const resolved = await untilResolvable(new URL(url).host, this.resolves);
278
+ if (liveUrl !== url) return;
279
+ if (!resolved) log.warn({ url }, `${this.driver.name} name still not resolving; announcing anyway`);
280
+ this.onUrl(url);
281
+ }
282
+
283
+ private read(d: Buffer | string): void {
284
+ const text = (typeof d === 'string' ? d : d.toString('utf8')).trim();
285
+ if (text === '') return;
286
+ this.output.push(text);
287
+ log.debug({ output: text }, this.driver.name);
288
+ this.noticeUrl(text);
289
+ }
290
+
291
+ private silentCheck(): void {
292
+ if (this.closed || liveUrl !== null || this.child === null) return;
293
+ log.warn(
294
+ { output: this.output.join('\n') },
295
+ `${this.driver.name}: no public address after ${String(SILENT_MS / 1000)}s; its own output follows`,
296
+ );
297
+ }
298
+
299
+ start(): void {
300
+ if (this.closed) return;
301
+ log.info({ command: this.driver.command, args: this.driver.args }, `${this.driver.name} starting`);
302
+ this.output = [];
303
+ const child = spawn(this.driver.command, this.driver.args, { stdio: ['ignore', 'pipe', 'pipe'] });
304
+ this.child = child;
305
+ child.stdout?.on('data', (d: Buffer | string) => {
306
+ this.read(d);
307
+ });
308
+ child.stderr?.on('data', (d: Buffer | string) => {
309
+ this.read(d);
310
+ });
311
+ setTimeout(() => {
312
+ this.silentCheck();
313
+ }, SILENT_MS).unref();
314
+ child.on('exit', (code) => {
315
+ this.child = null;
316
+ liveUrl = null;
317
+ if (this.closed) return;
318
+ this.afterExit(code).catch((err: unknown) => {
319
+ log.warn({ err: errMsg(err) }, `${this.driver.name}: exit handling failed`);
320
+ });
321
+ });
322
+ child.on('error', (err) => {
323
+ if ((err as NodeJS.ErrnoException).code === 'ENOENT') {
324
+ this.closed = true;
325
+ log.error(
326
+ { command: this.driver.command },
327
+ `${this.driver.name}: the binary is not installed, so there is no public address; install it and restart`,
328
+ );
329
+ return;
330
+ }
331
+ log.warn({ err: errMsg(err) }, `${this.driver.name} spawn error`);
332
+ });
333
+ }
334
+
335
+ private async afterExit(code: number | null): Promise<void> {
336
+ const output = this.output.join('\n');
337
+ if (this.driver.adopt !== undefined && TAKEN_RE.test(output)) {
338
+ const found = await this.driver.adopt();
339
+ if (this.closed) return;
340
+ if (found.url !== null) {
341
+ log.info({ url: found.url }, `${this.driver.name}: ${found.hint}`);
342
+ this.noticeUrl(found.url);
343
+ if (found.alive !== undefined) this.watchAdopted(found.url, found.alive);
344
+ return;
345
+ }
346
+ log.error({ output }, `${this.driver.name}: ${found.hint}`);
347
+ } else log.warn({ code, output: this.output.slice(-5).join('\n') }, `${this.driver.name} exited; restarting`);
348
+ setTimeout(() => {
349
+ this.start();
350
+ }, this.restartDelay);
351
+ this.restartDelay = Math.min(this.restartDelay * 2, RESTART_DELAY_MAX_MS);
352
+ }
353
+
354
+ private watchAdopted(url: string, alive: () => Promise<boolean>): void {
355
+ let misses = 0;
356
+ const tick = async (): Promise<void> => {
357
+ if (this.closed || liveUrl !== url) {
358
+ this.unwatch();
359
+ return;
360
+ }
361
+ misses = (await alive()) ? 0 : misses + 1;
362
+ if (misses < ADOPTED_MISSES) return;
363
+ this.unwatch();
364
+ log.warn({ url }, `${this.driver.name}: the Funnel this daemon adopted no longer answers; publishing our own`);
365
+ liveUrl = null;
366
+ this.restartDelay = RESTART_DELAY_MS;
367
+ this.start();
368
+ };
369
+ this.unwatch();
370
+ this.watch = setInterval(() => {
371
+ tick().catch((err: unknown) => {
372
+ log.warn({ err: errMsg(err) }, `${this.driver.name}: adopted-funnel check failed`);
373
+ });
374
+ }, adoptedCheckMs());
375
+ this.watch.unref();
376
+ }
377
+
378
+ private unwatch(): void {
379
+ if (this.watch !== null) clearInterval(this.watch);
380
+ this.watch = null;
381
+ }
382
+
383
+ stop(): void {
384
+ this.closed = true;
385
+ this.unwatch();
386
+ this.child?.kill('SIGINT');
387
+ this.child = null;
388
+ }
389
+ }
@@ -0,0 +1,24 @@
1
+ import type { IncomingMessage } from 'node:http';
2
+
3
+ export const WEBHOOK_BODY_MAX = 25 * 1024 * 1024;
4
+
5
+ export class BodyTooLargeError extends Error {
6
+ constructor(readonly limit: number) {
7
+ super(`request body exceeds ${limit} bytes`);
8
+ }
9
+ }
10
+
11
+ export async function readBody(
12
+ req: IncomingMessage,
13
+ maxBytes: number,
14
+ ): Promise<Buffer> {
15
+ const chunks: Buffer[] = [];
16
+ let total = 0;
17
+ for await (const c of req) {
18
+ const buf = c as Buffer;
19
+ total += buf.length;
20
+ if (total > maxBytes) throw new BodyTooLargeError(maxBytes);
21
+ chunks.push(buf);
22
+ }
23
+ return Buffer.concat(chunks);
24
+ }