@stage-labs/metro 0.1.0-beta.15 → 0.1.0-beta.151

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 (327) hide show
  1. package/dist/api.js +9 -0
  2. package/dist/claude-settings.js +40 -0
  3. package/dist/claude.js +182 -0
  4. package/dist/cli.js +86 -0
  5. package/dist/control.js +74 -0
  6. package/dist/hold.js +211 -0
  7. package/dist/local.js +77 -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/provider-flags.js +9 -0
  12. package/dist/route.js +50 -0
  13. package/dist/runtime-install.js +97 -0
  14. package/dist/runtime.js +74 -0
  15. package/dist/serve.js +155 -0
  16. package/dist/service.js +242 -0
  17. package/dist/tail.js +119 -0
  18. package/dist/update.js +45 -0
  19. package/dist/version.js +97 -0
  20. package/package.json +14 -28
  21. package/runtime/marketplace/.claude-plugin/marketplace.json +12 -0
  22. package/runtime/marketplace/plugin/.claude-plugin/plugin.json +18 -0
  23. package/runtime/marketplace/plugin/.mcp.json +1 -0
  24. package/runtime/marketplace/plugin/bin/guard.mjs +55 -0
  25. package/runtime/marketplace/plugin/bin/metro-plugin.mjs +40 -0
  26. package/runtime/marketplace/plugin/bin/session-start.mjs +31 -0
  27. package/runtime/marketplace/plugin/hooks/hooks.json +25 -0
  28. package/runtime/marketplace/plugin/orchestrator.md +16 -0
  29. package/runtime/node_modules/@metro-labs/core/package.json +43 -0
  30. package/runtime/node_modules/@metro-labs/core/src/address.ts +6 -0
  31. package/runtime/node_modules/@metro-labs/core/src/endpoints.ts +7 -0
  32. package/runtime/node_modules/@metro-labs/core/src/events.ts +120 -0
  33. package/runtime/node_modules/@metro-labs/core/src/identity.ts +7 -0
  34. package/runtime/node_modules/@metro-labs/core/src/ids.ts +21 -0
  35. package/runtime/node_modules/@metro-labs/core/src/is-record.ts +2 -0
  36. package/runtime/node_modules/@metro-labs/core/src/lines.ts +87 -0
  37. package/runtime/node_modules/@metro-labs/core/src/log.ts +42 -0
  38. package/runtime/node_modules/@metro-labs/core/src/protocol.ts +226 -0
  39. package/runtime/node_modules/@metro-labs/core/src/secure-fs.ts +57 -0
  40. package/runtime/node_modules/@metro-labs/core/src/station-names.ts +13 -0
  41. package/runtime/node_modules/@metro-labs/core/src/stations/account-store.ts +60 -0
  42. package/runtime/node_modules/@metro-labs/core/src/stations/attachments.ts +196 -0
  43. package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +88 -0
  44. package/runtime/node_modules/@metro-labs/core/src/stations/station-runtime.ts +64 -0
  45. package/runtime/node_modules/@metro-labs/core/src/stations/types.ts +108 -0
  46. package/runtime/node_modules/@metro-labs/core/src/str.ts +5 -0
  47. package/runtime/node_modules/@metro-labs/core/src/train-error.ts +53 -0
  48. package/runtime/node_modules/@metro-labs/core/src/version.ts +2 -0
  49. package/runtime/node_modules/@metro-labs/daemon/package.json +47 -0
  50. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-attach.ts +19 -0
  51. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-routes.ts +70 -0
  52. package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +365 -0
  53. package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +39 -0
  54. package/runtime/node_modules/@metro-labs/daemon/src/agents/allowlist.ts +37 -0
  55. package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +225 -0
  56. package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +122 -0
  57. package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +364 -0
  58. package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +185 -0
  59. package/runtime/node_modules/@metro-labs/daemon/src/agents/keys.ts +40 -0
  60. package/runtime/node_modules/@metro-labs/daemon/src/agents/map.ts +95 -0
  61. package/runtime/node_modules/@metro-labs/daemon/src/agents/scope.ts +64 -0
  62. package/runtime/node_modules/@metro-labs/daemon/src/agents/senders.ts +38 -0
  63. package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +200 -0
  64. package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +39 -0
  65. package/runtime/node_modules/@metro-labs/daemon/src/boot/crash-guard.ts +49 -0
  66. package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +30 -0
  67. package/runtime/node_modules/@metro-labs/daemon/src/boot/paths.ts +106 -0
  68. package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +25 -0
  69. package/runtime/node_modules/@metro-labs/daemon/src/channels/dedupe.ts +24 -0
  70. package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +380 -0
  71. package/runtime/node_modules/@metro-labs/daemon/src/channels/media-note.ts +85 -0
  72. package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +75 -0
  73. package/runtime/node_modules/@metro-labs/daemon/src/channels/relay.ts +129 -0
  74. package/runtime/node_modules/@metro-labs/daemon/src/channels/webhook-note.ts +51 -0
  75. package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +354 -0
  76. package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +358 -0
  77. package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +169 -0
  78. package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +56 -0
  79. package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +140 -0
  80. package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +90 -0
  81. package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +253 -0
  82. package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +117 -0
  83. package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +263 -0
  84. package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +118 -0
  85. package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +109 -0
  86. package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +313 -0
  87. package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +157 -0
  88. package/runtime/node_modules/@metro-labs/daemon/src/connectors/health.ts +18 -0
  89. package/runtime/node_modules/@metro-labs/daemon/src/connectors/model.ts +69 -0
  90. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-client.ts +166 -0
  91. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-discovery.ts +139 -0
  92. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-pending.ts +65 -0
  93. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-routes.ts +171 -0
  94. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth.ts +155 -0
  95. package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +122 -0
  96. package/runtime/node_modules/@metro-labs/daemon/src/connectors/reach.ts +27 -0
  97. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-target.ts +39 -0
  98. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +304 -0
  99. package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +299 -0
  100. package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +108 -0
  101. package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +59 -0
  102. package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +101 -0
  103. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +99 -0
  104. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-owner.ts +32 -0
  105. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +121 -0
  106. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +294 -0
  107. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-store.ts +181 -0
  108. package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +259 -0
  109. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +231 -0
  110. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
  111. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +288 -0
  112. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +203 -0
  113. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +231 -0
  114. package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +123 -0
  115. package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +187 -0
  116. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +247 -0
  117. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-auth.ts +182 -0
  118. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-client.ts +46 -0
  119. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-setup.ts +150 -0
  120. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-stream.ts +148 -0
  121. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-translate.ts +201 -0
  122. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini.ts +257 -0
  123. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +353 -0
  124. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +232 -0
  125. package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +90 -0
  126. package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +65 -0
  127. package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +17 -0
  128. package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +242 -0
  129. package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +151 -0
  130. package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +216 -0
  131. package/runtime/node_modules/@metro-labs/daemon/src/mcp/channel-owner.ts +29 -0
  132. package/runtime/node_modules/@metro-labs/daemon/src/mcp/ctx.ts +62 -0
  133. package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +91 -0
  134. package/runtime/node_modules/@metro-labs/daemon/src/mcp/frame-scope.ts +21 -0
  135. package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +129 -0
  136. package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +186 -0
  137. package/runtime/node_modules/@metro-labs/daemon/src/mcp/member-tools.ts +72 -0
  138. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +66 -0
  139. package/runtime/node_modules/@metro-labs/daemon/src/mcp/raw-get-stream.ts +207 -0
  140. package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +47 -0
  141. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-registry.ts +172 -0
  142. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-route.ts +43 -0
  143. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +215 -0
  144. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-dispatch.ts +215 -0
  145. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-schemas.ts +400 -0
  146. package/runtime/node_modules/@metro-labs/daemon/src/mcp/upload-tool.ts +66 -0
  147. package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +237 -0
  148. package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +389 -0
  149. package/runtime/node_modules/@metro-labs/daemon/src/routes/bearer.ts +35 -0
  150. package/runtime/node_modules/@metro-labs/daemon/src/routes/body.ts +24 -0
  151. package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +370 -0
  152. package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +210 -0
  153. package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +58 -0
  154. package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
  155. package/runtime/node_modules/@metro-labs/daemon/src/routes/threema-callback.ts +103 -0
  156. package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +65 -0
  157. package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +80 -0
  158. package/runtime/node_modules/@metro-labs/daemon/src/server/owner.ts +48 -0
  159. package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +117 -0
  160. package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
  161. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-inline.ts +100 -0
  162. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-interactive.ts +161 -0
  163. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +202 -0
  164. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-session.ts +280 -0
  165. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-xmtp.ts +111 -0
  166. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +248 -0
  167. package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +261 -0
  168. package/runtime/node_modules/@metro-labs/daemon/src/stations/registry.ts +37 -0
  169. package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
  170. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor-io.ts +97 -0
  171. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +320 -0
  172. package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
  173. package/runtime/node_modules/@metro-labs/daemon/src/stations/train-call.ts +22 -0
  174. package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +97 -0
  175. package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +123 -0
  176. package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +40 -0
  177. package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
  178. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -0
  179. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
  180. package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
  181. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
  182. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +220 -0
  183. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
  184. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
  185. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
  186. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
  187. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
  188. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
  189. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
  190. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
  191. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
  192. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
  193. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
  194. package/runtime/node_modules/@metro-labs/http/package.json +29 -0
  195. package/runtime/node_modules/@metro-labs/http/src/api-error.ts +8 -0
  196. package/runtime/node_modules/@metro-labs/http/src/api-http.ts +106 -0
  197. package/runtime/node_modules/@metro-labs/http/src/cors.ts +29 -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/workos-token.ts +150 -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 +223 -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 +37 -0
  295. package/runtime/trains/.keep +0 -0
  296. package/LICENSE +0 -21
  297. package/README.md +0 -139
  298. package/dist/broker/claims.js +0 -144
  299. package/dist/broker/history-stream.js +0 -149
  300. package/dist/cli/config.js +0 -179
  301. package/dist/cli/index.js +0 -175
  302. package/dist/cli/messenger-api.js +0 -214
  303. package/dist/cli/messenger-transcribe.js +0 -43
  304. package/dist/cli/messenger-uploads.js +0 -116
  305. package/dist/cli/monitor-api.js +0 -205
  306. package/dist/cli/tail.js +0 -95
  307. package/dist/cli/util.js +0 -72
  308. package/dist/cli/webhook.js +0 -181
  309. package/dist/codex-rc/client.js +0 -225
  310. package/dist/codex-rc/protocol.js +0 -38
  311. package/dist/dispatcher/server.js +0 -122
  312. package/dist/dispatcher.js +0 -97
  313. package/dist/history.js +0 -143
  314. package/dist/ipc.js +0 -90
  315. package/dist/lines.js +0 -54
  316. package/dist/local-identity.js +0 -80
  317. package/dist/log.js +0 -15
  318. package/dist/paths.js +0 -114
  319. package/dist/trains/protocol.js +0 -99
  320. package/dist/trains/supervisor.js +0 -210
  321. package/dist/tunnel.js +0 -102
  322. package/docs/broker.md +0 -166
  323. package/docs/monitor.md +0 -241
  324. package/docs/uri-scheme.md +0 -127
  325. package/examples/README.md +0 -32
  326. package/examples/telegram.ts +0 -121
  327. package/skills/metro/SKILL.md +0 -110
@@ -0,0 +1,248 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import {
3
+ DiscordTokenError,
4
+ verifyDiscordBotToken,
5
+ } from '@metro-labs/discord-bot/verify';
6
+ import {
7
+ TelegramTokenError,
8
+ verifyTelegramBotToken,
9
+ } from '@metro-labs/telegram-bot/verify';
10
+ import {
11
+ parsePrivateKey,
12
+ ThreemaGatewayError,
13
+ verifyThreemaGateway,
14
+ } from '@metro-labs/threema/verify';
15
+ import { ApiError } from '@metro-labs/http/api-error';
16
+ import { bodyField } from '@metro-labs/http/api-http';
17
+ import { ensureStationDeps } from './runtime-deps.js';
18
+ import { publicBaseOrDefault } from '../files/attach-serve.js';
19
+ import {
20
+ discardXmtpDb,
21
+ newXmtpDbPath,
22
+ verifyXmtpKeyOutOfProcess,
23
+ XmtpAttachError,
24
+ type VerifyXmtpKey,
25
+ } from './attach-xmtp.js';
26
+
27
+ export const ATTACHABLE_STATIONS = [
28
+ 'discord-bot',
29
+ 'telegram-bot',
30
+ 'threema',
31
+ 'xmtp',
32
+ 'webhook',
33
+ ] as const;
34
+
35
+ export type AttachStation = (typeof ATTACHABLE_STATIONS)[number];
36
+
37
+ export class StationAttachError extends ApiError {}
38
+
39
+ export interface AttachInput {
40
+ station: AttachStation;
41
+ token?: unknown;
42
+ gatewayId?: unknown;
43
+ secret?: unknown;
44
+ privateKey?: unknown;
45
+ }
46
+
47
+ export interface OneTimeSecret {
48
+ label: string;
49
+ value: string;
50
+ note: string;
51
+ }
52
+
53
+ export interface PreparedAccount {
54
+ config: Record<string, unknown>;
55
+ identity: Record<string, string>;
56
+ secret?: OneTimeSecret;
57
+ discard?: () => void;
58
+ }
59
+
60
+ const TOKEN_RE = /^[A-Za-z0-9._:-]{8,256}$/;
61
+
62
+ const SECP256K1_ORDER = BigInt(
63
+ '0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141',
64
+ );
65
+
66
+ export function attachInputOf(station: AttachStation, body: unknown): AttachInput {
67
+ return {
68
+ station,
69
+ token: bodyField(body, 'token'),
70
+ gatewayId: bodyField(body, 'gatewayId'),
71
+ secret: bodyField(body, 'secret'),
72
+ privateKey: bodyField(body, 'privateKey'),
73
+ };
74
+ }
75
+
76
+ export function isAttachStation(raw: unknown): raw is AttachStation {
77
+ return (
78
+ typeof raw === 'string' &&
79
+ (ATTACHABLE_STATIONS as readonly string[]).includes(raw)
80
+ );
81
+ }
82
+
83
+ function requireToken(raw: unknown, label: string): string {
84
+ const token = typeof raw === 'string' ? raw.trim() : '';
85
+ if (token === '')
86
+ throw new StationAttachError(`a ${label} bot token is required`, 400);
87
+ if (!TOKEN_RE.test(token))
88
+ throw new StationAttachError(
89
+ `that does not look like a ${label} bot token`,
90
+ 400,
91
+ );
92
+ return token;
93
+ }
94
+
95
+ function rejected(err: unknown, own: boolean, fallback: string): never {
96
+ throw new StationAttachError(
97
+ own && err instanceof Error ? err.message : fallback,
98
+ 400,
99
+ );
100
+ }
101
+
102
+ async function prepareDiscord(raw: unknown): Promise<PreparedAccount> {
103
+ const token = requireToken(raw, 'Discord');
104
+ try {
105
+ const bot = await verifyDiscordBotToken(token);
106
+ if (!bot.messageContent)
107
+ throw new StationAttachError(
108
+ 'this Discord application does not have the Message Content intent enabled. Turn it on under Bot / Privileged Gateway Intents in the developer portal, then attach it again',
109
+ 400,
110
+ );
111
+ return {
112
+ config: { token },
113
+ identity: { userId: bot.userId, username: bot.username },
114
+ };
115
+ } catch (err) {
116
+ if (err instanceof StationAttachError) throw err;
117
+ return rejected(
118
+ err,
119
+ err instanceof DiscordTokenError,
120
+ 'Discord rejected that bot token',
121
+ );
122
+ }
123
+ }
124
+
125
+ async function prepareTelegram(raw: unknown): Promise<PreparedAccount> {
126
+ const token = requireToken(raw, 'Telegram');
127
+ try {
128
+ const bot = await verifyTelegramBotToken(token);
129
+ return {
130
+ config: { token },
131
+ identity: { botId: String(bot.botId), username: bot.username },
132
+ };
133
+ } catch (err) {
134
+ return rejected(
135
+ err,
136
+ err instanceof TelegramTokenError,
137
+ 'Telegram rejected that bot token',
138
+ );
139
+ }
140
+ }
141
+
142
+ export function newXmtpPrivateKey(): string {
143
+ for (;;) {
144
+ const hex = randomBytes(32).toString('hex');
145
+ const value = BigInt(`0x${hex}`);
146
+ if (value > 0n && value < SECP256K1_ORDER) return `0x${hex}`;
147
+ }
148
+ }
149
+
150
+ async function prepareXmtp(verify: VerifyXmtpKey): Promise<PreparedAccount> {
151
+ const privateKey = newXmtpPrivateKey();
152
+ const dbPath = newXmtpDbPath();
153
+ const identity = await verify(privateKey, dbPath).catch((err: unknown) =>
154
+ rejected(
155
+ err,
156
+ err instanceof XmtpAttachError,
157
+ 'Metro could not open an XMTP inbox with the key it generated, so nothing was attached',
158
+ ),
159
+ );
160
+ return {
161
+ config: { privateKey, dbPath },
162
+ identity: { inboxId: identity.inboxId, address: identity.address },
163
+ secret: {
164
+ label: 'xmtp private key',
165
+ value: privateKey,
166
+ note: 'Metro generated this key, opened inbox ' +
167
+ `${identity.inboxId} with it, and stores it with the account. ` +
168
+ 'It is shown once, here, and is never returned by any API again.',
169
+ },
170
+ discard: () => {
171
+ discardXmtpDb(identity.dbPath);
172
+ },
173
+ };
174
+ }
175
+
176
+ export const hookUrl = (webhookId: string, secret: string): string =>
177
+ `${publicBaseOrDefault().replace(/\/+$/, '')}/api/webhooks/${webhookId}/${secret}`;
178
+
179
+ const WEBHOOK_ID_FLOOR = 10n ** 18n;
180
+
181
+ export function newWebhookId(): string {
182
+ const raw = BigInt(`0x${randomBytes(8).toString('hex')}`);
183
+ return String(WEBHOOK_ID_FLOOR + (raw % (9n * WEBHOOK_ID_FLOOR)));
184
+ }
185
+
186
+ export const threemaCallbackUrl = (callbackId: string, token: string): string =>
187
+ `${publicBaseOrDefault().replace(/\/+$/, '')}/api/threema/${callbackId}/${token}`;
188
+
189
+ function requireText(raw: unknown, label: string): string {
190
+ const text = typeof raw === 'string' ? raw.trim() : '';
191
+ if (text === '')
192
+ throw new StationAttachError(`the Threema ${label} is required`, 400);
193
+ return text;
194
+ }
195
+
196
+ async function prepareThreema(input: AttachInput): Promise<PreparedAccount> {
197
+ const gatewayId = requireText(input.gatewayId, 'Gateway ID');
198
+ const secret = requireText(input.secret, 'API secret');
199
+ const privateKey = requireText(input.privateKey, 'private key');
200
+ try {
201
+ const identity = await verifyThreemaGateway({ gatewayId, secret, privateKey });
202
+ const callbackId = newWebhookId();
203
+ const callbackToken = randomBytes(48).toString('base64url');
204
+ return {
205
+ config: {
206
+ gatewayId: identity.gatewayId,
207
+ secret,
208
+ privateKey: parsePrivateKey(privateKey) ?? privateKey,
209
+ callbackId,
210
+ callbackToken,
211
+ createdAt: new Date().toISOString(),
212
+ },
213
+ identity: {
214
+ gatewayId: identity.gatewayId,
215
+ credits: String(identity.credits),
216
+ callback: threemaCallbackUrl(callbackId, callbackToken),
217
+ },
218
+ };
219
+ } catch (err) {
220
+ if (err instanceof StationAttachError) throw err;
221
+ return rejected(
222
+ err,
223
+ err instanceof ThreemaGatewayError,
224
+ 'Threema rejected those Gateway credentials',
225
+ );
226
+ }
227
+ }
228
+
229
+ function prepareWebhook(): PreparedAccount {
230
+ const secret = randomBytes(48).toString('base64url');
231
+ const webhookId = newWebhookId();
232
+ return {
233
+ config: { secret, webhookId, createdAt: new Date().toISOString() },
234
+ identity: { endpoint: hookUrl(webhookId, secret) },
235
+ };
236
+ }
237
+
238
+ export async function prepareAccount(
239
+ input: AttachInput,
240
+ verify: VerifyXmtpKey = verifyXmtpKeyOutOfProcess,
241
+ ): Promise<PreparedAccount> {
242
+ ensureStationDeps(input.station);
243
+ if (input.station === 'discord-bot') return prepareDiscord(input.token);
244
+ if (input.station === 'telegram-bot') return prepareTelegram(input.token);
245
+ if (input.station === 'webhook') return prepareWebhook();
246
+ if (input.station === 'threema') return prepareThreema(input);
247
+ return prepareXmtp(verify);
248
+ }
@@ -0,0 +1,261 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ rmSync,
6
+ writeFileSync,
7
+ } from 'node:fs';
8
+ import { homedir } from 'node:os';
9
+ import { join } from 'node:path';
10
+ import { log } from '@metro-labs/core/log';
11
+ import { writeSecure } from '@metro-labs/core/secure-fs';
12
+ import { trainsDir } from '../boot/paths.js';
13
+ import {
14
+ setAgentMap,
15
+ setAllowlistMap,
16
+ setDisabledAccounts,
17
+ type AgentMap,
18
+ type AgentNameMap,
19
+ type AllowlistMap,
20
+ } from '../agents/map.js';
21
+ import { setKeyMap } from '../agents/keys.js';
22
+ import { STATIONS, type StationName } from '@metro-labs/core/station-names';
23
+
24
+ interface StationTarget {
25
+ file: string;
26
+ fileEnv: string;
27
+ trainImport: string | null;
28
+ }
29
+
30
+ export interface LoadedAccount {
31
+ station: StationName;
32
+ id: string;
33
+ allowlist: string[] | null;
34
+ enabled?: boolean;
35
+ config: Record<string, unknown>;
36
+ }
37
+
38
+ export interface LoadedConnector {
39
+ id: string;
40
+ name: string;
41
+ url: string;
42
+ transport: 'http';
43
+ config: Record<string, unknown>;
44
+ }
45
+
46
+ export interface LoadedAgent {
47
+ id: string;
48
+ name: string;
49
+ accounts: LoadedAccount[];
50
+ key: string | null;
51
+ connectors?: LoadedConnector[];
52
+ }
53
+
54
+ const METRO_DIR = join(homedir(), '.metro');
55
+
56
+ const STATION_TARGETS: Record<StationName, StationTarget> = {
57
+ xmtp: {
58
+ file: 'xmtp-accounts.json',
59
+ fileEnv: 'XMTP_ACCOUNTS_FILE',
60
+ trainImport: '@metro-labs/xmtp/train',
61
+ },
62
+ 'telegram-bot': {
63
+ file: 'telegram-bot-accounts.json',
64
+ fileEnv: 'TELEGRAM_BOT_ACCOUNTS_FILE',
65
+ trainImport: '@metro-labs/telegram-bot/train',
66
+ },
67
+ 'telegram': {
68
+ file: 'telegram-accounts.json',
69
+ fileEnv: 'TELEGRAM_ACCOUNTS_FILE',
70
+ trainImport: '@metro-labs/telegram/train',
71
+ },
72
+ 'discord-bot': {
73
+ file: 'discord-bot-accounts.json',
74
+ fileEnv: 'DISCORD_BOT_ACCOUNTS_FILE',
75
+ trainImport: '@metro-labs/discord-bot/train',
76
+ },
77
+ whatsapp: {
78
+ file: 'whatsapp-accounts.json',
79
+ fileEnv: 'WHATSAPP_ACCOUNTS_FILE',
80
+ trainImport: '@metro-labs/whatsapp/train',
81
+ },
82
+ threema: {
83
+ file: 'threema-accounts.json',
84
+ fileEnv: 'THREEMA_ACCOUNTS_FILE',
85
+ trainImport: '@metro-labs/threema/train',
86
+ },
87
+ webhook: {
88
+ file: 'webhook-accounts.json',
89
+ fileEnv: 'WEBHOOK_ACCOUNTS_FILE',
90
+ trainImport: null,
91
+ },
92
+ };
93
+
94
+ export function accountFilePath(station: StationName): string {
95
+ const target = STATION_TARGETS[station];
96
+ return process.env[target.fileEnv] ?? join(METRO_DIR, target.file);
97
+ }
98
+
99
+ export type StationSource = () => Promise<LoadedAgent[]>;
100
+
101
+ export const MOVABLE_STATIONS = new Set<StationName>(
102
+ STATIONS.filter((s) => s !== 'webhook'),
103
+ );
104
+
105
+ function trainStubPath(station: StationName): string {
106
+ return join(trainsDir(), `${station}.ts`);
107
+ }
108
+
109
+ function currentText(path: string): string | null {
110
+ try {
111
+ return readFileSync(path, 'utf8');
112
+ } catch {
113
+ return null;
114
+ }
115
+ }
116
+
117
+ export function writeIfChanged(path: string, content: string): boolean {
118
+ if (currentText(path) === content) return false;
119
+ writeFileSync(path, content);
120
+ return true;
121
+ }
122
+
123
+ interface WrittenStations {
124
+ active: Map<StationName, number>;
125
+ changed: StationName[];
126
+ }
127
+
128
+ function writeStations(list: LoadedAgent[]): WrittenStations {
129
+ mkdirSync(METRO_DIR, { recursive: true });
130
+ mkdirSync(trainsDir(), { recursive: true });
131
+
132
+ const byStation = new Map<StationName, LoadedAccount[]>();
133
+ const map: AgentMap = {};
134
+ const names: AgentNameMap = {};
135
+ const allow: AllowlistMap = {};
136
+ const disabled = new Set<string>();
137
+ for (const agent of list) {
138
+ names[agent.id] = agent.name;
139
+ for (const a of agent.accounts) {
140
+ if (!(a.station in STATION_TARGETS)) {
141
+ log.error(
142
+ { station: a.station, id: a.id },
143
+ 'unknown station — this metro is older or newer than the row; skipping it',
144
+ );
145
+ continue;
146
+ }
147
+ map[`${a.station}/${a.id}`] = agent.id;
148
+ if (a.allowlist) allow[`${a.station}/${a.id}`] = a.allowlist;
149
+ if (a.enabled === false) {
150
+ disabled.add(`${a.station}/${a.id}`);
151
+ continue;
152
+ }
153
+ const cur = byStation.get(a.station);
154
+ if (cur) cur.push(a);
155
+ else byStation.set(a.station, [a]);
156
+ }
157
+ }
158
+ setAgentMap(map, names);
159
+ setAllowlistMap(allow);
160
+ setDisabledAccounts(disabled);
161
+
162
+ const active = new Map<StationName, number>();
163
+ const changed: StationName[] = [];
164
+ for (const [station, accts] of byStation) {
165
+ const records = accts.map((a) => ({ id: a.id, ...a.config }));
166
+ const path = accountFilePath(station);
167
+ const content = JSON.stringify(records, null, 2);
168
+ if (currentText(path) !== content) {
169
+ writeSecure(path, content);
170
+ changed.push(station);
171
+ }
172
+ const { trainImport } = STATION_TARGETS[station];
173
+ if (trainImport !== null)
174
+ writeIfChanged(trainStubPath(station), `import '${trainImport}';\n`);
175
+ active.set(station, accts.length);
176
+ }
177
+ return { active, changed };
178
+ }
179
+
180
+ const BLANKED = '[]\n';
181
+
182
+ function pruneStations(present: Map<StationName, number>): StationName[] {
183
+ const removed: StationName[] = [];
184
+ for (const station of Object.keys(STATION_TARGETS) as StationName[]) {
185
+ if (present.has(station)) continue;
186
+ const hasTrain = STATION_TARGETS[station].trainImport !== null;
187
+ const stub = trainStubPath(station);
188
+ const stubGone = !hasTrain || !existsSync(stub);
189
+ const file = currentText(accountFilePath(station));
190
+ const fileInert = file === null || file === BLANKED;
191
+ if (stubGone && fileInert) continue;
192
+ if (!fileInert) writeSecure(accountFilePath(station), BLANKED);
193
+ if (hasTrain) rmSync(stub, { force: true });
194
+ removed.push(station);
195
+ }
196
+ return removed;
197
+ }
198
+
199
+ const stationLabels = (m: Map<StationName, number>): string[] =>
200
+ [...m].map(([station, n]) => `${station}(${n})`);
201
+
202
+ function applyKeyMap(list: LoadedAgent[]): void {
203
+ lastKey = list.find((a) => a.key !== null)?.key ?? null;
204
+ setKeyMap(
205
+ list.flatMap((agent) =>
206
+ agent.key === null ? [] : [{ key: agent.key, agentId: agent.id }],
207
+ ),
208
+ );
209
+ }
210
+
211
+ let lastKey: string | null = null;
212
+
213
+ export function localAgentKey(): string | null {
214
+ return lastKey;
215
+ }
216
+
217
+ async function loadAndWrite(
218
+ source: StationSource,
219
+ ): Promise<WrittenStations & { agents: number }> {
220
+ const list = await source();
221
+ applyKeyMap(list);
222
+ return { ...writeStations(list), agents: list.length };
223
+ }
224
+
225
+ export interface MaterializeOptions {
226
+ allowEmpty?: boolean;
227
+ }
228
+
229
+ export async function materializeFrom(
230
+ source: StationSource,
231
+ opts: MaterializeOptions = {},
232
+ ): Promise<void> {
233
+ const { active, agents: found } = await loadAndWrite(source);
234
+
235
+ if (found === 0 && opts.allowEmpty !== true)
236
+ throw new Error('no agents found — nothing to materialize');
237
+ const removed = pruneStations(active);
238
+ log.info(
239
+ { stations: stationLabels(active), agents: found, removed },
240
+ 'materialized station accounts',
241
+ );
242
+ }
243
+
244
+ export interface ReloadedStations {
245
+ active: StationName[];
246
+ removed: StationName[];
247
+ changed: StationName[];
248
+ }
249
+
250
+ export async function reloadFrom(
251
+ source: StationSource,
252
+ ): Promise<ReloadedStations> {
253
+ const { active, changed } = await loadAndWrite(source);
254
+ const removed = pruneStations(active);
255
+ if (removed.length > 0 || changed.length > 0)
256
+ log.info(
257
+ { stations: stationLabels(active), removed, changed },
258
+ 'reloaded station accounts',
259
+ );
260
+ return { active: [...active.keys()], removed, changed };
261
+ }
@@ -0,0 +1,37 @@
1
+ import type { Station } from '@metro-labs/core/stations/types';
2
+ import { Line } from '@metro-labs/core/lines';
3
+ import { xmtpStation } from '@metro-labs/xmtp';
4
+ import { telegramBotStation } from '@metro-labs/telegram-bot';
5
+ import { telegramStation } from '@metro-labs/telegram';
6
+ import { discordBotStation } from '@metro-labs/discord-bot';
7
+ import { whatsappStation } from '@metro-labs/whatsapp';
8
+ import { threemaStation } from '@metro-labs/threema';
9
+ import { webhookStation } from '@metro-labs/webhook';
10
+
11
+ export const STATIONS: readonly Station[] = [
12
+ xmtpStation,
13
+ telegramBotStation,
14
+ telegramStation,
15
+ discordBotStation,
16
+ whatsappStation,
17
+ threemaStation,
18
+ webhookStation,
19
+ ];
20
+
21
+ export const stationByName = (name: string): Station | undefined =>
22
+ STATIONS.find((s) => s.name === name);
23
+
24
+ export const stationForLine = (line: string): Station | undefined => {
25
+ const s = Line.station(line);
26
+ return s ? stationByName(s) : undefined;
27
+ };
28
+
29
+ export const accountStationNames = (): string[] =>
30
+ STATIONS.filter((s) => s.hasAccounts).map((s) => s.name);
31
+
32
+ export const accountStationCapabilities = (): Record<string, string[]> => {
33
+ const out: Record<string, string[]> = {};
34
+ for (const s of STATIONS)
35
+ if (s.hasAccounts) out[s.name] = [...s.messageVerbs].sort();
36
+ return out;
37
+ };
@@ -0,0 +1,82 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { log } from '@metro-labs/core/log';
5
+ import { isRecord } from '@metro-labs/core/is-record';
6
+ import type { StationName } from '@metro-labs/core/station-names';
7
+ import { knownAccounts } from '../agents/map.js';
8
+
9
+ const INSTALL_TIMEOUT_MS = 15 * 60_000;
10
+
11
+ export interface RuntimeManifest {
12
+ core: Record<string, string>;
13
+ stations: Record<string, Record<string, string>>;
14
+ }
15
+
16
+ export interface RuntimeStore {
17
+ dir: string;
18
+ manifest: RuntimeManifest;
19
+ }
20
+
21
+ const ranges = (raw: unknown): Record<string, string> =>
22
+ isRecord(raw)
23
+ ? Object.fromEntries(Object.entries(raw).filter((e): e is [string, string] => typeof e[1] === 'string'))
24
+ : {};
25
+
26
+ export function readManifest(path: string): RuntimeManifest {
27
+ const raw: unknown = JSON.parse(readFileSync(path, 'utf8'));
28
+ if (!isRecord(raw)) throw new Error(`${path} is not a runtime manifest`);
29
+ const stations = isRecord(raw.stations) ? raw.stations : {};
30
+ return {
31
+ core: ranges(raw.core),
32
+ stations: Object.fromEntries(Object.entries(stations).map(([name, deps]) => [name, ranges(deps)])),
33
+ };
34
+ }
35
+
36
+ export function runtimeStore(): RuntimeStore | null {
37
+ const dir = process.env.METRO_RUNTIME_STORE?.trim() ?? '';
38
+ const manifest = process.env.METRO_RUNTIME_MANIFEST?.trim() ?? '';
39
+ if (dir === '' || manifest === '') return null;
40
+ return { dir, manifest: readManifest(manifest) };
41
+ }
42
+
43
+ export function dependenciesFor(manifest: RuntimeManifest, stations: Iterable<string>): Record<string, string> {
44
+ const out: Record<string, string> = { ...manifest.core };
45
+ for (const station of new Set(stations)) Object.assign(out, manifest.stations[station] ?? {});
46
+ return Object.fromEntries(Object.entries(out).sort(([a], [b]) => a.localeCompare(b)));
47
+ }
48
+
49
+ const packageText = (deps: Record<string, string>): string =>
50
+ `${JSON.stringify({ name: 'metro-runtime', private: true, dependencies: deps }, null, 2)}\n`;
51
+
52
+ function current(dir: string): string | null {
53
+ try {
54
+ return readFileSync(join(dir, 'package.json'), 'utf8');
55
+ } catch {
56
+ return null;
57
+ }
58
+ }
59
+
60
+ export function installRuntime(store: RuntimeStore, stations: Iterable<string>): boolean {
61
+ const wanted = packageText(dependenciesFor(store.manifest, stations));
62
+ const installed = existsSync(join(store.dir, 'node_modules', '.metro-installed'));
63
+ if (installed && current(store.dir) === wanted) return false;
64
+ mkdirSync(store.dir, { recursive: true });
65
+ writeFileSync(join(store.dir, 'package.json'), wanted);
66
+ log.info({ dir: store.dir }, 'runtime: installing the channel SDKs this machine needs');
67
+ const run = spawnSync('bun', ['install', '--no-summary', '--no-progress'], {
68
+ cwd: store.dir,
69
+ stdio: ['ignore', 'inherit', 'inherit'],
70
+ timeout: INSTALL_TIMEOUT_MS,
71
+ });
72
+ if (run.error !== undefined || run.status !== 0)
73
+ throw new Error(`bun install failed in ${store.dir}: ${run.error?.message ?? `exit ${String(run.status)}`}`);
74
+ writeFileSync(join(store.dir, 'node_modules', '.metro-installed'), `${new Date().toISOString()}\n`);
75
+ return true;
76
+ }
77
+
78
+ export function ensureStationDeps(station: StationName): void {
79
+ const store = runtimeStore();
80
+ if (store === null) return;
81
+ installRuntime(store, [...knownAccounts().map((a) => a.station), station]);
82
+ }