@stage-labs/metro 0.1.0-beta.2 → 0.1.0-beta.200

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 (391) hide show
  1. package/dist/api.js +11 -0
  2. package/dist/claude-settings.js +40 -0
  3. package/dist/claude.js +163 -0
  4. package/dist/cli.js +77 -567
  5. package/dist/control.js +74 -0
  6. package/dist/hold.js +211 -0
  7. package/dist/local.js +43 -0
  8. package/dist/mcp-config.js +25 -0
  9. package/dist/node-name.js +71 -0
  10. package/dist/onboarding.js +44 -0
  11. package/dist/provider-flags.js +9 -0
  12. package/dist/route.js +58 -0
  13. package/dist/runtime-install.js +97 -0
  14. package/dist/runtime.js +74 -0
  15. package/dist/serve.js +158 -0
  16. package/dist/service.js +243 -0
  17. package/dist/tail.js +102 -144
  18. package/dist/update.js +45 -0
  19. package/dist/version.js +97 -0
  20. package/package.json +14 -23
  21. package/runtime/marketplace/.claude-plugin/marketplace.json +12 -0
  22. package/runtime/marketplace/plugin/.claude-plugin/plugin.json +17 -0
  23. package/runtime/marketplace/plugin/.mcp.json +1 -0
  24. package/runtime/marketplace/plugin/bin/guard.mjs +71 -0
  25. package/runtime/marketplace/plugin/bin/metro-plugin.mjs +30 -0
  26. package/runtime/marketplace/plugin/bin/policy.mjs +97 -0
  27. package/runtime/marketplace/plugin/bin/session-start.mjs +31 -0
  28. package/runtime/marketplace/plugin/hooks/hooks.json +25 -0
  29. package/runtime/marketplace/plugin/orchestrator.md +24 -0
  30. package/runtime/node_modules/@metro-labs/core/package.json +47 -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 +119 -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 +24 -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 +91 -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 +227 -0
  39. package/runtime/node_modules/@metro-labs/core/src/secure-fs.ts +74 -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-files.ts +41 -0
  42. package/runtime/node_modules/@metro-labs/core/src/stations/account-store.ts +60 -0
  43. package/runtime/node_modules/@metro-labs/core/src/stations/attachments.ts +196 -0
  44. package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +88 -0
  45. package/runtime/node_modules/@metro-labs/core/src/stations/profile.ts +58 -0
  46. package/runtime/node_modules/@metro-labs/core/src/stations/sender-profile.ts +64 -0
  47. package/runtime/node_modules/@metro-labs/core/src/stations/station-runtime.ts +61 -0
  48. package/runtime/node_modules/@metro-labs/core/src/stations/train-boot.ts +42 -0
  49. package/runtime/node_modules/@metro-labs/core/src/stations/train-events.ts +89 -0
  50. package/runtime/node_modules/@metro-labs/core/src/stations/types.ts +123 -0
  51. package/runtime/node_modules/@metro-labs/core/src/str.ts +5 -0
  52. package/runtime/node_modules/@metro-labs/core/src/tickets.ts +37 -0
  53. package/runtime/node_modules/@metro-labs/core/src/train-error.ts +53 -0
  54. package/runtime/node_modules/@metro-labs/core/src/version.ts +2 -0
  55. package/runtime/node_modules/@metro-labs/daemon/package.json +48 -0
  56. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/agent-fs.ts +134 -0
  57. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/files-api.ts +12 -0
  58. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/files.ts +119 -0
  59. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/home-fs.ts +113 -0
  60. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/job-env.ts +51 -0
  61. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/provision.ts +91 -0
  62. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedule-detail.ts +100 -0
  63. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules-api.ts +27 -0
  64. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules.ts +252 -0
  65. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/unit-files.ts +61 -0
  66. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/user.ts +91 -0
  67. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/view.ts +87 -0
  68. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-attach.ts +19 -0
  69. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-lookups.ts +55 -0
  70. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-routes.ts +76 -0
  71. package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +357 -0
  72. package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +30 -0
  73. package/runtime/node_modules/@metro-labs/daemon/src/agents/allowlist.ts +59 -0
  74. package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +131 -0
  75. package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +132 -0
  76. package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +305 -0
  77. package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +168 -0
  78. package/runtime/node_modules/@metro-labs/daemon/src/agents/keys.ts +35 -0
  79. package/runtime/node_modules/@metro-labs/daemon/src/agents/map.ts +128 -0
  80. package/runtime/node_modules/@metro-labs/daemon/src/agents/policy-route.ts +27 -0
  81. package/runtime/node_modules/@metro-labs/daemon/src/agents/scope.ts +64 -0
  82. package/runtime/node_modules/@metro-labs/daemon/src/agents/sender-cards.ts +86 -0
  83. package/runtime/node_modules/@metro-labs/daemon/src/agents/senders.ts +38 -0
  84. package/runtime/node_modules/@metro-labs/daemon/src/approvals/api.ts +55 -0
  85. package/runtime/node_modules/@metro-labs/daemon/src/approvals/needs.ts +2 -0
  86. package/runtime/node_modules/@metro-labs/daemon/src/approvals/pending.ts +149 -0
  87. package/runtime/node_modules/@metro-labs/daemon/src/approvals/preview.ts +66 -0
  88. package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +221 -0
  89. package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +39 -0
  90. package/runtime/node_modules/@metro-labs/daemon/src/boot/crash-guard.ts +49 -0
  91. package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +21 -0
  92. package/runtime/node_modules/@metro-labs/daemon/src/boot/paths.ts +106 -0
  93. package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +26 -0
  94. package/runtime/node_modules/@metro-labs/daemon/src/channels/dedupe.ts +12 -0
  95. package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +370 -0
  96. package/runtime/node_modules/@metro-labs/daemon/src/channels/media-note.ts +90 -0
  97. package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +78 -0
  98. package/runtime/node_modules/@metro-labs/daemon/src/channels/react-note.ts +13 -0
  99. package/runtime/node_modules/@metro-labs/daemon/src/channels/relay.ts +129 -0
  100. package/runtime/node_modules/@metro-labs/daemon/src/channels/reply-id.ts +1 -0
  101. package/runtime/node_modules/@metro-labs/daemon/src/channels/webhook-note.ts +51 -0
  102. package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +355 -0
  103. package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +359 -0
  104. package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +208 -0
  105. package/runtime/node_modules/@metro-labs/daemon/src/claude/memory.ts +82 -0
  106. package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +60 -0
  107. package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +149 -0
  108. package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +75 -0
  109. package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +283 -0
  110. package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +109 -0
  111. package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +271 -0
  112. package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +120 -0
  113. package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +113 -0
  114. package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +306 -0
  115. package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +176 -0
  116. package/runtime/node_modules/@metro-labs/daemon/src/connectors/gates.ts +72 -0
  117. package/runtime/node_modules/@metro-labs/daemon/src/connectors/health.ts +18 -0
  118. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-client.ts +159 -0
  119. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-discovery.ts +135 -0
  120. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-routes.ts +158 -0
  121. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth.ts +166 -0
  122. package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +131 -0
  123. package/runtime/node_modules/@metro-labs/daemon/src/connectors/reach.ts +27 -0
  124. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-policy.ts +90 -0
  125. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-target.ts +29 -0
  126. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +348 -0
  127. package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +358 -0
  128. package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +81 -0
  129. package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +37 -0
  130. package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +106 -0
  131. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +99 -0
  132. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-owner.ts +32 -0
  133. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-reaper.ts +174 -0
  134. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +123 -0
  135. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +278 -0
  136. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-store.ts +181 -0
  137. package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +233 -0
  138. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +208 -0
  139. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
  140. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +173 -0
  141. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +194 -0
  142. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +137 -0
  143. package/runtime/node_modules/@metro-labs/daemon/src/gateway/effort.ts +77 -0
  144. package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +123 -0
  145. package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +237 -0
  146. package/runtime/node_modules/@metro-labs/daemon/src/gateway/frames.ts +118 -0
  147. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +274 -0
  148. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-auth.ts +160 -0
  149. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-client.ts +46 -0
  150. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-setup.ts +150 -0
  151. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-stream.ts +139 -0
  152. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-translate.ts +223 -0
  153. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini.ts +208 -0
  154. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +199 -0
  155. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +299 -0
  156. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-signin.ts +193 -0
  157. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-store.ts +70 -0
  158. package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +90 -0
  159. package/runtime/node_modules/@metro-labs/daemon/src/gateway/pkce.ts +36 -0
  160. package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +66 -0
  161. package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +18 -0
  162. package/runtime/node_modules/@metro-labs/daemon/src/gateway/subscription.ts +120 -0
  163. package/runtime/node_modules/@metro-labs/daemon/src/gateway/text.ts +15 -0
  164. package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +244 -0
  165. package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +156 -0
  166. package/runtime/node_modules/@metro-labs/daemon/src/mcp/attachment-schema.ts +91 -0
  167. package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +226 -0
  168. package/runtime/node_modules/@metro-labs/daemon/src/mcp/ctx.ts +62 -0
  169. package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +90 -0
  170. package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +129 -0
  171. package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +137 -0
  172. package/runtime/node_modules/@metro-labs/daemon/src/mcp/instructions.ts +19 -0
  173. package/runtime/node_modules/@metro-labs/daemon/src/mcp/member-tools.ts +72 -0
  174. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-prompt.ts +87 -0
  175. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +79 -0
  176. package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-gate.ts +83 -0
  177. package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-snapshot.ts +79 -0
  178. package/runtime/node_modules/@metro-labs/daemon/src/mcp/profile-lookup.ts +60 -0
  179. package/runtime/node_modules/@metro-labs/daemon/src/mcp/profile-tool.ts +96 -0
  180. package/runtime/node_modules/@metro-labs/daemon/src/mcp/raw-get-stream.ts +207 -0
  181. package/runtime/node_modules/@metro-labs/daemon/src/mcp/read-tool.ts +87 -0
  182. package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +46 -0
  183. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-slot.ts +48 -0
  184. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +202 -0
  185. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-catalog.ts +88 -0
  186. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-def.ts +9 -0
  187. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-dispatch.ts +225 -0
  188. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-schemas.ts +333 -0
  189. package/runtime/node_modules/@metro-labs/daemon/src/mcp/upload-tool.ts +61 -0
  190. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/api.ts +43 -0
  191. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/helper-script.ts +126 -0
  192. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/move.ts +165 -0
  193. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/privilege.ts +48 -0
  194. package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +132 -0
  195. package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +316 -0
  196. package/runtime/node_modules/@metro-labs/daemon/src/policy/policy.ts +118 -0
  197. package/runtime/node_modules/@metro-labs/daemon/src/routes/bearer.ts +35 -0
  198. package/runtime/node_modules/@metro-labs/daemon/src/routes/body.ts +24 -0
  199. package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +365 -0
  200. package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +208 -0
  201. package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +71 -0
  202. package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
  203. package/runtime/node_modules/@metro-labs/daemon/src/routes/threema-callback.ts +103 -0
  204. package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +39 -0
  205. package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +58 -0
  206. package/runtime/node_modules/@metro-labs/daemon/src/server/owner.ts +26 -0
  207. package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +90 -0
  208. package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
  209. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-driver.ts +42 -0
  210. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-inline.ts +128 -0
  211. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-interactive.ts +134 -0
  212. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-outlook.ts +63 -0
  213. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +225 -0
  214. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-session.ts +288 -0
  215. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-xmtp.ts +111 -0
  216. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +248 -0
  217. package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +265 -0
  218. package/runtime/node_modules/@metro-labs/daemon/src/stations/registry.ts +51 -0
  219. package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
  220. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor-io.ts +97 -0
  221. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +320 -0
  222. package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
  223. package/runtime/node_modules/@metro-labs/daemon/src/stations/train-call.ts +22 -0
  224. package/runtime/node_modules/@metro-labs/daemon/src/stations/webhook-endpoints.ts +51 -0
  225. package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +84 -0
  226. package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +124 -0
  227. package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +15 -0
  228. package/runtime/node_modules/@metro-labs/daemon/src/vault/api.ts +45 -0
  229. package/runtime/node_modules/@metro-labs/daemon/src/vault/config.ts +51 -0
  230. package/runtime/node_modules/@metro-labs/daemon/src/vault/firewall.ts +48 -0
  231. package/runtime/node_modules/@metro-labs/daemon/src/vault/index.ts +96 -0
  232. package/runtime/node_modules/@metro-labs/daemon/src/vault/install.ts +56 -0
  233. package/runtime/node_modules/@metro-labs/daemon/src/vault/paths.ts +26 -0
  234. package/runtime/node_modules/@metro-labs/daemon/src/vault/proxy.ts +148 -0
  235. package/runtime/node_modules/@metro-labs/daemon/src/vault/store.ts +130 -0
  236. package/runtime/node_modules/@metro-labs/daemon/src/vault/trust.ts +61 -0
  237. package/runtime/node_modules/@metro-labs/discord-bot/package.json +26 -0
  238. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +117 -0
  239. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +240 -0
  240. package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
  241. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
  242. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +174 -0
  243. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
  244. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
  245. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +122 -0
  246. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
  247. package/runtime/node_modules/@metro-labs/discord-bot/src/profile.ts +68 -0
  248. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
  249. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +22 -0
  250. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
  251. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +1 -0
  252. package/runtime/node_modules/@metro-labs/http/package.json +29 -0
  253. package/runtime/node_modules/@metro-labs/http/src/api-error.ts +8 -0
  254. package/runtime/node_modules/@metro-labs/http/src/api-http.ts +138 -0
  255. package/runtime/node_modules/@metro-labs/http/src/cors.ts +29 -0
  256. package/runtime/node_modules/@metro-labs/http/src/mode-api.ts +32 -0
  257. package/runtime/node_modules/@metro-labs/http/src/return-to.ts +21 -0
  258. package/runtime/node_modules/@metro-labs/http/src/workos-token.ts +149 -0
  259. package/runtime/node_modules/@metro-labs/outlook/package.json +25 -0
  260. package/runtime/node_modules/@metro-labs/outlook/src/accounts.ts +107 -0
  261. package/runtime/node_modules/@metro-labs/outlook/src/actions.ts +20 -0
  262. package/runtime/node_modules/@metro-labs/outlook/src/attachments.ts +97 -0
  263. package/runtime/node_modules/@metro-labs/outlook/src/auth.ts +150 -0
  264. package/runtime/node_modules/@metro-labs/outlook/src/browser.ts +53 -0
  265. package/runtime/node_modules/@metro-labs/outlook/src/config.ts +36 -0
  266. package/runtime/node_modules/@metro-labs/outlook/src/format.ts +141 -0
  267. package/runtime/node_modules/@metro-labs/outlook/src/graph.ts +60 -0
  268. package/runtime/node_modules/@metro-labs/outlook/src/html.ts +72 -0
  269. package/runtime/node_modules/@metro-labs/outlook/src/inbound.ts +104 -0
  270. package/runtime/node_modules/@metro-labs/outlook/src/index.ts +12 -0
  271. package/runtime/node_modules/@metro-labs/outlook/src/login.ts +150 -0
  272. package/runtime/node_modules/@metro-labs/outlook/src/me.ts +41 -0
  273. package/runtime/node_modules/@metro-labs/outlook/src/outbound.ts +116 -0
  274. package/runtime/node_modules/@metro-labs/outlook/src/query.ts +119 -0
  275. package/runtime/node_modules/@metro-labs/outlook/src/state.ts +43 -0
  276. package/runtime/node_modules/@metro-labs/outlook/src/station.ts +15 -0
  277. package/runtime/node_modules/@metro-labs/outlook/src/trust.ts +70 -0
  278. package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
  279. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
  280. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +256 -0
  281. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
  282. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
  283. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +128 -0
  284. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
  285. package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
  286. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +57 -0
  287. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +7 -0
  288. package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
  289. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
  290. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
  291. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
  292. package/runtime/node_modules/@metro-labs/telegram/src/profile.ts +70 -0
  293. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
  294. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +27 -0
  295. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +6 -0
  296. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
  297. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
  298. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +119 -0
  299. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +246 -0
  300. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
  301. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +173 -0
  302. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +103 -0
  303. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +95 -0
  304. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
  305. package/runtime/node_modules/@metro-labs/telegram-bot/src/profile.ts +65 -0
  306. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +20 -0
  307. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +56 -0
  308. package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
  309. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +1 -0
  310. package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
  311. package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +106 -0
  312. package/runtime/node_modules/@metro-labs/threema/src/actions.ts +134 -0
  313. package/runtime/node_modules/@metro-labs/threema/src/api.ts +152 -0
  314. package/runtime/node_modules/@metro-labs/threema/src/crypto.ts +100 -0
  315. package/runtime/node_modules/@metro-labs/threema/src/files.ts +58 -0
  316. package/runtime/node_modules/@metro-labs/threema/src/format.ts +169 -0
  317. package/runtime/node_modules/@metro-labs/threema/src/groups.ts +96 -0
  318. package/runtime/node_modules/@metro-labs/threema/src/ids.ts +21 -0
  319. package/runtime/node_modules/@metro-labs/threema/src/inbound.ts +89 -0
  320. package/runtime/node_modules/@metro-labs/threema/src/index.ts +24 -0
  321. package/runtime/node_modules/@metro-labs/threema/src/messages.ts +264 -0
  322. package/runtime/node_modules/@metro-labs/threema/src/outbound.ts +160 -0
  323. package/runtime/node_modules/@metro-labs/threema/src/station.ts +13 -0
  324. package/runtime/node_modules/@metro-labs/threema/src/verify.ts +117 -0
  325. package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
  326. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
  327. package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
  328. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
  329. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
  330. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +213 -0
  331. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
  332. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
  333. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +388 -0
  334. package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
  335. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +83 -0
  336. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +40 -0
  337. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +7 -0
  338. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
  339. package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
  340. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
  341. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
  342. package/runtime/node_modules/@metro-labs/whatsapp/src/names.ts +28 -0
  343. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
  344. package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
  345. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
  346. package/runtime/node_modules/@metro-labs/whatsapp/src/resolve.ts +52 -0
  347. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +24 -0
  348. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +94 -0
  349. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +5 -0
  350. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +1 -0
  351. package/runtime/node_modules/@metro-labs/xmtp/package.json +34 -0
  352. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +114 -0
  353. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
  354. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +101 -0
  355. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +118 -0
  356. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +244 -0
  357. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
  358. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
  359. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
  360. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +316 -0
  361. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +69 -0
  362. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
  363. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
  364. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +47 -0
  365. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +122 -0
  366. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +139 -0
  367. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
  368. package/runtime/node_modules/@metro-labs/xmtp/src/names.ts +61 -0
  369. package/runtime/node_modules/@metro-labs/xmtp/src/profile.ts +153 -0
  370. package/runtime/node_modules/@metro-labs/xmtp/src/sender.ts +112 -0
  371. package/runtime/node_modules/@metro-labs/xmtp/src/smart.ts +72 -0
  372. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +17 -0
  373. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +79 -0
  374. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +198 -0
  375. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +88 -0
  376. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +98 -0
  377. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +51 -0
  378. package/runtime/runtime.json +3 -0
  379. package/runtime/server.ts +1 -0
  380. package/runtime/stations.json +37 -0
  381. package/runtime/trains/.keep +0 -0
  382. package/LICENSE +0 -21
  383. package/README.md +0 -78
  384. package/dist/channels/discord.js +0 -148
  385. package/dist/channels/telegram.js +0 -137
  386. package/dist/lib/address.js +0 -21
  387. package/dist/lib/codex-rc.js +0 -274
  388. package/dist/lib/dotenv.js +0 -31
  389. package/dist/log.js +0 -12
  390. package/dist/paths.js +0 -45
  391. package/skills/metro/SKILL.md +0 -122
@@ -0,0 +1,24 @@
1
+ import { errMsg } from '@metro-labs/core/log';
2
+ import { readCalls } from '@metro-labs/core/trains/protocol';
3
+ import { announceAccounts } from '@metro-labs/core/stations/train-boot';
4
+ import { accounts, bootAccount, loadAccounts } from './accounts.js';
5
+ import { handleCall } from './actions.js';
6
+ import { fetchCredits } from './api.js';
7
+
8
+ readCalls('threema', handleCall);
9
+
10
+ for (const cfg of loadAccounts()) accounts.set(cfg.id, bootAccount(cfg));
11
+ announceAccounts('threema', accounts.keys());
12
+
13
+ for (const acct of accounts.values())
14
+ fetchCredits(acct.cfg)
15
+ .then((credits) => {
16
+ process.stderr.write(
17
+ `threema[${acct.cfg.id}] ${acct.cfg.gatewayId}: ${credits} credits left\n`,
18
+ );
19
+ })
20
+ .catch((err: unknown) => {
21
+ process.stderr.write(
22
+ `threema[${acct.cfg.id}] credits check failed: ${errMsg(err)}\n`,
23
+ );
24
+ });
@@ -0,0 +1,264 @@
1
+ import { bytesToHex, hexToBytes } from './crypto.js';
2
+
3
+ export const MSG_TEXT = 0x01;
4
+ export const MSG_FILE = 0x17;
5
+ export const MSG_GROUP_TEXT = 0x41;
6
+ export const MSG_GROUP_FILE = 0x46;
7
+ export const MSG_GROUP_SETUP = 0x4a;
8
+ export const MSG_GROUP_RENAME = 0x4b;
9
+ export const MSG_GROUP_LEAVE = 0x4c;
10
+ export const MSG_GROUP_REQUEST_SYNC = 0x51;
11
+ export const MSG_DELIVERY_RECEIPT = 0x80;
12
+ export const MSG_GROUP_DELIVERY_RECEIPT = 0x81;
13
+ export const MSG_REACTION = 0x82;
14
+ export const MSG_GROUP_REACTION = 0x83;
15
+ export const MSG_TYPING = 0x90;
16
+
17
+ export const RECEIPT_ACK = 0x03;
18
+ export const RECEIPT_DECLINE = 0x04;
19
+
20
+ export const ID_BYTES = 8;
21
+ const MESSAGE_ID_BYTES = 8;
22
+ const PROTO_MESSAGE_ID = 0x09;
23
+ const PROTO_APPLY = 0x12;
24
+ const PROTO_WITHDRAW = 0x1a;
25
+
26
+ export interface GroupRef {
27
+ creator: string;
28
+ groupId: string;
29
+ }
30
+
31
+ export interface FileData {
32
+ blobId: string;
33
+ key: string;
34
+ mime: string;
35
+ name: string;
36
+ size: number | null;
37
+ caption: string | null;
38
+ media: boolean;
39
+ }
40
+
41
+ export type Decoded =
42
+ | { kind: 'text'; text: string }
43
+ | { kind: 'file'; group: GroupRef | null; file: FileData }
44
+ | { kind: 'group-text'; group: GroupRef; text: string }
45
+ | { kind: 'group-setup'; groupId: string; members: string[] }
46
+ | { kind: 'group-rename'; groupId: string; name: string }
47
+ | { kind: 'group-leave'; group: GroupRef }
48
+ | { kind: 'receipt'; group: GroupRef | null; status: number; messageIds: string[] }
49
+ | { kind: 'reaction'; group: GroupRef | null; messageId: string; emoji: string; removed: boolean }
50
+ | { kind: 'typing' }
51
+ | { kind: 'other'; type: number };
52
+
53
+ const ascii = (bytes: Uint8Array): string => Buffer.from(bytes).toString('ascii');
54
+ const utf8 = (bytes: Uint8Array): string => Buffer.from(bytes).toString('utf8');
55
+
56
+ const groupHeader = (group: GroupRef): Uint8Array =>
57
+ new Uint8Array([...Buffer.from(group.creator, 'ascii'), ...hexToBytes(group.groupId, 'group id')]);
58
+
59
+ const readGroup = (body: Uint8Array): GroupRef | null =>
60
+ body.length < ID_BYTES * 2 ? null : { creator: ascii(body.subarray(0, ID_BYTES)), groupId: bytesToHex(body.subarray(ID_BYTES, ID_BYTES * 2)) };
61
+
62
+ function ids(body: Uint8Array, from: number): string[] {
63
+ const out: string[] = [];
64
+ for (let at = from; at + MESSAGE_ID_BYTES <= body.length; at += MESSAGE_ID_BYTES) out.push(bytesToHex(body.subarray(at, at + MESSAGE_ID_BYTES)));
65
+ return out;
66
+ }
67
+
68
+ const members = (body: Uint8Array, from: number): string[] => {
69
+ const out: string[] = [];
70
+ for (let at = from; at + ID_BYTES <= body.length; at += ID_BYTES) out.push(ascii(body.subarray(at, at + ID_BYTES)));
71
+ return out;
72
+ };
73
+
74
+ function concat(...parts: Uint8Array[]): Uint8Array {
75
+ const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
76
+ let at = 0;
77
+ for (const p of parts) {
78
+ out.set(p, at);
79
+ at += p.length;
80
+ }
81
+ return out;
82
+ }
83
+
84
+ const typed = (type: number, ...parts: Uint8Array[]): Uint8Array => concat(new Uint8Array([type]), ...parts);
85
+
86
+ export const encodeText = (text: string): Uint8Array => typed(MSG_TEXT, Buffer.from(text, 'utf8'));
87
+
88
+ export const encodeGroupText = (group: GroupRef, text: string): Uint8Array => typed(MSG_GROUP_TEXT, groupHeader(group), Buffer.from(text, 'utf8'));
89
+
90
+ const fileJson = (f: FileData): Uint8Array =>
91
+ Buffer.from(
92
+ JSON.stringify({
93
+ b: f.blobId,
94
+ k: f.key,
95
+ m: f.mime,
96
+ n: f.name,
97
+ ...(f.size === null ? {} : { s: f.size }),
98
+ i: f.media ? 1 : 0,
99
+ j: f.media ? 1 : 0,
100
+ ...(f.caption === null ? {} : { d: f.caption }),
101
+ }),
102
+ 'utf8',
103
+ );
104
+
105
+ export const encodeFile = (f: FileData): Uint8Array => typed(MSG_FILE, fileJson(f));
106
+
107
+ export const encodeGroupFile = (group: GroupRef, f: FileData): Uint8Array => typed(MSG_GROUP_FILE, groupHeader(group), fileJson(f));
108
+
109
+ const HEX_RE = /^[0-9a-f]+$/i;
110
+
111
+ const str = (v: unknown, fallback: string): string => (typeof v === 'string' && v !== '' ? v : fallback);
112
+ const num = (v: unknown): number | null => (typeof v === 'number' ? v : null);
113
+ const hexOf = (v: unknown, length: number): string | null => {
114
+ const text = typeof v === 'string' ? v.toLowerCase() : '';
115
+ return text.length === length && HEX_RE.test(text) ? text : null;
116
+ };
117
+
118
+ function parseJson(raw: Uint8Array): Record<string, unknown> | null {
119
+ try {
120
+ const parsed: unknown = JSON.parse(utf8(raw));
121
+ return typeof parsed === 'object' && parsed !== null ? (parsed as Record<string, unknown>) : null;
122
+ } catch {
123
+ return null;
124
+ }
125
+ }
126
+
127
+ export function decodeFileJson(raw: Uint8Array): FileData | null {
128
+ const o = parseJson(raw);
129
+ if (o === null) return null;
130
+ const blobId = hexOf(o.b, 32);
131
+ const key = hexOf(o.k, 64);
132
+ if (blobId === null || key === null) return null;
133
+ const rendering = num(o.j) ?? num(o.i) ?? 0;
134
+ return {
135
+ blobId,
136
+ key,
137
+ mime: str(o.m, 'application/octet-stream'),
138
+ name: str(o.n, 'file'),
139
+ size: num(o.s),
140
+ caption: typeof o.d === 'string' && o.d !== '' ? o.d : null,
141
+ media: rendering === 1,
142
+ };
143
+ }
144
+
145
+ export const encodeGroupSyncRequest = (groupId: string): Uint8Array => typed(MSG_GROUP_REQUEST_SYNC, hexToBytes(groupId, 'group id'));
146
+
147
+ function varint(n: number): Uint8Array {
148
+ const out: number[] = [];
149
+ let rest = n;
150
+ while (rest >= 0x80) {
151
+ out.push((rest & 0x7f) | 0x80);
152
+ rest = Math.floor(rest / 0x80);
153
+ }
154
+ out.push(rest);
155
+ return new Uint8Array(out);
156
+ }
157
+
158
+ export function encodeReactionProto(messageId: string, emoji: string, removed: boolean): Uint8Array {
159
+ const emojiBytes = Buffer.from(emoji, 'utf8');
160
+ return concat(
161
+ new Uint8Array([PROTO_MESSAGE_ID]),
162
+ hexToBytes(messageId, 'message id'),
163
+ new Uint8Array([removed ? PROTO_WITHDRAW : PROTO_APPLY]),
164
+ varint(emojiBytes.length),
165
+ emojiBytes,
166
+ );
167
+ }
168
+
169
+ export const encodeReaction = (messageId: string, emoji: string, removed: boolean): Uint8Array =>
170
+ typed(MSG_REACTION, encodeReactionProto(messageId, emoji, removed));
171
+
172
+ export const encodeGroupReaction = (group: GroupRef, messageId: string, emoji: string, removed: boolean): Uint8Array =>
173
+ typed(MSG_GROUP_REACTION, groupHeader(group), encodeReactionProto(messageId, emoji, removed));
174
+
175
+ function readVarint(body: Uint8Array, at: number): { value: number; next: number } | null {
176
+ let value = 0;
177
+ let shift = 1;
178
+ for (let i = at; i < body.length; i++) {
179
+ const byte = body[i] ?? 0;
180
+ value += (byte & 0x7f) * shift;
181
+ if (byte < 0x80) return { value, next: i + 1 };
182
+ shift *= 0x80;
183
+ }
184
+ return null;
185
+ }
186
+
187
+ interface ProtoField {
188
+ tag: number;
189
+ bytes: Uint8Array;
190
+ next: number;
191
+ }
192
+
193
+ function readField(body: Uint8Array, at: number): ProtoField | null {
194
+ const tag = body[at] ?? 0;
195
+ if (tag === PROTO_MESSAGE_ID)
196
+ return at + 1 + MESSAGE_ID_BYTES > body.length ? null : { tag, bytes: body.subarray(at + 1, at + 1 + MESSAGE_ID_BYTES), next: at + 1 + MESSAGE_ID_BYTES };
197
+ if (tag !== PROTO_APPLY && tag !== PROTO_WITHDRAW) return null;
198
+ const len = readVarint(body, at + 1);
199
+ if (len === null || len.next + len.value > body.length) return null;
200
+ return { tag, bytes: body.subarray(len.next, len.next + len.value), next: len.next + len.value };
201
+ }
202
+
203
+ export function decodeReactionProto(body: Uint8Array): { messageId: string; emoji: string; removed: boolean } | null {
204
+ let messageId: string | null = null;
205
+ let emoji = '';
206
+ let removed = false;
207
+ for (let at = 0; at < body.length; ) {
208
+ const field = readField(body, at);
209
+ if (field === null) return null;
210
+ if (field.tag === PROTO_MESSAGE_ID) messageId = bytesToHex(field.bytes);
211
+ else {
212
+ emoji = utf8(field.bytes);
213
+ removed = field.tag === PROTO_WITHDRAW;
214
+ }
215
+ at = field.next;
216
+ }
217
+ return messageId === null || emoji === '' ? null : { messageId, emoji, removed };
218
+ }
219
+
220
+ function decodeGroup(type: number, body: Uint8Array): Decoded {
221
+ const group = readGroup(body);
222
+ if (group === null) return { kind: 'other', type };
223
+ const rest = body.subarray(ID_BYTES * 2);
224
+ if (type === MSG_GROUP_TEXT) return { kind: 'group-text', group, text: utf8(rest) };
225
+ if (type === MSG_GROUP_FILE) {
226
+ const file = decodeFileJson(rest);
227
+ return file === null ? { kind: 'other', type } : { kind: 'file', group, file };
228
+ }
229
+ if (type === MSG_GROUP_LEAVE) return { kind: 'group-leave', group };
230
+ if (type === MSG_GROUP_DELIVERY_RECEIPT) return { kind: 'receipt', group, status: rest[0] ?? 0, messageIds: ids(rest, 1) };
231
+ const reaction = decodeReactionProto(rest);
232
+ return reaction === null ? { kind: 'other', type } : { kind: 'reaction', group, ...reaction };
233
+ }
234
+
235
+ const GROUP_TYPES = new Set([MSG_GROUP_TEXT, MSG_GROUP_FILE, MSG_GROUP_LEAVE, MSG_GROUP_DELIVERY_RECEIPT, MSG_GROUP_REACTION]);
236
+ const DIRECT_TYPES = new Set([MSG_TEXT, MSG_FILE, MSG_TYPING, MSG_DELIVERY_RECEIPT, MSG_REACTION]);
237
+
238
+ function decodeControl(type: number, body: Uint8Array): Decoded {
239
+ if (body.length < ID_BYTES) return { kind: 'other', type };
240
+ const groupId = bytesToHex(body.subarray(0, ID_BYTES));
241
+ if (type === MSG_GROUP_SETUP) return { kind: 'group-setup', groupId, members: members(body, ID_BYTES) };
242
+ return { kind: 'group-rename', groupId, name: utf8(body.subarray(ID_BYTES)) };
243
+ }
244
+
245
+ function decodeDirect(type: number, body: Uint8Array): Decoded {
246
+ if (type === MSG_TEXT) return { kind: 'text', text: utf8(body) };
247
+ if (type === MSG_FILE) {
248
+ const file = decodeFileJson(body);
249
+ return file === null ? { kind: 'other', type } : { kind: 'file', group: null, file };
250
+ }
251
+ if (type === MSG_TYPING) return { kind: 'typing' };
252
+ if (type === MSG_DELIVERY_RECEIPT) return { kind: 'receipt', group: null, status: body[0] ?? 0, messageIds: ids(body, 1) };
253
+ const reaction = decodeReactionProto(body);
254
+ return reaction === null ? { kind: 'other', type } : { kind: 'reaction', group: null, ...reaction };
255
+ }
256
+
257
+ export function decode(plain: Uint8Array): Decoded {
258
+ const type = plain[0] ?? -1;
259
+ const body = plain.subarray(1);
260
+ if (DIRECT_TYPES.has(type)) return decodeDirect(type, body);
261
+ if (type === MSG_GROUP_SETUP || type === MSG_GROUP_RENAME) return decodeControl(type, body);
262
+ if (GROUP_TYPES.has(type)) return decodeGroup(type, body);
263
+ return { kind: 'other', type };
264
+ }
@@ -0,0 +1,160 @@
1
+ import { TrainError } from '@metro-labs/core/train-error';
2
+ import { respond } from '@metro-labs/core/stations/station-runtime';
3
+ import { publicKeyFor, targetOf, type Account, type Target } from './accounts.js';
4
+ import { sendE2E } from './api.js';
5
+ import { bytesToHex, quoted, seal } from './crypto.js';
6
+ import { encodeFileFor, fileLabel, packFile, type OutgoingFile } from './files.js';
7
+ import { emitOutbound, emitOutboundReaction } from './format.js';
8
+ import { MESSAGE_ID_RE } from './ids.js';
9
+ import {
10
+ encodeGroupReaction,
11
+ encodeGroupSyncRequest,
12
+ encodeGroupText,
13
+ encodeReaction,
14
+ encodeText,
15
+ type GroupRef,
16
+ } from './messages.js';
17
+
18
+ export const MAX_TEXT_BYTES = 3500;
19
+ const SENT_MAX = 2000;
20
+
21
+ type Args = Record<string, unknown>;
22
+
23
+ const sentIds = new Set<string>();
24
+
25
+ export function noteSent(messageId: string): void {
26
+ sentIds.add(messageId);
27
+ while (sentIds.size > SENT_MAX) {
28
+ const oldest = sentIds.values().next();
29
+ if (oldest.done) break;
30
+ sentIds.delete(oldest.value);
31
+ }
32
+ }
33
+
34
+ export const sentByUs = (messageId: string): boolean => sentIds.has(messageId);
35
+
36
+ async function deliver(acct: Account, to: string, plain: Uint8Array, group: boolean): Promise<string> {
37
+ const { nonce, box } = seal(plain, await publicKeyFor(acct, to), acct.keys);
38
+ const messageId = await sendE2E(acct.cfg, to, bytesToHex(nonce), bytesToHex(box), group);
39
+ noteSent(messageId);
40
+ return messageId;
41
+ }
42
+
43
+ function recipientsOf(acct: Account, group: GroupRef): string[] {
44
+ const members = acct.groups.recipients(group);
45
+ if (members === null)
46
+ throw new TrainError(
47
+ 'threema_unknown_group',
48
+ 'metro has not received this group\'s member list yet; it asked the group creator for it, try again in a moment',
49
+ { retryable: true },
50
+ );
51
+ return members;
52
+ }
53
+
54
+ export async function requestSync(acct: Account, group: GroupRef): Promise<void> {
55
+ await deliver(acct, group.creator, encodeGroupSyncRequest(group.groupId), true);
56
+ }
57
+
58
+ async function fanOut(acct: Account, target: Target, direct: Uint8Array, grouped: (g: GroupRef) => Uint8Array): Promise<string[]> {
59
+ if (target.kind === 'user') return [await deliver(acct, target.id, direct, false)];
60
+ const ids: string[] = [];
61
+ for (const member of recipientsOf(acct, target.group)) ids.push(await deliver(acct, member, grouped(target.group), true));
62
+ if (ids.length === 0) throw new TrainError('threema_empty_group', 'nobody else is in this group', { retryable: false });
63
+ return ids;
64
+ }
65
+
66
+ interface SendArgs {
67
+ line: string;
68
+ text?: unknown;
69
+ replyTo?: unknown;
70
+ account?: string;
71
+ attachments?: unknown;
72
+ }
73
+
74
+ const filesOf = (raw: unknown): OutgoingFile[] =>
75
+ (Array.isArray(raw) ? raw : [])
76
+ .map((a) => (a ?? {}) as Record<string, unknown>)
77
+ .filter((a) => typeof a.path === 'string' && a.path !== '')
78
+ .map((a) => ({ path: String(a.path), mime: typeof a.mime === 'string' && a.mime !== '' ? a.mime : 'application/octet-stream', name: typeof a.name === 'string' && a.name !== '' ? a.name : 'file' }));
79
+
80
+ function textOf(a: SendArgs, files: OutgoingFile[]): string {
81
+ const text = typeof a.text === 'string' ? a.text : '';
82
+ if (text === '' && files.length === 0)
83
+ throw new TrainError('threema_text_required', 'give some text or a file to send', { retryable: false });
84
+ return text;
85
+ }
86
+
87
+ async function sendFiles(acct: Account, target: Target, files: OutgoingFile[], caption: string | null): Promise<{ ids: string[]; labels: string[] }> {
88
+ const ids: string[] = [];
89
+ const labels: string[] = [];
90
+ for (const [i, file] of files.entries()) {
91
+ const packed = await packFile(acct, file, i === 0 ? caption : null);
92
+ const sent = await fanOut(acct, target, encodeFileFor(null, packed), (g) => encodeFileFor(g, packed));
93
+ ids.push(...sent);
94
+ labels.push(fileLabel(file));
95
+ }
96
+ return { ids, labels };
97
+ }
98
+
99
+ function messageIdOf(raw: unknown, field: string): string {
100
+ const id = typeof raw === 'string' ? raw.toLowerCase() : '';
101
+ if (!MESSAGE_ID_RE.test(id))
102
+ throw new TrainError('threema_bad_message_id', `${field} must be a Threema message id, 16 hex characters`, { retryable: false });
103
+ return id;
104
+ }
105
+
106
+ const replyTargetOf = (raw: unknown): string | undefined => (raw === undefined || raw === null || raw === '' ? undefined : messageIdOf(raw, 'replyTo'));
107
+
108
+ function bodyOf(text: string, replyTo: string | undefined): string {
109
+ const body = replyTo === undefined ? text : quoted(replyTo, text);
110
+ if (Buffer.byteLength(body, 'utf8') > MAX_TEXT_BYTES)
111
+ throw new TrainError('threema_message_too_long', `Threema carries at most ${MAX_TEXT_BYTES} bytes of text per message; split it up`, { retryable: false });
112
+ return body;
113
+ }
114
+
115
+ interface Sent {
116
+ ids: string[];
117
+ labels: string[];
118
+ }
119
+
120
+ const resultOf = (acct: Account, sent: Sent): Record<string, unknown> => ({
121
+ messageId: sent.ids[0] ?? '',
122
+ account: acct.cfg.id,
123
+ ...(sent.ids.length > 1 ? { messageIds: sent.ids } : {}),
124
+ ...(sent.labels.length > 0 ? { attachments: sent.labels } : {}),
125
+ });
126
+
127
+ export async function send(id: string, args: Args): Promise<void> {
128
+ const a = args as unknown as SendArgs;
129
+ const files = filesOf(a.attachments);
130
+ const text = textOf(a, files);
131
+ const replyTo = replyTargetOf(a.replyTo);
132
+ const { acct, target } = targetOf(a.line, a.account);
133
+ const body = bodyOf(text, replyTo);
134
+ const sent: Sent =
135
+ files.length === 0
136
+ ? { ids: await fanOut(acct, target, encodeText(body), (g) => encodeGroupText(g, body)), labels: [] }
137
+ : await sendFiles(acct, target, files, body === '' ? null : body);
138
+ emitOutbound(acct.cfg.id, a.line, sent.ids[0] ?? '', text === '' ? `[${sent.labels.join(', ')}]` : text, replyTo);
139
+ respond(id, { result: resultOf(acct, sent) });
140
+ }
141
+
142
+ interface ReactArgs {
143
+ line: string;
144
+ messageId?: unknown;
145
+ emoji?: unknown;
146
+ account?: string;
147
+ action?: unknown;
148
+ }
149
+
150
+ export async function react(id: string, args: Args): Promise<void> {
151
+ const a = args as unknown as ReactArgs;
152
+ const emoji = typeof a.emoji === 'string' ? a.emoji.trim() : '';
153
+ if (emoji === '') throw new TrainError('threema_emoji_required', 'react needs an emoji', { retryable: false });
154
+ const messageId = messageIdOf(a.messageId, 'messageId');
155
+ const removed = a.action === 'removed';
156
+ const { acct, target } = targetOf(a.line, a.account);
157
+ const ids = await fanOut(acct, target, encodeReaction(messageId, emoji, removed), (g) => encodeGroupReaction(g, messageId, emoji, removed));
158
+ emitOutboundReaction(acct.cfg.id, a.line, ids[0] ?? '', emoji, messageId, removed);
159
+ respond(id, { result: { messageId: ids[0] ?? '', account: acct.cfg.id, emoji, removed } });
160
+ }
@@ -0,0 +1,13 @@
1
+ import type { Station, Verb } from '@metro-labs/core/stations/types';
2
+ import { groupFiles } from './groups.js';
3
+
4
+ export const threemaStation: Station = {
5
+ name: 'threema',
6
+ hasAccounts: true,
7
+ hasTrain: true,
8
+ messageVerbs: new Set<Verb>(['send', 'reply', 'react', 'unreact']),
9
+ attachmentMode: 'canonical',
10
+ forget: groupFiles.forget,
11
+ forgetExcept: groupFiles.forgetExcept,
12
+ tools: [],
13
+ };
@@ -0,0 +1,117 @@
1
+ import { createPrivateKey, createPublicKey } from 'node:crypto';
2
+ import {
3
+ GATEWAY_API,
4
+ isGatewayId,
5
+ normalizeThreemaId,
6
+ parsePrivateKey,
7
+ } from './ids.js';
8
+
9
+ export { parsePrivateKey } from './ids.js';
10
+
11
+ export class ThreemaGatewayError extends Error {}
12
+
13
+ export interface ThreemaCredentials {
14
+ gatewayId: string;
15
+ secret: string;
16
+ privateKey: string;
17
+ }
18
+
19
+ export interface ThreemaGatewayIdentity {
20
+ gatewayId: string;
21
+ publicKey: string;
22
+ credits: number;
23
+ }
24
+
25
+ const PKCS8_X25519_PREFIX = Buffer.from(
26
+ '302e020100300506032b656e04220420',
27
+ 'hex',
28
+ );
29
+ const TIMEOUT_MS = 10_000;
30
+ const PUBLIC_KEY_RE = /^[0-9a-f]{64}$/;
31
+
32
+ export function publicKeyOf(privateKeyHex: string): string {
33
+ const der = Buffer.concat([
34
+ PKCS8_X25519_PREFIX,
35
+ Buffer.from(privateKeyHex, 'hex'),
36
+ ]);
37
+ const key = createPrivateKey({ key: der, format: 'der', type: 'pkcs8' });
38
+ const jwk = createPublicKey(key).export({ format: 'jwk' });
39
+ return Buffer.from(String(jwk.x), 'base64url').toString('hex');
40
+ }
41
+
42
+ async function gatewayGet(
43
+ path: string,
44
+ gatewayId: string,
45
+ secret: string,
46
+ ): Promise<Response> {
47
+ const query = new URLSearchParams({ from: gatewayId, secret });
48
+ try {
49
+ return await fetch(`${GATEWAY_API}${path}?${query.toString()}`, {
50
+ signal: AbortSignal.timeout(TIMEOUT_MS),
51
+ });
52
+ } catch {
53
+ throw new ThreemaGatewayError(
54
+ 'could not reach the Threema Gateway to check those credentials',
55
+ );
56
+ }
57
+ }
58
+
59
+ function refused(res: Response, what: string): ThreemaGatewayError {
60
+ if (res.status === 401)
61
+ return new ThreemaGatewayError(
62
+ 'Threema rejected that Gateway ID or API secret',
63
+ );
64
+ if (res.status === 404)
65
+ return new ThreemaGatewayError(`Threema does not know ${what}`);
66
+ return new ThreemaGatewayError(
67
+ `the Threema Gateway answered ${res.status} while checking ${what}`,
68
+ );
69
+ }
70
+
71
+ async function fetchCredits(gatewayId: string, secret: string): Promise<number> {
72
+ const res = await gatewayGet('/credits', gatewayId, secret);
73
+ if (!res.ok) throw refused(res, 'the credit balance');
74
+ const credits = Number((await res.text()).trim());
75
+ if (!Number.isInteger(credits))
76
+ throw new ThreemaGatewayError(
77
+ 'the Threema Gateway gave an unexpected answer for the credit balance',
78
+ );
79
+ return credits;
80
+ }
81
+
82
+ async function fetchPublicKey(gatewayId: string, secret: string): Promise<string> {
83
+ const res = await gatewayGet(
84
+ `/pubkeys/${encodeURIComponent(gatewayId)}`,
85
+ gatewayId,
86
+ secret,
87
+ );
88
+ if (!res.ok) throw refused(res, `the public key of ${gatewayId}`);
89
+ const hex = (await res.text()).trim().toLowerCase();
90
+ if (!PUBLIC_KEY_RE.test(hex))
91
+ throw new ThreemaGatewayError(
92
+ 'the Threema Gateway gave an unexpected answer for the public key',
93
+ );
94
+ return hex;
95
+ }
96
+
97
+ export async function verifyThreemaGateway(
98
+ input: ThreemaCredentials,
99
+ ): Promise<ThreemaGatewayIdentity> {
100
+ const gatewayId = normalizeThreemaId(input.gatewayId);
101
+ if (!isGatewayId(gatewayId))
102
+ throw new ThreemaGatewayError(
103
+ 'a Threema Gateway ID is a * followed by 7 letters or digits, as in *ABCDEFG',
104
+ );
105
+ const privateKey = parsePrivateKey(input.privateKey);
106
+ if (privateKey === null)
107
+ throw new ThreemaGatewayError(
108
+ 'the private key is the 64 hex characters from the key file Threema gave you, with or without its private: prefix',
109
+ );
110
+ const credits = await fetchCredits(gatewayId, input.secret);
111
+ const registered = await fetchPublicKey(gatewayId, input.secret);
112
+ if (registered !== publicKeyOf(privateKey))
113
+ throw new ThreemaGatewayError(
114
+ `that private key does not belong to ${gatewayId}: the Gateway holds a different public key for it`,
115
+ );
116
+ return { gatewayId, publicKey: registered, credits };
117
+ }
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@metro-labs/webhook",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "exports": {
7
+ ".": "./src/station.ts"
8
+ },
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "test": "tsc --noEmit && bun test test/"
12
+ },
13
+ "dependencies": {
14
+ "@metro-labs/core": "workspace:*"
15
+ },
16
+ "devDependencies": {
17
+ "@stage-labs/config": "0.1.0-beta.2",
18
+ "eslint": "^10.3.0",
19
+ "knip": "^6.15.0",
20
+ "madge": "^8.0.0",
21
+ "typescript": "^5"
22
+ }
23
+ }
@@ -0,0 +1,37 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { Line } from '@metro-labs/core/lines';
3
+ import { mintId, type MetroEvent } from '@metro-labs/core/events';
4
+ import type { Endpoint } from '@metro-labs/core/endpoints';
5
+ import type { Station, Verb } from '@metro-labs/core/stations/types';
6
+
7
+ export const webhookStation: Station = {
8
+ name: 'webhook',
9
+ hasAccounts: true,
10
+ hasTrain: false,
11
+ messageVerbs: new Set<Verb>(),
12
+ attachmentMode: 'none',
13
+ tools: [],
14
+ };
15
+
16
+ export function webhookEntry(
17
+ endpoint: Endpoint,
18
+ headers: Record<string, string>,
19
+ body: unknown,
20
+ method: string,
21
+ url: string,
22
+ ): MetroEvent {
23
+ const line = Line.webhook(endpoint.id);
24
+ return {
25
+ id: mintId(),
26
+ ts: new Date().toISOString(),
27
+ station: 'webhook',
28
+ line,
29
+ lineName: endpoint.label,
30
+ from: line,
31
+ to: line,
32
+ messageId:
33
+ headers['x-github-delivery'] ?? headers['x-request-id'] ?? randomUUID(),
34
+ text: `${headers['x-github-event'] ?? headers['x-intercom-topic'] ?? 'event'} ${method} ${url}`,
35
+ payload: { headers, body },
36
+ };
37
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "@metro-labs/whatsapp",
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
+ "./tokens": "./src/token-store.ts"
11
+ },
12
+ "scripts": {
13
+ "build": "tsc",
14
+ "test": "tsc --noEmit && bun test test/"
15
+ },
16
+ "dependencies": {
17
+ "@metro-labs/core": "workspace:*",
18
+ "baileys": "7.0.0-rc14"
19
+ },
20
+ "devDependencies": {
21
+ "@stage-labs/config": "0.1.0-beta.2",
22
+ "eslint": "^10.3.0",
23
+ "knip": "^6.15.0",
24
+ "madge": "^8.0.0",
25
+ "typescript": "^5"
26
+ }
27
+ }