@stage-labs/metro 0.1.0-beta.7 → 0.1.0-beta.71

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 (279) hide show
  1. package/dist/api.js +14 -0
  2. package/dist/bedrock-eventstream.js +120 -0
  3. package/dist/bedrock-proxy.js +260 -0
  4. package/dist/bedrock-upstream.js +72 -0
  5. package/dist/bedrock.js +37 -0
  6. package/dist/claude-settings.js +40 -0
  7. package/dist/claude.js +107 -0
  8. package/dist/cli.js +101 -0
  9. package/dist/control.js +74 -0
  10. package/dist/hold.js +211 -0
  11. package/dist/local.js +93 -0
  12. package/dist/node-name.js +51 -0
  13. package/dist/plugin.js +80 -0
  14. package/dist/provider-flags.js +9 -0
  15. package/dist/runtime-install.js +84 -0
  16. package/dist/runtime.js +70 -0
  17. package/dist/serve.js +147 -0
  18. package/dist/service.js +224 -0
  19. package/dist/tail.js +119 -0
  20. package/dist/update.js +45 -0
  21. package/dist/version.js +97 -0
  22. package/package.json +14 -25
  23. package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
  24. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -0
  25. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
  26. package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
  27. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
  28. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +220 -0
  29. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
  30. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
  31. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
  32. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
  33. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
  34. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
  35. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
  36. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
  37. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
  38. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
  39. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
  40. package/runtime/node_modules/@metro-labs/mcp/package.json +64 -0
  41. package/runtime/node_modules/@metro-labs/mcp/src/channels/addressed.ts +25 -0
  42. package/runtime/node_modules/@metro-labs/mcp/src/channels/dedupe.ts +24 -0
  43. package/runtime/node_modules/@metro-labs/mcp/src/channels/inbound.ts +377 -0
  44. package/runtime/node_modules/@metro-labs/mcp/src/channels/media-note.ts +85 -0
  45. package/runtime/node_modules/@metro-labs/mcp/src/channels/pending.ts +75 -0
  46. package/runtime/node_modules/@metro-labs/mcp/src/channels/relay.ts +129 -0
  47. package/runtime/node_modules/@metro-labs/mcp/src/channels/webhook-note.ts +51 -0
  48. package/runtime/node_modules/@metro-labs/mcp/src/daemon/account-api.ts +311 -0
  49. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +302 -0
  50. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-error.ts +8 -0
  51. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-http.ts +95 -0
  52. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-grant.ts +99 -0
  53. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-owner.ts +32 -0
  54. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-serve.ts +128 -0
  55. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-session.ts +280 -0
  56. package/runtime/node_modules/@metro-labs/mcp/src/daemon/boot.ts +204 -0
  57. package/runtime/node_modules/@metro-labs/mcp/src/daemon/bundle-api.ts +111 -0
  58. package/runtime/node_modules/@metro-labs/mcp/src/daemon/claude-api.ts +97 -0
  59. package/runtime/node_modules/@metro-labs/mcp/src/daemon/claude-files.ts +329 -0
  60. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connect-hint.ts +39 -0
  61. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-api.ts +307 -0
  62. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-json.ts +21 -0
  63. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth-routes.ts +157 -0
  64. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth.ts +127 -0
  65. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-tools.ts +98 -0
  66. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-url.ts +70 -0
  67. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-verify.ts +330 -0
  68. package/runtime/node_modules/@metro-labs/mcp/src/daemon/control-api.ts +64 -0
  69. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cors.ts +29 -0
  70. package/runtime/node_modules/@metro-labs/mcp/src/daemon/crash-guard.ts +49 -0
  71. package/runtime/node_modules/@metro-labs/mcp/src/daemon/events.ts +120 -0
  72. package/runtime/node_modules/@metro-labs/mcp/src/daemon/http.ts +390 -0
  73. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity-key.ts +33 -0
  74. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity-registry.ts +13 -0
  75. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity-routes.ts +54 -0
  76. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +7 -0
  77. package/runtime/node_modules/@metro-labs/mcp/src/daemon/is-record.ts +2 -0
  78. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-cli-api.ts +63 -0
  79. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-mode.ts +187 -0
  80. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-owner.ts +14 -0
  81. package/runtime/node_modules/@metro-labs/mcp/src/daemon/log.ts +42 -0
  82. package/runtime/node_modules/@metro-labs/mcp/src/daemon/machine-api.ts +61 -0
  83. package/runtime/node_modules/@metro-labs/mcp/src/daemon/mode-api.ts +32 -0
  84. package/runtime/node_modules/@metro-labs/mcp/src/daemon/model-api.ts +169 -0
  85. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-client.ts +159 -0
  86. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-discovery.ts +117 -0
  87. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-pending.ts +65 -0
  88. package/runtime/node_modules/@metro-labs/mcp/src/daemon/paths.ts +109 -0
  89. package/runtime/node_modules/@metro-labs/mcp/src/daemon/protocol.ts +226 -0
  90. package/runtime/node_modules/@metro-labs/mcp/src/daemon/relay.ts +295 -0
  91. package/runtime/node_modules/@metro-labs/mcp/src/daemon/return-to.ts +21 -0
  92. package/runtime/node_modules/@metro-labs/mcp/src/daemon/runtime-deps.ts +82 -0
  93. package/runtime/node_modules/@metro-labs/mcp/src/daemon/secure-fs.ts +57 -0
  94. package/runtime/node_modules/@metro-labs/mcp/src/daemon/server-types.ts +21 -0
  95. package/runtime/node_modules/@metro-labs/mcp/src/daemon/servers-api.ts +80 -0
  96. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-api.ts +29 -0
  97. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-apis.ts +66 -0
  98. package/runtime/node_modules/@metro-labs/mcp/src/daemon/signed-identity.ts +36 -0
  99. package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor-io.ts +97 -0
  100. package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor.ts +320 -0
  101. package/runtime/node_modules/@metro-labs/mcp/src/daemon/terminal-api.ts +71 -0
  102. package/runtime/node_modules/@metro-labs/mcp/src/daemon/terminal-tickets.ts +40 -0
  103. package/runtime/node_modules/@metro-labs/mcp/src/daemon/terminal-ws.ts +114 -0
  104. package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-call.ts +22 -0
  105. package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-error.ts +53 -0
  106. package/runtime/node_modules/@metro-labs/mcp/src/daemon/tunnel.ts +352 -0
  107. package/runtime/node_modules/@metro-labs/mcp/src/daemon/update-api.ts +116 -0
  108. package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-api.ts +294 -0
  109. package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-store.ts +181 -0
  110. package/runtime/node_modules/@metro-labs/mcp/src/daemon/vault-api.ts +78 -0
  111. package/runtime/node_modules/@metro-labs/mcp/src/daemon/vault-types.ts +12 -0
  112. package/runtime/node_modules/@metro-labs/mcp/src/daemon/version.ts +2 -0
  113. package/runtime/node_modules/@metro-labs/mcp/src/db/account-attach.ts +19 -0
  114. package/runtime/node_modules/@metro-labs/mcp/src/db/address.ts +6 -0
  115. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-admin.ts +43 -0
  116. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-map.ts +84 -0
  117. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-scope.ts +64 -0
  118. package/runtime/node_modules/@metro-labs/mcp/src/db/client.ts +32 -0
  119. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-config.ts +144 -0
  120. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-relay.ts +39 -0
  121. package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +69 -0
  122. package/runtime/node_modules/@metro-labs/mcp/src/db/file-admin.ts +329 -0
  123. package/runtime/node_modules/@metro-labs/mcp/src/db/file-source.ts +155 -0
  124. package/runtime/node_modules/@metro-labs/mcp/src/db/ids.ts +21 -0
  125. package/runtime/node_modules/@metro-labs/mcp/src/db/key-map.ts +45 -0
  126. package/runtime/node_modules/@metro-labs/mcp/src/db/local-connectors.ts +277 -0
  127. package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +253 -0
  128. package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +33 -0
  129. package/runtime/node_modules/@metro-labs/mcp/src/db/servers.ts +83 -0
  130. package/runtime/node_modules/@metro-labs/mcp/src/db/stations.ts +12 -0
  131. package/runtime/node_modules/@metro-labs/mcp/src/db/vault.ts +109 -0
  132. package/runtime/node_modules/@metro-labs/mcp/src/gateway/bedrock.ts +252 -0
  133. package/runtime/node_modules/@metro-labs/mcp/src/gateway/codex-auth.ts +231 -0
  134. package/runtime/node_modules/@metro-labs/mcp/src/gateway/codex-device.ts +91 -0
  135. package/runtime/node_modules/@metro-labs/mcp/src/gateway/codex-stream.ts +288 -0
  136. package/runtime/node_modules/@metro-labs/mcp/src/gateway/codex-translate.ts +203 -0
  137. package/runtime/node_modules/@metro-labs/mcp/src/gateway/codex.ts +211 -0
  138. package/runtime/node_modules/@metro-labs/mcp/src/gateway/eventstream.ts +123 -0
  139. package/runtime/node_modules/@metro-labs/mcp/src/gateway/forward.ts +169 -0
  140. package/runtime/node_modules/@metro-labs/mcp/src/gateway/gateway.ts +193 -0
  141. package/runtime/node_modules/@metro-labs/mcp/src/gateway/model-config.ts +180 -0
  142. package/runtime/node_modules/@metro-labs/mcp/src/mcp/accounts.ts +141 -0
  143. package/runtime/node_modules/@metro-labs/mcp/src/mcp/call-tools.ts +216 -0
  144. package/runtime/node_modules/@metro-labs/mcp/src/mcp/channel-owner.ts +29 -0
  145. package/runtime/node_modules/@metro-labs/mcp/src/mcp/ctx.ts +62 -0
  146. package/runtime/node_modules/@metro-labs/mcp/src/mcp/event-store.ts +91 -0
  147. package/runtime/node_modules/@metro-labs/mcp/src/mcp/frame-scope.ts +21 -0
  148. package/runtime/node_modules/@metro-labs/mcp/src/mcp/group-tools.ts +129 -0
  149. package/runtime/node_modules/@metro-labs/mcp/src/mcp/index.ts +191 -0
  150. package/runtime/node_modules/@metro-labs/mcp/src/mcp/member-tools.ts +72 -0
  151. package/runtime/node_modules/@metro-labs/mcp/src/mcp/permission-relay.ts +66 -0
  152. package/runtime/node_modules/@metro-labs/mcp/src/mcp/raw-get-stream.ts +207 -0
  153. package/runtime/node_modules/@metro-labs/mcp/src/mcp/request-identity.ts +47 -0
  154. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-registry.ts +172 -0
  155. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-route.ts +43 -0
  156. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session.ts +215 -0
  157. package/runtime/node_modules/@metro-labs/mcp/src/mcp/str.ts +5 -0
  158. package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-dispatch.ts +215 -0
  159. package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-schemas.ts +400 -0
  160. package/runtime/node_modules/@metro-labs/mcp/src/mcp/upload-tool.ts +66 -0
  161. package/runtime/node_modules/@metro-labs/mcp/src/monitor/api.ts +237 -0
  162. package/runtime/node_modules/@metro-labs/mcp/src/server.ts +2 -0
  163. package/runtime/node_modules/@metro-labs/mcp/src/stations/account-store.ts +60 -0
  164. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-inline.ts +100 -0
  165. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-interactive.ts +161 -0
  166. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-resolve.ts +200 -0
  167. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-xmtp.ts +111 -0
  168. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach.ts +184 -0
  169. package/runtime/node_modules/@metro-labs/mcp/src/stations/attachments.ts +196 -0
  170. package/runtime/node_modules/@metro-labs/mcp/src/stations/lines.ts +82 -0
  171. package/runtime/node_modules/@metro-labs/mcp/src/stations/messaging-normalize.ts +88 -0
  172. package/runtime/node_modules/@metro-labs/mcp/src/stations/registry.ts +35 -0
  173. package/runtime/node_modules/@metro-labs/mcp/src/stations/station-runtime.ts +64 -0
  174. package/runtime/node_modules/@metro-labs/mcp/src/stations/types.ts +107 -0
  175. package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
  176. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
  177. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +254 -0
  178. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
  179. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
  180. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +156 -0
  181. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
  182. package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
  183. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
  184. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
  185. package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
  186. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
  187. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
  188. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
  189. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
  190. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
  191. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
  192. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
  193. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
  194. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
  195. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
  196. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
  197. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +201 -0
  198. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +109 -0
  199. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
  200. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
  201. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
  202. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +58 -0
  203. package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
  204. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
  205. package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
  206. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
  207. package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
  208. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
  209. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
  210. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +178 -0
  211. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
  212. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
  213. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +327 -0
  214. package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
  215. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +138 -0
  216. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
  217. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
  218. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
  219. package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
  220. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
  221. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
  222. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
  223. package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
  224. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
  225. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +17 -0
  226. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
  227. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
  228. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
  229. package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
  230. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +112 -0
  231. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
  232. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
  233. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
  234. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
  235. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
  236. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
  237. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
  238. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
  239. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
  240. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +335 -0
  241. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +80 -0
  242. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
  243. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
  244. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
  245. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
  246. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
  247. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
  248. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
  249. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
  250. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
  251. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
  252. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
  253. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
  254. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +53 -0
  255. package/runtime/runtime.json +3 -0
  256. package/runtime/stations.json +36 -0
  257. package/runtime/trains/.keep +0 -0
  258. package/LICENSE +0 -21
  259. package/README.md +0 -193
  260. package/dist/cache.js +0 -75
  261. package/dist/cli/actions.js +0 -139
  262. package/dist/cli/config.js +0 -185
  263. package/dist/cli/index.js +0 -159
  264. package/dist/cli/skill.js +0 -62
  265. package/dist/cli/util.js +0 -72
  266. package/dist/codex-rc.js +0 -236
  267. package/dist/dispatcher.js +0 -95
  268. package/dist/history.js +0 -84
  269. package/dist/ipc.js +0 -72
  270. package/dist/log.js +0 -15
  271. package/dist/paths.js +0 -68
  272. package/dist/stations/discord.js +0 -200
  273. package/dist/stations/index.js +0 -73
  274. package/dist/stations/telegram-md.js +0 -34
  275. package/dist/stations/telegram-upload.js +0 -113
  276. package/dist/stations/telegram.js +0 -213
  277. package/docs/agents.md +0 -185
  278. package/docs/uri-scheme.md +0 -71
  279. package/skills/metro/SKILL.md +0 -241
@@ -0,0 +1,302 @@
1
+ import { webhookPort } from './tunnel.js';
2
+ import type { IncomingMessage, ServerResponse } from 'node:http';
3
+ import { parseId } from '../db/ids.js';
4
+ import { errMsg, log } from './log.js';
5
+ import {
6
+ apiFailure,
7
+ apiSession,
8
+ projectParam,
9
+ cors,
10
+ readJsonBody,
11
+ sendJson,
12
+ type ApiSession,
13
+ } from './api-http.js';
14
+ import {
15
+ accountRoute,
16
+ accountRouteAllows,
17
+ ATTACHABLE,
18
+ handleAccountRoute,
19
+ type AccountApiDeps,
20
+ type AccountRoute,
21
+ } from './account-api.js';
22
+ import {
23
+ type AgentSummary,
24
+ type CreatedAgent,
25
+ type DeletedAgent,
26
+ type ResetAgentKey,
27
+ } from '../db/agent-admin.js';
28
+
29
+ const PREFIX = '/api/agents';
30
+ const SERVER_NAME = 'metro';
31
+
32
+ export interface AgentApiDeps extends AccountApiDeps {
33
+ listAgents: (subject: string, project: string) => Promise<AgentSummary[]>;
34
+ createAgent: (
35
+ subject: string,
36
+ project: string,
37
+ name: string,
38
+ ) => Promise<CreatedAgent>;
39
+ deleteAgent: (
40
+ subject: string,
41
+ id: string,
42
+ ) => Promise<DeletedAgent>;
43
+ resetKey: (
44
+ subject: string,
45
+ id: string,
46
+ ) => Promise<ResetAgentKey>;
47
+ gatherAccounts: (allowed: Set<string>) => Promise<{
48
+ accounts: Record<string, unknown[]>;
49
+ unavailable: string[];
50
+ }>;
51
+ capabilities: () => Record<string, string[]>;
52
+ attachable?: string[];
53
+ liveness: () => Map<string, { connected: boolean; lastSeenAt: number }>;
54
+ connectorIds: (agentIds: string[]) => Promise<Map<string, string[]>>;
55
+ }
56
+
57
+ type Routable =
58
+ | { kind: 'collection' }
59
+ | { kind: 'agent'; id: string }
60
+ | { kind: 'key'; id: string }
61
+ | { kind: 'accounts'; id: string; route: AccountRoute };
62
+
63
+ type Target = Routable | { kind: 'unknown' } | null;
64
+
65
+ function subTarget(id: string, rest: string[]): Target {
66
+ if (rest.length === 0) return { kind: 'agent', id };
67
+ if (rest.length === 1 && rest[0] === 'key') return { kind: 'key', id };
68
+ if (rest[0] !== 'accounts') return { kind: 'unknown' };
69
+ const route = accountRoute(rest.slice(1));
70
+ return route === null ? { kind: 'unknown' } : { kind: 'accounts', id, route };
71
+ }
72
+
73
+ export function target(path: string): Target {
74
+ if (path === PREFIX || path === `${PREFIX}/`) return { kind: 'collection' };
75
+ if (!path.startsWith(`${PREFIX}/`)) return null;
76
+ const segments = path.slice(PREFIX.length + 1).split('/').filter(Boolean);
77
+ const head = segments[0];
78
+ if (head === undefined) return { kind: 'collection' };
79
+ const id = parseId(head);
80
+ return id === null ? { kind: 'unknown' } : subTarget(id, segments.slice(1));
81
+ }
82
+
83
+ const localMcpEndpoint = (): string => `http://127.0.0.1:${String(webhookPort())}/mcp`;
84
+
85
+ export function mcpAddCommand(key: string): string {
86
+ const url = `${localMcpEndpoint()}?token=${key}`;
87
+ return `claude mcp add --transport http ${SERVER_NAME} "${url}"`;
88
+ }
89
+
90
+ interface KeyPayload {
91
+ key: string | null;
92
+ endpoint: string | null;
93
+ command: string | null;
94
+ }
95
+
96
+ function credentials(key: string): KeyPayload {
97
+ return {
98
+ key,
99
+ endpoint: `${localMcpEndpoint()}?token=${key}`,
100
+ command: mcpAddCommand(key),
101
+ };
102
+ }
103
+
104
+ function keyPayload(agent: AgentSummary): KeyPayload {
105
+ const value = agent.owned ? agent.key : null;
106
+ if (value === null) return { key: null, endpoint: null, command: null };
107
+ return credentials(value);
108
+ }
109
+
110
+ function livenessPayload(
111
+ agent: AgentSummary,
112
+ live: Map<string, { connected: boolean; lastSeenAt: number }>,
113
+ ): Record<string, unknown> {
114
+ const found = agent.owned ? live.get(agent.id) : undefined;
115
+ if (found === undefined) return { connected: false, last_seen: null };
116
+ return {
117
+ connected: found.connected,
118
+ last_seen: new Date(found.lastSeenAt).toISOString(),
119
+ };
120
+ }
121
+
122
+ function agentPayload(
123
+ agent: AgentSummary,
124
+ live: Map<string, { connected: boolean; lastSeenAt: number }>,
125
+ connectors: Map<string, string[]>,
126
+ ): Record<string, unknown> {
127
+ return {
128
+ id: agent.id,
129
+ name: agent.name,
130
+ owned: agent.owned,
131
+ connector_ids: connectors.get(agent.id) ?? [],
132
+ ...livenessPayload(agent, live),
133
+ ...keyPayload(agent),
134
+ };
135
+ }
136
+
137
+ function wantsAccounts(req: IncomingMessage): boolean {
138
+ const query = (req.url ?? '').split('?')[1] ?? '';
139
+ return new URLSearchParams(query).get('accounts') === '1';
140
+ }
141
+
142
+ async function handleList(
143
+ req: IncomingMessage,
144
+ res: ServerResponse,
145
+ deps: AgentApiDeps,
146
+ session: ApiSession,
147
+ ): Promise<void> {
148
+ const project = projectParam(req);
149
+ if (project === null) {
150
+ sendJson(req, res, 400, { error: 'a project is required' });
151
+ return;
152
+ }
153
+ const list = await deps.listAgents(session.subject, project);
154
+ const live = deps.liveness();
155
+ const connectors = await deps.connectorIds(list.map((a) => a.id));
156
+ const base = {
157
+ agents: list.map((a) => agentPayload(a, live, connectors)),
158
+ capabilities: deps.capabilities(),
159
+ attachable: deps.attachable ?? ATTACHABLE,
160
+ };
161
+ if (!wantsAccounts(req)) {
162
+ sendJson(req, res, 200, base);
163
+ return;
164
+ }
165
+ const { accounts, unavailable } = await deps.gatherAccounts(
166
+ new Set(list.map((a) => a.id)),
167
+ );
168
+ sendJson(req, res, 200, { ...base, accounts, unavailable });
169
+ }
170
+
171
+ async function handleCreate(
172
+ req: IncomingMessage,
173
+ res: ServerResponse,
174
+ deps: AgentApiDeps,
175
+ session: ApiSession,
176
+ ): Promise<void> {
177
+ const body = await readJsonBody(req);
178
+ const name = (body as { name?: unknown }).name;
179
+ const project = projectParam(req);
180
+ if (project === null) {
181
+ sendJson(req, res, 400, { error: 'a project is required' });
182
+ return;
183
+ }
184
+ const created = await deps.createAgent(session.subject, project, name as string);
185
+ log.info(
186
+ { agent: created.name, id: created.id, owner: session.subject },
187
+ 'agent-api: created agent',
188
+ );
189
+ sendJson(req, res, 201, {
190
+ id: created.id,
191
+ name: created.name,
192
+ ...credentials(created.key),
193
+ });
194
+ }
195
+
196
+ async function handleResetKey(
197
+ req: IncomingMessage,
198
+ res: ServerResponse,
199
+ deps: AgentApiDeps,
200
+ session: ApiSession,
201
+ id: string,
202
+ ): Promise<void> {
203
+ const reset = await deps.resetKey(session.subject, id);
204
+ log.info(
205
+ { agent: reset.name, id: reset.id, owner: session.subject },
206
+ 'agent-api: reset agent key',
207
+ );
208
+ sendJson(req, res, 200, {
209
+ id: reset.id,
210
+ name: reset.name,
211
+ reset: true,
212
+ ...credentials(reset.key),
213
+ });
214
+ }
215
+
216
+ async function handleDelete(
217
+ req: IncomingMessage,
218
+ res: ServerResponse,
219
+ deps: AgentApiDeps,
220
+ session: ApiSession,
221
+ id: string,
222
+ ): Promise<void> {
223
+ const gone = await deps.deleteAgent(session.subject, id);
224
+ log.info(
225
+ { agent: gone.name, id: gone.id, owner: session.subject },
226
+ 'agent-api: deleted agent',
227
+ );
228
+ sendJson(req, res, 200, { id: gone.id, name: gone.name, deleted: true });
229
+ }
230
+
231
+ type AgentTarget = Exclude<Routable, { kind: 'accounts' }>;
232
+
233
+ async function routeAgent(
234
+ req: IncomingMessage,
235
+ res: ServerResponse,
236
+ deps: AgentApiDeps,
237
+ session: ApiSession,
238
+ tgt: AgentTarget,
239
+ ): Promise<void> {
240
+ try {
241
+ if (tgt.kind === 'key') await handleResetKey(req, res, deps, session, tgt.id);
242
+ else if (tgt.kind === 'agent')
243
+ await handleDelete(req, res, deps, session, tgt.id);
244
+ else if (req.method === 'GET') await handleList(req, res, deps, session);
245
+ else await handleCreate(req, res, deps, session);
246
+ } catch (err) {
247
+ apiFailure(req, res, err);
248
+ }
249
+ }
250
+
251
+ const ALLOWED: Record<AgentTarget['kind'], string[]> = {
252
+ collection: ['GET', 'POST'],
253
+ agent: ['DELETE'],
254
+ key: ['POST'],
255
+ };
256
+
257
+ function methodAllowed(tgt: Routable, method: string | undefined): boolean {
258
+ if (tgt.kind === 'accounts') return accountRouteAllows(tgt.route, method);
259
+ return ALLOWED[tgt.kind].includes(method ?? '');
260
+ }
261
+
262
+ async function dispatch(
263
+ req: IncomingMessage,
264
+ res: ServerResponse,
265
+ deps: AgentApiDeps,
266
+ tgt: Routable,
267
+ ): Promise<void> {
268
+ const session = await apiSession(req);
269
+ if (!session) {
270
+ sendJson(req, res, 401, { error: 'unauthorized' });
271
+ return;
272
+ }
273
+ if (tgt.kind === 'accounts')
274
+ await handleAccountRoute(req, res, deps, session, tgt.id, tgt.route);
275
+ else await routeAgent(req, res, deps, session, tgt);
276
+ }
277
+
278
+ export function handleAgentApiRequest(
279
+ req: IncomingMessage,
280
+ res: ServerResponse,
281
+ deps: AgentApiDeps,
282
+ ): boolean {
283
+ const tgt = target((req.url ?? '').split('?')[0] ?? '');
284
+ if (tgt === null) return false;
285
+ if (req.method === 'OPTIONS') {
286
+ res.writeHead(204, cors(req)).end();
287
+ return true;
288
+ }
289
+ if (tgt.kind === 'unknown') {
290
+ sendJson(req, res, 404, { error: 'no such agent' });
291
+ return true;
292
+ }
293
+ if (!methodAllowed(tgt, req.method)) {
294
+ sendJson(req, res, 405, { error: 'method not allowed' });
295
+ return true;
296
+ }
297
+ dispatch(req, res, deps, tgt).catch((err: unknown) => {
298
+ log.warn({ err: errMsg(err) }, 'agent-api: unhandled error');
299
+ if (!res.headersSent) sendJson(req, res, 500, { error: 'agent api failed' });
300
+ });
301
+ return true;
302
+ }
@@ -0,0 +1,8 @@
1
+ export class ApiError extends Error {
2
+ constructor(
3
+ message: string,
4
+ readonly status: number,
5
+ ) {
6
+ super(message);
7
+ }
8
+ }
@@ -0,0 +1,95 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { ApiError } from './api-error.js';
3
+ import { errMsg, log } from './log.js';
4
+ import { signedIdentity } from './signed-identity.js';
5
+ import { identitySubject } from './identity-registry.js';
6
+
7
+ const BODY_MAX = 4 * 1024;
8
+
9
+ export interface ApiSession {
10
+ subject: string;
11
+ }
12
+
13
+ export function cors(req: IncomingMessage): Record<string, string> {
14
+ return {
15
+ 'access-control-allow-origin': req.headers.origin ?? '*',
16
+ 'access-control-allow-methods': 'GET, POST, PUT, DELETE, OPTIONS',
17
+ 'access-control-allow-headers': 'Authorization, Content-Type',
18
+ 'access-control-allow-private-network': 'true',
19
+ 'access-control-max-age': '86400',
20
+ vary: 'Origin',
21
+ };
22
+ }
23
+
24
+ export function sendJson(
25
+ req: IncomingMessage,
26
+ res: ServerResponse,
27
+ status: number,
28
+ body: unknown,
29
+ ): void {
30
+ res.writeHead(status, {
31
+ 'content-type': 'application/json',
32
+ 'cache-control': 'no-store',
33
+ ...cors(req),
34
+ });
35
+ res.end(JSON.stringify(body));
36
+ }
37
+
38
+ export async function apiSession(req: IncomingMessage): Promise<ApiSession | null> {
39
+ const address = await signedIdentity(req);
40
+ const subject = address === null ? undefined : identitySubject(address);
41
+ return subject === undefined ? null : { subject };
42
+ }
43
+
44
+ export interface AgentIdentity {
45
+ subject: string;
46
+ agentId: string;
47
+ }
48
+
49
+ export function projectParam(req: IncomingMessage): string | null {
50
+ const raw = new URL(req.url ?? '/', 'http://localhost').searchParams.get(
51
+ 'project',
52
+ );
53
+ return raw === null || raw === '' ? null : raw;
54
+ }
55
+
56
+ export async function readJsonBody(req: IncomingMessage, max = BODY_MAX): Promise<unknown> {
57
+ const chunks: Buffer[] = [];
58
+ let total = 0;
59
+ let over = false;
60
+ for await (const c of req) {
61
+ const buf = c as Buffer;
62
+ total += buf.length;
63
+ if (total > max) over = true;
64
+ if (!over) chunks.push(buf);
65
+ else if (total > 2 * max) break;
66
+ }
67
+ if (over) throw new ApiError('request body too large', 413);
68
+ const raw = Buffer.concat(chunks).toString('utf8').trim();
69
+ if (raw === '') return {};
70
+ try {
71
+ return JSON.parse(raw);
72
+ } catch {
73
+ throw new ApiError('body must be JSON', 400);
74
+ }
75
+ }
76
+
77
+ export function apiFailure(
78
+ req: IncomingMessage,
79
+ res: ServerResponse,
80
+ err: unknown,
81
+ label = 'agent-api',
82
+ ): void {
83
+ if (err instanceof ApiError) {
84
+ sendJson(req, res, err.status, { error: err.message });
85
+ return;
86
+ }
87
+ log.warn({ err: errMsg(err) }, `${label}: request failed`);
88
+ if (!res.headersSent) sendJson(req, res, 500, { error: `${label} failed` });
89
+ }
90
+
91
+ export function bodyField(body: unknown, key: string): unknown {
92
+ return typeof body === 'object' && body !== null && !Array.isArray(body)
93
+ ? (body as Record<string, unknown>)[key]
94
+ : undefined;
95
+ }
@@ -0,0 +1,99 @@
1
+ import { randomBytes, timingSafeEqual } from 'node:crypto';
2
+ import { ID_RE } from '../db/ids.js';
3
+ import { readFileSync } from 'node:fs';
4
+ import { resolveCachedAttachment } from '../stations/attachments.js';
5
+ import { writeSecure } from './secure-fs.js';
6
+
7
+ const GRANT_SUFFIX = '.grant';
8
+
9
+ export interface AttachmentGrant {
10
+ token: string;
11
+ agentId: string;
12
+ mintedAt: number;
13
+ }
14
+
15
+ function newAttachmentToken(prefix = 'at'): string {
16
+ return `${prefix}_${randomBytes(32).toString('base64url')}`;
17
+ }
18
+
19
+ function parseGrant(raw: string): AttachmentGrant | undefined {
20
+ let parsed: unknown;
21
+ try {
22
+ parsed = JSON.parse(raw);
23
+ } catch {
24
+ return undefined;
25
+ }
26
+ if (typeof parsed !== 'object' || parsed === null) return undefined;
27
+ const { token, agentId, mintedAt } = parsed as Record<string, unknown>;
28
+ if (typeof token !== 'string' || token === '') return undefined;
29
+ if (typeof agentId !== 'string' || !ID_RE.test(agentId)) return undefined;
30
+ return {
31
+ token,
32
+ agentId,
33
+ mintedAt: typeof mintedAt === 'number' ? mintedAt : 0,
34
+ };
35
+ }
36
+
37
+ function readGrant(path: string): AttachmentGrant | undefined {
38
+ try {
39
+ return parseGrant(readFileSync(`${path}${GRANT_SUFFIX}`, 'utf8'));
40
+ } catch {
41
+ return undefined;
42
+ }
43
+ }
44
+
45
+ export function issueGrant(
46
+ path: string,
47
+ agentId: string,
48
+ prefix?: string,
49
+ ): string {
50
+ const existing = readGrant(path);
51
+ if (existing?.agentId === agentId) return existing.token;
52
+ const grant: AttachmentGrant = {
53
+ token: newAttachmentToken(prefix),
54
+ agentId,
55
+ mintedAt: Date.now(),
56
+ };
57
+ writeSecure(`${path}${GRANT_SUFFIX}`, `${JSON.stringify(grant)}\n`);
58
+ return grant.token;
59
+ }
60
+
61
+ function sameToken(presented: string, stored: string): boolean {
62
+ const a = Buffer.from(presented, 'utf8');
63
+ const b = Buffer.from(stored, 'utf8');
64
+ return a.length === b.length && timingSafeEqual(a, b);
65
+ }
66
+
67
+ export function grantAllowsPath(
68
+ path: string,
69
+ owner: string,
70
+ presented: string,
71
+ ): boolean {
72
+ if (presented === '') return false;
73
+ const grant = readGrant(path);
74
+ if (grant === undefined) return false;
75
+ if (grant.agentId !== owner) return false;
76
+ return sameToken(presented, grant.token);
77
+ }
78
+
79
+ export function readAttachmentGrant(name: string): AttachmentGrant | undefined {
80
+ const path = resolveCachedAttachment(name);
81
+ return path === null ? undefined : readGrant(path);
82
+ }
83
+
84
+ export function issueAttachmentGrant(
85
+ name: string,
86
+ agentId: string,
87
+ ): string | undefined {
88
+ const path = resolveCachedAttachment(name);
89
+ return path === null ? undefined : issueGrant(path, agentId);
90
+ }
91
+
92
+ export function grantAllows(
93
+ name: string,
94
+ owner: string,
95
+ presented: string,
96
+ ): boolean {
97
+ const path = resolveCachedAttachment(name);
98
+ return path === null ? false : grantAllowsPath(path, owner, presented);
99
+ }
@@ -0,0 +1,32 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { ID_RE } from '../db/ids.js';
3
+ import { resolveCachedAttachment } from '../stations/attachments.js';
4
+ import { writeSecure } from './secure-fs.js';
5
+
6
+ const OWNER_SUFFIX = '.owner';
7
+
8
+ export function recordOwner(path: string, agentId: string): void {
9
+ writeSecure(`${path}${OWNER_SUFFIX}`, `${agentId}\n`);
10
+ }
11
+
12
+ export function ownerOf(path: string): string | undefined {
13
+ let raw: string;
14
+ try {
15
+ raw = readFileSync(`${path}${OWNER_SUFFIX}`, 'utf8');
16
+ } catch {
17
+ return undefined;
18
+ }
19
+ const agentId = raw.trim();
20
+ return ID_RE.test(agentId) ? agentId : undefined;
21
+ }
22
+
23
+ export function recordAttachmentOwner(name: string, agentId: string): void {
24
+ const path = resolveCachedAttachment(name);
25
+ if (path === null) return;
26
+ recordOwner(path, agentId);
27
+ }
28
+
29
+ export function attachmentOwner(name: string): string | undefined {
30
+ const path = resolveCachedAttachment(name);
31
+ return path === null ? undefined : ownerOf(path);
32
+ }
@@ -0,0 +1,128 @@
1
+ import { createReadStream } from 'node:fs';
2
+ import { stat } from 'node:fs/promises';
3
+ import type { IncomingMessage, ServerResponse } from 'node:http';
4
+ import {
5
+ guessMime,
6
+ resolveCachedAttachment,
7
+ } from '../stations/attachments.js';
8
+ import {
9
+ allowedAgents,
10
+ authenticate,
11
+ extractToken,
12
+ } from '../mcp/request-identity.js';
13
+ import { attachmentOwner, recordAttachmentOwner } from './attach-owner.js';
14
+ import { grantAllows, issueAttachmentGrant } from './attach-grant.js';
15
+ import { errMsg, log } from './log.js';
16
+ import { currentTunnelUrl, webhookPort } from './tunnel.js';
17
+ import { isLocalMode } from './paths.js';
18
+
19
+ function authorized(req: IncomingMessage, name: string): boolean {
20
+ const owner = attachmentOwner(name);
21
+ if (owner === undefined) return false;
22
+ if (grantAllows(name, owner, extractToken(req) ?? '')) return true;
23
+ return allowedAgents(authenticate(req) ?? undefined).has(
24
+ owner,
25
+ );
26
+ }
27
+
28
+ const DEFAULT_PUBLIC_BASE = 'https://api.metro.box';
29
+
30
+ export function publicBaseUrl(): string | null {
31
+ const env = process.env.METRO_PUBLIC_URL?.trim();
32
+ if (env) return env.replace(/\/+$/, '');
33
+ return currentTunnelUrl();
34
+ }
35
+
36
+ const servesLocally = (): boolean => isLocalMode();
37
+
38
+ const localBase = (): string | null =>
39
+ servesLocally() ? `http://127.0.0.1:${String(webhookPort())}` : null;
40
+
41
+ export const publicBaseOrDefault = (): string =>
42
+ publicBaseUrl() ?? localBase() ?? DEFAULT_PUBLIC_BASE;
43
+
44
+ export function attachmentUrl(
45
+ pathOrName: string,
46
+ agentId: string,
47
+ ): string | null {
48
+ const base = publicBaseUrl() ?? localBase();
49
+ if (!base) return null;
50
+ const name = pathOrName.split('/').pop();
51
+ if (!name || !resolveCachedAttachment(name)) return null;
52
+ recordAttachmentOwner(name, agentId);
53
+ const token = issueAttachmentGrant(name, agentId);
54
+ if (token === undefined) return null;
55
+ return `${base}/attach/${encodeURIComponent(name)}?token=${encodeURIComponent(token)}`;
56
+ }
57
+
58
+ export function attachmentEventUrl(
59
+ payload: Record<string, unknown>,
60
+ agentId: string,
61
+ ): string | null {
62
+ if (payload.contentType !== 'attachmentSaved') return null;
63
+ if (typeof payload.url === 'string' && payload.url.length > 0) return null;
64
+ const p = payload.attachmentPath ?? payload.localPath;
65
+ return typeof p === 'string' ? attachmentUrl(p, agentId) : null;
66
+ }
67
+
68
+ async function serveFile(
69
+ res: ServerResponse,
70
+ method: string,
71
+ name: string,
72
+ ): Promise<void> {
73
+ const path = resolveCachedAttachment(name);
74
+ if (!path) {
75
+ res.writeHead(404).end();
76
+ return;
77
+ }
78
+ let size: number;
79
+ try {
80
+ size = (await stat(path)).size;
81
+ } catch {
82
+ res.writeHead(404).end();
83
+ return;
84
+ }
85
+ res.writeHead(200, {
86
+ 'content-type': guessMime(path),
87
+ 'content-length': String(size),
88
+ 'cache-control': 'private, max-age=86400',
89
+ });
90
+ if (method === 'HEAD') {
91
+ res.end();
92
+ return;
93
+ }
94
+ const stream = createReadStream(path);
95
+ stream.on('error', (err) => {
96
+ log.warn({ err: errMsg(err) }, 'attach: read stream error');
97
+ res.end();
98
+ });
99
+ stream.pipe(res);
100
+ }
101
+
102
+ export function handleAttachRequest(
103
+ req: IncomingMessage,
104
+ res: ServerResponse,
105
+ ): boolean {
106
+ const [rawPath] = (req.url ?? '').split('?', 2);
107
+ const m = /^\/attach\/([^/]+)$/.exec(rawPath ?? '');
108
+ if (!m) return false;
109
+ const method = req.method ?? 'GET';
110
+ if (method !== 'GET' && method !== 'HEAD') {
111
+ res.writeHead(405).end();
112
+ return true;
113
+ }
114
+ const name = decodeURIComponent(m[1] ?? '');
115
+ if (resolveCachedAttachment(name) === null) {
116
+ res.writeHead(404).end();
117
+ return true;
118
+ }
119
+ if (!authorized(req, name)) {
120
+ res.writeHead(401).end();
121
+ return true;
122
+ }
123
+ serveFile(res, method, name).catch((err: unknown) => {
124
+ log.warn({ err: errMsg(err) }, 'attach: serve error');
125
+ if (!res.headersSent) res.writeHead(500).end();
126
+ });
127
+ return true;
128
+ }