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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/dist/api.js +14 -0
  2. package/dist/claude-settings.js +40 -0
  3. package/dist/claude.js +176 -0
  4. package/dist/cli.js +101 -0
  5. package/dist/control.js +74 -0
  6. package/dist/hold.js +211 -0
  7. package/dist/local.js +93 -0
  8. package/dist/mcp-config.js +25 -0
  9. package/dist/node-name.js +71 -0
  10. package/dist/onboarding.js +43 -0
  11. package/dist/plugin.js +120 -0
  12. package/dist/provider-flags.js +9 -0
  13. package/dist/route.js +38 -0
  14. package/dist/runtime-install.js +97 -0
  15. package/dist/runtime.js +74 -0
  16. package/dist/serve.js +150 -0
  17. package/dist/service.js +242 -0
  18. package/dist/tail.js +119 -0
  19. package/dist/update.js +45 -0
  20. package/dist/version.js +97 -0
  21. package/package.json +14 -27
  22. package/runtime/marketplace/.claude-plugin/marketplace.json +12 -0
  23. package/runtime/marketplace/plugin/.claude-plugin/plugin.json +18 -0
  24. package/runtime/marketplace/plugin/.mcp.json +1 -0
  25. package/runtime/marketplace/plugin/bin/guard.mjs +55 -0
  26. package/runtime/marketplace/plugin/bin/metro-plugin.mjs +122 -0
  27. package/runtime/marketplace/plugin/bin/session-start.mjs +31 -0
  28. package/runtime/marketplace/plugin/hooks/hooks.json +25 -0
  29. package/runtime/marketplace/plugin/orchestrator.md +16 -0
  30. package/runtime/marketplace/plugin/skills/refresh/SKILL.md +15 -0
  31. package/runtime/node_modules/@metro-labs/core/package.json +43 -0
  32. package/runtime/node_modules/@metro-labs/core/src/address.ts +6 -0
  33. package/runtime/node_modules/@metro-labs/core/src/endpoints.ts +7 -0
  34. package/runtime/node_modules/@metro-labs/core/src/events.ts +120 -0
  35. package/runtime/node_modules/@metro-labs/core/src/identity.ts +7 -0
  36. package/runtime/node_modules/@metro-labs/core/src/ids.ts +21 -0
  37. package/runtime/node_modules/@metro-labs/core/src/is-record.ts +2 -0
  38. package/runtime/node_modules/@metro-labs/core/src/lines.ts +87 -0
  39. package/runtime/node_modules/@metro-labs/core/src/log.ts +42 -0
  40. package/runtime/node_modules/@metro-labs/core/src/protocol.ts +226 -0
  41. package/runtime/node_modules/@metro-labs/core/src/secure-fs.ts +57 -0
  42. package/runtime/node_modules/@metro-labs/core/src/station-names.ts +13 -0
  43. package/runtime/node_modules/@metro-labs/core/src/stations/account-store.ts +60 -0
  44. package/runtime/node_modules/@metro-labs/core/src/stations/attachments.ts +196 -0
  45. package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +88 -0
  46. package/runtime/node_modules/@metro-labs/core/src/stations/station-runtime.ts +64 -0
  47. package/runtime/node_modules/@metro-labs/core/src/stations/types.ts +108 -0
  48. package/runtime/node_modules/@metro-labs/core/src/str.ts +5 -0
  49. package/runtime/node_modules/@metro-labs/core/src/train-error.ts +53 -0
  50. package/runtime/node_modules/@metro-labs/core/src/version.ts +2 -0
  51. package/runtime/node_modules/@metro-labs/daemon/package.json +48 -0
  52. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-attach.ts +19 -0
  53. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-routes.ts +70 -0
  54. package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +365 -0
  55. package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +43 -0
  56. package/runtime/node_modules/@metro-labs/daemon/src/agents/allowlist.ts +37 -0
  57. package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +300 -0
  58. package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +122 -0
  59. package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +371 -0
  60. package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +155 -0
  61. package/runtime/node_modules/@metro-labs/daemon/src/agents/keys.ts +45 -0
  62. package/runtime/node_modules/@metro-labs/daemon/src/agents/map.ts +95 -0
  63. package/runtime/node_modules/@metro-labs/daemon/src/agents/scope.ts +64 -0
  64. package/runtime/node_modules/@metro-labs/daemon/src/agents/seed.ts +33 -0
  65. package/runtime/node_modules/@metro-labs/daemon/src/agents/senders.ts +38 -0
  66. package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +209 -0
  67. package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +39 -0
  68. package/runtime/node_modules/@metro-labs/daemon/src/boot/crash-guard.ts +49 -0
  69. package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +14 -0
  70. package/runtime/node_modules/@metro-labs/daemon/src/boot/paths.ts +106 -0
  71. package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +25 -0
  72. package/runtime/node_modules/@metro-labs/daemon/src/channels/dedupe.ts +24 -0
  73. package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +377 -0
  74. package/runtime/node_modules/@metro-labs/daemon/src/channels/media-note.ts +85 -0
  75. package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +75 -0
  76. package/runtime/node_modules/@metro-labs/daemon/src/channels/relay.ts +129 -0
  77. package/runtime/node_modules/@metro-labs/daemon/src/channels/webhook-note.ts +51 -0
  78. package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +309 -0
  79. package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +358 -0
  80. package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +169 -0
  81. package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +56 -0
  82. package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +140 -0
  83. package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +90 -0
  84. package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +253 -0
  85. package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +117 -0
  86. package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +239 -0
  87. package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +154 -0
  88. package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +109 -0
  89. package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +310 -0
  90. package/runtime/node_modules/@metro-labs/daemon/src/connectors/cli-api.ts +63 -0
  91. package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +175 -0
  92. package/runtime/node_modules/@metro-labs/daemon/src/connectors/json.ts +21 -0
  93. package/runtime/node_modules/@metro-labs/daemon/src/connectors/model.ts +77 -0
  94. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-client.ts +166 -0
  95. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-discovery.ts +139 -0
  96. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-pending.ts +65 -0
  97. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-routes.ts +171 -0
  98. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth.ts +155 -0
  99. package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +122 -0
  100. package/runtime/node_modules/@metro-labs/daemon/src/connectors/reach.ts +27 -0
  101. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-target.ts +39 -0
  102. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +296 -0
  103. package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +286 -0
  104. package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +98 -0
  105. package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +70 -0
  106. package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +332 -0
  107. package/runtime/node_modules/@metro-labs/daemon/src/connectors/watch.ts +54 -0
  108. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +99 -0
  109. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-owner.ts +32 -0
  110. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +121 -0
  111. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +294 -0
  112. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-store.ts +181 -0
  113. package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +259 -0
  114. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +231 -0
  115. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
  116. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +288 -0
  117. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +203 -0
  118. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +231 -0
  119. package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +123 -0
  120. package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +187 -0
  121. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +229 -0
  122. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +269 -0
  123. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +207 -0
  124. package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +90 -0
  125. package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +65 -0
  126. package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +17 -0
  127. package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +229 -0
  128. package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +151 -0
  129. package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +216 -0
  130. package/runtime/node_modules/@metro-labs/daemon/src/mcp/channel-owner.ts +29 -0
  131. package/runtime/node_modules/@metro-labs/daemon/src/mcp/ctx.ts +62 -0
  132. package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +91 -0
  133. package/runtime/node_modules/@metro-labs/daemon/src/mcp/frame-scope.ts +21 -0
  134. package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +129 -0
  135. package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +191 -0
  136. package/runtime/node_modules/@metro-labs/daemon/src/mcp/member-tools.ts +72 -0
  137. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +66 -0
  138. package/runtime/node_modules/@metro-labs/daemon/src/mcp/raw-get-stream.ts +207 -0
  139. package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +47 -0
  140. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-registry.ts +172 -0
  141. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-route.ts +43 -0
  142. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +215 -0
  143. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-dispatch.ts +215 -0
  144. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-schemas.ts +400 -0
  145. package/runtime/node_modules/@metro-labs/daemon/src/mcp/upload-tool.ts +66 -0
  146. package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +237 -0
  147. package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +389 -0
  148. package/runtime/node_modules/@metro-labs/daemon/src/routes/body.ts +24 -0
  149. package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +372 -0
  150. package/runtime/node_modules/@metro-labs/daemon/src/routes/identity.ts +54 -0
  151. package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +223 -0
  152. package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +60 -0
  153. package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
  154. package/runtime/node_modules/@metro-labs/daemon/src/routes/threema-callback.ts +103 -0
  155. package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +64 -0
  156. package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +61 -0
  157. package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +116 -0
  158. package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
  159. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-inline.ts +100 -0
  160. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-interactive.ts +161 -0
  161. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +202 -0
  162. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-session.ts +280 -0
  163. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-xmtp.ts +111 -0
  164. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +248 -0
  165. package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +261 -0
  166. package/runtime/node_modules/@metro-labs/daemon/src/stations/registry.ts +37 -0
  167. package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
  168. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor-io.ts +97 -0
  169. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +320 -0
  170. package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
  171. package/runtime/node_modules/@metro-labs/daemon/src/stations/train-call.ts +22 -0
  172. package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +96 -0
  173. package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +123 -0
  174. package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +40 -0
  175. package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
  176. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -0
  177. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
  178. package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
  179. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
  180. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +220 -0
  181. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
  182. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
  183. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
  184. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
  185. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
  186. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
  187. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
  188. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
  189. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
  190. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
  191. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
  192. package/runtime/node_modules/@metro-labs/http/package.json +33 -0
  193. package/runtime/node_modules/@metro-labs/http/src/api-error.ts +8 -0
  194. package/runtime/node_modules/@metro-labs/http/src/api-http.ts +95 -0
  195. package/runtime/node_modules/@metro-labs/http/src/cors.ts +29 -0
  196. package/runtime/node_modules/@metro-labs/http/src/identity-key.ts +33 -0
  197. package/runtime/node_modules/@metro-labs/http/src/identity-registry.ts +13 -0
  198. package/runtime/node_modules/@metro-labs/http/src/mode-api.ts +32 -0
  199. package/runtime/node_modules/@metro-labs/http/src/return-to.ts +21 -0
  200. package/runtime/node_modules/@metro-labs/http/src/signed-identity.ts +36 -0
  201. package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
  202. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
  203. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +254 -0
  204. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
  205. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
  206. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +156 -0
  207. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
  208. package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
  209. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
  210. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
  211. package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
  212. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
  213. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
  214. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
  215. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
  216. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
  217. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
  218. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
  219. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
  220. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
  221. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
  222. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
  223. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +201 -0
  224. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +109 -0
  225. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
  226. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
  227. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
  228. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +58 -0
  229. package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
  230. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
  231. package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
  232. package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +100 -0
  233. package/runtime/node_modules/@metro-labs/threema/src/actions.ts +219 -0
  234. package/runtime/node_modules/@metro-labs/threema/src/api.ts +130 -0
  235. package/runtime/node_modules/@metro-labs/threema/src/crypto.ts +132 -0
  236. package/runtime/node_modules/@metro-labs/threema/src/format.ts +123 -0
  237. package/runtime/node_modules/@metro-labs/threema/src/ids.ts +21 -0
  238. package/runtime/node_modules/@metro-labs/threema/src/index.ts +29 -0
  239. package/runtime/node_modules/@metro-labs/threema/src/station.ts +10 -0
  240. package/runtime/node_modules/@metro-labs/threema/src/verify.ts +117 -0
  241. package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
  242. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
  243. package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
  244. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
  245. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
  246. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +190 -0
  247. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
  248. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
  249. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +347 -0
  250. package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
  251. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +138 -0
  252. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
  253. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
  254. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
  255. package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
  256. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
  257. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
  258. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
  259. package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
  260. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
  261. package/runtime/node_modules/@metro-labs/whatsapp/src/resolve.ts +52 -0
  262. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +18 -0
  263. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
  264. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
  265. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
  266. package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
  267. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +112 -0
  268. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
  269. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
  270. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
  271. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
  272. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
  273. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
  274. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
  275. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
  276. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
  277. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +335 -0
  278. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +80 -0
  279. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
  280. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
  281. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
  282. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
  283. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
  284. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
  285. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
  286. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
  287. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
  288. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
  289. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
  290. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
  291. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +53 -0
  292. package/runtime/runtime.json +3 -0
  293. package/runtime/server.ts +1 -0
  294. package/runtime/stations.json +39 -0
  295. package/runtime/trains/.keep +0 -0
  296. package/LICENSE +0 -21
  297. package/README.md +0 -252
  298. package/dist/broker.js +0 -202
  299. package/dist/cache.js +0 -69
  300. package/dist/cli/actions.js +0 -206
  301. package/dist/cli/config.js +0 -185
  302. package/dist/cli/index.js +0 -188
  303. package/dist/cli/skill.js +0 -62
  304. package/dist/cli/tail.js +0 -164
  305. package/dist/cli/util.js +0 -72
  306. package/dist/cli/webhook.js +0 -81
  307. package/dist/codex-rc.js +0 -245
  308. package/dist/dispatcher.js +0 -128
  309. package/dist/history.js +0 -121
  310. package/dist/ipc.js +0 -72
  311. package/dist/log.js +0 -15
  312. package/dist/monitor.js +0 -194
  313. package/dist/paths.js +0 -68
  314. package/dist/registry.js +0 -48
  315. package/dist/stations/claude.js +0 -45
  316. package/dist/stations/codex.js +0 -68
  317. package/dist/stations/discord.js +0 -216
  318. package/dist/stations/index.js +0 -129
  319. package/dist/stations/telegram-md.js +0 -34
  320. package/dist/stations/telegram-upload.js +0 -113
  321. package/dist/stations/telegram.js +0 -234
  322. package/dist/stations/webhook.js +0 -103
  323. package/dist/tunnel.js +0 -64
  324. package/dist/webhooks.js +0 -41
  325. package/docs/broker.md +0 -214
  326. package/docs/monitor.md +0 -163
  327. package/docs/uri-scheme.md +0 -124
  328. package/docs/users.md +0 -226
  329. package/skills/metro/SKILL.md +0 -256
@@ -0,0 +1,219 @@
1
+ import { TrainError } from '@metro-labs/core/train-error';
2
+ import {
3
+ makeStation,
4
+ respond,
5
+ type CallMsg,
6
+ } from '@metro-labs/core/stations/station-runtime';
7
+ import type { Normalized } from '@metro-labs/core/stations/messaging-normalize';
8
+ import { accountFor, accounts, publicKeyFor, targetOf, type Account } from './accounts.js';
9
+ import { sendE2E } from './api.js';
10
+ import {
11
+ bytesToHex,
12
+ decode,
13
+ encodeText,
14
+ hexToBytes,
15
+ macMatches,
16
+ open,
17
+ quoted,
18
+ seal,
19
+ type Decoded,
20
+ } from './crypto.js';
21
+ import {
22
+ emitInbound,
23
+ emitOutbound,
24
+ receiptEnvelope,
25
+ textEnvelope,
26
+ type InboundMeta,
27
+ } from './format.js';
28
+ import { isThreemaId, MESSAGE_ID_RE, normalizeThreemaId } from './ids.js';
29
+
30
+ export type { CallMsg };
31
+
32
+ export const MAX_TEXT_BYTES = 3500;
33
+ const SENT_MAX = 2000;
34
+
35
+ type Args = Record<string, unknown>;
36
+
37
+ const sentIds = new Set<string>();
38
+
39
+ function noteSent(messageId: string): void {
40
+ sentIds.add(messageId);
41
+ while (sentIds.size > SENT_MAX) {
42
+ const oldest = sentIds.values().next();
43
+ if (oldest.done) break;
44
+ sentIds.delete(oldest.value);
45
+ }
46
+ }
47
+
48
+ const sentByUs = (messageId: string): boolean => sentIds.has(messageId);
49
+
50
+ function listAccounts(id: string): void {
51
+ const list = [...accounts.values()].map((a) => ({
52
+ id: a.cfg.id,
53
+ handle: a.cfg.gatewayId,
54
+ url: null,
55
+ owner: a.cfg.owner ?? null,
56
+ gatewayId: a.cfg.gatewayId,
57
+ ...(a.cfg.callbackId && a.cfg.callbackToken
58
+ ? { callbackId: a.cfg.callbackId, callbackToken: a.cfg.callbackToken }
59
+ : {}),
60
+ }));
61
+ respond(id, { result: { accounts: list } });
62
+ }
63
+
64
+ interface SendArgs {
65
+ line: string;
66
+ text?: unknown;
67
+ replyTo?: unknown;
68
+ account?: string;
69
+ }
70
+
71
+ function requireText(a: SendArgs): string {
72
+ const text = typeof a.text === 'string' ? a.text : '';
73
+ if (text === '')
74
+ throw new TrainError(
75
+ 'threema_text_required',
76
+ 'threema carries text only; give some text to send',
77
+ { retryable: false },
78
+ );
79
+ return text;
80
+ }
81
+
82
+ function replyTargetOf(a: SendArgs): string | undefined {
83
+ if (a.replyTo === undefined || a.replyTo === null || a.replyTo === '') return undefined;
84
+ const target = typeof a.replyTo === 'string' ? a.replyTo.toLowerCase() : '';
85
+ if (!MESSAGE_ID_RE.test(target))
86
+ throw new TrainError(
87
+ 'threema_bad_reply_target',
88
+ 'replyTo must be a Threema message id, 16 hex characters',
89
+ { retryable: false },
90
+ );
91
+ return target;
92
+ }
93
+
94
+ async function send(id: string, args: Args): Promise<void> {
95
+ const a = args as unknown as SendArgs;
96
+ const text = requireText(a);
97
+ const replyTo = replyTargetOf(a);
98
+ const { acct, to } = targetOf(a.line, a.account);
99
+ const plain = encodeText(replyTo === undefined ? text : quoted(replyTo, text));
100
+ if (plain.length - 1 > MAX_TEXT_BYTES)
101
+ throw new TrainError(
102
+ 'threema_message_too_long',
103
+ `Threema carries at most ${MAX_TEXT_BYTES} bytes of text per message; split it up`,
104
+ { retryable: false },
105
+ );
106
+ const { nonce, box } = seal(plain, await publicKeyFor(acct, to), acct.keys);
107
+ const messageId = await sendE2E(acct.cfg, to, bytesToHex(nonce), bytesToHex(box));
108
+ noteSent(messageId);
109
+ emitOutbound(acct.cfg.id, a.line, messageId, text, replyTo);
110
+ respond(id, { result: { messageId, account: acct.cfg.id } });
111
+ }
112
+
113
+ interface CallbackArgs {
114
+ account: string;
115
+ from: string;
116
+ to: string;
117
+ messageId: string;
118
+ date: string;
119
+ nonce: string;
120
+ box: string;
121
+ mac: string;
122
+ nickname?: string;
123
+ }
124
+
125
+ const str = (v: unknown): string => (typeof v === 'string' ? v : '');
126
+
127
+ function parseCallback(args: Args): CallbackArgs {
128
+ const fields = {
129
+ account: str(args.account),
130
+ from: str(args.from),
131
+ to: str(args.to),
132
+ messageId: str(args.messageId),
133
+ date: str(args.date),
134
+ nonce: str(args.nonce),
135
+ box: str(args.box),
136
+ mac: str(args.mac),
137
+ };
138
+ for (const [key, value] of Object.entries(fields))
139
+ if (value === '')
140
+ throw new TrainError('threema_bad_callback', `callback is missing ${key}`, {
141
+ retryable: false,
142
+ });
143
+ const nickname = str(args.nickname);
144
+ return nickname === '' ? fields : { ...fields, nickname };
145
+ }
146
+
147
+ function deliver(acct: Account, m: InboundMeta, decoded: Decoded): string {
148
+ const owner = acct.cfg.owner;
149
+ if (decoded.kind === 'text') {
150
+ emitInbound(acct.cfg.id, owner, textEnvelope(acct.cfg.id, m, decoded.text, sentByUs));
151
+ return 'text';
152
+ }
153
+ if (decoded.kind === 'receipt') {
154
+ let reactions = 0;
155
+ for (const target of decoded.messageIds) {
156
+ const env = receiptEnvelope(acct.cfg.id, m, decoded.status, target);
157
+ if (env === null) continue;
158
+ emitInbound(acct.cfg.id, owner, env);
159
+ reactions += 1;
160
+ }
161
+ return reactions > 0 ? 'reaction' : `receipt:${decoded.status}`;
162
+ }
163
+ if (decoded.kind === 'typing') return 'typing';
164
+ const type = `0x${decoded.type.toString(16)}`;
165
+ process.stderr.write(
166
+ `threema[${acct.cfg.id}]: ignored a message of type ${type} from ${m.from}\n`,
167
+ );
168
+ return `ignored:${type}`;
169
+ }
170
+
171
+ async function callback(id: string, args: Args): Promise<void> {
172
+ const cb = parseCallback(args);
173
+ const acct = accountFor(cb.account);
174
+ if (!macMatches(acct.cfg.secret, cb, cb.mac))
175
+ throw new TrainError(
176
+ 'threema_bad_mac',
177
+ 'the callback MAC does not match this account API secret',
178
+ { retryable: false },
179
+ );
180
+ const from = normalizeThreemaId(cb.from);
181
+ if (!isThreemaId(from))
182
+ throw new TrainError('threema_bad_callback', `'${cb.from}' is not a Threema ID`, {
183
+ retryable: false,
184
+ });
185
+ if (normalizeThreemaId(cb.to) !== acct.cfg.gatewayId)
186
+ throw new TrainError(
187
+ 'threema_wrong_recipient',
188
+ `callback addressed to ${cb.to}, not to ${acct.cfg.gatewayId}`,
189
+ { retryable: false },
190
+ );
191
+ const plain = open(
192
+ hexToBytes(cb.box, 'box'),
193
+ hexToBytes(cb.nonce, 'nonce'),
194
+ await publicKeyFor(acct, from),
195
+ acct.keys,
196
+ );
197
+ if (plain === null)
198
+ throw new TrainError(
199
+ 'threema_undecryptable',
200
+ `could not decrypt a message from ${from}; the private key may not match the Gateway ID`,
201
+ { retryable: false },
202
+ );
203
+ const meta: InboundMeta = { ...cb, from, messageId: cb.messageId.toLowerCase() };
204
+ respond(id, { result: { ok: true, kind: deliver(acct, meta, decode(plain)) } });
205
+ }
206
+
207
+ export function normalizeThreema(action: string, env: Args): Normalized {
208
+ if (action === 'reply')
209
+ return {
210
+ action: 'send',
211
+ args: { line: env.line, text: env.text, replyTo: env.replyTo, account: env.account },
212
+ };
213
+ return { action, args: env };
214
+ }
215
+
216
+ export const handleCall = makeStation({
217
+ handlers: { accounts: listAccounts, send, callback },
218
+ normalize: normalizeThreema,
219
+ });
@@ -0,0 +1,130 @@
1
+ import { errMsg } from '@metro-labs/core/log';
2
+ import { TrainError } from '@metro-labs/core/train-error';
3
+ import { GATEWAY_API, MESSAGE_ID_RE } from './ids.js';
4
+
5
+ const TIMEOUT_MS = 15_000;
6
+ const PUBLIC_KEY_RE = /^[0-9a-f]{64}$/;
7
+
8
+ export interface GatewayCreds {
9
+ gatewayId: string;
10
+ secret: string;
11
+ }
12
+
13
+ async function call(
14
+ path: string,
15
+ init: RequestInit,
16
+ label: string,
17
+ ): Promise<Response> {
18
+ try {
19
+ return await fetch(`${GATEWAY_API}${path}`, {
20
+ ...init,
21
+ signal: AbortSignal.timeout(TIMEOUT_MS),
22
+ });
23
+ } catch (err) {
24
+ throw new TrainError(
25
+ 'threema_unreachable',
26
+ `${label}: could not reach the Threema Gateway (${errMsg(err)})`,
27
+ { retryable: true },
28
+ );
29
+ }
30
+ }
31
+
32
+ const authQuery = (c: GatewayCreds): string =>
33
+ new URLSearchParams({ from: c.gatewayId, secret: c.secret }).toString();
34
+
35
+ function refusal(status: number, what: string): TrainError {
36
+ if (status === 401)
37
+ return new TrainError(
38
+ 'threema_unauthorized',
39
+ 'the Threema Gateway rejected this account credentials; check the Gateway ID and API secret',
40
+ { retryable: false },
41
+ );
42
+ if (status === 402)
43
+ return new TrainError(
44
+ 'threema_no_credits',
45
+ 'this Threema Gateway ID has no credits left; top it up at gateway.threema.ch',
46
+ { retryable: false },
47
+ );
48
+ if (status === 404)
49
+ return new TrainError('threema_unknown_id', `Threema does not know the ID ${what}`, {
50
+ retryable: false,
51
+ });
52
+ if (status === 413)
53
+ return new TrainError(
54
+ 'threema_message_too_long',
55
+ 'Threema refused the message as too long',
56
+ { retryable: false },
57
+ );
58
+ if (status === 400)
59
+ return new TrainError('threema_bad_recipient', `Threema refused the recipient ${what}`, {
60
+ retryable: false,
61
+ });
62
+ return new TrainError(
63
+ 'threema_gateway_error',
64
+ `the Threema Gateway answered ${status}`,
65
+ { retryable: status >= 500 },
66
+ );
67
+ }
68
+
69
+ export async function fetchPublicKey(
70
+ c: GatewayCreds,
71
+ threemaId: string,
72
+ ): Promise<string> {
73
+ const res = await call(
74
+ `/pubkeys/${encodeURIComponent(threemaId)}?${authQuery(c)}`,
75
+ {},
76
+ 'pubkeys',
77
+ );
78
+ if (!res.ok) throw refusal(res.status, threemaId);
79
+ const hex = (await res.text()).trim().toLowerCase();
80
+ if (!PUBLIC_KEY_RE.test(hex))
81
+ throw new TrainError(
82
+ 'threema_gateway_error',
83
+ `pubkeys: the Threema Gateway gave an unexpected answer for ${threemaId}`,
84
+ );
85
+ return hex;
86
+ }
87
+
88
+ export async function fetchCredits(c: GatewayCreds): Promise<number> {
89
+ const res = await call(`/credits?${authQuery(c)}`, {}, 'credits');
90
+ if (!res.ok) throw refusal(res.status, c.gatewayId);
91
+ const credits = Number((await res.text()).trim());
92
+ if (!Number.isInteger(credits))
93
+ throw new TrainError(
94
+ 'threema_gateway_error',
95
+ 'credits: the Threema Gateway gave an unexpected answer',
96
+ );
97
+ return credits;
98
+ }
99
+
100
+ export async function sendE2E(
101
+ c: GatewayCreds,
102
+ to: string,
103
+ nonceHex: string,
104
+ boxHex: string,
105
+ ): Promise<string> {
106
+ const body = new URLSearchParams({
107
+ from: c.gatewayId,
108
+ to,
109
+ nonce: nonceHex,
110
+ box: boxHex,
111
+ secret: c.secret,
112
+ });
113
+ const res = await call(
114
+ '/send_e2e',
115
+ {
116
+ method: 'POST',
117
+ headers: { 'content-type': 'application/x-www-form-urlencoded' },
118
+ body: body.toString(),
119
+ },
120
+ 'send_e2e',
121
+ );
122
+ if (!res.ok) throw refusal(res.status, to);
123
+ const messageId = (await res.text()).trim().toLowerCase();
124
+ if (!MESSAGE_ID_RE.test(messageId))
125
+ throw new TrainError(
126
+ 'threema_gateway_error',
127
+ 'send_e2e: the Threema Gateway answered without a message id',
128
+ );
129
+ return messageId;
130
+ }
@@ -0,0 +1,132 @@
1
+ import { createHmac, randomInt, timingSafeEqual } from 'node:crypto';
2
+ import nacl from 'tweetnacl';
3
+
4
+ export const MSG_TEXT = 0x01;
5
+ export const MSG_DELIVERY_RECEIPT = 0x80;
6
+ export const MSG_TYPING = 0x90;
7
+
8
+ export const RECEIPT_ACK = 0x03;
9
+ export const RECEIPT_DECLINE = 0x04;
10
+
11
+ const MESSAGE_ID_BYTES = 8;
12
+ const HEX_RE = /^(?:[0-9a-fA-F]{2})*$/;
13
+
14
+ export interface KeyPair {
15
+ publicKey: Uint8Array;
16
+ secretKey: Uint8Array;
17
+ }
18
+
19
+ export function hexToBytes(hex: string, label: string): Uint8Array {
20
+ if (!HEX_RE.test(hex)) throw new Error(`${label} is not hex`);
21
+ return new Uint8Array(Buffer.from(hex, 'hex'));
22
+ }
23
+
24
+ export const bytesToHex = (bytes: Uint8Array): string =>
25
+ Buffer.from(bytes).toString('hex');
26
+
27
+ export const keyPairFrom = (privateKeyHex: string): KeyPair =>
28
+ nacl.box.keyPair.fromSecretKey(hexToBytes(privateKeyHex, 'private key'));
29
+
30
+ function pad(plain: Uint8Array): Uint8Array {
31
+ const n = randomInt(1, 256);
32
+ const out = new Uint8Array(plain.length + n);
33
+ out.set(plain);
34
+ out.fill(n, plain.length);
35
+ return out;
36
+ }
37
+
38
+ function unpad(data: Uint8Array): Uint8Array | null {
39
+ const n = data[data.length - 1];
40
+ if (n === undefined || n < 1 || n > data.length) return null;
41
+ return data.subarray(0, data.length - n);
42
+ }
43
+
44
+ export function seal(
45
+ plain: Uint8Array,
46
+ theirPublicKey: Uint8Array,
47
+ mine: KeyPair,
48
+ ): { nonce: Uint8Array; box: Uint8Array } {
49
+ const nonce = nacl.randomBytes(nacl.box.nonceLength);
50
+ return {
51
+ nonce,
52
+ box: nacl.box(pad(plain), nonce, theirPublicKey, mine.secretKey),
53
+ };
54
+ }
55
+
56
+ export function open(
57
+ box: Uint8Array,
58
+ nonce: Uint8Array,
59
+ theirPublicKey: Uint8Array,
60
+ mine: KeyPair,
61
+ ): Uint8Array | null {
62
+ const padded = nacl.box.open(box, nonce, theirPublicKey, mine.secretKey);
63
+ return padded === null ? null : unpad(padded);
64
+ }
65
+
66
+ export function encodeText(text: string): Uint8Array {
67
+ const utf8 = Buffer.from(text, 'utf8');
68
+ const out = new Uint8Array(1 + utf8.length);
69
+ out[0] = MSG_TEXT;
70
+ out.set(utf8, 1);
71
+ return out;
72
+ }
73
+
74
+ export type Decoded =
75
+ | { kind: 'text'; text: string }
76
+ | { kind: 'receipt'; status: number; messageIds: string[] }
77
+ | { kind: 'typing' }
78
+ | { kind: 'other'; type: number };
79
+
80
+ function receiptIds(body: Uint8Array): string[] {
81
+ const ids: string[] = [];
82
+ for (let at = 1; at + MESSAGE_ID_BYTES <= body.length; at += MESSAGE_ID_BYTES)
83
+ ids.push(bytesToHex(body.subarray(at, at + MESSAGE_ID_BYTES)));
84
+ return ids;
85
+ }
86
+
87
+ export function decode(plain: Uint8Array): Decoded {
88
+ const type = plain[0];
89
+ const body = plain.subarray(1);
90
+ if (type === MSG_TEXT)
91
+ return { kind: 'text', text: Buffer.from(body).toString('utf8') };
92
+ if (type === MSG_DELIVERY_RECEIPT)
93
+ return { kind: 'receipt', status: body[0] ?? 0, messageIds: receiptIds(body) };
94
+ if (type === MSG_TYPING) return { kind: 'typing' };
95
+ return { kind: 'other', type: type ?? -1 };
96
+ }
97
+
98
+ export interface CallbackFields {
99
+ from: string;
100
+ to: string;
101
+ messageId: string;
102
+ date: string;
103
+ nonce: string;
104
+ box: string;
105
+ }
106
+
107
+ export const callbackMac = (secret: string, f: CallbackFields): string =>
108
+ createHmac('sha256', secret)
109
+ .update(f.from + f.to + f.messageId + f.date + f.nonce + f.box)
110
+ .digest('hex');
111
+
112
+ export function macMatches(
113
+ secret: string,
114
+ fields: CallbackFields,
115
+ mac: string,
116
+ ): boolean {
117
+ const want = Buffer.from(callbackMac(secret, fields), 'hex');
118
+ const got = Buffer.from(mac.toLowerCase(), 'hex');
119
+ return want.length === got.length && timingSafeEqual(want, got);
120
+ }
121
+
122
+ const QUOTE_RE = /^> quote #([0-9a-f]{16})\n\n([\s\S]*)$/;
123
+
124
+ export const quoted = (replyTo: string, text: string): string =>
125
+ `> quote #${replyTo}\n\n${text}`;
126
+
127
+ export function unquote(text: string): { replyTo?: string; text: string } {
128
+ const m = QUOTE_RE.exec(text);
129
+ const replyTo = m?.[1];
130
+ const body = m?.[2];
131
+ return replyTo === undefined || body === undefined ? { text } : { replyTo, text: body };
132
+ }
@@ -0,0 +1,123 @@
1
+ import { emit, mintId } from '@metro-labs/core/stations/station-runtime';
2
+ import { RECEIPT_ACK, RECEIPT_DECLINE, unquote } from './crypto.js';
3
+
4
+ const SELF_URI = process.env.METRO_SELF_URI ?? '';
5
+
6
+ const REACTION_OF: Record<number, string> = {
7
+ [RECEIPT_ACK]: '👍',
8
+ [RECEIPT_DECLINE]: '👎',
9
+ };
10
+
11
+ export const lineOf = (accountId: string, threemaId: string): string =>
12
+ `metro://threema/${accountId}/${threemaId}`;
13
+
14
+ const userOf = (accountId: string, threemaId: string): string =>
15
+ `metro://threema/${accountId}/user/${threemaId}`;
16
+
17
+ export interface InboundMeta {
18
+ from: string;
19
+ to: string;
20
+ messageId: string;
21
+ date: string;
22
+ nickname?: string;
23
+ }
24
+
25
+ function tsOf(date: string): string {
26
+ const seconds = Number(date);
27
+ return Number.isFinite(seconds) && seconds > 0
28
+ ? new Date(seconds * 1000).toISOString()
29
+ : new Date().toISOString();
30
+ }
31
+
32
+ function base(
33
+ accountId: string,
34
+ m: InboundMeta,
35
+ type: string,
36
+ ): Record<string, unknown> {
37
+ return {
38
+ kind: 'inbound',
39
+ id: mintId(),
40
+ ts: tsOf(m.date),
41
+ station: 'threema',
42
+ line: lineOf(accountId, m.from),
43
+ line_name: m.nickname ?? m.from,
44
+ from: userOf(accountId, m.from),
45
+ from_name: m.nickname,
46
+ from_display_name: m.nickname,
47
+ is_private: true,
48
+ payload: { from: m.from, to: m.to, nickname: m.nickname ?? null, type },
49
+ };
50
+ }
51
+
52
+ export function textEnvelope(
53
+ accountId: string,
54
+ m: InboundMeta,
55
+ raw: string,
56
+ sentByUs: (messageId: string) => boolean,
57
+ ): Record<string, unknown> {
58
+ const { replyTo, text } = unquote(raw);
59
+ return {
60
+ ...base(accountId, m, 'text'),
61
+ message_id: m.messageId,
62
+ text,
63
+ ...(replyTo === undefined
64
+ ? {}
65
+ : {
66
+ reply_to: replyTo,
67
+ event: { type: 'reply', replyTo },
68
+ reply_to_self: sentByUs(replyTo),
69
+ }),
70
+ };
71
+ }
72
+
73
+ export function receiptEnvelope(
74
+ accountId: string,
75
+ m: InboundMeta,
76
+ status: number,
77
+ targetId: string,
78
+ ): Record<string, unknown> | null {
79
+ const emoji = REACTION_OF[status];
80
+ if (emoji === undefined) return null;
81
+ return {
82
+ ...base(accountId, m, 'receipt'),
83
+ message_id: `${m.messageId}:${targetId}`,
84
+ emoji,
85
+ event: { type: 'react', emoji, targetId },
86
+ };
87
+ }
88
+
89
+ export function emitInbound(
90
+ accountId: string,
91
+ owner: string | undefined,
92
+ env: Record<string, unknown>,
93
+ ): void {
94
+ const payload = {
95
+ ...(env.payload as Record<string, unknown> | undefined),
96
+ account: accountId,
97
+ };
98
+ emit({ ...env, ...(owner ? { to: owner } : {}), account: accountId, payload });
99
+ }
100
+
101
+ export function emitOutbound(
102
+ accountId: string,
103
+ line: string,
104
+ messageId: string,
105
+ text: string,
106
+ replyTo?: string,
107
+ ): void {
108
+ emit({
109
+ kind: 'outbound',
110
+ id: mintId(),
111
+ ts: new Date().toISOString(),
112
+ station: 'threema',
113
+ line,
114
+ from: SELF_URI,
115
+ to: line,
116
+ message_id: messageId,
117
+ text,
118
+ reply_to: replyTo,
119
+ ...(replyTo ? { event: { type: 'reply', replyTo } } : {}),
120
+ account: accountId,
121
+ payload: { account: accountId },
122
+ });
123
+ }
@@ -0,0 +1,21 @@
1
+ export const GATEWAY_API = (
2
+ process.env.THREEMA_GATEWAY_URL ?? 'https://msgapi.threema.ch'
3
+ ).replace(/\/+$/, '');
4
+
5
+ const THREEMA_ID_RE = /^(?:[A-Z0-9]{8}|\*[A-Z0-9]{7})$/;
6
+ const KEY_HEX_RE = /^[0-9a-f]{64}$/;
7
+
8
+ export const MESSAGE_ID_RE = /^[0-9a-f]{16}$/;
9
+
10
+ export const isThreemaId = (raw: string): boolean => THREEMA_ID_RE.test(raw);
11
+
12
+ export const isGatewayId = (raw: string): boolean =>
13
+ raw.startsWith('*') && isThreemaId(raw);
14
+
15
+ export const normalizeThreemaId = (raw: string): string =>
16
+ raw.trim().toUpperCase();
17
+
18
+ export function parsePrivateKey(raw: string): string | null {
19
+ const text = raw.trim().replace(/^private:/i, '').trim().toLowerCase();
20
+ return KEY_HEX_RE.test(text) ? text : null;
21
+ }
@@ -0,0 +1,29 @@
1
+ import { errMsg } from '@metro-labs/core/log';
2
+ import { readCalls } from '@metro-labs/core/trains/protocol';
3
+ import { accounts, bootAccount, loadAccounts } from './accounts.js';
4
+ import { handleCall } from './actions.js';
5
+ import { fetchCredits } from './api.js';
6
+
7
+ readCalls('threema', handleCall);
8
+
9
+ for (const cfg of loadAccounts()) accounts.set(cfg.id, bootAccount(cfg));
10
+ if (accounts.size === 0) {
11
+ process.stderr.write('threema: no accounts booted, exiting\n');
12
+ process.exit(2);
13
+ }
14
+ process.stderr.write(
15
+ `threema train ready — ${accounts.size} account(s): ${[...accounts.keys()].join(', ')}\n`,
16
+ );
17
+
18
+ for (const acct of accounts.values())
19
+ fetchCredits(acct.cfg)
20
+ .then((credits) => {
21
+ process.stderr.write(
22
+ `threema[${acct.cfg.id}] ${acct.cfg.gatewayId}: ${credits} credits left\n`,
23
+ );
24
+ })
25
+ .catch((err: unknown) => {
26
+ process.stderr.write(
27
+ `threema[${acct.cfg.id}] credits check failed: ${errMsg(err)}\n`,
28
+ );
29
+ });
@@ -0,0 +1,10 @@
1
+ import type { Station, Verb } from '@metro-labs/core/stations/types';
2
+
3
+ export const threemaStation: Station = {
4
+ name: 'threema',
5
+ hasAccounts: true,
6
+ hasTrain: true,
7
+ messageVerbs: new Set<Verb>(['send', 'reply']),
8
+ attachmentMode: 'none',
9
+ tools: [],
10
+ };