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

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 (279) hide show
  1. package/dist/api.js +14 -0
  2. package/dist/bedrock-eventstream.js +120 -0
  3. package/dist/bedrock-proxy.js +260 -0
  4. package/dist/bedrock-upstream.js +72 -0
  5. package/dist/bedrock.js +37 -0
  6. package/dist/claude-settings.js +40 -0
  7. package/dist/claude.js +107 -0
  8. package/dist/cli.js +101 -0
  9. package/dist/control.js +74 -0
  10. package/dist/hold.js +211 -0
  11. package/dist/local.js +93 -0
  12. package/dist/node-name.js +51 -0
  13. package/dist/plugin.js +80 -0
  14. package/dist/provider-flags.js +9 -0
  15. package/dist/runtime-install.js +84 -0
  16. package/dist/runtime.js +70 -0
  17. package/dist/serve.js +147 -0
  18. package/dist/service.js +224 -0
  19. package/dist/tail.js +119 -0
  20. package/dist/update.js +45 -0
  21. package/dist/version.js +97 -0
  22. package/package.json +14 -25
  23. package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
  24. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -0
  25. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
  26. package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
  27. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
  28. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +220 -0
  29. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
  30. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
  31. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
  32. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
  33. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
  34. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
  35. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
  36. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
  37. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
  38. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
  39. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
  40. package/runtime/node_modules/@metro-labs/mcp/package.json +64 -0
  41. package/runtime/node_modules/@metro-labs/mcp/src/channels/addressed.ts +25 -0
  42. package/runtime/node_modules/@metro-labs/mcp/src/channels/dedupe.ts +24 -0
  43. package/runtime/node_modules/@metro-labs/mcp/src/channels/inbound.ts +377 -0
  44. package/runtime/node_modules/@metro-labs/mcp/src/channels/media-note.ts +85 -0
  45. package/runtime/node_modules/@metro-labs/mcp/src/channels/pending.ts +75 -0
  46. package/runtime/node_modules/@metro-labs/mcp/src/channels/relay.ts +129 -0
  47. package/runtime/node_modules/@metro-labs/mcp/src/channels/webhook-note.ts +51 -0
  48. package/runtime/node_modules/@metro-labs/mcp/src/daemon/account-api.ts +311 -0
  49. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +302 -0
  50. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-error.ts +8 -0
  51. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-http.ts +95 -0
  52. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-grant.ts +99 -0
  53. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-owner.ts +32 -0
  54. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-serve.ts +128 -0
  55. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-session.ts +280 -0
  56. package/runtime/node_modules/@metro-labs/mcp/src/daemon/boot.ts +204 -0
  57. package/runtime/node_modules/@metro-labs/mcp/src/daemon/bundle-api.ts +111 -0
  58. package/runtime/node_modules/@metro-labs/mcp/src/daemon/claude-api.ts +97 -0
  59. package/runtime/node_modules/@metro-labs/mcp/src/daemon/claude-files.ts +329 -0
  60. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connect-hint.ts +39 -0
  61. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-api.ts +307 -0
  62. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-json.ts +21 -0
  63. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth-routes.ts +157 -0
  64. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth.ts +127 -0
  65. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-tools.ts +98 -0
  66. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-url.ts +70 -0
  67. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-verify.ts +330 -0
  68. package/runtime/node_modules/@metro-labs/mcp/src/daemon/control-api.ts +64 -0
  69. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cors.ts +29 -0
  70. package/runtime/node_modules/@metro-labs/mcp/src/daemon/crash-guard.ts +49 -0
  71. package/runtime/node_modules/@metro-labs/mcp/src/daemon/events.ts +120 -0
  72. package/runtime/node_modules/@metro-labs/mcp/src/daemon/http.ts +390 -0
  73. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity-key.ts +33 -0
  74. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity-registry.ts +13 -0
  75. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity-routes.ts +54 -0
  76. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +7 -0
  77. package/runtime/node_modules/@metro-labs/mcp/src/daemon/is-record.ts +2 -0
  78. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-cli-api.ts +63 -0
  79. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-mode.ts +187 -0
  80. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-owner.ts +14 -0
  81. package/runtime/node_modules/@metro-labs/mcp/src/daemon/log.ts +42 -0
  82. package/runtime/node_modules/@metro-labs/mcp/src/daemon/machine-api.ts +61 -0
  83. package/runtime/node_modules/@metro-labs/mcp/src/daemon/mode-api.ts +32 -0
  84. package/runtime/node_modules/@metro-labs/mcp/src/daemon/model-api.ts +169 -0
  85. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-client.ts +159 -0
  86. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-discovery.ts +117 -0
  87. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-pending.ts +65 -0
  88. package/runtime/node_modules/@metro-labs/mcp/src/daemon/paths.ts +109 -0
  89. package/runtime/node_modules/@metro-labs/mcp/src/daemon/protocol.ts +226 -0
  90. package/runtime/node_modules/@metro-labs/mcp/src/daemon/relay.ts +295 -0
  91. package/runtime/node_modules/@metro-labs/mcp/src/daemon/return-to.ts +21 -0
  92. package/runtime/node_modules/@metro-labs/mcp/src/daemon/runtime-deps.ts +82 -0
  93. package/runtime/node_modules/@metro-labs/mcp/src/daemon/secure-fs.ts +57 -0
  94. package/runtime/node_modules/@metro-labs/mcp/src/daemon/server-types.ts +21 -0
  95. package/runtime/node_modules/@metro-labs/mcp/src/daemon/servers-api.ts +80 -0
  96. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-api.ts +29 -0
  97. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-apis.ts +66 -0
  98. package/runtime/node_modules/@metro-labs/mcp/src/daemon/signed-identity.ts +36 -0
  99. package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor-io.ts +97 -0
  100. package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor.ts +320 -0
  101. package/runtime/node_modules/@metro-labs/mcp/src/daemon/terminal-api.ts +71 -0
  102. package/runtime/node_modules/@metro-labs/mcp/src/daemon/terminal-tickets.ts +40 -0
  103. package/runtime/node_modules/@metro-labs/mcp/src/daemon/terminal-ws.ts +114 -0
  104. package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-call.ts +22 -0
  105. package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-error.ts +53 -0
  106. package/runtime/node_modules/@metro-labs/mcp/src/daemon/tunnel.ts +352 -0
  107. package/runtime/node_modules/@metro-labs/mcp/src/daemon/update-api.ts +116 -0
  108. package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-api.ts +294 -0
  109. package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-store.ts +181 -0
  110. package/runtime/node_modules/@metro-labs/mcp/src/daemon/vault-api.ts +78 -0
  111. package/runtime/node_modules/@metro-labs/mcp/src/daemon/vault-types.ts +12 -0
  112. package/runtime/node_modules/@metro-labs/mcp/src/daemon/version.ts +2 -0
  113. package/runtime/node_modules/@metro-labs/mcp/src/db/account-attach.ts +19 -0
  114. package/runtime/node_modules/@metro-labs/mcp/src/db/address.ts +6 -0
  115. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-admin.ts +43 -0
  116. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-map.ts +84 -0
  117. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-scope.ts +64 -0
  118. package/runtime/node_modules/@metro-labs/mcp/src/db/client.ts +32 -0
  119. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-config.ts +144 -0
  120. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-relay.ts +39 -0
  121. package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +69 -0
  122. package/runtime/node_modules/@metro-labs/mcp/src/db/file-admin.ts +329 -0
  123. package/runtime/node_modules/@metro-labs/mcp/src/db/file-source.ts +155 -0
  124. package/runtime/node_modules/@metro-labs/mcp/src/db/ids.ts +21 -0
  125. package/runtime/node_modules/@metro-labs/mcp/src/db/key-map.ts +45 -0
  126. package/runtime/node_modules/@metro-labs/mcp/src/db/local-connectors.ts +277 -0
  127. package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +253 -0
  128. package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +33 -0
  129. package/runtime/node_modules/@metro-labs/mcp/src/db/servers.ts +83 -0
  130. package/runtime/node_modules/@metro-labs/mcp/src/db/stations.ts +12 -0
  131. package/runtime/node_modules/@metro-labs/mcp/src/db/vault.ts +109 -0
  132. package/runtime/node_modules/@metro-labs/mcp/src/gateway/bedrock.ts +252 -0
  133. package/runtime/node_modules/@metro-labs/mcp/src/gateway/codex-auth.ts +231 -0
  134. package/runtime/node_modules/@metro-labs/mcp/src/gateway/codex-device.ts +91 -0
  135. package/runtime/node_modules/@metro-labs/mcp/src/gateway/codex-stream.ts +288 -0
  136. package/runtime/node_modules/@metro-labs/mcp/src/gateway/codex-translate.ts +203 -0
  137. package/runtime/node_modules/@metro-labs/mcp/src/gateway/codex.ts +211 -0
  138. package/runtime/node_modules/@metro-labs/mcp/src/gateway/eventstream.ts +123 -0
  139. package/runtime/node_modules/@metro-labs/mcp/src/gateway/forward.ts +169 -0
  140. package/runtime/node_modules/@metro-labs/mcp/src/gateway/gateway.ts +193 -0
  141. package/runtime/node_modules/@metro-labs/mcp/src/gateway/model-config.ts +180 -0
  142. package/runtime/node_modules/@metro-labs/mcp/src/mcp/accounts.ts +141 -0
  143. package/runtime/node_modules/@metro-labs/mcp/src/mcp/call-tools.ts +216 -0
  144. package/runtime/node_modules/@metro-labs/mcp/src/mcp/channel-owner.ts +29 -0
  145. package/runtime/node_modules/@metro-labs/mcp/src/mcp/ctx.ts +62 -0
  146. package/runtime/node_modules/@metro-labs/mcp/src/mcp/event-store.ts +91 -0
  147. package/runtime/node_modules/@metro-labs/mcp/src/mcp/frame-scope.ts +21 -0
  148. package/runtime/node_modules/@metro-labs/mcp/src/mcp/group-tools.ts +129 -0
  149. package/runtime/node_modules/@metro-labs/mcp/src/mcp/index.ts +191 -0
  150. package/runtime/node_modules/@metro-labs/mcp/src/mcp/member-tools.ts +72 -0
  151. package/runtime/node_modules/@metro-labs/mcp/src/mcp/permission-relay.ts +66 -0
  152. package/runtime/node_modules/@metro-labs/mcp/src/mcp/raw-get-stream.ts +207 -0
  153. package/runtime/node_modules/@metro-labs/mcp/src/mcp/request-identity.ts +47 -0
  154. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-registry.ts +172 -0
  155. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-route.ts +43 -0
  156. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session.ts +215 -0
  157. package/runtime/node_modules/@metro-labs/mcp/src/mcp/str.ts +5 -0
  158. package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-dispatch.ts +215 -0
  159. package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-schemas.ts +400 -0
  160. package/runtime/node_modules/@metro-labs/mcp/src/mcp/upload-tool.ts +66 -0
  161. package/runtime/node_modules/@metro-labs/mcp/src/monitor/api.ts +237 -0
  162. package/runtime/node_modules/@metro-labs/mcp/src/server.ts +2 -0
  163. package/runtime/node_modules/@metro-labs/mcp/src/stations/account-store.ts +60 -0
  164. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-inline.ts +100 -0
  165. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-interactive.ts +161 -0
  166. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-resolve.ts +200 -0
  167. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-xmtp.ts +111 -0
  168. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach.ts +184 -0
  169. package/runtime/node_modules/@metro-labs/mcp/src/stations/attachments.ts +196 -0
  170. package/runtime/node_modules/@metro-labs/mcp/src/stations/lines.ts +82 -0
  171. package/runtime/node_modules/@metro-labs/mcp/src/stations/messaging-normalize.ts +88 -0
  172. package/runtime/node_modules/@metro-labs/mcp/src/stations/registry.ts +35 -0
  173. package/runtime/node_modules/@metro-labs/mcp/src/stations/station-runtime.ts +64 -0
  174. package/runtime/node_modules/@metro-labs/mcp/src/stations/types.ts +107 -0
  175. package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
  176. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
  177. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +254 -0
  178. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
  179. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
  180. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +156 -0
  181. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
  182. package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
  183. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
  184. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
  185. package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
  186. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
  187. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
  188. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
  189. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
  190. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
  191. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
  192. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
  193. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
  194. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
  195. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
  196. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
  197. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +201 -0
  198. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +109 -0
  199. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
  200. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
  201. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
  202. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +58 -0
  203. package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
  204. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
  205. package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
  206. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
  207. package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
  208. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
  209. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
  210. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +178 -0
  211. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
  212. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
  213. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +327 -0
  214. package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
  215. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +138 -0
  216. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
  217. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
  218. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
  219. package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
  220. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
  221. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
  222. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
  223. package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
  224. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
  225. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +17 -0
  226. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
  227. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
  228. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
  229. package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
  230. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +112 -0
  231. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
  232. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
  233. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
  234. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
  235. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
  236. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
  237. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
  238. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
  239. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
  240. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +335 -0
  241. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +80 -0
  242. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
  243. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
  244. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
  245. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
  246. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
  247. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
  248. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
  249. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
  250. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
  251. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
  252. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
  253. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
  254. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +53 -0
  255. package/runtime/runtime.json +3 -0
  256. package/runtime/stations.json +36 -0
  257. package/runtime/trains/.keep +0 -0
  258. package/LICENSE +0 -21
  259. package/README.md +0 -193
  260. package/dist/cache.js +0 -75
  261. package/dist/cli/actions.js +0 -139
  262. package/dist/cli/config.js +0 -185
  263. package/dist/cli/index.js +0 -159
  264. package/dist/cli/skill.js +0 -62
  265. package/dist/cli/util.js +0 -72
  266. package/dist/codex-rc.js +0 -236
  267. package/dist/dispatcher.js +0 -95
  268. package/dist/history.js +0 -84
  269. package/dist/ipc.js +0 -72
  270. package/dist/log.js +0 -15
  271. package/dist/paths.js +0 -68
  272. package/dist/stations/discord.js +0 -200
  273. package/dist/stations/index.js +0 -73
  274. package/dist/stations/telegram-md.js +0 -34
  275. package/dist/stations/telegram-upload.js +0 -113
  276. package/dist/stations/telegram.js +0 -213
  277. package/docs/agents.md +0 -185
  278. package/docs/uri-scheme.md +0 -71
  279. package/skills/metro/SKILL.md +0 -241
@@ -0,0 +1,295 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import type { RelayTarget } from '../db/connector-relay.js';
3
+ import type { AgentIdentity } from './api-http.js';
4
+ import { ApiError } from './api-error.js';
5
+ import { errMsg, log } from './log.js';
6
+
7
+ export interface RelayApiDeps {
8
+ target: (connectorId: string, force: boolean) => Promise<RelayTarget>;
9
+ identify: (req: IncomingMessage) => AgentIdentity | null;
10
+ }
11
+
12
+ const ID_PATH_RE = /^\/relay\/([A-Za-z0-9][A-Za-z0-9_-]{10})$/;
13
+ const METHODS = new Set(['POST', 'GET', 'DELETE']);
14
+ const RELAY_BODY_MAX = 8 * 1024 * 1024;
15
+ const FIRST_BYTE_MS = 120_000;
16
+ const PASS_REQ = [
17
+ 'accept',
18
+ 'content-type',
19
+ 'mcp-session-id',
20
+ 'mcp-protocol-version',
21
+ 'last-event-id',
22
+ ] as const;
23
+ const PASS_RES = [
24
+ 'content-type',
25
+ 'mcp-session-id',
26
+ 'mcp-protocol-version',
27
+ 'cache-control',
28
+ ] as const;
29
+
30
+ const keepaliveMs = (): number =>
31
+ Number(process.env.METRO_RELAY_KEEPALIVE_MS) || 25_000;
32
+
33
+ const reconnectUrl = (id: string): string =>
34
+ `https://metro.box/#/connector/${id}`;
35
+
36
+ function answer(
37
+ res: ServerResponse,
38
+ status: number,
39
+ body: Record<string, unknown>,
40
+ ): void {
41
+ if (res.headersSent) return;
42
+ res.writeHead(status, { 'content-type': 'application/json' });
43
+ res.end(JSON.stringify(body));
44
+ }
45
+
46
+ const signinAnswer = (res: ServerResponse, id: string): void => {
47
+ answer(res, 424, {
48
+ error: 'this connector needs signing in again',
49
+ reconnect: reconnectUrl(id),
50
+ });
51
+ };
52
+
53
+ function closeIfBodyUnread(req: IncomingMessage, res: ServerResponse): void {
54
+ if (req.readableEnded) return;
55
+ const drop = (): void => {
56
+ req.socket?.destroy();
57
+ };
58
+ if (res.writableFinished) drop();
59
+ else res.once('finish', drop);
60
+ }
61
+
62
+ async function readCapped(req: IncomingMessage): Promise<Uint8Array<ArrayBuffer>> {
63
+ const declared = Number(req.headers['content-length']);
64
+ if (Number.isFinite(declared) && declared > RELAY_BODY_MAX)
65
+ throw new ApiError('relay body exceeds 8 MiB', 413);
66
+ const chunks: Buffer[] = [];
67
+ let total = 0;
68
+ for await (const chunk of req) {
69
+ const buf = chunk as Buffer;
70
+ total += buf.length;
71
+ if (total > RELAY_BODY_MAX)
72
+ throw new ApiError('relay body exceeds 8 MiB', 413);
73
+ chunks.push(buf);
74
+ }
75
+ const merged = Buffer.concat(chunks);
76
+ const out = new Uint8Array(new ArrayBuffer(merged.byteLength));
77
+ out.set(merged);
78
+ return out;
79
+ }
80
+
81
+ function upstreamHeaders(
82
+ req: IncomingMessage,
83
+ injected: Record<string, string>,
84
+ ): Record<string, string> {
85
+ const out: Record<string, string> = {};
86
+ for (const name of PASS_REQ) {
87
+ const value = req.headers[name];
88
+ if (typeof value === 'string') out[name] = value;
89
+ }
90
+ return { ...out, ...injected };
91
+ }
92
+
93
+ async function forward(
94
+ req: IncomingMessage,
95
+ target: { url: string; headers: Record<string, string> },
96
+ body: Uint8Array<ArrayBuffer> | null,
97
+ signal: AbortSignal,
98
+ ): Promise<Response> {
99
+ const firstByte = setTimeout(() => {
100
+ log.warn({ url: target.url }, 'relay: upstream sent no response in time');
101
+ }, FIRST_BYTE_MS);
102
+ firstByte.unref?.();
103
+ try {
104
+ return await fetch(target.url, {
105
+ method: req.method,
106
+ headers: upstreamHeaders(req, target.headers),
107
+ ...(body === null ? {} : { body }),
108
+ signal,
109
+ redirect: 'manual',
110
+ });
111
+ } finally {
112
+ clearTimeout(firstByte);
113
+ }
114
+ }
115
+
116
+ interface Keepalive {
117
+ touch: () => void;
118
+ stop: () => void;
119
+ }
120
+
121
+ function startKeepalive(res: ServerResponse): Keepalive {
122
+ let timer: ReturnType<typeof setTimeout> | undefined;
123
+ const arm = (): void => {
124
+ timer = setTimeout(() => {
125
+ try {
126
+ res.write(': keepalive\n\n');
127
+ } catch {
128
+ log.debug('relay: keepalive write failed');
129
+ }
130
+ arm();
131
+ }, keepaliveMs());
132
+ timer.unref?.();
133
+ };
134
+ arm();
135
+ return {
136
+ touch: (): void => {
137
+ if (timer) clearTimeout(timer);
138
+ arm();
139
+ },
140
+ stop: (): void => {
141
+ if (timer) clearTimeout(timer);
142
+ },
143
+ };
144
+ }
145
+
146
+ async function pumpBody(
147
+ res: ServerResponse,
148
+ body: ReadableStream<Uint8Array>,
149
+ touch: () => void,
150
+ ): Promise<void> {
151
+ const reader = body.getReader();
152
+ for (;;) {
153
+ const { done, value } = await reader.read();
154
+ if (done) return;
155
+ touch();
156
+ const ok = res.write(value);
157
+ if (!ok)
158
+ await new Promise((resolve) => {
159
+ res.once('drain', resolve);
160
+ });
161
+ }
162
+ }
163
+
164
+ async function pipe(res: ServerResponse, upstream: Response): Promise<void> {
165
+ const headers: Record<string, string> = {};
166
+ for (const name of PASS_RES) {
167
+ const value = upstream.headers.get(name);
168
+ if (value !== null) headers[name] = value;
169
+ }
170
+ const sse = (headers['content-type'] ?? '').includes('text/event-stream');
171
+ if (sse) headers['x-accel-buffering'] = 'no';
172
+ res.writeHead(upstream.status, headers);
173
+ if (upstream.body === null) {
174
+ res.end();
175
+ return;
176
+ }
177
+ const keepalive = sse ? startKeepalive(res) : null;
178
+ try {
179
+ await pumpBody(res, upstream.body, () => keepalive?.touch());
180
+ } finally {
181
+ keepalive?.stop();
182
+ }
183
+ res.end();
184
+ }
185
+
186
+ const authFailed = (status: number): boolean =>
187
+ status === 401 || status === 403;
188
+
189
+ type Exchanged =
190
+ | { kind: 'response'; upstream: Response }
191
+ | { kind: 'missing' }
192
+ | { kind: 'signin' };
193
+
194
+ async function exchange(
195
+ req: IncomingMessage,
196
+ connectorId: string,
197
+ deps: RelayApiDeps,
198
+ body: Uint8Array<ArrayBuffer> | null,
199
+ signal: AbortSignal,
200
+ ): Promise<Exchanged> {
201
+ const target = await deps.target(connectorId, false);
202
+ if (target.kind !== 'ok') return { kind: target.kind };
203
+ let upstream = await forward(req, target, body, signal);
204
+ if (!authFailed(upstream.status)) return { kind: 'response', upstream };
205
+ await upstream.body?.cancel();
206
+ const fresh = await deps.target(connectorId, true);
207
+ if (fresh.kind !== 'ok') return { kind: 'signin' };
208
+ upstream = await forward(req, fresh, body, signal);
209
+ if (!authFailed(upstream.status)) return { kind: 'response', upstream };
210
+ await upstream.body?.cancel();
211
+ return { kind: 'signin' };
212
+ }
213
+
214
+ async function relayExchange(
215
+ req: IncomingMessage,
216
+ res: ServerResponse,
217
+ connectorId: string,
218
+ deps: RelayApiDeps,
219
+ ): Promise<void> {
220
+ const control = new AbortController();
221
+ const bail = (): void => {
222
+ if (!res.writableEnded) control.abort();
223
+ };
224
+ res.once('close', bail);
225
+ res.socket?.once('close', bail);
226
+ res.once('finish', () => {
227
+ res.socket?.removeListener('close', bail);
228
+ });
229
+ const body = req.method === 'POST' ? await readCapped(req) : null;
230
+ const out = await exchange(
231
+ req,
232
+ connectorId,
233
+ deps,
234
+ body,
235
+ control.signal,
236
+ );
237
+ if (out.kind === 'missing') {
238
+ answer(res, 404, { error: 'no such connector' });
239
+ return;
240
+ }
241
+ if (out.kind === 'signin') {
242
+ signinAnswer(res, connectorId);
243
+ return;
244
+ }
245
+ if (out.upstream.status >= 300 && out.upstream.status < 400) {
246
+ await out.upstream.body?.cancel();
247
+ answer(res, 502, { error: 'the connector redirected; metro does not follow' });
248
+ return;
249
+ }
250
+ await pipe(res, out.upstream);
251
+ }
252
+
253
+ function dispatch(
254
+ req: IncomingMessage,
255
+ res: ServerResponse,
256
+ connectorId: string,
257
+ deps: RelayApiDeps,
258
+ ): void {
259
+ const who = deps.identify(req);
260
+ if (who === null) {
261
+ answer(res, 401, { error: 'unauthorized' });
262
+ return;
263
+ }
264
+ relayExchange(req, res, connectorId, deps)
265
+ .catch((err: unknown) => {
266
+ if (err instanceof ApiError) {
267
+ answer(res, err.status, { error: err.message });
268
+ } else {
269
+ log.warn({ err: errMsg(err), connector: connectorId }, 'relay: failed');
270
+ answer(res, 502, { error: 'metro could not reach the connector' });
271
+ if (!res.writableEnded) res.end();
272
+ }
273
+ closeIfBodyUnread(req, res);
274
+ });
275
+ }
276
+
277
+ export function handleRelayRequest(
278
+ req: IncomingMessage,
279
+ res: ServerResponse,
280
+ deps: RelayApiDeps,
281
+ ): boolean {
282
+ const path = (req.url ?? '').split('?')[0] ?? '';
283
+ if (path !== '/relay' && !path.startsWith('/relay/')) return false;
284
+ const match = ID_PATH_RE.exec(path);
285
+ if (match?.[1] === undefined) {
286
+ answer(res, 404, { error: 'no such connector' });
287
+ return true;
288
+ }
289
+ if (!METHODS.has(req.method ?? '')) {
290
+ answer(res, 405, { error: 'method not allowed' });
291
+ return true;
292
+ }
293
+ dispatch(req, res, match[1], deps);
294
+ return true;
295
+ }
@@ -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,82 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { log } from './log.js';
5
+ import { isRecord } from './is-record.js';
6
+ import type { StationName } from '../db/stations.js';
7
+ import { knownAccounts } from '../db/agent-map.js';
8
+
9
+ const INSTALL_TIMEOUT_MS = 15 * 60_000;
10
+
11
+ export interface RuntimeManifest {
12
+ core: Record<string, string>;
13
+ stations: Record<string, Record<string, string>>;
14
+ }
15
+
16
+ export interface RuntimeStore {
17
+ dir: string;
18
+ manifest: RuntimeManifest;
19
+ }
20
+
21
+ const ranges = (raw: unknown): Record<string, string> =>
22
+ isRecord(raw)
23
+ ? Object.fromEntries(Object.entries(raw).filter((e): e is [string, string] => typeof e[1] === 'string'))
24
+ : {};
25
+
26
+ export function readManifest(path: string): RuntimeManifest {
27
+ const raw: unknown = JSON.parse(readFileSync(path, 'utf8'));
28
+ if (!isRecord(raw)) throw new Error(`${path} is not a runtime manifest`);
29
+ const stations = isRecord(raw.stations) ? raw.stations : {};
30
+ return {
31
+ core: ranges(raw.core),
32
+ stations: Object.fromEntries(Object.entries(stations).map(([name, deps]) => [name, ranges(deps)])),
33
+ };
34
+ }
35
+
36
+ export function runtimeStore(): RuntimeStore | null {
37
+ const dir = process.env.METRO_RUNTIME_STORE?.trim() ?? '';
38
+ const manifest = process.env.METRO_RUNTIME_MANIFEST?.trim() ?? '';
39
+ if (dir === '' || manifest === '') return null;
40
+ return { dir, manifest: readManifest(manifest) };
41
+ }
42
+
43
+ export function dependenciesFor(manifest: RuntimeManifest, stations: Iterable<string>): Record<string, string> {
44
+ const out: Record<string, string> = { ...manifest.core };
45
+ for (const station of new Set(stations)) Object.assign(out, manifest.stations[station] ?? {});
46
+ return Object.fromEntries(Object.entries(out).sort(([a], [b]) => a.localeCompare(b)));
47
+ }
48
+
49
+ const packageText = (deps: Record<string, string>): string =>
50
+ `${JSON.stringify({ name: 'metro-runtime', private: true, dependencies: deps }, null, 2)}\n`;
51
+
52
+ function current(dir: string): string | null {
53
+ try {
54
+ return readFileSync(join(dir, 'package.json'), 'utf8');
55
+ } catch {
56
+ return null;
57
+ }
58
+ }
59
+
60
+ export function installRuntime(store: RuntimeStore, stations: Iterable<string>): boolean {
61
+ const wanted = packageText(dependenciesFor(store.manifest, stations));
62
+ const installed = existsSync(join(store.dir, 'node_modules', '.metro-installed'));
63
+ if (installed && current(store.dir) === wanted) return false;
64
+ mkdirSync(store.dir, { recursive: true });
65
+ writeFileSync(join(store.dir, 'package.json'), wanted);
66
+ log.info({ dir: store.dir }, 'runtime: installing the channel SDKs this machine needs');
67
+ const run = spawnSync('bun', ['install', '--no-summary', '--no-progress'], {
68
+ cwd: store.dir,
69
+ stdio: ['ignore', 'inherit', 'inherit'],
70
+ timeout: INSTALL_TIMEOUT_MS,
71
+ });
72
+ if (run.error !== undefined || run.status !== 0)
73
+ throw new Error(`bun install failed in ${store.dir}: ${run.error?.message ?? `exit ${String(run.status)}`}`);
74
+ writeFileSync(join(store.dir, 'node_modules', '.metro-installed'), `${new Date().toISOString()}\n`);
75
+ return true;
76
+ }
77
+
78
+ export function ensureStationDeps(station: StationName): void {
79
+ const store = runtimeStore();
80
+ if (store === null) return;
81
+ installRuntime(store, [...knownAccounts().map((a) => a.station), station]);
82
+ }
@@ -0,0 +1,57 @@
1
+ import {
2
+ chmodSync,
3
+ existsSync,
4
+ mkdirSync,
5
+ readFileSync,
6
+ renameSync,
7
+ statSync,
8
+ writeFileSync,
9
+ } from 'node:fs';
10
+ import { dirname } from 'node:path';
11
+ import { errMsg, log } from './log.js';
12
+
13
+ export function ensureSecureDir(dir: string): void {
14
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
15
+ try {
16
+ chmodSync(dir, 0o700);
17
+ } catch {
18
+ }
19
+ }
20
+
21
+ export function chmodIfExists(path: string, mode = 0o600): void {
22
+ if (!existsSync(path)) return;
23
+ try {
24
+ if ((statSync(path).mode & 0o777) !== mode) chmodSync(path, mode);
25
+ } catch {
26
+ }
27
+ }
28
+
29
+ export function writeSecure(path: string, data: string): void {
30
+ ensureSecureDir(dirname(path));
31
+ const tmp = `${path}.tmp-${process.pid}`;
32
+ writeFileSync(tmp, data, { mode: 0o600 });
33
+ try {
34
+ chmodSync(tmp, 0o600);
35
+ } catch {
36
+ }
37
+ renameSync(tmp, path);
38
+ chmodIfExists(path, 0o600);
39
+ }
40
+
41
+ export function readJson<T>(
42
+ path: string,
43
+ fallback: T,
44
+ opts?: { warn?: string },
45
+ ): T {
46
+ if (!existsSync(path)) return fallback;
47
+ try {
48
+ return JSON.parse(readFileSync(path, 'utf8')) as T;
49
+ } catch (err) {
50
+ if (opts?.warn) log.warn({ err: errMsg(err), path }, opts.warn);
51
+ return fallback;
52
+ }
53
+ }
54
+
55
+ export function writeJson(path: string, value: unknown): void {
56
+ writeFileSync(path, JSON.stringify(value, null, 2));
57
+ }
@@ -0,0 +1,21 @@
1
+ export interface ServerEntry {
2
+ id: string;
3
+ host: string;
4
+ name: string | null;
5
+ addedAt: string;
6
+ }
7
+
8
+ const HOST_RE = /^[a-z0-9][a-z0-9.-]*(?::\d{1,5})?$/;
9
+ const HOST_MAX = 253;
10
+ const NAME_MAX = 40;
11
+
12
+ export function parseServerHost(raw: string): string | null {
13
+ const host = raw.trim().toLowerCase();
14
+ return host.length <= HOST_MAX && HOST_RE.test(host) ? host : null;
15
+ }
16
+
17
+ export function parseServerName(raw: unknown): string | null {
18
+ if (typeof raw !== 'string') return null;
19
+ const name = raw.replace(/[\p{Cc}]/gu, '').trim().slice(0, NAME_MAX);
20
+ return name === '' ? null : name;
21
+ }
@@ -0,0 +1,80 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { log } from './log.js';
3
+ import { apiFailure, cors, readJsonBody, sendJson } from './api-http.js';
4
+ import { signedIdentity } from './signed-identity.js';
5
+ import type { ServerEntry } from './server-types.js';
6
+ import { parseId } from '../db/ids.js';
7
+
8
+ const PREFIX = '/api/servers';
9
+
10
+ export interface ServersApiDeps {
11
+ list: (subject: string) => Promise<ServerEntry[]>;
12
+ add: (subject: string, body: unknown) => Promise<ServerEntry>;
13
+ rename: (subject: string, id: string, body: unknown) => Promise<ServerEntry>;
14
+ remove: (subject: string, id: string) => Promise<{ id: string; host: string }>;
15
+ }
16
+
17
+ type Target = { kind: 'index' } | { kind: 'server'; id: string } | { kind: 'unknown' } | null;
18
+
19
+ function target(path: string): Target {
20
+ if (path === PREFIX || path === `${PREFIX}/`) return { kind: 'index' };
21
+ if (!path.startsWith(`${PREFIX}/`)) return null;
22
+ const segments = path.slice(PREFIX.length + 1).split('/').filter(Boolean);
23
+ const id = parseId(segments[0] ?? '');
24
+ return id === null || segments.length !== 1 ? { kind: 'unknown' } : { kind: 'server', id };
25
+ }
26
+
27
+ const ALLOWED: Record<'index' | 'server', string[]> = { index: ['GET', 'POST'], server: ['PUT', 'DELETE'] };
28
+
29
+ async function answer(
30
+ req: IncomingMessage,
31
+ deps: ServersApiDeps,
32
+ owner: string,
33
+ tgt: { kind: 'index' } | { kind: 'server'; id: string },
34
+ ): Promise<unknown> {
35
+ if (tgt.kind === 'index')
36
+ return req.method === 'GET' ? { servers: await deps.list(owner) } : deps.add(owner, await readJsonBody(req));
37
+ return req.method === 'DELETE' ? deps.remove(owner, tgt.id) : deps.rename(owner, tgt.id, await readJsonBody(req));
38
+ }
39
+
40
+ async function route(
41
+ req: IncomingMessage,
42
+ res: ServerResponse,
43
+ deps: ServersApiDeps,
44
+ tgt: { kind: 'index' } | { kind: 'server'; id: string },
45
+ ): Promise<void> {
46
+ try {
47
+ const owner = await signedIdentity(req);
48
+ if (owner === null) {
49
+ sendJson(req, res, 401, { error: 'unauthorized' });
50
+ return;
51
+ }
52
+ const body = await answer(req, deps, owner, tgt);
53
+ if (req.method !== 'GET') log.info({ method: req.method, target: tgt }, 'servers-api: list changed');
54
+ sendJson(req, res, 200, body);
55
+ } catch (err) {
56
+ apiFailure(req, res, err, 'servers-api');
57
+ }
58
+ }
59
+
60
+ export function handleServersApiRequest(req: IncomingMessage, res: ServerResponse, deps: ServersApiDeps): boolean {
61
+ const path = (req.url ?? '').split('?')[0] ?? '';
62
+ const tgt = target(path);
63
+ if (tgt === null) return false;
64
+ if (req.method === 'OPTIONS') {
65
+ res.writeHead(204, cors(req)).end();
66
+ return true;
67
+ }
68
+ if (tgt.kind === 'unknown') {
69
+ sendJson(req, res, 404, { error: 'no such server' });
70
+ return true;
71
+ }
72
+ if (!ALLOWED[tgt.kind].includes(req.method ?? '')) {
73
+ sendJson(req, res, 405, { error: 'method not allowed' });
74
+ return true;
75
+ }
76
+ route(req, res, deps, tgt).catch((err: unknown) => {
77
+ apiFailure(req, res, err, 'servers-api');
78
+ });
79
+ return true;
80
+ }
@@ -0,0 +1,29 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { apiFailure, apiSession, cors, sendJson } from './api-http.js';
3
+
4
+ const PATH = '/api/session';
5
+
6
+ export function handleSessionApiRequest(
7
+ req: IncomingMessage,
8
+ res: ServerResponse,
9
+ ): boolean {
10
+ const path = (req.url ?? '').split('?')[0] ?? '';
11
+ if (path !== PATH) return false;
12
+ if (req.method === 'OPTIONS') {
13
+ res.writeHead(204, cors(req)).end();
14
+ return true;
15
+ }
16
+ if (req.method !== 'GET') {
17
+ sendJson(req, res, 405, { error: 'method not allowed' });
18
+ return true;
19
+ }
20
+ apiSession(req)
21
+ .then((session) => {
22
+ if (!session) sendJson(req, res, 401, { error: 'unauthorized' });
23
+ else sendJson(req, res, 200, { subject: session.subject });
24
+ })
25
+ .catch((err: unknown) => {
26
+ apiFailure(req, res, err, 'session');
27
+ });
28
+ return true;
29
+ }
@@ -0,0 +1,66 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { handleSessionApiRequest } from './session-api.js';
3
+ import { handleAgentApiRequest, type AgentApiDeps } from './agent-api.js';
4
+ import {
5
+ handleConnectorApiRequest,
6
+ type ConnectorApiDeps,
7
+ } from './connector-api.js';
8
+ import type { RelayApiDeps } from './relay.js';
9
+ import type { IdentityRouteDeps } from './identity-routes.js';
10
+ import { handleLocalCliRequest, type LocalCliDeps } from './local-cli-api.js';
11
+ import { handleClaudeRequest, type ClaudeApiDeps } from './claude-api.js';
12
+ import type { ModeInfo } from './mode-api.js';
13
+ import { handleVaultApiRequest, type VaultApiDeps } from './vault-api.js';
14
+ import { handleServersApiRequest, type ServersApiDeps } from './servers-api.js';
15
+ import { handleBundleRequest, type BundleApiDeps } from './bundle-api.js';
16
+ import { handleUpdateRequest, type UpdateApiDeps } from './update-api.js';
17
+ import { handleControlRequest, type ControlApiDeps } from './control-api.js';
18
+ import { handleMachineRequest, type MachineApiDeps } from './machine-api.js';
19
+ import { handleModelRequest, type ModelApiDeps } from './model-api.js';
20
+ import type { GatewayDeps } from '../gateway/gateway.js';
21
+ import { handleTerminalRequest, type TerminalApiDeps } from './terminal-api.js';
22
+
23
+ export interface SessionApis {
24
+ agentApi?: AgentApiDeps;
25
+ bundleApi?: BundleApiDeps;
26
+ vaultApi?: VaultApiDeps;
27
+ serversApi?: ServersApiDeps;
28
+ updateApi?: UpdateApiDeps;
29
+ controlApi?: ControlApiDeps;
30
+ machineApi?: MachineApiDeps;
31
+ modelApi?: ModelApiDeps;
32
+ gateway?: GatewayDeps;
33
+ terminalApi?: TerminalApiDeps;
34
+ localCli?: LocalCliDeps;
35
+ claudeApi?: ClaudeApiDeps;
36
+ connectorApi?: ConnectorApiDeps;
37
+ relayApi?: RelayApiDeps;
38
+ identity?: IdentityRouteDeps;
39
+ mode?: () => ModeInfo;
40
+ }
41
+
42
+ const when = <T>(dep: T | undefined, run: (dep: T) => boolean): (() => boolean)[] =>
43
+ dep === undefined ? [] : [() => run(dep)];
44
+
45
+ export function handleSessionApis(
46
+ req: IncomingMessage,
47
+ res: ServerResponse,
48
+ apis: SessionApis,
49
+ ): boolean {
50
+ const routes: (() => boolean)[] = [
51
+ () => handleSessionApiRequest(req, res),
52
+ ...when(apis.localCli, (d) => handleLocalCliRequest(req, res, d)),
53
+ ...when(apis.connectorApi, (d) => handleConnectorApiRequest(req, res, d)),
54
+ ...when(apis.bundleApi, (d) => handleBundleRequest(req, res, d)),
55
+ ...when(apis.vaultApi, (d) => handleVaultApiRequest(req, res, d)),
56
+ ...when(apis.serversApi, (d) => handleServersApiRequest(req, res, d)),
57
+ ...when(apis.updateApi, (d) => handleUpdateRequest(req, res, d)),
58
+ ...when(apis.controlApi, (d) => handleControlRequest(req, res, d)),
59
+ ...when(apis.machineApi, (d) => handleMachineRequest(req, res, d)),
60
+ ...when(apis.modelApi, (d) => handleModelRequest(req, res, d)),
61
+ ...when(apis.terminalApi, (d) => handleTerminalRequest(req, res, d)),
62
+ ...when(apis.claudeApi, (d) => handleClaudeRequest(req, res, d)),
63
+ ...when(apis.agentApi, (d) => handleAgentApiRequest(req, res, d)),
64
+ ];
65
+ return routes.some((run) => run());
66
+ }
@@ -0,0 +1,36 @@
1
+ import type { IncomingMessage } from 'node:http';
2
+ import { verifyMessage } from 'viem';
3
+ import { normalizeAddress } from '../db/address.js';
4
+
5
+ export const AUTH_SCHEME = 'Metro';
6
+ const SKEW_MS = 5 * 60_000;
7
+ const SIGNATURE_RE = /^0x[0-9a-fA-F]{130}$/;
8
+
9
+ export const identityChallenge = (method: string, path: string, at: number): string =>
10
+ `metro-auth\n${method} ${path}\n${String(at)}`;
11
+
12
+ export interface SignedHeader {
13
+ address: string;
14
+ at: number;
15
+ signature: `0x${string}`;
16
+ }
17
+
18
+ export function parseIdentityHeader(header: string): SignedHeader | null {
19
+ const [scheme, rawAddress, rawAt, signature] = header.trim().split(/\s+/);
20
+ if (scheme !== AUTH_SCHEME || rawAddress === undefined || rawAt === undefined || signature === undefined) return null;
21
+ const address = normalizeAddress(rawAddress);
22
+ const at = Number(rawAt);
23
+ if (address === null || !Number.isFinite(at) || !SIGNATURE_RE.test(signature)) return null;
24
+ return { address, at, signature: signature as `0x${string}` };
25
+ }
26
+
27
+ export async function signedIdentity(req: IncomingMessage, now = Date.now()): Promise<string | null> {
28
+ const proof = parseIdentityHeader(req.headers.authorization ?? '');
29
+ if (proof === null || Math.abs(now - proof.at) > SKEW_MS) return null;
30
+ const path = (req.url ?? '').split('?')[0] ?? '';
31
+ const message = identityChallenge(req.method ?? '', path, proof.at);
32
+ const ok = await verifyMessage({ address: proof.address as `0x${string}`, message, signature: proof.signature }).catch(
33
+ () => false,
34
+ );
35
+ return ok ? proof.address : null;
36
+ }