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

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 (266) hide show
  1. package/dist/api.js +14 -0
  2. package/dist/bedrock-eventstream.js +120 -0
  3. package/dist/bedrock-proxy.js +260 -0
  4. package/dist/bedrock-upstream.js +72 -0
  5. package/dist/bedrock.js +106 -0
  6. package/dist/claude.js +15 -0
  7. package/dist/cli.js +101 -0
  8. package/dist/control.js +74 -0
  9. package/dist/hold.js +197 -0
  10. package/dist/local.js +93 -0
  11. package/dist/node-name.js +51 -0
  12. package/dist/plugin.js +80 -0
  13. package/dist/runtime-install.js +84 -0
  14. package/dist/runtime.js +70 -0
  15. package/dist/serve.js +147 -0
  16. package/dist/service.js +224 -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 -25
  21. package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
  22. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -0
  23. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
  24. package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
  25. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
  26. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +220 -0
  27. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
  28. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
  29. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
  30. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
  31. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
  32. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
  33. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
  34. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
  35. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
  36. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
  37. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
  38. package/runtime/node_modules/@metro-labs/mcp/package.json +64 -0
  39. package/runtime/node_modules/@metro-labs/mcp/src/channels/addressed.ts +25 -0
  40. package/runtime/node_modules/@metro-labs/mcp/src/channels/dedupe.ts +24 -0
  41. package/runtime/node_modules/@metro-labs/mcp/src/channels/inbound.ts +377 -0
  42. package/runtime/node_modules/@metro-labs/mcp/src/channels/media-note.ts +85 -0
  43. package/runtime/node_modules/@metro-labs/mcp/src/channels/pending.ts +75 -0
  44. package/runtime/node_modules/@metro-labs/mcp/src/channels/relay.ts +129 -0
  45. package/runtime/node_modules/@metro-labs/mcp/src/channels/webhook-note.ts +51 -0
  46. package/runtime/node_modules/@metro-labs/mcp/src/daemon/account-api.ts +311 -0
  47. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +302 -0
  48. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-error.ts +8 -0
  49. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-http.ts +95 -0
  50. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-grant.ts +99 -0
  51. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-owner.ts +32 -0
  52. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-serve.ts +128 -0
  53. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-session.ts +280 -0
  54. package/runtime/node_modules/@metro-labs/mcp/src/daemon/boot.ts +204 -0
  55. package/runtime/node_modules/@metro-labs/mcp/src/daemon/bundle-api.ts +111 -0
  56. package/runtime/node_modules/@metro-labs/mcp/src/daemon/claude-api.ts +97 -0
  57. package/runtime/node_modules/@metro-labs/mcp/src/daemon/claude-files.ts +329 -0
  58. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connect-hint.ts +39 -0
  59. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-api.ts +307 -0
  60. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-json.ts +21 -0
  61. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth-routes.ts +157 -0
  62. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth.ts +127 -0
  63. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-tools.ts +98 -0
  64. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-url.ts +70 -0
  65. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-verify.ts +330 -0
  66. package/runtime/node_modules/@metro-labs/mcp/src/daemon/control-api.ts +64 -0
  67. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cors.ts +29 -0
  68. package/runtime/node_modules/@metro-labs/mcp/src/daemon/crash-guard.ts +49 -0
  69. package/runtime/node_modules/@metro-labs/mcp/src/daemon/events.ts +120 -0
  70. package/runtime/node_modules/@metro-labs/mcp/src/daemon/http.ts +384 -0
  71. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity-key.ts +33 -0
  72. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity-registry.ts +13 -0
  73. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity-routes.ts +54 -0
  74. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +7 -0
  75. package/runtime/node_modules/@metro-labs/mcp/src/daemon/is-record.ts +2 -0
  76. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-cli-api.ts +63 -0
  77. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-mode.ts +184 -0
  78. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-owner.ts +14 -0
  79. package/runtime/node_modules/@metro-labs/mcp/src/daemon/log.ts +42 -0
  80. package/runtime/node_modules/@metro-labs/mcp/src/daemon/machine-api.ts +61 -0
  81. package/runtime/node_modules/@metro-labs/mcp/src/daemon/mode-api.ts +32 -0
  82. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-client.ts +159 -0
  83. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-discovery.ts +117 -0
  84. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-pending.ts +65 -0
  85. package/runtime/node_modules/@metro-labs/mcp/src/daemon/paths.ts +109 -0
  86. package/runtime/node_modules/@metro-labs/mcp/src/daemon/protocol.ts +226 -0
  87. package/runtime/node_modules/@metro-labs/mcp/src/daemon/relay.ts +295 -0
  88. package/runtime/node_modules/@metro-labs/mcp/src/daemon/return-to.ts +21 -0
  89. package/runtime/node_modules/@metro-labs/mcp/src/daemon/runtime-deps.ts +82 -0
  90. package/runtime/node_modules/@metro-labs/mcp/src/daemon/secure-fs.ts +57 -0
  91. package/runtime/node_modules/@metro-labs/mcp/src/daemon/server-types.ts +21 -0
  92. package/runtime/node_modules/@metro-labs/mcp/src/daemon/servers-api.ts +80 -0
  93. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-api.ts +29 -0
  94. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-apis.ts +61 -0
  95. package/runtime/node_modules/@metro-labs/mcp/src/daemon/signed-identity.ts +36 -0
  96. package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor-io.ts +97 -0
  97. package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor.ts +320 -0
  98. package/runtime/node_modules/@metro-labs/mcp/src/daemon/terminal-api.ts +71 -0
  99. package/runtime/node_modules/@metro-labs/mcp/src/daemon/terminal-tickets.ts +40 -0
  100. package/runtime/node_modules/@metro-labs/mcp/src/daemon/terminal-ws.ts +114 -0
  101. package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-call.ts +22 -0
  102. package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-error.ts +53 -0
  103. package/runtime/node_modules/@metro-labs/mcp/src/daemon/tunnel.ts +352 -0
  104. package/runtime/node_modules/@metro-labs/mcp/src/daemon/update-api.ts +116 -0
  105. package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-api.ts +294 -0
  106. package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-store.ts +181 -0
  107. package/runtime/node_modules/@metro-labs/mcp/src/daemon/vault-api.ts +78 -0
  108. package/runtime/node_modules/@metro-labs/mcp/src/daemon/vault-types.ts +12 -0
  109. package/runtime/node_modules/@metro-labs/mcp/src/daemon/version.ts +2 -0
  110. package/runtime/node_modules/@metro-labs/mcp/src/db/account-attach.ts +19 -0
  111. package/runtime/node_modules/@metro-labs/mcp/src/db/address.ts +6 -0
  112. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-admin.ts +43 -0
  113. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-map.ts +84 -0
  114. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-scope.ts +64 -0
  115. package/runtime/node_modules/@metro-labs/mcp/src/db/client.ts +32 -0
  116. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-config.ts +144 -0
  117. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-relay.ts +39 -0
  118. package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +69 -0
  119. package/runtime/node_modules/@metro-labs/mcp/src/db/file-admin.ts +329 -0
  120. package/runtime/node_modules/@metro-labs/mcp/src/db/file-source.ts +155 -0
  121. package/runtime/node_modules/@metro-labs/mcp/src/db/ids.ts +21 -0
  122. package/runtime/node_modules/@metro-labs/mcp/src/db/key-map.ts +45 -0
  123. package/runtime/node_modules/@metro-labs/mcp/src/db/local-connectors.ts +277 -0
  124. package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +253 -0
  125. package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +33 -0
  126. package/runtime/node_modules/@metro-labs/mcp/src/db/servers.ts +83 -0
  127. package/runtime/node_modules/@metro-labs/mcp/src/db/stations.ts +12 -0
  128. package/runtime/node_modules/@metro-labs/mcp/src/db/vault.ts +109 -0
  129. package/runtime/node_modules/@metro-labs/mcp/src/mcp/accounts.ts +141 -0
  130. package/runtime/node_modules/@metro-labs/mcp/src/mcp/call-tools.ts +216 -0
  131. package/runtime/node_modules/@metro-labs/mcp/src/mcp/channel-owner.ts +29 -0
  132. package/runtime/node_modules/@metro-labs/mcp/src/mcp/ctx.ts +62 -0
  133. package/runtime/node_modules/@metro-labs/mcp/src/mcp/event-store.ts +91 -0
  134. package/runtime/node_modules/@metro-labs/mcp/src/mcp/frame-scope.ts +21 -0
  135. package/runtime/node_modules/@metro-labs/mcp/src/mcp/group-tools.ts +129 -0
  136. package/runtime/node_modules/@metro-labs/mcp/src/mcp/index.ts +191 -0
  137. package/runtime/node_modules/@metro-labs/mcp/src/mcp/member-tools.ts +72 -0
  138. package/runtime/node_modules/@metro-labs/mcp/src/mcp/permission-relay.ts +66 -0
  139. package/runtime/node_modules/@metro-labs/mcp/src/mcp/raw-get-stream.ts +207 -0
  140. package/runtime/node_modules/@metro-labs/mcp/src/mcp/request-identity.ts +47 -0
  141. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-registry.ts +172 -0
  142. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-route.ts +43 -0
  143. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session.ts +215 -0
  144. package/runtime/node_modules/@metro-labs/mcp/src/mcp/str.ts +5 -0
  145. package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-dispatch.ts +215 -0
  146. package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-schemas.ts +400 -0
  147. package/runtime/node_modules/@metro-labs/mcp/src/mcp/upload-tool.ts +66 -0
  148. package/runtime/node_modules/@metro-labs/mcp/src/monitor/api.ts +237 -0
  149. package/runtime/node_modules/@metro-labs/mcp/src/server.ts +2 -0
  150. package/runtime/node_modules/@metro-labs/mcp/src/stations/account-store.ts +60 -0
  151. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-inline.ts +100 -0
  152. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-interactive.ts +161 -0
  153. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-resolve.ts +200 -0
  154. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-xmtp.ts +111 -0
  155. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach.ts +184 -0
  156. package/runtime/node_modules/@metro-labs/mcp/src/stations/attachments.ts +196 -0
  157. package/runtime/node_modules/@metro-labs/mcp/src/stations/lines.ts +82 -0
  158. package/runtime/node_modules/@metro-labs/mcp/src/stations/messaging-normalize.ts +88 -0
  159. package/runtime/node_modules/@metro-labs/mcp/src/stations/registry.ts +35 -0
  160. package/runtime/node_modules/@metro-labs/mcp/src/stations/station-runtime.ts +64 -0
  161. package/runtime/node_modules/@metro-labs/mcp/src/stations/types.ts +107 -0
  162. package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
  163. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
  164. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +254 -0
  165. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
  166. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
  167. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +156 -0
  168. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
  169. package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
  170. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
  171. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
  172. package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
  173. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
  174. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
  175. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
  176. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
  177. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
  178. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
  179. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
  180. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
  181. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
  182. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
  183. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
  184. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +201 -0
  185. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +109 -0
  186. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
  187. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
  188. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
  189. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +58 -0
  190. package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
  191. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
  192. package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
  193. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
  194. package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
  195. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
  196. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
  197. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +178 -0
  198. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
  199. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
  200. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +327 -0
  201. package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
  202. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +138 -0
  203. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
  204. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
  205. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
  206. package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
  207. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
  208. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
  209. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
  210. package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
  211. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
  212. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +17 -0
  213. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
  214. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
  215. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
  216. package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
  217. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +112 -0
  218. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
  219. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
  220. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
  221. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
  222. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
  223. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
  224. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
  225. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
  226. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
  227. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +335 -0
  228. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +80 -0
  229. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
  230. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
  231. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
  232. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
  233. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
  234. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
  235. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
  236. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
  237. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
  238. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
  239. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
  240. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
  241. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +53 -0
  242. package/runtime/runtime.json +3 -0
  243. package/runtime/stations.json +36 -0
  244. package/runtime/trains/.keep +0 -0
  245. package/LICENSE +0 -21
  246. package/README.md +0 -193
  247. package/dist/cache.js +0 -75
  248. package/dist/cli/actions.js +0 -139
  249. package/dist/cli/config.js +0 -185
  250. package/dist/cli/index.js +0 -159
  251. package/dist/cli/skill.js +0 -62
  252. package/dist/cli/util.js +0 -72
  253. package/dist/codex-rc.js +0 -236
  254. package/dist/dispatcher.js +0 -95
  255. package/dist/history.js +0 -84
  256. package/dist/ipc.js +0 -72
  257. package/dist/log.js +0 -15
  258. package/dist/paths.js +0 -68
  259. package/dist/stations/discord.js +0 -200
  260. package/dist/stations/index.js +0 -73
  261. package/dist/stations/telegram-md.js +0 -34
  262. package/dist/stations/telegram-upload.js +0 -113
  263. package/dist/stations/telegram.js +0 -213
  264. package/docs/agents.md +0 -185
  265. package/docs/uri-scheme.md +0 -71
  266. package/skills/metro/SKILL.md +0 -241
@@ -0,0 +1,261 @@
1
+ import type {
2
+ GroupResult,
3
+ MemberOutcome,
4
+ } from '@metro-labs/mcp/stations/types';
5
+ import { TrainError } from '@metro-labs/mcp/train-error';
6
+ import { errMsg } from '@metro-labs/mcp/log';
7
+ import type { UserClient } from './client.js';
8
+ import { lineOf } from './accounts.js';
9
+
10
+ export interface MissingInvitee {
11
+ userId: number;
12
+ }
13
+
14
+ interface CreateGroupResponse {
15
+ chat: { id: number };
16
+ missing: MissingInvitee[];
17
+ }
18
+
19
+ interface InviteLink {
20
+ link: string;
21
+ }
22
+
23
+ interface TgGroup {
24
+ createGroup: (p: {
25
+ title: string;
26
+ users: string[];
27
+ }) => Promise<CreateGroupResponse>;
28
+ addChatMembers: (
29
+ chatId: number,
30
+ users: string[],
31
+ params: { forwardCount: number },
32
+ ) => Promise<MissingInvitee[]>;
33
+ kickChatMember: (p: { chatId: number; userId: string }) => Promise<unknown>;
34
+ exportInviteLink: (chatId: number) => Promise<InviteLink>;
35
+ resolvePeer: (peer: string) => Promise<{
36
+ userId?: number;
37
+ channelId?: number;
38
+ chatId?: number;
39
+ }>;
40
+ }
41
+
42
+ const tgGroup = (client: UserClient): TgGroup => {
43
+ const tg: unknown = client.tg;
44
+ return tg as TgGroup;
45
+ };
46
+
47
+ const INVITE_REASON =
48
+ 'could not be direct-added (not a mutual contact or privacy-restricted); share the invite link';
49
+
50
+ const ADD_FAILURE_CODES = [
51
+ 'CHAT_MEMBER_ADD_FAILED',
52
+ 'USER_PRIVACY_RESTRICTED',
53
+ 'USER_NOT_MUTUAL_CONTACT',
54
+ 'USER_CHANNELS_TOO_MUCH',
55
+ 'USER_ALREADY_PARTICIPANT',
56
+ 'USER_BLOCKED',
57
+ 'USER_BOT',
58
+ 'USER_KICKED',
59
+ 'PEER_FLOOD',
60
+ 'USERS_TOO_MUCH',
61
+ ];
62
+
63
+ function isAddFailure(e: unknown): boolean {
64
+ const msg = errMsg(e).toUpperCase();
65
+ return ADD_FAILURE_CODES.some((code) => msg.includes(code));
66
+ }
67
+
68
+ const added = (id: string): MemberOutcome => ({ id, status: 'added' });
69
+ const invited = (id: string): MemberOutcome => ({
70
+ id,
71
+ status: 'invited',
72
+ reason: INVITE_REASON,
73
+ });
74
+
75
+ function membersOf(args: Record<string, unknown>): string[] {
76
+ const raw = args.members;
77
+ if (!Array.isArray(raw)) return [];
78
+ return raw.filter((v) => typeof v === 'string' && v.length > 0) as string[];
79
+ }
80
+
81
+ async function addOne(
82
+ client: UserClient,
83
+ chatId: number,
84
+ member: string,
85
+ ): Promise<MemberOutcome> {
86
+ try {
87
+ const missing = await tgGroup(client).addChatMembers(chatId, [member], {
88
+ forwardCount: 0,
89
+ });
90
+ return missing.length ? invited(member) : added(member);
91
+ } catch (e) {
92
+ if (isAddFailure(e)) return invited(member);
93
+ throw e;
94
+ }
95
+ }
96
+
97
+ interface CreatedChat {
98
+ chatId: number;
99
+ outcomes: MemberOutcome[];
100
+ }
101
+
102
+ async function createWithAll(
103
+ client: UserClient,
104
+ title: string,
105
+ members: string[],
106
+ ): Promise<CreatedChat> {
107
+ const res = await tgGroup(client).createGroup({ title, users: members });
108
+ const missing = new Set(res.missing.map((m) => m.userId));
109
+ const outcomes: MemberOutcome[] = [];
110
+ for (const m of members) {
111
+ const uid = await idOf(client, m);
112
+ outcomes.push(
113
+ uid !== null && missing.has(Number(uid)) ? invited(m) : added(m),
114
+ );
115
+ }
116
+ return { chatId: res.chat.id, outcomes };
117
+ }
118
+
119
+ async function createPerMember(
120
+ client: UserClient,
121
+ title: string,
122
+ members: string[],
123
+ ): Promise<CreatedChat> {
124
+ let chatId: number | null = null;
125
+ const outcomes: MemberOutcome[] = [];
126
+ const pending: string[] = [];
127
+ for (const m of members) {
128
+ if (chatId !== null) {
129
+ pending.push(m);
130
+ continue;
131
+ }
132
+ try {
133
+ const res = await tgGroup(client).createGroup({ title, users: [m] });
134
+ chatId = res.chat.id;
135
+ outcomes.push(res.missing.length ? invited(m) : added(m));
136
+ } catch (e) {
137
+ if (!isAddFailure(e)) throw e;
138
+ outcomes.push(invited(m));
139
+ }
140
+ }
141
+ if (chatId === null)
142
+ throw new TrainError(
143
+ 'telegram_call',
144
+ 'could not create group: no requested member could be added',
145
+ );
146
+ for (const m of pending) outcomes.push(await addOne(client, chatId, m));
147
+ return { chatId, outcomes };
148
+ }
149
+
150
+ async function createChat(
151
+ client: UserClient,
152
+ title: string,
153
+ members: string[],
154
+ ): Promise<CreatedChat> {
155
+ try {
156
+ return await createWithAll(client, title, members);
157
+ } catch (e) {
158
+ if (!isAddFailure(e)) throw e;
159
+ return createPerMember(client, title, members);
160
+ }
161
+ }
162
+
163
+ async function finalize(
164
+ client: UserClient,
165
+ base: GroupResult,
166
+ chatId: number,
167
+ outcomes: MemberOutcome[],
168
+ ): Promise<GroupResult> {
169
+ const result: GroupResult = { ...base, members: outcomes };
170
+ if (outcomes.some((o) => o.status === 'invited'))
171
+ result.inviteLink = (await tgGroup(client).exportInviteLink(chatId)).link;
172
+ return result;
173
+ }
174
+
175
+ async function idOf(client: UserClient, member: string): Promise<string | null> {
176
+ const trimmed = member.replace(/^@/, '');
177
+ if (/^-?\d+$/.test(trimmed)) return trimmed;
178
+ try {
179
+ const peer = await tgGroup(client).resolvePeer(member);
180
+ const uid = peer.userId ?? peer.channelId ?? peer.chatId;
181
+ return uid !== undefined ? String(uid) : null;
182
+ } catch {
183
+ return null;
184
+ }
185
+ }
186
+
187
+ export async function groupCreate(
188
+ client: UserClient,
189
+ accountId: string,
190
+ args: Record<string, unknown>,
191
+ ): Promise<GroupResult> {
192
+ const name = typeof args.name === 'string' ? args.name : '';
193
+ if (!name) throw new TrainError('bad_request', 'groupCreate requires a `name`');
194
+ const members = membersOf(args);
195
+ if (!members.length)
196
+ throw new TrainError('bad_request', 'groupCreate requires `members`');
197
+ const { chatId, outcomes } = await createChat(client, name, members);
198
+ return finalize(
199
+ client,
200
+ {
201
+ capability: { supported: true },
202
+ line: lineOf(accountId, chatId),
203
+ id: String(chatId),
204
+ name,
205
+ },
206
+ chatId,
207
+ outcomes,
208
+ );
209
+ }
210
+
211
+ export async function groupAddMembers(
212
+ client: UserClient,
213
+ chatId: number,
214
+ line: string,
215
+ args: Record<string, unknown>,
216
+ ): Promise<GroupResult> {
217
+ const members = membersOf(args);
218
+ if (!members.length)
219
+ throw new TrainError('bad_request', 'groupAddMembers requires `members`');
220
+ const outcomes: MemberOutcome[] = [];
221
+ for (const m of members) outcomes.push(await addOne(client, chatId, m));
222
+ return finalize(
223
+ client,
224
+ { capability: { supported: true }, line, id: String(chatId) },
225
+ chatId,
226
+ outcomes,
227
+ );
228
+ }
229
+
230
+ export async function groupRemoveMembers(
231
+ client: UserClient,
232
+ chatId: number,
233
+ line: string,
234
+ args: Record<string, unknown>,
235
+ ): Promise<GroupResult> {
236
+ const members = membersOf(args);
237
+ if (!members.length)
238
+ throw new TrainError('bad_request', 'groupRemoveMembers requires `members`');
239
+ for (const m of members)
240
+ await tgGroup(client).kickChatMember({ chatId, userId: m });
241
+ return {
242
+ capability: { supported: true },
243
+ line,
244
+ id: String(chatId),
245
+ members: members.map((id) => ({ id, status: 'removed' as const })),
246
+ };
247
+ }
248
+
249
+ export async function groupInviteLink(
250
+ client: UserClient,
251
+ chatId: number,
252
+ line: string,
253
+ ): Promise<GroupResult> {
254
+ const link = await tgGroup(client).exportInviteLink(chatId);
255
+ return {
256
+ capability: { supported: true },
257
+ line,
258
+ id: String(chatId),
259
+ inviteLink: link.link,
260
+ };
261
+ }
@@ -0,0 +1,52 @@
1
+ import type { Message } from '@mtcute/bun';
2
+ import { lineOf } from './accounts.js';
3
+ import { displayName, senderName } from './format.js';
4
+
5
+ const DEFAULT_LIMIT = 20;
6
+ const MAX_LIMIT = 100;
7
+
8
+ export function clampLimit(limit: unknown): number {
9
+ const n = typeof limit === 'number' ? limit : Number(limit);
10
+ if (!Number.isFinite(n)) return DEFAULT_LIMIT;
11
+ return Math.min(Math.max(1, Math.trunc(n)), MAX_LIMIT);
12
+ }
13
+
14
+ function senderUri(accountId: string, m: Message): string {
15
+ return `metro://telegram/${accountId}/user/${m.sender.id}`;
16
+ }
17
+
18
+ function textOf(m: Message): string {
19
+ if (m.text) return m.text;
20
+ if (m.media !== null) return `[${m.media.type}]`;
21
+ return '';
22
+ }
23
+
24
+ export interface ReadMessage {
25
+ id: string;
26
+ ts: string;
27
+ from: string;
28
+ from_name?: string;
29
+ from_display_name?: string;
30
+ text: string;
31
+ }
32
+
33
+ export function shapeHistory(
34
+ accountId: string,
35
+ chatId: number,
36
+ messages: readonly Message[],
37
+ ): { line: string; count: number; messages: ReadMessage[] } {
38
+ const ordered = [...messages].reverse();
39
+ const shaped = ordered.map((m) => ({
40
+ id: String(m.id),
41
+ ts: m.date.toISOString(),
42
+ from: senderUri(accountId, m),
43
+ from_name: senderName(m.sender),
44
+ from_display_name: displayName(m.sender),
45
+ text: textOf(m),
46
+ }));
47
+ return {
48
+ line: lineOf(accountId, chatId),
49
+ count: shaped.length,
50
+ messages: shaped,
51
+ };
52
+ }
@@ -0,0 +1,78 @@
1
+ import type { Message } from '@mtcute/bun';
2
+ import { errMsg, log } from '@metro-labs/mcp/log';
3
+ import { emit } from './wire.js';
4
+ import { envelope, isOwnEcho, attachmentSavedEnvelope } from './format.js';
5
+ import { downloadMedia, isDownloadable } from './attachments.js';
6
+ import { subscribeReactions } from './reactions.js';
7
+ import type { UserClient } from './client.js';
8
+
9
+ async function saveMediaAndEmit(
10
+ client: UserClient,
11
+ m: Message,
12
+ env: Record<string, unknown>,
13
+ ): Promise<void> {
14
+ const { media } = m;
15
+ if (media === null || !isDownloadable(media)) return;
16
+ const accountId = client.account.id;
17
+ try {
18
+ const saved = await downloadMedia(client, media, String(m.id), 0);
19
+ emit(
20
+ attachmentSavedEnvelope(
21
+ accountId,
22
+ env.line as string,
23
+ env.id as string,
24
+ saved,
25
+ ),
26
+ );
27
+ } catch (e) {
28
+ process.stderr.write(
29
+ `telegram[${accountId}] media save failed: ${errMsg(e)}\n`,
30
+ );
31
+ }
32
+ }
33
+
34
+ function emitMessage(client: UserClient, m: Message): void {
35
+ log.debug(
36
+ { train: 'telegram', account: client.account.id, messageId: m.id },
37
+ 'inbound message received',
38
+ );
39
+ if (isOwnEcho(m)) return;
40
+ const env = envelope(client.account.id, m);
41
+ emit(env);
42
+ if (m.media !== null)
43
+ saveMediaAndEmit(client, m, env).catch((err: unknown) => {
44
+ process.stderr.write(
45
+ `telegram[${client.account.id}] media save failed: ${errMsg(err)}\n`,
46
+ );
47
+ });
48
+ }
49
+
50
+ function subscribe(client: UserClient): void {
51
+ const accountId = client.account.id;
52
+ const onMessage = (m: Message): void => {
53
+ try {
54
+ emitMessage(client, m);
55
+ } catch (e) {
56
+ process.stderr.write(
57
+ `telegram[${accountId}] normalize failed: ${errMsg(e)}\n`,
58
+ );
59
+ }
60
+ };
61
+ client.tg.onNewMessage.add(onMessage);
62
+ client.tg.onEditMessage.add(onMessage);
63
+ subscribeReactions(client);
64
+ }
65
+
66
+ export async function startInbound(client: UserClient): Promise<void> {
67
+ const accountId = client.account.id;
68
+ try {
69
+ subscribe(client);
70
+ await client.connect();
71
+ await client.startUpdates();
72
+ process.stderr.write(`telegram[${accountId}] inbound connected\n`);
73
+ } catch (e) {
74
+ process.stderr.write(
75
+ `telegram[${accountId}] connect failed: ${errMsg(e)}\n`,
76
+ );
77
+ }
78
+ }
@@ -0,0 +1,46 @@
1
+ import { TrainError } from '@metro-labs/mcp/train-error';
2
+ import { errMsg } from '@metro-labs/mcp/log';
3
+ import { readCalls } from '@metro-labs/mcp/trains/protocol';
4
+ import {
5
+ accounts,
6
+ loadAccounts,
7
+ accountFor,
8
+ lineOf,
9
+ targetOf,
10
+ } from './accounts.js';
11
+ import { createClient, type UserClient } from './client.js';
12
+ import { startInbound } from './inbound.js';
13
+ import { makeHandleCall } from './actions.js';
14
+
15
+ const clients = new Map<string, UserClient>();
16
+
17
+ function clientFor(accountId: string): UserClient {
18
+ let client = clients.get(accountId);
19
+ if (!client) {
20
+ const account = accounts.get(accountId);
21
+ if (!account)
22
+ throw new TrainError('not_implemented', `unknown account '${accountId}'`);
23
+ client = createClient(account);
24
+ clients.set(accountId, client);
25
+ }
26
+ return client;
27
+ }
28
+
29
+ const handleCall = makeHandleCall(clientFor);
30
+
31
+ readCalls('telegram', handleCall);
32
+
33
+ function boot(): void {
34
+ for (const cfg of loadAccounts()) accounts.set(cfg.id, cfg);
35
+ for (const id of accounts.keys())
36
+ startInbound(clientFor(id)).catch((err: unknown) => {
37
+ process.stderr.write(`telegram[${id}] inbound failed: ${errMsg(err)}\n`);
38
+ });
39
+ process.stderr.write(
40
+ `telegram train ready (inbound+outbound) — ${accounts.size} account(s): ${[...accounts.keys()].join(', ')}\n`,
41
+ );
42
+ }
43
+
44
+ boot();
45
+
46
+ export { clients, accounts, clientFor, accountFor, lineOf, targetOf };
@@ -0,0 +1,166 @@
1
+ import { TelegramClient } from '@mtcute/bun';
2
+
3
+ export class TelegramLoginError extends Error {}
4
+
5
+ export interface TelegramCredentials {
6
+ apiId: number;
7
+ apiHash: string;
8
+ phone: string;
9
+ }
10
+
11
+ export interface TelegramSignedIn {
12
+ config: { session: string; apiId: number; apiHash: string };
13
+ identity: { userId: string; displayName: string };
14
+ }
15
+
16
+ export type TelegramStep = 'code' | 'password';
17
+
18
+ const PHONE_RE = /^[0-9]{6,15}$/;
19
+ const API_HASH_RE = /^[a-f0-9]{32}$/i;
20
+
21
+ export function rpcErrorText(err: unknown): string {
22
+ if (typeof err !== 'object' || err === null) return '';
23
+ const text = (err as { text?: unknown }).text;
24
+ return typeof text === 'string' ? text : '';
25
+ }
26
+
27
+ export function needsPassword(err: unknown): boolean {
28
+ return rpcErrorText(err) === 'SESSION_PASSWORD_NEEDED';
29
+ }
30
+
31
+ export function readableAuthError(err: unknown): string {
32
+ const text = rpcErrorText(err);
33
+ if (text.startsWith('PHONE_CODE_INVALID')) return 'that login code is not right';
34
+ if (text.startsWith('PHONE_CODE_EXPIRED'))
35
+ return 'that login code has expired, start again';
36
+ if (text.startsWith('PHONE_NUMBER_INVALID'))
37
+ return 'Telegram does not recognise that phone number';
38
+ if (text.startsWith('PHONE_NUMBER_BANNED'))
39
+ return 'Telegram has banned that phone number';
40
+ if (text.startsWith('PASSWORD_HASH_INVALID'))
41
+ return 'that two-step verification password is not right';
42
+ if (text.startsWith('FLOOD_WAIT'))
43
+ return 'Telegram is rate-limiting this number, try again later';
44
+ return text === '' ? 'Telegram refused the sign-in' : `Telegram said ${text}`;
45
+ }
46
+
47
+ export function validateCredentials(raw: {
48
+ apiId: unknown;
49
+ apiHash: unknown;
50
+ phone: unknown;
51
+ }): TelegramCredentials {
52
+ const apiId = Number(raw.apiId);
53
+ if (!Number.isInteger(apiId) || apiId <= 0)
54
+ throw new TelegramLoginError(
55
+ 'api id must be the integer from my.telegram.org',
56
+ );
57
+ const apiHash = typeof raw.apiHash === 'string' ? raw.apiHash.trim() : '';
58
+ if (!API_HASH_RE.test(apiHash))
59
+ throw new TelegramLoginError(
60
+ 'api hash must be the 32-character hash from my.telegram.org',
61
+ );
62
+ const phone =
63
+ typeof raw.phone === 'string' ? raw.phone.replace(/[^0-9]/g, '') : '';
64
+ if (!PHONE_RE.test(phone))
65
+ throw new TelegramLoginError(
66
+ 'phone must be an international number, digits only',
67
+ );
68
+ return { apiId, apiHash, phone };
69
+ }
70
+
71
+ interface SentCodeLike {
72
+ phoneCodeHash: string;
73
+ length: number;
74
+ type: string;
75
+ }
76
+
77
+ const isSentCode = (v: unknown): v is SentCodeLike =>
78
+ typeof v === 'object' &&
79
+ v !== null &&
80
+ typeof (v as { phoneCodeHash?: unknown }).phoneCodeHash === 'string';
81
+
82
+ interface UserLike {
83
+ id: number;
84
+ displayName: string;
85
+ }
86
+
87
+ export class TelegramLogin {
88
+ private tg: TelegramClient;
89
+ private phoneCodeHash = '';
90
+ private closed = false;
91
+
92
+ constructor(private creds: TelegramCredentials) {
93
+ this.tg = new TelegramClient({
94
+ apiId: creds.apiId,
95
+ apiHash: creds.apiHash,
96
+ storage: ':memory:',
97
+ });
98
+ }
99
+
100
+ async requestCode(): Promise<{ step: TelegramStep; length: number }> {
101
+ await this.tg.connect();
102
+ const sent = await this.tg
103
+ .sendCode({ phone: this.creds.phone })
104
+ .catch((err: unknown) => {
105
+ throw new TelegramLoginError(readableAuthError(err));
106
+ });
107
+ if (!isSentCode(sent))
108
+ throw new TelegramLoginError(
109
+ 'Telegram signed this number in without a code, which Metro cannot use',
110
+ );
111
+ this.phoneCodeHash = sent.phoneCodeHash;
112
+ return { step: 'code', length: sent.length };
113
+ }
114
+
115
+ async submitCode(raw: string): Promise<TelegramSignedIn | 'password'> {
116
+ const phoneCode = raw.replace(/[^0-9]/g, '');
117
+ if (phoneCode === '')
118
+ throw new TelegramLoginError('enter the login code Telegram sent you');
119
+ try {
120
+ const user = await this.tg.signIn({
121
+ phone: this.creds.phone,
122
+ phoneCodeHash: this.phoneCodeHash,
123
+ phoneCode,
124
+ });
125
+ return await this.exportFor(user);
126
+ } catch (err) {
127
+ if (needsPassword(err)) return 'password';
128
+ throw new TelegramLoginError(readableAuthError(err));
129
+ }
130
+ }
131
+
132
+ async submitPassword(password: string): Promise<TelegramSignedIn> {
133
+ if (password === '')
134
+ throw new TelegramLoginError(
135
+ 'enter your Telegram two-step verification password',
136
+ );
137
+ try {
138
+ const user = await this.tg.checkPassword(password);
139
+ return await this.exportFor(user);
140
+ } catch (err) {
141
+ throw new TelegramLoginError(readableAuthError(err));
142
+ }
143
+ }
144
+
145
+ private async exportFor(user: UserLike): Promise<TelegramSignedIn> {
146
+ const session = await this.tg.exportSession();
147
+ await this.close();
148
+ return {
149
+ config: {
150
+ session,
151
+ apiId: this.creds.apiId,
152
+ apiHash: this.creds.apiHash,
153
+ },
154
+ identity: {
155
+ userId: String(user.id),
156
+ displayName: user.displayName,
157
+ },
158
+ };
159
+ }
160
+
161
+ async close(): Promise<void> {
162
+ if (this.closed) return;
163
+ this.closed = true;
164
+ await this.tg.destroy().catch(() => undefined);
165
+ }
166
+ }
@@ -0,0 +1,80 @@
1
+ import type { InputMediaLike, Message } from '@mtcute/bun';
2
+ import { InputMedia } from '@mtcute/bun';
3
+ import { isImageMime, isImageExt } from '@metro-labs/mcp/stations/attachments';
4
+ import type { UserClient } from './client.js';
5
+
6
+ export interface CanonicalAttachment {
7
+ kind?: string;
8
+ path?: string;
9
+ url?: string;
10
+ mime?: string;
11
+ name?: string;
12
+ }
13
+
14
+ const srcOf = (att: CanonicalAttachment): string => att.path ?? att.url ?? '';
15
+
16
+ function isImage(att: CanonicalAttachment): boolean {
17
+ if (att.kind === 'image') return true;
18
+ if (att.mime !== undefined) return isImageMime(att.mime);
19
+ const src = srcOf(att);
20
+ return src !== '' && isImageExt(src);
21
+ }
22
+
23
+ function fileRef(att: CanonicalAttachment): string {
24
+ const src = srcOf(att);
25
+ if (src === '' || /^https?:\/\//i.test(src)) return src;
26
+ return src.startsWith('file:') ? src : `file:${src}`;
27
+ }
28
+
29
+ export interface OutgoingMedia {
30
+ media: InputMediaLike;
31
+ kind: string;
32
+ }
33
+
34
+ export function buildInputMedia(
35
+ att: CanonicalAttachment,
36
+ caption: string | undefined,
37
+ ): OutgoingMedia {
38
+ const file = fileRef(att);
39
+ const params = {
40
+ ...(caption ? { caption } : {}),
41
+ ...(att.name ? { fileName: att.name } : {}),
42
+ };
43
+ return isImage(att)
44
+ ? { media: InputMedia.photo(file, params), kind: 'image' }
45
+ : { media: InputMedia.document(file, params), kind: 'file' };
46
+ }
47
+
48
+ interface SendMediaTarget {
49
+ client: UserClient;
50
+ chatId: number;
51
+ replyTo?: number;
52
+ }
53
+
54
+ export interface SentMedia {
55
+ message: Message;
56
+ delivered: string[];
57
+ }
58
+
59
+ export async function sendAttachments(
60
+ target: SendMediaTarget,
61
+ attachments: CanonicalAttachment[],
62
+ text: string,
63
+ ): Promise<SentMedia> {
64
+ const { client, chatId, replyTo } = target;
65
+ const peer = await client.tg.resolvePeer(chatId);
66
+ let last: Message | undefined;
67
+ const delivered: string[] = [];
68
+ for (let i = 0; i < attachments.length; i++) {
69
+ const att = attachments[i];
70
+ if (!att || srcOf(att) === '') continue;
71
+ const caption = i === 0 ? text : undefined;
72
+ const out = buildInputMedia(att, caption);
73
+ last = await client.tg.sendMedia(peer, out.media, {
74
+ ...(replyTo !== undefined ? { replyTo } : {}),
75
+ });
76
+ delivered.push(out.kind);
77
+ }
78
+ if (!last) throw new Error('no attachments were sent');
79
+ return { message: last, delivered };
80
+ }