@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,63 @@
1
+ import type { OutlookLogin } from '@metro-labs/outlook/login';
2
+ import { StationAttachError } from './attach.js';
3
+ import type { DriverHooks, StartedAttach, StepInput } from './attach-driver.js';
4
+
5
+ const BROWSER_PROMPT =
6
+ 'Sign in with Microsoft in a new tab, with the mailbox this agent should read. This page updates on its own once you are done.';
7
+ const DEVICE_PROMPT =
8
+ 'Open the Microsoft sign-in page, type this code, and sign in with the mailbox this agent should read.';
9
+ const OTHER_ATTEMPT = 'This sign-in link belongs to another attempt. Start again from the Channels page.';
10
+ const BROWSER_TTL_MS = 15 * 60_000;
11
+
12
+ const text = (v: unknown): string => (typeof v === 'string' ? v.trim() : '');
13
+
14
+ export async function startOutlook(input: Record<string, unknown>, hooks: DriverHooks): Promise<StartedAttach> {
15
+ const { OutlookBrowserLogin, OutlookLogin: DeviceLogin, OutlookLoginError, failureOf, parseMailbox } = await import('@metro-labs/outlook/login');
16
+ const said = (err: unknown, fallback: string): string =>
17
+ err instanceof OutlookLoginError && err.message !== '' ? err.message : fallback;
18
+ const { browser, mailbox } = (() => {
19
+ try {
20
+ const wanted = parseMailbox(input.mailbox);
21
+ return { browser: new OutlookBrowserLogin({ mailbox: wanted }), mailbox: wanted };
22
+ } catch (err) {
23
+ throw new StationAttachError(said(err, 'Microsoft refused to start the sign-in'), 400);
24
+ }
25
+ })();
26
+ let device: OutlookLogin | null = null;
27
+
28
+ const toDevice = async (): Promise<void> => {
29
+ if (device !== null) return;
30
+ const login = new DeviceLogin({ onDone: hooks.done, onFailed: hooks.fail }, { mailbox });
31
+ const code = await login.start().catch((err: unknown) => {
32
+ throw new StationAttachError(said(err, 'Microsoft refused to start the sign-in'), 400);
33
+ });
34
+ device = login;
35
+ hooks.prompt({ step: 'device', prompt: DEVICE_PROMPT, userCode: code.userCode, verificationUri: code.verificationUri });
36
+ };
37
+
38
+ const finish = async (input: StepInput): Promise<void> => {
39
+ const state = text(input.state);
40
+ if (state !== browser.state) throw new StationAttachError(OTHER_ATTEMPT, 400);
41
+ const error = text(input.error);
42
+ if (error !== '') {
43
+ hooks.fail(failureOf({ error, error_description: text(input.errorDescription) }));
44
+ return;
45
+ }
46
+ const code = text(input.code);
47
+ if (code === '') throw new StationAttachError('Microsoft did not send a sign-in code back', 400);
48
+ try {
49
+ hooks.done(await browser.finish(code, state));
50
+ } catch (err) {
51
+ hooks.fail(said(err, 'Metro could not finish the Microsoft sign-in.'));
52
+ }
53
+ };
54
+
55
+ return {
56
+ prompt: { step: 'browser', prompt: BROWSER_PROMPT, authorizeUrl: browser.authorizeUrl },
57
+ expiresAt: Date.now() + BROWSER_TTL_MS,
58
+ driver: {
59
+ cancel: () => device?.cancel() ?? Promise.resolve(),
60
+ submit: (input) => (input.mode === 'device' ? toDevice() : finish(input)),
61
+ },
62
+ };
63
+ }
@@ -0,0 +1,225 @@
1
+ import {
2
+ assertAttachmentSize,
3
+ assertContentLength,
4
+ guessMime,
5
+ } from '@metro-labs/core/stations/attachments';
6
+ import {
7
+ assertInlineTotal,
8
+ decodeInline,
9
+ removeInlineTemp,
10
+ splitInlineData,
11
+ streamToTemp,
12
+ writeInlineTemp,
13
+ } from './attach-inline.js';
14
+ import { realpathSync } from 'node:fs';
15
+ import { agentCommand, agentUser, type AgentUser } from '../agent-user/user.js';
16
+ import { log } from '@metro-labs/core/log';
17
+ import { readUpload, UPLOAD_TTL_MS } from '../files/upload-store.js';
18
+ import type { CanonicalAttachment } from '@metro-labs/core/stations/types';
19
+
20
+ export interface ResolvedAttachment {
21
+ path: string;
22
+ mime: string;
23
+ name: string;
24
+ bytes: number;
25
+ temp?: string;
26
+ }
27
+
28
+ export interface ResolveOptions {
29
+ allowed?: Set<string>;
30
+ }
31
+
32
+ interface InlineBudget {
33
+ used: number;
34
+ }
35
+
36
+ const FETCH_TIMEOUT_MS = 60_000;
37
+
38
+ const basenameOf = (src: string): string =>
39
+ src.split('?')[0]?.split('#')[0]?.split('/').filter(Boolean).pop() ?? '';
40
+
41
+ const isHttpUrl = (src: string): boolean => /^https?:\/\//i.test(src);
42
+
43
+ const HIDDEN_SEGMENT = /(^|\/)\.[^/]/;
44
+
45
+ function assertReadablePath(path: string): void {
46
+ let real: string;
47
+ try {
48
+ real = realpathSync(path);
49
+ } catch {
50
+ return;
51
+ }
52
+ if (HIDDEN_SEGMENT.test(real))
53
+ throw new Error(
54
+ `attachment path '${path}' resolves into a hidden directory ` +
55
+ `('${real}') and will not be read. \`path\` is read on the daemon ` +
56
+ 'machine; put the file somewhere non-hidden or pass `upload` instead.',
57
+ );
58
+ }
59
+
60
+ async function fromPathAs(
61
+ user: AgentUser,
62
+ a: CanonicalAttachment,
63
+ path: string,
64
+ ): Promise<ResolvedAttachment> {
65
+ const proc = Bun.spawn(agentCommand(['cat', '--', path]), { stdout: 'pipe', stderr: 'pipe' });
66
+ const name = a.name ?? basenameOf(path) ?? 'attachment';
67
+ let saved: Awaited<ReturnType<typeof streamToTemp>>;
68
+ try {
69
+ saved = await streamToTemp(proc.stdout, name);
70
+ } catch (err) {
71
+ proc.kill();
72
+ throw err;
73
+ }
74
+ if ((await proc.exited) !== 0) {
75
+ await removeInlineTemp(saved.dir);
76
+ throw new Error(
77
+ `attachment path '${path}' cannot be read by the ${user.name} user Claude Code runs as. ` +
78
+ '`path` is read on the daemon machine with the agent\'s own rights; pass `upload` instead.',
79
+ );
80
+ }
81
+ assertAttachmentSize(saved.bytes);
82
+ return { path: saved.path, mime: a.mime ?? guessMime(path), name, bytes: saved.bytes, temp: saved.dir };
83
+ }
84
+
85
+ async function fromPath(
86
+ a: CanonicalAttachment,
87
+ path: string,
88
+ ): Promise<ResolvedAttachment> {
89
+ assertReadablePath(path);
90
+ const user = agentUser();
91
+ if (user !== null) return fromPathAs(user, a, path);
92
+ const file = Bun.file(path);
93
+ if (!(await file.exists()))
94
+ throw new Error(
95
+ `attachment path '${path}' does not exist on the metro host. ` +
96
+ '`path` is read on the daemon machine, not on the caller\'s machine; ' +
97
+ 'pass `url` instead if the file is not already on the daemon.',
98
+ );
99
+ assertAttachmentSize(file.size);
100
+ return {
101
+ path,
102
+ mime: a.mime ?? guessMime(path),
103
+ name: a.name ?? basenameOf(path) ?? 'attachment',
104
+ bytes: file.size,
105
+ };
106
+ }
107
+
108
+ async function fromUrl(
109
+ a: CanonicalAttachment,
110
+ url: string,
111
+ ): Promise<ResolvedAttachment> {
112
+ if (!isHttpUrl(url))
113
+ throw new Error(
114
+ `attachment url '${url}' is not an http(s) url; use \`path\` for a local file`,
115
+ );
116
+ const res = await fetch(url, {
117
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS),
118
+ });
119
+ if (!res.ok)
120
+ throw new Error(`attachment fetch failed ${res.status} for '${url}'`);
121
+ assertContentLength(res.headers.get('content-length'));
122
+ const mime =
123
+ a.mime ?? res.headers.get('content-type')?.split(';')[0] ?? guessMime(url);
124
+ const name = a.name ?? basenameOf(url) ?? 'attachment';
125
+ if (res.body === null) throw new Error(`attachment fetch returned no body for '${url}'`);
126
+ const { dir, path, bytes } = await streamToTemp(res.body, name);
127
+ return { path, mime, name, bytes, temp: dir };
128
+ }
129
+
130
+ async function fromData(
131
+ a: CanonicalAttachment,
132
+ data: string,
133
+ budget: InlineBudget,
134
+ ): Promise<ResolvedAttachment> {
135
+ const name = a.name ?? 'attachment';
136
+ const source = splitInlineData(data);
137
+ const bytes = decodeInline(source.data, `'${name}'`);
138
+ budget.used += bytes.length;
139
+ assertInlineTotal(budget.used);
140
+ const { dir, path } = await writeInlineTemp(bytes, a.name);
141
+ return {
142
+ path,
143
+ mime: a.mime ?? source.mime ?? guessMime(name),
144
+ name,
145
+ bytes: bytes.length,
146
+ temp: dir,
147
+ };
148
+ }
149
+
150
+ const uploadMissing = (id: string): Error =>
151
+ new Error(
152
+ `upload '${id}' is not a live upload of yours. Either it was never created, or its ` +
153
+ 'bytes were never pushed to `POST /api/uploads`, or it belongs to another agent, or ' +
154
+ `it expired (an upload lives ${Math.round(UPLOAD_TTL_MS / 60_000)} minutes). ` +
155
+ 'Create a new one with `create_upload` and push the file again.',
156
+ );
157
+
158
+ function fromUpload(
159
+ a: CanonicalAttachment,
160
+ id: string,
161
+ allowed: Set<string> | undefined,
162
+ ): ResolvedAttachment {
163
+ const rec = allowed === undefined ? undefined : readUpload(id);
164
+ if (rec === undefined || !(allowed?.has(rec.agentId) ?? false))
165
+ throw uploadMissing(id);
166
+ const name = a.name ?? rec.name;
167
+ return { path: rec.path, mime: a.mime ?? rec.mime, name, bytes: rec.bytes };
168
+ }
169
+
170
+ export const SOURCE_KEYS = ['path', 'url', 'data', 'upload'] as const;
171
+
172
+ const sourcesOf = (a: CanonicalAttachment): string[] =>
173
+ SOURCE_KEYS.filter((k) => {
174
+ const v = a[k];
175
+ return typeof v === 'string' && v.length > 0;
176
+ });
177
+
178
+ async function resolveAttachment(
179
+ a: CanonicalAttachment,
180
+ budget: InlineBudget = { used: 0 },
181
+ opts: ResolveOptions = {},
182
+ ): Promise<ResolvedAttachment> {
183
+ const sources = sourcesOf(a);
184
+ if (sources.length === 0)
185
+ throw new Error(
186
+ 'attachment requires exactly one of `upload`, `data`, `url` or `path`',
187
+ );
188
+ if (sources.length > 1)
189
+ throw new Error(
190
+ `attachment names ${sources.length} sources (${sources
191
+ .map((s) => `\`${s}\``)
192
+ .join(', ')}); pass exactly one of \`upload\`, \`data\`, \`url\` or \`path\``,
193
+ );
194
+ log.info({ source: sources[0] }, 'send: attachment source');
195
+ if (a.upload) return fromUpload(a, a.upload, opts.allowed);
196
+ if (a.data) return fromData(a, a.data, budget);
197
+ if (a.path) return fromPath(a, a.path);
198
+ return fromUrl(a, a.url ?? '');
199
+ }
200
+
201
+ export async function cleanupAttachments(
202
+ atts: ResolvedAttachment[],
203
+ ): Promise<void> {
204
+ for (const a of atts) if (a.temp) await removeInlineTemp(a.temp);
205
+ }
206
+
207
+ export async function resolveAttachments(
208
+ atts: CanonicalAttachment[],
209
+ opts: ResolveOptions = {},
210
+ ): Promise<ResolvedAttachment[]> {
211
+ const out: ResolvedAttachment[] = [];
212
+ const budget: InlineBudget = { used: 0 };
213
+ for (let i = 0; i < atts.length; i++) {
214
+ const a = atts[i];
215
+ if (!a) continue;
216
+ try {
217
+ out.push(await resolveAttachment(a, budget, opts));
218
+ } catch (e) {
219
+ await cleanupAttachments(out);
220
+ const why = e instanceof Error ? e.message : String(e);
221
+ throw new Error(`attachment ${i + 1} of ${atts.length}: ${why}`);
222
+ }
223
+ }
224
+ return out;
225
+ }
@@ -0,0 +1,288 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { ApiError } from '@metro-labs/http/api-error';
3
+ import { errMsg, log } from '@metro-labs/core/log';
4
+ import type { StationName } from '@metro-labs/core/station-names';
5
+ import { ensureStationDeps } from './runtime-deps.js';
6
+ import { startInteractiveAttach, type InteractiveStation } from './attach-interactive.js';
7
+ import type {
8
+ AttachDriver,
9
+ AttachOutcome,
10
+ AttachPrompt,
11
+ AttachStep,
12
+ DriverHooks,
13
+ StartedAttach,
14
+ StepInput,
15
+ } from './attach-driver.js';
16
+
17
+ export type StartAttach = (
18
+ station: InteractiveStation,
19
+ input: Record<string, unknown>,
20
+ hooks: DriverHooks,
21
+ ) => Promise<StartedAttach>;
22
+
23
+ export const ATTACH_ID_RE = /^as_[A-Za-z0-9_-]{22}$/;
24
+
25
+ const PENDING_TTL_MS = 5 * 60_000;
26
+ const SETTLED_TTL_MS = 60_000;
27
+ const SWEEP_MS = 15_000;
28
+ const MAX_PER_AGENT = 2;
29
+ const MAX_TOTAL = 40;
30
+
31
+ export interface AttachOwner {
32
+ agentId: string;
33
+ }
34
+
35
+ export interface AttachView {
36
+ attachId: string;
37
+ station: string;
38
+ status: 'pending' | 'done' | 'failed';
39
+ step: AttachStep | null;
40
+ prompt: string;
41
+ qr: string | null;
42
+ pairingCode: string | null;
43
+ userCode: string | null;
44
+ verificationUri: string | null;
45
+ authorizeUrl: string | null;
46
+ accountId: string | null;
47
+ identity: Record<string, string>;
48
+ activated: boolean;
49
+ error: string | null;
50
+ expiresAt: number;
51
+ }
52
+
53
+ export type CompleteAttach = (
54
+ owner: AttachOwner,
55
+ station: StationName,
56
+ config: Record<string, unknown>,
57
+ ) => Promise<{ accountId: string; activated: boolean }>;
58
+
59
+ export type AuthorizeAttach = (owner: AttachOwner) => Promise<void>;
60
+
61
+ export interface AttachSessionDeps {
62
+ authorize: AuthorizeAttach;
63
+ complete: CompleteAttach;
64
+ start?: StartAttach;
65
+ }
66
+
67
+ interface Session {
68
+ owner: AttachOwner;
69
+ driver: AttachDriver | null;
70
+ view: AttachView;
71
+ }
72
+
73
+ const missing = (): ApiError => new ApiError('no such attach session', 404);
74
+
75
+ function newAttachId(): string {
76
+ return `as_${randomBytes(16).toString('base64url')}`;
77
+ }
78
+
79
+ function blankView(attachId: string, station: string): AttachView {
80
+ return {
81
+ attachId,
82
+ station,
83
+ status: 'pending',
84
+ step: null,
85
+ prompt: '',
86
+ qr: null,
87
+ pairingCode: null,
88
+ userCode: null,
89
+ verificationUri: null,
90
+ authorizeUrl: null,
91
+ accountId: null,
92
+ identity: {},
93
+ activated: false,
94
+ error: null,
95
+ expiresAt: Date.now() + PENDING_TTL_MS,
96
+ };
97
+ }
98
+
99
+ export class AttachSessions {
100
+ private sessions = new Map<string, Session>();
101
+ private sweeper: ReturnType<typeof setInterval> | null = null;
102
+
103
+ constructor(private deps: AttachSessionDeps) {}
104
+
105
+ private ensureSweeper(): void {
106
+ if (this.sweeper) return;
107
+ this.sweeper = setInterval(() => {
108
+ this.sweep().catch((err: unknown) => {
109
+ log.warn({ err: errMsg(err) }, 'attach-session: sweep failed');
110
+ });
111
+ }, SWEEP_MS);
112
+ this.sweeper.unref();
113
+ }
114
+
115
+ private own(owner: AttachOwner, attachId: string): Session {
116
+ const session = this.sessions.get(attachId);
117
+ if (session?.owner.agentId !== owner.agentId) throw missing();
118
+ return session;
119
+ }
120
+
121
+ private admit(owner: AttachOwner): void {
122
+ if (this.sessions.size >= MAX_TOTAL)
123
+ throw new ApiError(
124
+ 'too many sign-ins are in flight on this daemon, try again shortly',
125
+ 429,
126
+ );
127
+ const mine = [...this.sessions.values()].filter(
128
+ (s) => s.owner.agentId === owner.agentId && s.view.status === 'pending',
129
+ );
130
+ if (mine.length >= MAX_PER_AGENT)
131
+ throw new ApiError(
132
+ 'this agent already has a sign-in in progress, finish or cancel it first',
133
+ 409,
134
+ );
135
+ }
136
+
137
+ private settle(session: Session, patch: Partial<AttachView>): void {
138
+ Object.assign(session.view, patch, {
139
+ qr: null,
140
+ pairingCode: null,
141
+ userCode: null,
142
+ verificationUri: null,
143
+ authorizeUrl: null,
144
+ step: null,
145
+ expiresAt: Date.now() + SETTLED_TTL_MS,
146
+ });
147
+ session.driver = null;
148
+ }
149
+
150
+ private async finish(
151
+ attachId: string,
152
+ station: StationName,
153
+ outcome: AttachOutcome,
154
+ ): Promise<void> {
155
+ const session = this.sessions.get(attachId);
156
+ if (session?.view.status !== 'pending') return;
157
+ try {
158
+ const landed = await this.deps.complete(
159
+ session.owner,
160
+ station,
161
+ outcome.config,
162
+ );
163
+ this.settle(session, {
164
+ status: 'done',
165
+ prompt: '',
166
+ identity: outcome.identity,
167
+ accountId: landed.accountId,
168
+ activated: landed.activated,
169
+ });
170
+ log.info(
171
+ { agentId: session.owner.agentId, station, account: landed.accountId },
172
+ 'attach-session: signed in and attached a station account',
173
+ );
174
+ } catch (err) {
175
+ const reason =
176
+ err instanceof ApiError ? err.message : 'could not store the account';
177
+ log.warn({ station, err: errMsg(err) }, 'attach-session: store failed');
178
+ this.settle(session, { status: 'failed', prompt: '', error: reason });
179
+ }
180
+ }
181
+
182
+ private hooksFor(attachId: string, station: StationName) {
183
+ return {
184
+ prompt: (p: AttachPrompt): void => {
185
+ const session = this.sessions.get(attachId);
186
+ if (session?.view.status !== 'pending') return;
187
+ session.view.step = p.step;
188
+ session.view.prompt = p.prompt;
189
+ session.view.qr = p.qr ?? null;
190
+ session.view.pairingCode = p.pairingCode ?? null;
191
+ session.view.userCode = p.userCode ?? null;
192
+ session.view.verificationUri = p.verificationUri ?? null;
193
+ session.view.authorizeUrl = p.authorizeUrl ?? null;
194
+ },
195
+ done: (o: AttachOutcome): void => {
196
+ this.finish(attachId, station, o).catch((err: unknown) => {
197
+ log.warn(
198
+ { attachId, station, err: errMsg(err) },
199
+ 'attach-session: completion failed',
200
+ );
201
+ });
202
+ },
203
+ fail: (message: string): void => {
204
+ const session = this.sessions.get(attachId);
205
+ if (session?.view.status !== 'pending') return;
206
+ this.settle(session, { status: 'failed', prompt: '', error: message });
207
+ },
208
+ };
209
+ }
210
+
211
+ async start(
212
+ owner: AttachOwner,
213
+ station: InteractiveStation,
214
+ input: Record<string, unknown>,
215
+ ): Promise<AttachView> {
216
+ await this.deps.authorize(owner);
217
+ this.admit(owner);
218
+ this.ensureSweeper();
219
+ const attachId = newAttachId();
220
+ const session: Session = {
221
+ owner,
222
+ driver: null,
223
+ view: blankView(attachId, station),
224
+ };
225
+ this.sessions.set(attachId, session);
226
+ try {
227
+ ensureStationDeps(station);
228
+ const started = await (this.deps.start ?? startInteractiveAttach)(
229
+ station,
230
+ input,
231
+ this.hooksFor(attachId, station),
232
+ );
233
+ session.driver = started.driver;
234
+ if (started.expiresAt !== undefined) session.view.expiresAt = started.expiresAt;
235
+ this.hooksFor(attachId, station).prompt(started.prompt);
236
+ } catch (err) {
237
+ this.sessions.delete(attachId);
238
+ throw err;
239
+ }
240
+ log.info(
241
+ { agentId: owner.agentId, station, attachId },
242
+ 'attach-session: started',
243
+ );
244
+ return { ...session.view };
245
+ }
246
+
247
+ view(owner: AttachOwner, attachId: string): AttachView {
248
+ return { ...this.own(owner, attachId).view };
249
+ }
250
+
251
+ async submit(
252
+ owner: AttachOwner,
253
+ attachId: string,
254
+ input: StepInput,
255
+ ): Promise<AttachView> {
256
+ const session = this.own(owner, attachId);
257
+ const driver = session.driver;
258
+ if (session.view.status !== 'pending' || !driver)
259
+ throw new ApiError('this sign-in is already finished', 409);
260
+ await driver.submit(input);
261
+ return { ...session.view };
262
+ }
263
+
264
+ async cancel(owner: AttachOwner, attachId: string): Promise<void> {
265
+ const session = this.own(owner, attachId);
266
+ this.sessions.delete(attachId);
267
+ await session.driver?.cancel().catch(() => undefined);
268
+ log.info({ attachId }, 'attach-session: cancelled');
269
+ }
270
+
271
+ async sweep(now = Date.now()): Promise<void> {
272
+ for (const [attachId, session] of [...this.sessions]) {
273
+ if (session.view.expiresAt > now) continue;
274
+ this.sessions.delete(attachId);
275
+ const driver = session.driver;
276
+ session.driver = null;
277
+ if (!driver) continue;
278
+ log.info({ attachId }, 'attach-session: expired, credentials discarded');
279
+ await driver.cancel().catch(() => undefined);
280
+ }
281
+ }
282
+
283
+ async stop(): Promise<void> {
284
+ if (this.sweeper) clearInterval(this.sweeper);
285
+ this.sweeper = null;
286
+ await this.sweep(Number.MAX_SAFE_INTEGER);
287
+ }
288
+ }
@@ -0,0 +1,111 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { rmSync } from 'node:fs';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { errMsg, log } from '@metro-labs/core/log';
5
+
6
+ export class XmtpAttachError extends Error {}
7
+
8
+ export interface XmtpIdentity {
9
+ inboxId: string;
10
+ address: string;
11
+ installationId: string;
12
+ dbPath: string;
13
+ }
14
+
15
+ export type VerifyXmtpKey = (
16
+ privateKey: string,
17
+ dbPath: string,
18
+ ) => Promise<XmtpIdentity>;
19
+
20
+ const VERIFY_TIMEOUT_MS = 90_000;
21
+ const UNREACHABLE =
22
+ 'Metro could not open an XMTP inbox with the key it generated, so nothing was attached';
23
+
24
+ export function newXmtpDbPath(): string {
25
+ return `~/.metro/xmtp-production-${randomBytes(8).toString('hex')}.db3`;
26
+ }
27
+
28
+ export function discardXmtpDb(dbPath: string): void {
29
+ for (const suffix of ['', '-wal', '-shm'])
30
+ rmSync(`${dbPath}${suffix}`, { force: true });
31
+ }
32
+
33
+ export function withoutKey(message: string, privateKey: string): string {
34
+ return message.split(privateKey).join('[redacted]');
35
+ }
36
+
37
+ interface VerifyReply {
38
+ ok?: unknown;
39
+ inboxId?: unknown;
40
+ address?: unknown;
41
+ installationId?: unknown;
42
+ dbPath?: unknown;
43
+ error?: unknown;
44
+ }
45
+
46
+ const str = (value: unknown): string => (typeof value === 'string' ? value : '');
47
+
48
+ function readReply(raw: string): VerifyReply {
49
+ const line = raw.trim().split('\n').at(-1) ?? '';
50
+ try {
51
+ const parsed: unknown = JSON.parse(line);
52
+ return typeof parsed === 'object' && parsed !== null ? parsed : {};
53
+ } catch {
54
+ return {};
55
+ }
56
+ }
57
+
58
+ async function runVerifier(payload: string): Promise<string> {
59
+ const script = fileURLToPath(import.meta.resolve('@metro-labs/xmtp/verify'));
60
+ const proc = Bun.spawn(['bun', 'run', script], {
61
+ stdin: 'pipe',
62
+ stdout: 'pipe',
63
+ stderr: 'pipe',
64
+ });
65
+ await proc.stdin.write(payload);
66
+ await proc.stdin.end();
67
+ const timer = setTimeout(() => {
68
+ proc.kill();
69
+ }, VERIFY_TIMEOUT_MS);
70
+ try {
71
+ const [out, err, code] = await Promise.all([
72
+ new Response(proc.stdout).text(),
73
+ new Response(proc.stderr).text(),
74
+ proc.exited,
75
+ ]);
76
+ if (code !== 0)
77
+ log.warn(
78
+ { code, stderr: err.trim().slice(-500) },
79
+ 'attach-xmtp: the inbox check exited badly',
80
+ );
81
+ return out;
82
+ } finally {
83
+ clearTimeout(timer);
84
+ }
85
+ }
86
+
87
+ export async function verifyXmtpKeyOutOfProcess(
88
+ privateKey: string,
89
+ dbPath: string,
90
+ ): Promise<XmtpIdentity> {
91
+ const raw = await runVerifier(
92
+ JSON.stringify({ privateKey, dbPath, smart: true }),
93
+ ).catch((err: unknown) => {
94
+ log.warn({ err: errMsg(err) }, 'attach-xmtp: could not run the inbox check');
95
+ throw new XmtpAttachError(UNREACHABLE);
96
+ });
97
+ const reply = readReply(raw);
98
+ if (reply.ok !== true)
99
+ throw new XmtpAttachError(
100
+ withoutKey(str(reply.error) || UNREACHABLE, privateKey),
101
+ );
102
+ const identity = {
103
+ inboxId: str(reply.inboxId),
104
+ address: str(reply.address),
105
+ installationId: str(reply.installationId),
106
+ dbPath: str(reply.dbPath),
107
+ };
108
+ if (identity.inboxId === '' || identity.address === '')
109
+ throw new XmtpAttachError(UNREACHABLE);
110
+ return identity;
111
+ }