@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,68 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import type { Client } from 'discord.js';
3
+ import { TrainError } from '@metro-labs/core/train-error';
4
+ import {
5
+ assertImage,
6
+ fieldsOf,
7
+ parseProfileChange,
8
+ type ProfileApplied,
9
+ type ProfileChange,
10
+ } from '@metro-labs/core/stations/profile';
11
+ import type { SenderProfile } from '@metro-labs/core/stations/sender-profile';
12
+ import { accountFor, accounts } from './accounts.js';
13
+ import { respond } from './wire.js';
14
+
15
+ export async function applyProfile(
16
+ client: Client,
17
+ change: ProfileChange,
18
+ ): Promise<void> {
19
+ const user = client.user;
20
+ if (!user) throw new TrainError('discord_not_ready', 'the gateway is not ready yet');
21
+ if (change.avatar !== undefined) assertImage(change.avatar);
22
+ const avatar = change.avatar === undefined ? undefined : await readFile(change.avatar.path);
23
+ if (change.name !== undefined || avatar !== undefined)
24
+ await user.edit({
25
+ ...(change.name === undefined ? {} : { username: change.name }),
26
+ ...(avatar === undefined ? {} : { avatar }),
27
+ });
28
+ if (change.bio !== undefined) {
29
+ const app = client.application;
30
+ if (!app) throw new TrainError('discord_not_ready', 'the application is not loaded yet');
31
+ await app.edit({ description: change.bio });
32
+ }
33
+ }
34
+
35
+ export async function setProfile(
36
+ id: string,
37
+ args: Record<string, unknown>,
38
+ ): Promise<void> {
39
+ const accountId = accountFor({ account: args.account as string | undefined });
40
+ const acct = accounts.get(accountId);
41
+ if (!acct) {
42
+ respond(id, { error: `unknown account '${accountId}'` });
43
+ return;
44
+ }
45
+ const change = parseProfileChange(args);
46
+ await applyProfile(acct.client, change);
47
+ const result: ProfileApplied = { account: accountId, applied: fieldsOf(change) };
48
+ respond(id, { result });
49
+ }
50
+
51
+ export async function readProfile(id: string, args: Record<string, unknown>): Promise<void> {
52
+ const accountId = accountFor({ account: args.account as string | undefined });
53
+ const acct = accounts.get(accountId);
54
+ if (!acct) {
55
+ respond(id, { error: `unknown account '${accountId}'` });
56
+ return;
57
+ }
58
+ const user = typeof args.user === 'string' ? args.user.trim() : '';
59
+ if (!/^\d+$/.test(user)) throw new TrainError('discord_user_required', 'profile needs the Discord id of the person', { retryable: false });
60
+ const found = await acct.client.users.fetch(user);
61
+ const result: SenderProfile = {
62
+ id: user,
63
+ name: found.username,
64
+ ...(found.globalName === null ? {} : { display_name: found.globalName }),
65
+ avatar: found.displayAvatarURL({ size: 256 }),
66
+ };
67
+ respond(id, { result });
68
+ }
@@ -0,0 +1,43 @@
1
+ import { appendFile } from '@metro-labs/core/stations/attachments';
2
+
3
+ export interface OutgoingFile {
4
+ path: string;
5
+ name: string;
6
+ kind: string;
7
+ }
8
+
9
+ const basenameOf = (path: string, index: number): string =>
10
+ path.split('/').pop() ?? `file-${index}`;
11
+
12
+ export function outgoingFiles(
13
+ paths: string[],
14
+ names: string[] | undefined,
15
+ kinds: string[] | undefined,
16
+ ): OutgoingFile[] {
17
+ const out: OutgoingFile[] = [];
18
+ for (let i = 0; i < paths.length; i++) {
19
+ const path = paths[i];
20
+ if (path === undefined || path === '') continue;
21
+ const given = names?.[i];
22
+ out.push({
23
+ path,
24
+ name: given !== undefined && given !== '' ? given : basenameOf(path, i),
25
+ kind: kinds?.[i] ?? 'file',
26
+ });
27
+ }
28
+ return out;
29
+ }
30
+
31
+ export async function appendFiles(
32
+ form: FormData,
33
+ files: OutgoingFile[],
34
+ ): Promise<string[]> {
35
+ const delivered: string[] = [];
36
+ for (let i = 0; i < files.length; i++) {
37
+ const file = files[i];
38
+ if (!file) continue;
39
+ await appendFile(form, `files[${i}]`, file.path, file.name);
40
+ delivered.push(file.kind);
41
+ }
42
+ return delivered;
43
+ }
@@ -0,0 +1,22 @@
1
+ import type { GroupOp, Station, Verb } from '@metro-labs/core/stations/types';
2
+ import { PROFILE_FIELDS, type ProfileField } from '@metro-labs/core/stations/profile';
3
+
4
+ export const discordBotStation: Station = {
5
+ name: 'discord-bot',
6
+ hasAccounts: true,
7
+ hasTrain: true,
8
+ messageVerbs: new Set<Verb>([
9
+ 'send',
10
+ 'reply',
11
+ 'react',
12
+ 'unreact',
13
+ 'edit',
14
+ 'delete',
15
+ 'read',
16
+ ]),
17
+ groupOps: new Set<GroupOp>(['create_group', 'add_members', 'remove_members']),
18
+ attachmentMode: 'canonical',
19
+ profileFields: new Set<ProfileField>(PROFILE_FIELDS),
20
+ readsProfiles: true,
21
+ tools: [],
22
+ };
@@ -0,0 +1,82 @@
1
+ import { API } from './api-base.js';
2
+
3
+ export class DiscordTokenError extends Error {}
4
+
5
+ export interface DiscordBotIdentity {
6
+ userId: string;
7
+ username: string;
8
+ messageContent: boolean;
9
+ }
10
+
11
+ const MESSAGE_CONTENT = 1 << 18;
12
+ const MESSAGE_CONTENT_LIMITED = 1 << 19;
13
+ const TIMEOUT_MS = 10_000;
14
+
15
+ function botFetch(token: string, path: string): Promise<Response> {
16
+ return fetch(`${API}${path}`, {
17
+ headers: {
18
+ authorization: `Bot ${token}`,
19
+ 'user-agent': 'metro-daemon (https://github.com/bonustrack/metro)',
20
+ },
21
+ signal: AbortSignal.timeout(TIMEOUT_MS),
22
+ });
23
+ }
24
+
25
+ async function readJson(res: Response): Promise<Record<string, unknown>> {
26
+ try {
27
+ const body: unknown = await res.json();
28
+ return typeof body === 'object' && body !== null
29
+ ? (body as Record<string, unknown>)
30
+ : {};
31
+ } catch {
32
+ throw new DiscordTokenError('Discord returned a response Metro could not read');
33
+ }
34
+ }
35
+
36
+ async function selfUser(token: string): Promise<Record<string, unknown>> {
37
+ let res: Response;
38
+ try {
39
+ res = await botFetch(token, '/users/@me');
40
+ } catch {
41
+ throw new DiscordTokenError(
42
+ 'could not reach the Discord API to check that bot token',
43
+ );
44
+ }
45
+ if (res.status === 401 || res.status === 403)
46
+ throw new DiscordTokenError('Discord rejected that bot token');
47
+ if (!res.ok)
48
+ throw new DiscordTokenError(
49
+ `Discord answered ${res.status} while checking that bot token`,
50
+ );
51
+ return readJson(res);
52
+ }
53
+
54
+ async function messageContentEnabled(token: string): Promise<boolean> {
55
+ try {
56
+ const res = await botFetch(token, '/applications/@me');
57
+ if (!res.ok) return true;
58
+ const flags = (await readJson(res)).flags;
59
+ if (typeof flags !== 'number') return true;
60
+ return (flags & (MESSAGE_CONTENT | MESSAGE_CONTENT_LIMITED)) !== 0;
61
+ } catch {
62
+ return true;
63
+ }
64
+ }
65
+
66
+ export async function verifyDiscordBotToken(
67
+ token: string,
68
+ ): Promise<DiscordBotIdentity> {
69
+ const me = await selfUser(token);
70
+ const userId = typeof me.id === 'string' ? me.id : '';
71
+ if (userId === '')
72
+ throw new DiscordTokenError('Discord returned no bot identity for that token');
73
+ if (me.bot !== true)
74
+ throw new DiscordTokenError(
75
+ 'that token belongs to a Discord user, not a bot application',
76
+ );
77
+ return {
78
+ userId,
79
+ username: typeof me.username === 'string' ? me.username : '',
80
+ messageContent: await messageContentEnabled(token),
81
+ };
82
+ }
@@ -0,0 +1 @@
1
+ export { emit, respond, mintId } from '@metro-labs/core/stations/station-runtime';
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "@metro-labs/http",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "description": "How a metro server authenticates and answers a request: CORS, the JSON helpers, ApiError, the signed-identity header and the identity registry. Shared by the daemon and api.metro.box, never by a station.",
6
+ "type": "module",
7
+ "exports": {
8
+ "./cors": "./src/cors.ts",
9
+ "./api-http": "./src/api-http.ts",
10
+ "./api-error": "./src/api-error.ts",
11
+ "./return-to": "./src/return-to.ts",
12
+ "./mode-api": "./src/mode-api.ts",
13
+ "./workos-token": "./src/workos-token.ts"
14
+ },
15
+ "scripts": {
16
+ "build": "tsc",
17
+ "test": "tsc --noEmit && bun test test/"
18
+ },
19
+ "dependencies": {
20
+ "@metro-labs/core": "workspace:*"
21
+ },
22
+ "devDependencies": {
23
+ "@stage-labs/config": "0.1.0-beta.2",
24
+ "eslint": "^10.3.0",
25
+ "knip": "^6.15.0",
26
+ "madge": "^8.0.0",
27
+ "typescript": "^5"
28
+ }
29
+ }
@@ -0,0 +1,8 @@
1
+ export class ApiError extends Error {
2
+ constructor(
3
+ message: string,
4
+ readonly status: number,
5
+ ) {
6
+ super(message);
7
+ }
8
+ }
@@ -0,0 +1,138 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { ApiError } from './api-error.js';
3
+ import { errMsg, log } from '@metro-labs/core/log';
4
+
5
+ const BODY_MAX = 4 * 1024;
6
+
7
+ export type Role = 'admin' | 'member';
8
+
9
+ export interface ApiSession {
10
+ subject: string;
11
+ role: Role;
12
+ }
13
+
14
+ export type BearerSessions = (req: IncomingMessage) => Promise<ApiSession | null>;
15
+
16
+ let bearerSessions: BearerSessions | null = null;
17
+
18
+ export function setBearerSessions(fn: BearerSessions | null): void {
19
+ bearerSessions = fn;
20
+ }
21
+
22
+ export function requireAdmin(session: ApiSession): void {
23
+ if (session.role !== 'admin') throw new ApiError('this needs the admin role in your organization', 403);
24
+ }
25
+
26
+ export function cors(req: IncomingMessage): Record<string, string> {
27
+ return {
28
+ 'access-control-allow-origin': req.headers.origin ?? '*',
29
+ 'access-control-allow-methods': 'GET, POST, PUT, DELETE, OPTIONS',
30
+ 'access-control-allow-headers': 'Authorization, Content-Type',
31
+ 'access-control-allow-private-network': 'true',
32
+ 'access-control-max-age': '86400',
33
+ vary: 'Origin',
34
+ };
35
+ }
36
+
37
+ export function sendJson(
38
+ req: IncomingMessage,
39
+ res: ServerResponse,
40
+ status: number,
41
+ body: unknown,
42
+ ): void {
43
+ res.writeHead(status, {
44
+ 'content-type': 'application/json',
45
+ 'cache-control': 'no-store',
46
+ ...cors(req),
47
+ });
48
+ res.end(JSON.stringify(body));
49
+ }
50
+
51
+ export function apiSession(req: IncomingMessage): Promise<ApiSession | null> {
52
+ return bearerSessions === null ? Promise.resolve(null) : bearerSessions(req);
53
+ }
54
+
55
+ export interface AgentIdentity {
56
+ subject: string;
57
+ agentId: string;
58
+ }
59
+
60
+ export async function readJsonBody(req: IncomingMessage, max = BODY_MAX): Promise<unknown> {
61
+ const chunks: Buffer[] = [];
62
+ let total = 0;
63
+ let over = false;
64
+ for await (const c of req) {
65
+ const buf = c as Buffer;
66
+ total += buf.length;
67
+ if (total > max) over = true;
68
+ if (!over) chunks.push(buf);
69
+ else if (total > 2 * max) break;
70
+ }
71
+ if (over) throw new ApiError('request body too large', 413);
72
+ const raw = Buffer.concat(chunks).toString('utf8').trim();
73
+ if (raw === '') return {};
74
+ try {
75
+ return JSON.parse(raw);
76
+ } catch {
77
+ throw new ApiError('body must be JSON', 400);
78
+ }
79
+ }
80
+
81
+ export function apiFailure(
82
+ req: IncomingMessage,
83
+ res: ServerResponse,
84
+ err: unknown,
85
+ label = 'agent-api',
86
+ ): void {
87
+ if (err instanceof ApiError) {
88
+ sendJson(req, res, err.status, { error: err.message });
89
+ return;
90
+ }
91
+ log.warn({ err: errMsg(err) }, `${label}: request failed`);
92
+ if (!res.headersSent) sendJson(req, res, 500, { error: `${label} failed` });
93
+ }
94
+
95
+ export interface SessionRoute {
96
+ methods: Readonly<Record<string, readonly string[]>>;
97
+ admin: boolean | readonly string[];
98
+ label: string;
99
+ }
100
+
101
+ const needsAdmin = (route: SessionRoute, method: string): boolean =>
102
+ route.admin === true || (Array.isArray(route.admin) && route.admin.includes(method));
103
+
104
+ export function sessionRoute(
105
+ req: IncomingMessage,
106
+ res: ServerResponse,
107
+ route: SessionRoute,
108
+ handler: (session: ApiSession, path: string) => Promise<unknown>,
109
+ ): boolean {
110
+ const path = (req.url ?? '').split('?')[0] ?? '';
111
+ const methods = Object.hasOwn(route.methods, path) ? route.methods[path] : undefined;
112
+ if (methods === undefined) return false;
113
+ const method = req.method ?? '';
114
+ if (method === 'OPTIONS') {
115
+ res.writeHead(204, cors(req)).end();
116
+ return true;
117
+ }
118
+ if (!methods.includes(method)) {
119
+ sendJson(req, res, 405, { error: 'method not allowed' });
120
+ return true;
121
+ }
122
+ apiSession(req)
123
+ .then(async (session) => {
124
+ if (!session) throw new ApiError('unauthorized', 401);
125
+ if (needsAdmin(route, method)) requireAdmin(session);
126
+ sendJson(req, res, 200, await handler(session, path));
127
+ })
128
+ .catch((err: unknown) => {
129
+ apiFailure(req, res, err, route.label);
130
+ });
131
+ return true;
132
+ }
133
+
134
+ export function bodyField(body: unknown, key: string): unknown {
135
+ return typeof body === 'object' && body !== null && !Array.isArray(body)
136
+ ? (body as Record<string, unknown>)[key]
137
+ : undefined;
138
+ }
@@ -0,0 +1,29 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+
3
+ export function applyMcpCors(
4
+ req: IncomingMessage,
5
+ res: ServerResponse,
6
+ ): void {
7
+ const origin = req.headers.origin;
8
+ res.setHeader(
9
+ 'Access-Control-Allow-Origin',
10
+ origin !== undefined && origin !== '' ? origin : '*',
11
+ );
12
+ res.setHeader('Vary', 'Origin');
13
+ res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
14
+ res.setHeader(
15
+ 'Access-Control-Allow-Headers',
16
+ 'Authorization, Content-Type, Accept, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID',
17
+ );
18
+ res.setHeader('Access-Control-Expose-Headers', 'Mcp-Session-Id');
19
+ res.setHeader('Access-Control-Max-Age', '86400');
20
+ }
21
+
22
+ export function handleMcpPreflight(
23
+ req: IncomingMessage,
24
+ res: ServerResponse,
25
+ ): boolean {
26
+ if (req.method !== 'OPTIONS') return false;
27
+ res.writeHead(204).end();
28
+ return true;
29
+ }
@@ -0,0 +1,32 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { cors, sendJson } from './api-http.js';
3
+
4
+ const PATH = '/api/mode';
5
+
6
+ export type DaemonMode = 'hosted' | 'local';
7
+
8
+ export interface ModeInfo {
9
+ mode: DaemonMode;
10
+ owner: string | null;
11
+ project: string | null;
12
+ version: string;
13
+ }
14
+
15
+ export function handleModeRequest(
16
+ req: IncomingMessage,
17
+ res: ServerResponse,
18
+ info: () => ModeInfo,
19
+ ): boolean {
20
+ const path = (req.url ?? '').split('?')[0] ?? '';
21
+ if (path !== PATH) return false;
22
+ if (req.method === 'OPTIONS') {
23
+ res.writeHead(204, cors(req)).end();
24
+ return true;
25
+ }
26
+ if (req.method !== 'GET') {
27
+ sendJson(req, res, 405, { error: 'method not allowed' });
28
+ return true;
29
+ }
30
+ sendJson(req, res, 200, info());
31
+ return true;
32
+ }
@@ -0,0 +1,21 @@
1
+ const PREVIEW = /^([a-z0-9-]+--)?metro-ui\.netlify\.app$/;
2
+
3
+ export function allowedWebHost(host: string): boolean {
4
+ if (host === 'metro.box' || host === 'localhost' || host === '127.0.0.1')
5
+ return true;
6
+ return PREVIEW.test(host);
7
+ }
8
+
9
+ export function validateReturnTo(returnTo: string): boolean {
10
+ let url: URL;
11
+ try {
12
+ url = new URL(returnTo);
13
+ } catch {
14
+ return false;
15
+ }
16
+ const host = url.hostname;
17
+ if (url.protocol === 'http:' && (host === 'localhost' || host === '127.0.0.1'))
18
+ return true;
19
+ if (url.protocol !== 'https:') return false;
20
+ return allowedWebHost(host);
21
+ }
@@ -0,0 +1,149 @@
1
+ import type { IncomingMessage } from 'node:http';
2
+ import { createPublicKey, verify, type JsonWebKey, type KeyObject } from 'node:crypto';
3
+
4
+ const WORKOS_API = 'https://api.workos.com';
5
+ const ORG_RE = /^org_[A-Za-z0-9]{10,64}$/;
6
+ export const isOrganizationId = (value: string): boolean => ORG_RE.test(value);
7
+ export const DEFAULT_CLIENT_ID = 'client_01M2TJJJ6RM9CT081QB4XZK3G2';
8
+ const SKEW_S = 60;
9
+ const REFETCH_COOLDOWN_MS = 60_000;
10
+ const FETCH_MS = 10_000;
11
+
12
+ export interface Session {
13
+ userId: string;
14
+ sessionId: string;
15
+ organization: string | null;
16
+ role: string | null;
17
+ expiresAt: number;
18
+ }
19
+
20
+ export interface KeyStore {
21
+ read: () => string | null;
22
+ write: (text: string) => void;
23
+ }
24
+
25
+ export const filled = (value: string | undefined): string | null => {
26
+ const text = value?.trim() ?? '';
27
+ return text === '' ? null : text;
28
+ };
29
+
30
+ export const clientId = (env: NodeJS.ProcessEnv = process.env): string => filled(env.WORKOS_CLIENT_ID) ?? DEFAULT_CLIENT_ID;
31
+ export const workosBase = (env: NodeJS.ProcessEnv = process.env): string => filled(env.WORKOS_API_BASE) ?? WORKOS_API;
32
+ export const jwksUrl = (id: string, base = WORKOS_API): string => `${base}/sso/jwks/${id}`;
33
+
34
+ function jwksKeys(text: string): Map<string, KeyObject> {
35
+ const keys = new Map<string, KeyObject>();
36
+ const parsed: unknown = JSON.parse(text);
37
+ const list = typeof parsed === 'object' && parsed !== null ? (parsed as { keys?: unknown }).keys : undefined;
38
+ if (!Array.isArray(list)) return keys;
39
+ for (const jwk of list) {
40
+ if (typeof jwk !== 'object' || jwk === null) continue;
41
+ const { kid, kty } = jwk as { kid?: unknown; kty?: unknown };
42
+ if (typeof kid !== 'string' || kty !== 'RSA') continue;
43
+ try {
44
+ keys.set(kid, createPublicKey({ key: jwk as JsonWebKey, format: 'jwk' }));
45
+ } catch {
46
+ continue;
47
+ }
48
+ }
49
+ return keys;
50
+ }
51
+
52
+ export class SigningKeys {
53
+ private keys = new Map<string, KeyObject>();
54
+ private lastMiss = 0;
55
+ private loaded = false;
56
+
57
+ constructor(
58
+ private readonly url: string,
59
+ private readonly store?: KeyStore,
60
+ ) {}
61
+
62
+ private load(): void {
63
+ if (this.loaded) return;
64
+ this.loaded = true;
65
+ const cached = this.store?.read() ?? null;
66
+ if (cached !== null) this.keys = jwksKeys(cached);
67
+ }
68
+
69
+ private async refresh(now: number): Promise<void> {
70
+ if (now - this.lastMiss < REFETCH_COOLDOWN_MS) return;
71
+ const res = await fetch(this.url, { signal: AbortSignal.timeout(FETCH_MS) });
72
+ if (!res.ok) throw new Error(`the signing keys answered ${String(res.status)}`);
73
+ const text = await res.text();
74
+ const keys = jwksKeys(text);
75
+ if (keys.size === 0) throw new Error('the signing keys answered no RSA key');
76
+ this.keys = keys;
77
+ this.store?.write(text);
78
+ }
79
+
80
+ async keyFor(kid: string, now = Date.now()): Promise<KeyObject | null> {
81
+ this.load();
82
+ const held = this.keys.get(kid);
83
+ if (held !== undefined) return held;
84
+ await this.refresh(now);
85
+ const fetched = this.keys.get(kid);
86
+ if (fetched === undefined) this.lastMiss = now;
87
+ return fetched ?? null;
88
+ }
89
+ }
90
+
91
+ const b64url = (text: string): Buffer => Buffer.from(text.replace(/-/g, '+').replace(/_/g, '/'), 'base64');
92
+
93
+ function jsonPart(text: string): Record<string, unknown> | null {
94
+ try {
95
+ const parsed: unknown = JSON.parse(b64url(text).toString('utf8'));
96
+ return typeof parsed === 'object' && parsed !== null ? (parsed as Record<string, unknown>) : null;
97
+ } catch {
98
+ return null;
99
+ }
100
+ }
101
+
102
+ const str = (value: unknown): string | null => (typeof value === 'string' && value !== '' ? value : null);
103
+
104
+ const issuedBy = (iss: unknown, issuer: string): boolean => typeof iss === 'string' && (iss === issuer || iss.startsWith(`${issuer}/`));
105
+
106
+ function sessionOf(claims: Record<string, unknown>, issuer: string, now: number): Session | null {
107
+ const exp = typeof claims.exp === 'number' ? claims.exp : null;
108
+ const userId = str(claims.sub);
109
+ const sessionId = str(claims.sid);
110
+ if (!issuedBy(claims.iss, issuer) || exp === null || userId === null || sessionId === null) return null;
111
+ if (exp * 1000 < now - SKEW_S * 1000) return null;
112
+ return { userId, sessionId, organization: str(claims.org_id), role: str(claims.role), expiresAt: exp * 1000 };
113
+ }
114
+
115
+ interface Parts {
116
+ signed: string;
117
+ body: string;
118
+ signature: Buffer;
119
+ kid: string;
120
+ }
121
+
122
+ function parseToken(token: string): Parts | null {
123
+ const [head, body, sig] = token.split('.');
124
+ if (head === undefined || body === undefined || sig === undefined) return null;
125
+ const header = jsonPart(head);
126
+ const kid = str(header?.kid);
127
+ if (header?.alg !== 'RS256' || kid === null) return null;
128
+ return { signed: `${head}.${body}`, body, signature: b64url(sig), kid };
129
+ }
130
+
131
+ export async function verifyToken(token: string, keys: SigningKeys, issuer = WORKOS_API, now = Date.now()): Promise<Session | null> {
132
+ const parts = parseToken(token);
133
+ if (parts === null) return null;
134
+ const key = await keys.keyFor(parts.kid, now).catch(() => null);
135
+ if (key === null || !verify('sha256', Buffer.from(parts.signed), key, parts.signature)) return null;
136
+ const claims = jsonPart(parts.body);
137
+ return claims === null ? null : sessionOf(claims, issuer, now);
138
+ }
139
+
140
+ export function bearerToken(req: IncomingMessage): string | null {
141
+ const header = req.headers.authorization ?? '';
142
+ const [scheme, token] = header.trim().split(/\s+/);
143
+ return scheme?.toLowerCase() === 'bearer' && token?.includes('.') === true ? token : null;
144
+ }
145
+
146
+ export async function bearerSession(req: IncomingMessage, keys: SigningKeys, issuer = WORKOS_API): Promise<Session | null> {
147
+ const token = bearerToken(req);
148
+ return token === null ? null : verifyToken(token, keys, issuer);
149
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@metro-labs/outlook",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "exports": {
7
+ ".": "./src/station.ts",
8
+ "./train": "./src/index.ts",
9
+ "./login": "./src/login.ts"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc",
13
+ "test": "tsc --noEmit && bun test test/"
14
+ },
15
+ "dependencies": {
16
+ "@metro-labs/core": "workspace:*"
17
+ },
18
+ "devDependencies": {
19
+ "@stage-labs/config": "0.1.0-beta.2",
20
+ "eslint": "^10.3.0",
21
+ "knip": "^6.15.0",
22
+ "madge": "^8.0.0",
23
+ "typescript": "^5"
24
+ }
25
+ }