@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,21 @@
1
+ const PREVIEW = /^([a-z0-9-]+--)?metro-ui\.netlify\.app$/;
2
+
3
+ export function allowedWebHost(host: string): boolean {
4
+ if (host === 'metro.box' || host === 'localhost' || host === '127.0.0.1')
5
+ return true;
6
+ return PREVIEW.test(host);
7
+ }
8
+
9
+ export function validateReturnTo(returnTo: string): boolean {
10
+ let url: URL;
11
+ try {
12
+ url = new URL(returnTo);
13
+ } catch {
14
+ return false;
15
+ }
16
+ const host = url.hostname;
17
+ if (url.protocol === 'http:' && (host === 'localhost' || host === '127.0.0.1'))
18
+ return true;
19
+ if (url.protocol !== 'https:') return false;
20
+ return allowedWebHost(host);
21
+ }
@@ -0,0 +1,150 @@
1
+ import type { IncomingMessage } from 'node:http';
2
+ import { createPublicKey, verify, type JsonWebKey, type KeyObject } from 'node:crypto';
3
+
4
+ export const WORKOS_API = 'https://api.workos.com';
5
+ const ORG_RE = /^org_[A-Za-z0-9]{10,64}$/;
6
+ export const isOrganizationId = (value: string): boolean => ORG_RE.test(value);
7
+ export const WORKOS_ISSUER = 'https://api.workos.com';
8
+ export const DEFAULT_CLIENT_ID = 'client_01M2TJJJ6RM9CT081QB4XZK3G2';
9
+ const SKEW_S = 60;
10
+ const REFETCH_COOLDOWN_MS = 60_000;
11
+ const FETCH_MS = 10_000;
12
+
13
+ export interface Session {
14
+ userId: string;
15
+ sessionId: string;
16
+ organization: string | null;
17
+ role: string | null;
18
+ expiresAt: number;
19
+ }
20
+
21
+ export interface KeyStore {
22
+ read: () => string | null;
23
+ write: (text: string) => void;
24
+ }
25
+
26
+ export const filled = (value: string | undefined): string | null => {
27
+ const text = value?.trim() ?? '';
28
+ return text === '' ? null : text;
29
+ };
30
+
31
+ export const clientId = (env: NodeJS.ProcessEnv = process.env): string => filled(env.WORKOS_CLIENT_ID) ?? DEFAULT_CLIENT_ID;
32
+ export const workosBase = (env: NodeJS.ProcessEnv = process.env): string => filled(env.WORKOS_API_BASE) ?? WORKOS_API;
33
+ export const jwksUrl = (id: string, base = WORKOS_API): string => `${base}/sso/jwks/${id}`;
34
+
35
+ function jwksKeys(text: string): Map<string, KeyObject> {
36
+ const keys = new Map<string, KeyObject>();
37
+ const parsed: unknown = JSON.parse(text);
38
+ const list = typeof parsed === 'object' && parsed !== null ? (parsed as { keys?: unknown }).keys : undefined;
39
+ if (!Array.isArray(list)) return keys;
40
+ for (const jwk of list) {
41
+ if (typeof jwk !== 'object' || jwk === null) continue;
42
+ const { kid, kty } = jwk as { kid?: unknown; kty?: unknown };
43
+ if (typeof kid !== 'string' || kty !== 'RSA') continue;
44
+ try {
45
+ keys.set(kid, createPublicKey({ key: jwk as JsonWebKey, format: 'jwk' }));
46
+ } catch {
47
+ continue;
48
+ }
49
+ }
50
+ return keys;
51
+ }
52
+
53
+ export class SigningKeys {
54
+ private keys = new Map<string, KeyObject>();
55
+ private lastMiss = 0;
56
+ private loaded = false;
57
+
58
+ constructor(
59
+ private readonly url: string,
60
+ private readonly store?: KeyStore,
61
+ ) {}
62
+
63
+ private load(): void {
64
+ if (this.loaded) return;
65
+ this.loaded = true;
66
+ const cached = this.store?.read() ?? null;
67
+ if (cached !== null) this.keys = jwksKeys(cached);
68
+ }
69
+
70
+ private async refresh(now: number): Promise<void> {
71
+ if (now - this.lastMiss < REFETCH_COOLDOWN_MS) return;
72
+ const res = await fetch(this.url, { signal: AbortSignal.timeout(FETCH_MS) });
73
+ if (!res.ok) throw new Error(`the signing keys answered ${String(res.status)}`);
74
+ const text = await res.text();
75
+ const keys = jwksKeys(text);
76
+ if (keys.size === 0) throw new Error('the signing keys answered no RSA key');
77
+ this.keys = keys;
78
+ this.store?.write(text);
79
+ }
80
+
81
+ async keyFor(kid: string, now = Date.now()): Promise<KeyObject | null> {
82
+ this.load();
83
+ const held = this.keys.get(kid);
84
+ if (held !== undefined) return held;
85
+ await this.refresh(now);
86
+ const fetched = this.keys.get(kid);
87
+ if (fetched === undefined) this.lastMiss = now;
88
+ return fetched ?? null;
89
+ }
90
+ }
91
+
92
+ const b64url = (text: string): Buffer => Buffer.from(text.replace(/-/g, '+').replace(/_/g, '/'), 'base64');
93
+
94
+ function jsonPart(text: string): Record<string, unknown> | null {
95
+ try {
96
+ const parsed: unknown = JSON.parse(b64url(text).toString('utf8'));
97
+ return typeof parsed === 'object' && parsed !== null ? (parsed as Record<string, unknown>) : null;
98
+ } catch {
99
+ return null;
100
+ }
101
+ }
102
+
103
+ const str = (value: unknown): string | null => (typeof value === 'string' && value !== '' ? value : null);
104
+
105
+ const issuedBy = (iss: unknown, issuer: string): boolean => typeof iss === 'string' && (iss === issuer || iss.startsWith(`${issuer}/`));
106
+
107
+ function sessionOf(claims: Record<string, unknown>, issuer: string, now: number): Session | null {
108
+ const exp = typeof claims.exp === 'number' ? claims.exp : null;
109
+ const userId = str(claims.sub);
110
+ const sessionId = str(claims.sid);
111
+ if (!issuedBy(claims.iss, issuer) || exp === null || userId === null || sessionId === null) return null;
112
+ if (exp * 1000 < now - SKEW_S * 1000) return null;
113
+ return { userId, sessionId, organization: str(claims.org_id), role: str(claims.role), expiresAt: exp * 1000 };
114
+ }
115
+
116
+ interface Parts {
117
+ signed: string;
118
+ body: string;
119
+ signature: Buffer;
120
+ kid: string;
121
+ }
122
+
123
+ function parseToken(token: string): Parts | null {
124
+ const [head, body, sig] = token.split('.');
125
+ if (head === undefined || body === undefined || sig === undefined) return null;
126
+ const header = jsonPart(head);
127
+ const kid = str(header?.kid);
128
+ if (header?.alg !== 'RS256' || kid === null) return null;
129
+ return { signed: `${head}.${body}`, body, signature: b64url(sig), kid };
130
+ }
131
+
132
+ export async function verifyToken(token: string, keys: SigningKeys, issuer = WORKOS_ISSUER, now = Date.now()): Promise<Session | null> {
133
+ const parts = parseToken(token);
134
+ if (parts === null) return null;
135
+ const key = await keys.keyFor(parts.kid, now).catch(() => null);
136
+ if (key === null || !verify('sha256', Buffer.from(parts.signed), key, parts.signature)) return null;
137
+ const claims = jsonPart(parts.body);
138
+ return claims === null ? null : sessionOf(claims, issuer, now);
139
+ }
140
+
141
+ export function bearerToken(req: IncomingMessage): string | null {
142
+ const header = req.headers.authorization ?? '';
143
+ const [scheme, token] = header.trim().split(/\s+/);
144
+ return scheme?.toLowerCase() === 'bearer' && token?.includes('.') === true ? token : null;
145
+ }
146
+
147
+ export async function bearerSession(req: IncomingMessage, keys: SigningKeys, issuer = WORKOS_ISSUER): Promise<Session | null> {
148
+ const token = bearerToken(req);
149
+ return token === null ? null : verifyToken(token, keys, issuer);
150
+ }
@@ -0,0 +1,26 @@
1
+ {
2
+ "name": "@metro-labs/telegram",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "exports": {
7
+ ".": "./src/station.ts",
8
+ "./train": "./src/index.ts",
9
+ "./login": "./src/login.ts"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "test": "tsc --noEmit && bun test test/"
14
+ },
15
+ "dependencies": {
16
+ "@metro-labs/core": "workspace:*",
17
+ "@mtcute/bun": "0.30.1"
18
+ },
19
+ "devDependencies": {
20
+ "@stage-labs/config": "0.1.0-beta.2",
21
+ "eslint": "^10.3.0",
22
+ "knip": "^6.15.0",
23
+ "madge": "^8.0.0",
24
+ "typescript": "^5"
25
+ }
26
+ }
@@ -0,0 +1,80 @@
1
+ import { homedir } from 'node:os';
2
+ import { join } from 'node:path';
3
+ import {
4
+ makeAccountStore,
5
+ resolveAccountId,
6
+ type Die,
7
+ } from '@metro-labs/core/stations/account-store';
8
+ import type { UserAccount } from './types.js';
9
+
10
+ const ACCOUNTS_FILE =
11
+ process.env.TELEGRAM_ACCOUNTS_FILE ??
12
+ join(homedir(), '.metro', 'telegram-accounts.json');
13
+
14
+ const isSignedInt = (s: string): boolean => /^-?\d+$/.test(s);
15
+ const isTopic = (s: string): boolean => /^\d+$/.test(s);
16
+
17
+ function validateAccount(a: UserAccount, seen: Set<string>, die: Die): void {
18
+ if (!a.id) die('account missing id');
19
+ if (!a.session || typeof a.session !== 'string')
20
+ die(`account '${a.id}' missing session`);
21
+ if (!Number.isInteger(a.apiId) || (a.apiId ?? 0) <= 0)
22
+ die(`account '${a.id}' missing apiId`);
23
+ if (!a.apiHash || typeof a.apiHash !== 'string')
24
+ die(`account '${a.id}' missing apiHash`);
25
+ if (seen.has(a.id)) die(`duplicate account id '${a.id}'`);
26
+ seen.add(a.id);
27
+ }
28
+
29
+ export const { loadAccounts } = makeAccountStore<UserAccount>({
30
+ prefix: 'telegram',
31
+ file: ACCOUNTS_FILE,
32
+ validate(raw, die) {
33
+ const seen = new Set<string>();
34
+ for (const a of raw) validateAccount(a, seen, die);
35
+ },
36
+ });
37
+
38
+ export const accounts = new Map<string, UserAccount>();
39
+
40
+ export function accountFor(args: { account?: string; line?: string }): string {
41
+ return resolveAccountId(accounts, args, (line) => targetOf(line)?.accountId);
42
+ }
43
+
44
+ export function lineOf(
45
+ accountId: string,
46
+ chatId: number | string,
47
+ topicId?: number,
48
+ ): string {
49
+ const tail = topicId !== undefined ? `${chatId}/${topicId}` : `${chatId}`;
50
+ return `metro://telegram/${accountId}/${tail}`;
51
+ }
52
+
53
+ interface Target {
54
+ accountId: string;
55
+ chatId: number;
56
+ topicId?: number;
57
+ }
58
+
59
+ function splitScoped(path: string[]): { accountId: string; rest: string[] } {
60
+ const first = path[0];
61
+ if (path.length >= 2 && first !== undefined && !isSignedInt(first))
62
+ return { accountId: first, rest: path.slice(1) };
63
+ return { accountId: 'default', rest: path };
64
+ }
65
+
66
+ export function targetOf(line: string): Target | undefined {
67
+ const prefix = 'metro://telegram/';
68
+ if (!line.startsWith(prefix)) return undefined;
69
+ const path = line.slice(prefix.length).split('/').filter(Boolean);
70
+ const { accountId, rest } = splitScoped(path);
71
+ const [chatId, topicId] = rest;
72
+ if (rest.length < 1 || rest.length > 2 || chatId === undefined) return undefined;
73
+ if (!isSignedInt(chatId)) return undefined;
74
+ if (topicId !== undefined && !isTopic(topicId)) return undefined;
75
+ return {
76
+ accountId,
77
+ chatId: Number(chatId),
78
+ ...(topicId !== undefined ? { topicId: Number(topicId) } : {}),
79
+ };
80
+ }
@@ -0,0 +1,254 @@
1
+ import { TrainError } from '@metro-labs/core/train-error';
2
+ import { errMsg } from '@metro-labs/core/log';
3
+ import {
4
+ makeStation,
5
+ respond,
6
+ type CallMsg,
7
+ type StationHandler,
8
+ } from '@metro-labs/core/stations/station-runtime';
9
+ import { accountFor, accounts, targetOf } from './accounts.js';
10
+ import type { UserClient } from './client.js';
11
+ import { normalizeTelegramUser } from './normalize.js';
12
+ import {
13
+ sendAttachments,
14
+ type CanonicalAttachment,
15
+ } from './media-actions.js';
16
+ import { clampLimit, shapeHistory } from './history.js';
17
+ import { fetchMembers, isRestricted, restrictedMemberList } from './members.js';
18
+ import {
19
+ groupAddMembers,
20
+ groupCreate,
21
+ groupInviteLink,
22
+ groupRemoveMembers,
23
+ } from './group.js';
24
+
25
+ type Args = Record<string, unknown>;
26
+
27
+ interface Resolved {
28
+ accountId: string;
29
+ client: UserClient;
30
+ chatId: number;
31
+ topicId?: number;
32
+ }
33
+
34
+ type ClientFor = (accountId: string) => UserClient;
35
+
36
+ function str(v: unknown): string | undefined {
37
+ return typeof v === 'string' ? v : undefined;
38
+ }
39
+
40
+ function lineOfArgs(args: Args): string {
41
+ const line = str(args.line);
42
+ if (!line) throw new TrainError('bad_request', 'missing line');
43
+ return line;
44
+ }
45
+
46
+ function resolve(args: Args, clientFor: ClientFor): Resolved {
47
+ const line = lineOfArgs(args);
48
+ const target = targetOf(line);
49
+ if (!target) throw new TrainError('bad_request', `bad line '${line}'`);
50
+ const accountId = accountFor({ account: str(args.account), line });
51
+ return {
52
+ accountId,
53
+ client: clientFor(accountId),
54
+ chatId: target.chatId,
55
+ ...(target.topicId !== undefined ? { topicId: target.topicId } : {}),
56
+ };
57
+ }
58
+
59
+ async function guard<T>(run: () => Promise<T>): Promise<T> {
60
+ try {
61
+ return await run();
62
+ } catch (e) {
63
+ const msg = errMsg(e);
64
+ if (/FLOOD_WAIT|SLOWMODE_WAIT/i.test(msg))
65
+ throw new TrainError('rate_limited', msg);
66
+ throw new TrainError('telegram_call', msg);
67
+ }
68
+ }
69
+
70
+ function attachmentsOf(args: Args): CanonicalAttachment[] {
71
+ const raw = args.attachments;
72
+ return Array.isArray(raw) ? (raw as CanonicalAttachment[]) : [];
73
+ }
74
+
75
+ function makeSend(clientFor: ClientFor): StationHandler {
76
+ return async (id, args) => {
77
+ const { accountId, client, chatId, topicId } = resolve(args, clientFor);
78
+ const text = str(args.text) ?? '';
79
+ const replyTo = str(args.replyTo);
80
+ const explicitReplyTo = replyTo ? Number(replyTo) : undefined;
81
+ const replyParam = explicitReplyTo ?? topicId;
82
+ const attachments = attachmentsOf(args);
83
+ if (attachments.length) {
84
+ const sent = await guard(() =>
85
+ sendAttachments({ client, chatId, replyTo: replyParam }, attachments, text),
86
+ );
87
+ respond(id, {
88
+ result: {
89
+ messageId: String(sent.message.id),
90
+ account: accountId,
91
+ attachments: sent.delivered,
92
+ },
93
+ });
94
+ return;
95
+ }
96
+ const peer = await guard(() => client.tg.resolvePeer(chatId));
97
+ const sent = await guard(() =>
98
+ client.tg.sendText(
99
+ peer,
100
+ text,
101
+ replyParam !== undefined ? { replyTo: replyParam } : undefined,
102
+ ),
103
+ );
104
+ respond(id, {
105
+ result: { messageId: String(sent.id), account: accountId },
106
+ });
107
+ };
108
+ }
109
+
110
+ function makeReact(clientFor: ClientFor): StationHandler {
111
+ return async (id, args) => {
112
+ const { accountId, client, chatId } = resolve(args, clientFor);
113
+ const message = Number(str(args.messageId));
114
+ const raw = str(args.emoji);
115
+ const emoji = raw === undefined || raw === '' ? null : raw;
116
+ await guard(() =>
117
+ client.tg.sendReaction({ chatId, message, emoji }),
118
+ );
119
+ respond(id, { result: { ok: true, account: accountId } });
120
+ };
121
+ }
122
+
123
+ function makeEdit(clientFor: ClientFor): StationHandler {
124
+ return async (id, args) => {
125
+ const { accountId, client, chatId } = resolve(args, clientFor);
126
+ const message = Number(str(args.messageId));
127
+ const text = str(args.text) ?? '';
128
+ await guard(() => client.tg.editMessage({ chatId, message, text }));
129
+ respond(id, { result: { ok: true, account: accountId } });
130
+ };
131
+ }
132
+
133
+ function makeDelete(clientFor: ClientFor): StationHandler {
134
+ return async (id, args) => {
135
+ const { accountId, client, chatId } = resolve(args, clientFor);
136
+ const message = Number(str(args.messageId));
137
+ await guard(() =>
138
+ client.tg.deleteMessagesById(chatId, [message], { revoke: true }),
139
+ );
140
+ respond(id, { result: { ok: true, account: accountId } });
141
+ };
142
+ }
143
+
144
+ function makeRead(clientFor: ClientFor): StationHandler {
145
+ return async (id, args) => {
146
+ const { accountId, client, chatId } = resolve(args, clientFor);
147
+ const limit = clampLimit(args.limit);
148
+ const before = str(args.before);
149
+ const beforeId = before ? Number(before) : undefined;
150
+ const offset =
151
+ beforeId !== undefined && Number.isFinite(beforeId)
152
+ ? { offset: { id: beforeId, date: 0 } }
153
+ : {};
154
+ const page = await guard(() =>
155
+ client.tg.getHistory(chatId, { limit, ...offset }),
156
+ );
157
+ respond(id, { result: shapeHistory(accountId, chatId, page) });
158
+ };
159
+ }
160
+
161
+ function makeListMembers(clientFor: ClientFor): StationHandler {
162
+ return async (id, args) => {
163
+ const { client, chatId } = resolve(args, clientFor);
164
+ const limit = typeof args.limit === 'number' ? args.limit : undefined;
165
+ try {
166
+ respond(id, { result: await fetchMembers(client, chatId, limit) });
167
+ } catch (e) {
168
+ const msg = errMsg(e);
169
+ if (isRestricted(msg)) {
170
+ respond(id, { result: restrictedMemberList(msg) });
171
+ return;
172
+ }
173
+ throw e;
174
+ }
175
+ };
176
+ }
177
+
178
+ function makeGroupCreate(clientFor: ClientFor): StationHandler {
179
+ return async (id, args) => {
180
+ const accountId = accountFor({ account: str(args.account) });
181
+ const client = clientFor(accountId);
182
+ respond(id, {
183
+ result: await guard(() => groupCreate(client, accountId, args)),
184
+ });
185
+ };
186
+ }
187
+
188
+ function makeGroupAdd(clientFor: ClientFor): StationHandler {
189
+ return async (id, args) => {
190
+ const { client, chatId } = resolve(args, clientFor);
191
+ const line = lineOfArgs(args);
192
+ respond(id, {
193
+ result: await guard(() => groupAddMembers(client, chatId, line, args)),
194
+ });
195
+ };
196
+ }
197
+
198
+ function makeGroupRemove(clientFor: ClientFor): StationHandler {
199
+ return async (id, args) => {
200
+ const { client, chatId } = resolve(args, clientFor);
201
+ const line = lineOfArgs(args);
202
+ respond(id, {
203
+ result: await guard(() => groupRemoveMembers(client, chatId, line, args)),
204
+ });
205
+ };
206
+ }
207
+
208
+ function makeGroupInvite(clientFor: ClientFor): StationHandler {
209
+ return async (id, args) => {
210
+ const { client, chatId } = resolve(args, clientFor);
211
+ const line = lineOfArgs(args);
212
+ respond(id, {
213
+ result: await guard(() => groupInviteLink(client, chatId, line)),
214
+ });
215
+ };
216
+ }
217
+
218
+ function makeAccounts(clientFor: ClientFor): StationHandler {
219
+ return (id) => {
220
+ const list = [...accounts.values()].map((a) => {
221
+ const me = clientFor(a.id).self();
222
+ const username = me?.username ?? null;
223
+ return {
224
+ id: a.id,
225
+ handle: username === null ? me?.phone ?? null : `@${username}`,
226
+ url: username === null ? null : `https://t.me/${username}`,
227
+ owner: a.owner ?? null,
228
+ };
229
+ });
230
+ respond(id, { result: { accounts: list } });
231
+ return Promise.resolve();
232
+ };
233
+ }
234
+
235
+ export function makeHandleCall(
236
+ clientFor: ClientFor,
237
+ ): (msg: CallMsg) => Promise<void> {
238
+ return makeStation({
239
+ handlers: {
240
+ accounts: makeAccounts(clientFor),
241
+ send: makeSend(clientFor),
242
+ react: makeReact(clientFor),
243
+ edit: makeEdit(clientFor),
244
+ delete: makeDelete(clientFor),
245
+ read: makeRead(clientFor),
246
+ listMembers: makeListMembers(clientFor),
247
+ groupCreate: makeGroupCreate(clientFor),
248
+ groupAddMembers: makeGroupAdd(clientFor),
249
+ groupRemoveMembers: makeGroupRemove(clientFor),
250
+ groupInviteLink: makeGroupInvite(clientFor),
251
+ },
252
+ normalize: normalizeTelegramUser,
253
+ });
254
+ }
@@ -0,0 +1,87 @@
1
+ import type { Message } from '@mtcute/bun';
2
+ import { FileLocation } from '@mtcute/bun';
3
+ import {
4
+ saveBufferToCache,
5
+ type SavedAttachment,
6
+ } from '@metro-labs/core/stations/attachments';
7
+ import type { UserClient } from './client.js';
8
+
9
+ type Media = NonNullable<Message['media']>;
10
+ export type DownloadableMedia = Media & FileLocation;
11
+
12
+ export function isDownloadable(media: Media): media is DownloadableMedia {
13
+ return media instanceof FileLocation;
14
+ }
15
+
16
+ interface MediaMeta {
17
+ mime?: string;
18
+ name?: string;
19
+ }
20
+
21
+ function fallbackMime(media: DownloadableMedia): string | undefined {
22
+ const type = (media as { type?: unknown }).type;
23
+ if (type === 'photo') return 'image/jpeg';
24
+ if (type === 'sticker') return 'image/webp';
25
+ return undefined;
26
+ }
27
+
28
+ function metaOf(media: DownloadableMedia): MediaMeta {
29
+ const withMime = media as { mimeType?: unknown };
30
+ const withName = media as { fileName?: unknown };
31
+ const mime =
32
+ (typeof withMime.mimeType === 'string' ? withMime.mimeType : undefined) ??
33
+ fallbackMime(media);
34
+ const name =
35
+ typeof withName.fileName === 'string' ? withName.fileName : undefined;
36
+ return {
37
+ ...(mime ? { mime } : {}),
38
+ ...(name ? { name } : {}),
39
+ };
40
+ }
41
+
42
+ export interface PendingDescriptor {
43
+ kind: string;
44
+ name?: string;
45
+ }
46
+
47
+ const KIND_BY_TYPE: Record<string, string> = {
48
+ photo: 'image',
49
+ sticker: 'image',
50
+ voice: 'audio',
51
+ audio: 'audio',
52
+ video: 'video',
53
+ video_note: 'video',
54
+ animation: 'video',
55
+ };
56
+
57
+ function mediaType(media: DownloadableMedia): string {
58
+ const type = (media as { type?: unknown }).type;
59
+ return typeof type === 'string' ? type : '';
60
+ }
61
+
62
+ function kindOf(media: DownloadableMedia, mime: string | undefined): string {
63
+ const byType = KIND_BY_TYPE[mediaType(media)];
64
+ if (byType) return byType;
65
+ if (mime?.startsWith('image/')) return 'image';
66
+ if (mime?.startsWith('audio/')) return 'audio';
67
+ if (mime?.startsWith('video/')) return 'video';
68
+ return 'file';
69
+ }
70
+
71
+ export function pendingDescriptorOf(media: DownloadableMedia): PendingDescriptor {
72
+ const meta = metaOf(media);
73
+ return {
74
+ kind: kindOf(media, meta.mime),
75
+ ...(meta.name ? { name: meta.name } : {}),
76
+ };
77
+ }
78
+
79
+ export async function downloadMedia(
80
+ client: UserClient,
81
+ media: DownloadableMedia,
82
+ messageId: string,
83
+ index = 0,
84
+ ): Promise<SavedAttachment> {
85
+ const buf = await client.tg.downloadAsBuffer(media);
86
+ return saveBufferToCache(buf, messageId, index, metaOf(media));
87
+ }
@@ -0,0 +1,77 @@
1
+ import { homedir } from 'node:os';
2
+ import { join } from 'node:path';
3
+ import { mkdirSync } from 'node:fs';
4
+ import { TelegramClient } from '@mtcute/bun';
5
+ import { TrainError } from '@metro-labs/core/train-error';
6
+ import { errMsg } from '@metro-labs/core/log';
7
+ import type { UserAccount } from './types.js';
8
+
9
+ const STATE_DIR =
10
+ process.env.METRO_STATE_DIR ?? join(homedir(), '.cache', 'metro');
11
+
12
+ export interface Self {
13
+ username: string | null;
14
+ phone: string | null;
15
+ }
16
+
17
+ export interface UserClient {
18
+ account: UserAccount;
19
+ tg: TelegramClient;
20
+ self: () => Self | null;
21
+ connect: () => Promise<void>;
22
+ startUpdates: () => Promise<void>;
23
+ disconnect: () => Promise<void>;
24
+ }
25
+
26
+ function storagePath(accountId: string): string {
27
+ const dir = join(STATE_DIR, 'telegram');
28
+ mkdirSync(dir, { recursive: true });
29
+ return join(dir, `${accountId}.session`);
30
+ }
31
+
32
+ export function createClient(account: UserAccount): UserClient {
33
+ const apiId = account.apiId ?? 0;
34
+ const { apiHash } = account;
35
+ if (!Number.isInteger(apiId) || apiId <= 0 || !apiHash)
36
+ throw new TrainError(
37
+ 'telegram_auth',
38
+ `account '${account.id}' missing apiId/apiHash`,
39
+ );
40
+ const tg = new TelegramClient({
41
+ apiId,
42
+ apiHash,
43
+ storage: storagePath(account.id),
44
+ });
45
+
46
+ let me: Self | null = null;
47
+
48
+ const connect = async (): Promise<void> => {
49
+ try {
50
+ await tg.importSession(account.session);
51
+ await tg.connect();
52
+ } catch (e) {
53
+ throw new TrainError(
54
+ 'telegram_auth',
55
+ `account '${account.id}' connect failed: ${errMsg(e)}`,
56
+ );
57
+ }
58
+ try {
59
+ const user = await tg.getMe();
60
+ me = { username: user.username ?? null, phone: user.phoneNumber ?? null };
61
+ } catch (e) {
62
+ process.stderr.write(
63
+ `telegram[${account.id}] identity unavailable: ${errMsg(e)}\n`,
64
+ );
65
+ }
66
+ };
67
+
68
+ const startUpdates = async (): Promise<void> => {
69
+ await tg.startUpdatesLoop();
70
+ };
71
+
72
+ const disconnect = async (): Promise<void> => {
73
+ await tg.disconnect();
74
+ };
75
+
76
+ return { account, tg, self: () => me, connect, startUpdates, disconnect };
77
+ }