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

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 (329) 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/seed.ts +33 -0
  65. package/runtime/node_modules/@metro-labs/daemon/src/agents/senders.ts +38 -0
  66. package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +209 -0
  67. package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +39 -0
  68. package/runtime/node_modules/@metro-labs/daemon/src/boot/crash-guard.ts +49 -0
  69. package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +14 -0
  70. package/runtime/node_modules/@metro-labs/daemon/src/boot/paths.ts +106 -0
  71. package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +25 -0
  72. package/runtime/node_modules/@metro-labs/daemon/src/channels/dedupe.ts +24 -0
  73. package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +377 -0
  74. package/runtime/node_modules/@metro-labs/daemon/src/channels/media-note.ts +85 -0
  75. package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +75 -0
  76. package/runtime/node_modules/@metro-labs/daemon/src/channels/relay.ts +129 -0
  77. package/runtime/node_modules/@metro-labs/daemon/src/channels/webhook-note.ts +51 -0
  78. package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +309 -0
  79. package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +358 -0
  80. package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +169 -0
  81. package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +56 -0
  82. package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +140 -0
  83. package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +90 -0
  84. package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +253 -0
  85. package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +117 -0
  86. package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +239 -0
  87. package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +154 -0
  88. package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +109 -0
  89. package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +310 -0
  90. package/runtime/node_modules/@metro-labs/daemon/src/connectors/cli-api.ts +63 -0
  91. package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +175 -0
  92. package/runtime/node_modules/@metro-labs/daemon/src/connectors/json.ts +21 -0
  93. package/runtime/node_modules/@metro-labs/daemon/src/connectors/model.ts +77 -0
  94. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-client.ts +166 -0
  95. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-discovery.ts +139 -0
  96. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-pending.ts +65 -0
  97. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-routes.ts +171 -0
  98. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth.ts +155 -0
  99. package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +122 -0
  100. package/runtime/node_modules/@metro-labs/daemon/src/connectors/reach.ts +27 -0
  101. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-target.ts +39 -0
  102. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +296 -0
  103. package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +286 -0
  104. package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +98 -0
  105. package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +70 -0
  106. package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +332 -0
  107. package/runtime/node_modules/@metro-labs/daemon/src/connectors/watch.ts +54 -0
  108. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +99 -0
  109. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-owner.ts +32 -0
  110. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +121 -0
  111. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +294 -0
  112. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-store.ts +181 -0
  113. package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +259 -0
  114. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +231 -0
  115. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
  116. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +288 -0
  117. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +203 -0
  118. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +231 -0
  119. package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +123 -0
  120. package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +187 -0
  121. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +229 -0
  122. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +269 -0
  123. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +207 -0
  124. package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +90 -0
  125. package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +65 -0
  126. package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +17 -0
  127. package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +229 -0
  128. package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +151 -0
  129. package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +216 -0
  130. package/runtime/node_modules/@metro-labs/daemon/src/mcp/channel-owner.ts +29 -0
  131. package/runtime/node_modules/@metro-labs/daemon/src/mcp/ctx.ts +62 -0
  132. package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +91 -0
  133. package/runtime/node_modules/@metro-labs/daemon/src/mcp/frame-scope.ts +21 -0
  134. package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +129 -0
  135. package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +191 -0
  136. package/runtime/node_modules/@metro-labs/daemon/src/mcp/member-tools.ts +72 -0
  137. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +66 -0
  138. package/runtime/node_modules/@metro-labs/daemon/src/mcp/raw-get-stream.ts +207 -0
  139. package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +47 -0
  140. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-registry.ts +172 -0
  141. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-route.ts +43 -0
  142. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +215 -0
  143. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-dispatch.ts +215 -0
  144. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-schemas.ts +400 -0
  145. package/runtime/node_modules/@metro-labs/daemon/src/mcp/upload-tool.ts +66 -0
  146. package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +237 -0
  147. package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +389 -0
  148. package/runtime/node_modules/@metro-labs/daemon/src/routes/body.ts +24 -0
  149. package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +372 -0
  150. package/runtime/node_modules/@metro-labs/daemon/src/routes/identity.ts +54 -0
  151. package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +223 -0
  152. package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +60 -0
  153. package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
  154. package/runtime/node_modules/@metro-labs/daemon/src/routes/threema-callback.ts +103 -0
  155. package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +64 -0
  156. package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +61 -0
  157. package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +116 -0
  158. package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
  159. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-inline.ts +100 -0
  160. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-interactive.ts +161 -0
  161. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +202 -0
  162. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-session.ts +280 -0
  163. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-xmtp.ts +111 -0
  164. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +248 -0
  165. package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +261 -0
  166. package/runtime/node_modules/@metro-labs/daemon/src/stations/registry.ts +37 -0
  167. package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
  168. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor-io.ts +97 -0
  169. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +320 -0
  170. package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
  171. package/runtime/node_modules/@metro-labs/daemon/src/stations/train-call.ts +22 -0
  172. package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +96 -0
  173. package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +123 -0
  174. package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +40 -0
  175. package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
  176. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -0
  177. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
  178. package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
  179. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
  180. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +220 -0
  181. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
  182. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
  183. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
  184. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
  185. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
  186. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
  187. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
  188. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
  189. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
  190. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
  191. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
  192. package/runtime/node_modules/@metro-labs/http/package.json +33 -0
  193. package/runtime/node_modules/@metro-labs/http/src/api-error.ts +8 -0
  194. package/runtime/node_modules/@metro-labs/http/src/api-http.ts +95 -0
  195. package/runtime/node_modules/@metro-labs/http/src/cors.ts +29 -0
  196. package/runtime/node_modules/@metro-labs/http/src/identity-key.ts +33 -0
  197. package/runtime/node_modules/@metro-labs/http/src/identity-registry.ts +13 -0
  198. package/runtime/node_modules/@metro-labs/http/src/mode-api.ts +32 -0
  199. package/runtime/node_modules/@metro-labs/http/src/return-to.ts +21 -0
  200. package/runtime/node_modules/@metro-labs/http/src/signed-identity.ts +36 -0
  201. package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
  202. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
  203. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +254 -0
  204. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
  205. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
  206. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +156 -0
  207. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
  208. package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
  209. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
  210. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
  211. package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
  212. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
  213. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
  214. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
  215. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
  216. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
  217. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
  218. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
  219. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
  220. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
  221. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
  222. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
  223. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +201 -0
  224. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +109 -0
  225. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
  226. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
  227. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
  228. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +58 -0
  229. package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
  230. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
  231. package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
  232. package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +100 -0
  233. package/runtime/node_modules/@metro-labs/threema/src/actions.ts +219 -0
  234. package/runtime/node_modules/@metro-labs/threema/src/api.ts +130 -0
  235. package/runtime/node_modules/@metro-labs/threema/src/crypto.ts +132 -0
  236. package/runtime/node_modules/@metro-labs/threema/src/format.ts +123 -0
  237. package/runtime/node_modules/@metro-labs/threema/src/ids.ts +21 -0
  238. package/runtime/node_modules/@metro-labs/threema/src/index.ts +29 -0
  239. package/runtime/node_modules/@metro-labs/threema/src/station.ts +10 -0
  240. package/runtime/node_modules/@metro-labs/threema/src/verify.ts +117 -0
  241. package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
  242. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
  243. package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
  244. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
  245. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
  246. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +190 -0
  247. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
  248. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
  249. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +347 -0
  250. package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
  251. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +138 -0
  252. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
  253. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
  254. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
  255. package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
  256. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
  257. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
  258. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
  259. package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
  260. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
  261. package/runtime/node_modules/@metro-labs/whatsapp/src/resolve.ts +52 -0
  262. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +18 -0
  263. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
  264. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
  265. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
  266. package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
  267. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +112 -0
  268. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
  269. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
  270. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
  271. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
  272. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
  273. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
  274. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
  275. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
  276. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
  277. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +335 -0
  278. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +80 -0
  279. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
  280. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
  281. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
  282. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
  283. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
  284. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
  285. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
  286. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
  287. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
  288. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
  289. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
  290. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
  291. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +53 -0
  292. package/runtime/runtime.json +3 -0
  293. package/runtime/server.ts +1 -0
  294. package/runtime/stations.json +39 -0
  295. package/runtime/trains/.keep +0 -0
  296. package/LICENSE +0 -21
  297. package/README.md +0 -252
  298. package/dist/broker.js +0 -202
  299. package/dist/cache.js +0 -69
  300. package/dist/cli/actions.js +0 -206
  301. package/dist/cli/config.js +0 -185
  302. package/dist/cli/index.js +0 -188
  303. package/dist/cli/skill.js +0 -62
  304. package/dist/cli/tail.js +0 -164
  305. package/dist/cli/util.js +0 -72
  306. package/dist/cli/webhook.js +0 -81
  307. package/dist/codex-rc.js +0 -245
  308. package/dist/dispatcher.js +0 -128
  309. package/dist/history.js +0 -121
  310. package/dist/ipc.js +0 -72
  311. package/dist/log.js +0 -15
  312. package/dist/monitor.js +0 -194
  313. package/dist/paths.js +0 -68
  314. package/dist/registry.js +0 -48
  315. package/dist/stations/claude.js +0 -45
  316. package/dist/stations/codex.js +0 -68
  317. package/dist/stations/discord.js +0 -216
  318. package/dist/stations/index.js +0 -129
  319. package/dist/stations/telegram-md.js +0 -34
  320. package/dist/stations/telegram-upload.js +0 -113
  321. package/dist/stations/telegram.js +0 -234
  322. package/dist/stations/webhook.js +0 -103
  323. package/dist/tunnel.js +0 -64
  324. package/dist/webhooks.js +0 -41
  325. package/docs/broker.md +0 -214
  326. package/docs/monitor.md +0 -163
  327. package/docs/uri-scheme.md +0 -124
  328. package/docs/users.md +0 -226
  329. package/skills/metro/SKILL.md +0 -256
@@ -0,0 +1,332 @@
1
+ import { errMsg, log } from '@metro-labs/core/log';
2
+ import { whyUnreachable } from './reach.js';
3
+ import {
4
+ ConnectorNotMcp,
5
+ ConnectorUnauthorized,
6
+ refused,
7
+ safeIconSrc,
8
+ } from './url.js';
9
+ import { toToolList, type ToolInfo } from './tools.js';
10
+
11
+ export {
12
+ ConnectorUnauthorized,
13
+ ConnectorVerifyError,
14
+ connectorUrlText,
15
+ parseConnectorUrl,
16
+ } from './url.js';
17
+ import { isRecord } from '@metro-labs/core/is-record';
18
+
19
+ export interface OAuthTokens {
20
+ accessToken: string;
21
+ refreshToken?: string;
22
+ expiresAt?: number;
23
+ }
24
+
25
+ export interface OAuthAuth extends OAuthTokens {
26
+ kind: 'oauth';
27
+ issuer: string;
28
+ tokenEndpoint: string;
29
+ clientId: string;
30
+ clientSecret?: string;
31
+ scope?: string;
32
+ }
33
+
34
+ export type ConnectorAuth =
35
+ | { kind: 'none' }
36
+ | { kind: 'header'; name: string; value: string }
37
+ | OAuthAuth;
38
+
39
+ export interface VerifiedServer {
40
+ server: string;
41
+ version: string;
42
+ protocol: string;
43
+ icon: string;
44
+ tools: number;
45
+ catalog: ToolInfo[];
46
+ }
47
+
48
+ export interface VerifiedRecord extends VerifiedServer {
49
+ at: string;
50
+ }
51
+
52
+ const PROTOCOL_VERSIONS = ['2025-11-25', '2025-06-18'] as const;
53
+ const MAX_TOOL_PAGES = 10;
54
+
55
+ function notMcp(url: URL): ConnectorNotMcp {
56
+ return new ConnectorNotMcp(
57
+ `${url.hostname} answered, but it does not speak MCP.`,
58
+ 400,
59
+ );
60
+ }
61
+ const PROBE_TIMEOUT_MS = 10_000;
62
+ const ACCEPT = 'application/json, text/event-stream';
63
+
64
+ async function discard(res: Response): Promise<void> {
65
+ await res.body?.cancel().catch((err: unknown) => {
66
+ log.debug(
67
+ { err: errMsg(err) },
68
+ 'connector-verify: could not discard a response body',
69
+ );
70
+ });
71
+ }
72
+
73
+ function authHeaders(auth: ConnectorAuth): Record<string, string> {
74
+ if (auth.kind === 'header') return { [auth.name]: auth.value };
75
+ if (auth.kind === 'oauth')
76
+ return { authorization: `Bearer ${auth.accessToken}` };
77
+ return {};
78
+ }
79
+
80
+ async function frame(
81
+ url: URL,
82
+ auth: ConnectorAuth,
83
+ extra: Record<string, string>,
84
+ body: unknown,
85
+ signal: AbortSignal,
86
+ ): Promise<Response> {
87
+ let res: Response;
88
+ try {
89
+ res = await fetch(url, {
90
+ method: 'POST',
91
+ redirect: 'manual',
92
+ signal,
93
+ headers: {
94
+ 'content-type': 'application/json',
95
+ accept: ACCEPT,
96
+ ...authHeaders(auth),
97
+ ...extra,
98
+ },
99
+ body: JSON.stringify(body),
100
+ });
101
+ } catch (err) {
102
+ throw refused(`Metro could not reach ${url.hostname}: ${whyUnreachable(err)}`);
103
+ }
104
+ if (res.status >= 300 && res.status < 400) {
105
+ await discard(res);
106
+ throw refused('that url redirects — use the url it redirects to');
107
+ }
108
+ return res;
109
+ }
110
+
111
+ function sseData(body: string): string {
112
+ for (const line of body.replace(/\r\n/g, '\n').split('\n'))
113
+ if (line.startsWith('data:')) return line.slice(5).trim();
114
+ return '';
115
+ }
116
+
117
+ async function payloadOf(res: Response): Promise<string> {
118
+ const contentType = res.headers.get('content-type') ?? '';
119
+ const text = await res.text().catch(() => '');
120
+ return contentType.includes('text/event-stream') ? sseData(text) : text;
121
+ }
122
+
123
+ async function resultOrNull(
124
+ res: Response,
125
+ ): Promise<Record<string, unknown> | null> {
126
+ const payload = await payloadOf(res);
127
+ let parsed: unknown;
128
+ try {
129
+ parsed = JSON.parse(payload);
130
+ } catch {
131
+ return null;
132
+ }
133
+ if (!isRecord(parsed) || !isRecord(parsed.result)) return null;
134
+ return parsed.result;
135
+ }
136
+
137
+ function initializeBody(version: string): unknown {
138
+ return {
139
+ jsonrpc: '2.0',
140
+ id: 1,
141
+ method: 'initialize',
142
+ params: {
143
+ protocolVersion: version,
144
+ capabilities: {},
145
+ clientInfo: { name: 'metro', version: '0.1.0' },
146
+ },
147
+ };
148
+ }
149
+
150
+ function iconOf(info: Record<string, unknown>): string {
151
+ const icons = info.icons;
152
+ if (!Array.isArray(icons)) return '';
153
+ for (const entry of icons) {
154
+ if (!isRecord(entry)) continue;
155
+ const src = safeIconSrc(entry.src);
156
+ if (src !== '') return src;
157
+ }
158
+ return '';
159
+ }
160
+
161
+ function serverFrom(url: URL, result: Record<string, unknown>): VerifiedServer {
162
+ const protocol = result.protocolVersion;
163
+ if (typeof protocol !== 'string') throw notMcp(url);
164
+ const info = isRecord(result.serverInfo) ? result.serverInfo : {};
165
+ const title = typeof info.title === 'string' ? info.title : '';
166
+ return {
167
+ server:
168
+ title !== ''
169
+ ? title
170
+ : typeof info.name === 'string'
171
+ ? info.name
172
+ : url.hostname,
173
+ version: typeof info.version === 'string' ? info.version : '',
174
+ protocol,
175
+ icon: iconOf(info),
176
+ tools: 0,
177
+ catalog: [],
178
+ };
179
+ }
180
+
181
+ async function initialize(
182
+ url: URL,
183
+ auth: ConnectorAuth,
184
+ signal: AbortSignal,
185
+ version: string,
186
+ ): Promise<{ session: string | null; server: VerifiedServer }> {
187
+ const res = await frame(url, auth, {}, initializeBody(version), signal);
188
+ if (res.status === 401 || res.status === 403) {
189
+ await discard(res);
190
+ throw new ConnectorUnauthorized(
191
+ auth.kind === 'none'
192
+ ? `${url.hostname} requires authorization.`
193
+ : `${url.hostname} rejected that credential.`,
194
+ 400,
195
+ );
196
+ }
197
+ if (res.status === 404 || res.status === 405) {
198
+ await discard(res);
199
+ throw refused(
200
+ `${url.hostname} did not accept an MCP initialize over HTTP POST. ` +
201
+ 'If it is a legacy SSE server, Metro cannot verify it yet.',
202
+ );
203
+ }
204
+ if (!res.ok) {
205
+ await discard(res);
206
+ throw notMcp(url);
207
+ }
208
+ const result = await resultOrNull(res);
209
+ if (result === null) throw notMcp(url);
210
+ return {
211
+ session: res.headers.get('mcp-session-id'),
212
+ server: serverFrom(url, result),
213
+ };
214
+ }
215
+
216
+ function sessionHeaders(
217
+ session: string | null,
218
+ version: string,
219
+ ): Record<string, string> {
220
+ const headers: Record<string, string> = {
221
+ 'mcp-protocol-version': version,
222
+ };
223
+ if (session !== null && session !== '') headers['mcp-session-id'] = session;
224
+ return headers;
225
+ }
226
+
227
+ async function announce(
228
+ url: URL,
229
+ auth: ConnectorAuth,
230
+ headers: Record<string, string>,
231
+ signal: AbortSignal,
232
+ ): Promise<void> {
233
+ const res = await frame(
234
+ url,
235
+ auth,
236
+ headers,
237
+ { jsonrpc: '2.0', method: 'notifications/initialized' },
238
+ signal,
239
+ );
240
+ await discard(res);
241
+ }
242
+
243
+ async function toolPage(
244
+ url: URL,
245
+ auth: ConnectorAuth,
246
+ headers: Record<string, string>,
247
+ signal: AbortSignal,
248
+ cursor: string,
249
+ ): Promise<Record<string, unknown> | null> {
250
+ const params = cursor === '' ? {} : { cursor };
251
+ const res = await frame(
252
+ url,
253
+ auth,
254
+ headers,
255
+ { jsonrpc: '2.0', id: 2, method: 'tools/list', params },
256
+ signal,
257
+ );
258
+ if (!res.ok) {
259
+ await discard(res);
260
+ return null;
261
+ }
262
+ return resultOrNull(res);
263
+ }
264
+
265
+ async function listTools(
266
+ url: URL,
267
+ auth: ConnectorAuth,
268
+ headers: Record<string, string>,
269
+ signal: AbortSignal,
270
+ ): Promise<ToolInfo[]> {
271
+ const tools: ToolInfo[] = [];
272
+ let cursor = '';
273
+ for (let page = 0; page < MAX_TOOL_PAGES; page += 1) {
274
+ const result = await toolPage(url, auth, headers, signal, cursor);
275
+ if (result === null) break;
276
+ tools.push(...toToolList(result.tools));
277
+ const next = result.nextCursor;
278
+ if (typeof next !== 'string' || next === '' || next === cursor) break;
279
+ cursor = next;
280
+ }
281
+ return tools;
282
+ }
283
+
284
+ async function terminate(
285
+ url: URL,
286
+ auth: ConnectorAuth,
287
+ headers: Record<string, string>,
288
+ signal: AbortSignal,
289
+ ): Promise<void> {
290
+ const res = await fetch(url, {
291
+ method: 'DELETE',
292
+ redirect: 'manual',
293
+ signal,
294
+ headers: { ...authHeaders(auth), ...headers },
295
+ });
296
+ await discard(res);
297
+ }
298
+
299
+ async function probeWith(
300
+ url: URL,
301
+ auth: ConnectorAuth,
302
+ version: string,
303
+ ): Promise<VerifiedServer> {
304
+ const signal = AbortSignal.timeout(PROBE_TIMEOUT_MS);
305
+ const { session, server } = await initialize(url, auth, signal, version);
306
+ const headers = sessionHeaders(session, server.protocol);
307
+ await announce(url, auth, headers, signal);
308
+ const catalog = await listTools(url, auth, headers, signal);
309
+ await terminate(url, auth, headers, signal).catch((err: unknown) => {
310
+ log.debug(
311
+ { host: url.hostname, err: errMsg(err) },
312
+ 'connector-verify: the remote declined to end the session',
313
+ );
314
+ });
315
+ return { ...server, tools: catalog.length, catalog };
316
+ }
317
+
318
+ export async function verifyRemoteMcp(
319
+ url: URL,
320
+ auth: ConnectorAuth,
321
+ ): Promise<VerifiedServer> {
322
+ let last: unknown;
323
+ for (const version of PROTOCOL_VERSIONS) {
324
+ try {
325
+ return await probeWith(url, auth, version);
326
+ } catch (err) {
327
+ if (!(err instanceof ConnectorNotMcp)) throw err;
328
+ last = err;
329
+ }
330
+ }
331
+ throw last;
332
+ }
@@ -0,0 +1,54 @@
1
+ import { watch, type FSWatcher } from 'node:fs';
2
+ import { errMsg, log } from '@metro-labs/core/log';
3
+
4
+ const DEBOUNCE_MS = 300;
5
+ const FILE = 'connectors.json';
6
+
7
+ export class ConnectorWatch {
8
+ private watcher: FSWatcher | null = null;
9
+ private timer: ReturnType<typeof setTimeout> | null = null;
10
+
11
+ constructor(
12
+ private readonly dir: string,
13
+ private readonly onChange: () => void,
14
+ ) {}
15
+
16
+ start(): void {
17
+ try {
18
+ this.watcher = watch(this.dir, (_event, file) => {
19
+ if (file === null || file === FILE) this.schedule();
20
+ });
21
+ this.watcher.on('error', (err: unknown) => {
22
+ log.warn({ err: errMsg(err) }, 'connectors: watcher failed; the plugin list refreshes at the next start');
23
+ });
24
+ this.watcher.unref();
25
+ } catch (err) {
26
+ log.warn({ err: errMsg(err) }, 'connectors: could not watch the agents dir');
27
+ }
28
+ this.fire();
29
+ }
30
+
31
+ stop(): void {
32
+ this.watcher?.close();
33
+ this.watcher = null;
34
+ if (this.timer !== null) clearTimeout(this.timer);
35
+ this.timer = null;
36
+ }
37
+
38
+ private schedule(): void {
39
+ if (this.timer !== null) clearTimeout(this.timer);
40
+ this.timer = setTimeout(() => {
41
+ this.timer = null;
42
+ this.fire();
43
+ }, DEBOUNCE_MS);
44
+ this.timer.unref();
45
+ }
46
+
47
+ private fire(): void {
48
+ try {
49
+ this.onChange();
50
+ } catch (err) {
51
+ log.warn({ err: errMsg(err) }, 'connectors: change handler failed');
52
+ }
53
+ }
54
+ }
@@ -0,0 +1,99 @@
1
+ import { randomBytes, timingSafeEqual } from 'node:crypto';
2
+ import { ID_RE } from '@metro-labs/core/ids';
3
+ import { readFileSync } from 'node:fs';
4
+ import { resolveCachedAttachment } from '@metro-labs/core/stations/attachments';
5
+ import { writeSecure } from '@metro-labs/core/secure-fs';
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 '@metro-labs/core/ids';
3
+ import { resolveCachedAttachment } from '@metro-labs/core/stations/attachments';
4
+ import { writeSecure } from '@metro-labs/core/secure-fs';
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,121 @@
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 '@metro-labs/core/stations/attachments';
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 '@metro-labs/core/log';
16
+ import { currentTunnelUrl, webhookPort } from '../net/tunnel.js';
17
+
18
+ function authorized(req: IncomingMessage, name: string): boolean {
19
+ const owner = attachmentOwner(name);
20
+ if (owner === undefined) return false;
21
+ if (grantAllows(name, owner, extractToken(req) ?? '')) return true;
22
+ return allowedAgents(authenticate(req) ?? undefined).has(
23
+ owner,
24
+ );
25
+ }
26
+
27
+ export function publicBaseUrl(): string | null {
28
+ const env = process.env.METRO_PUBLIC_URL?.trim();
29
+ if (env) return env.replace(/\/+$/, '');
30
+ return currentTunnelUrl();
31
+ }
32
+
33
+ export const loopbackBase = (): string => `http://127.0.0.1:${String(webhookPort())}`;
34
+
35
+ export const publicBaseOrDefault = (): string => publicBaseUrl() ?? loopbackBase();
36
+
37
+ export function attachmentUrl(
38
+ pathOrName: string,
39
+ agentId: string,
40
+ ): string | null {
41
+ const base = publicBaseUrl() ?? loopbackBase();
42
+ if (!base) return null;
43
+ const name = pathOrName.split('/').pop();
44
+ if (!name || !resolveCachedAttachment(name)) return null;
45
+ recordAttachmentOwner(name, agentId);
46
+ const token = issueAttachmentGrant(name, agentId);
47
+ if (token === undefined) return null;
48
+ return `${base}/attach/${encodeURIComponent(name)}?token=${encodeURIComponent(token)}`;
49
+ }
50
+
51
+ export function attachmentEventUrl(
52
+ payload: Record<string, unknown>,
53
+ agentId: string,
54
+ ): string | null {
55
+ if (payload.contentType !== 'attachmentSaved') return null;
56
+ if (typeof payload.url === 'string' && payload.url.length > 0) return null;
57
+ const p = payload.attachmentPath ?? payload.localPath;
58
+ return typeof p === 'string' ? attachmentUrl(p, agentId) : null;
59
+ }
60
+
61
+ async function serveFile(
62
+ res: ServerResponse,
63
+ method: string,
64
+ name: string,
65
+ ): Promise<void> {
66
+ const path = resolveCachedAttachment(name);
67
+ if (!path) {
68
+ res.writeHead(404).end();
69
+ return;
70
+ }
71
+ let size: number;
72
+ try {
73
+ size = (await stat(path)).size;
74
+ } catch {
75
+ res.writeHead(404).end();
76
+ return;
77
+ }
78
+ res.writeHead(200, {
79
+ 'content-type': guessMime(path),
80
+ 'content-length': String(size),
81
+ 'cache-control': 'private, max-age=86400',
82
+ });
83
+ if (method === 'HEAD') {
84
+ res.end();
85
+ return;
86
+ }
87
+ const stream = createReadStream(path);
88
+ stream.on('error', (err) => {
89
+ log.warn({ err: errMsg(err) }, 'attach: read stream error');
90
+ res.end();
91
+ });
92
+ stream.pipe(res);
93
+ }
94
+
95
+ export function handleAttachRequest(
96
+ req: IncomingMessage,
97
+ res: ServerResponse,
98
+ ): boolean {
99
+ const [rawPath] = (req.url ?? '').split('?', 2);
100
+ const m = /^\/attach\/([^/]+)$/.exec(rawPath ?? '');
101
+ if (!m) return false;
102
+ const method = req.method ?? 'GET';
103
+ if (method !== 'GET' && method !== 'HEAD') {
104
+ res.writeHead(405).end();
105
+ return true;
106
+ }
107
+ const name = decodeURIComponent(m[1] ?? '');
108
+ if (resolveCachedAttachment(name) === null) {
109
+ res.writeHead(404).end();
110
+ return true;
111
+ }
112
+ if (!authorized(req, name)) {
113
+ res.writeHead(401).end();
114
+ return true;
115
+ }
116
+ serveFile(res, method, name).catch((err: unknown) => {
117
+ log.warn({ err: errMsg(err) }, 'attach: serve error');
118
+ if (!res.headersSent) res.writeHead(500).end();
119
+ });
120
+ return true;
121
+ }