@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,187 @@
1
+ import { ApiError } from './api-error.js';
2
+ import { errMsg, log } from './log.js';
3
+ import { AttachSessions } from './attach-session.js';
4
+ import type { AgentApiDeps } from './agent-api.js';
5
+ import { ATTACHABLE, type AccountApiDeps } from './account-api.js';
6
+ import type { SessionApis } from './session-apis.js';
7
+ import type { ModeInfo } from './mode-api.js';
8
+ import { loadedAgentOf, type AgentBundle, type BundleApiDeps } from './bundle-api.js';
9
+ import { METRO_VERSION } from './version.js';
10
+ import type { ConnectorApiDeps } from './connector-api.js';
11
+ import { allowLocalConnectors } from './connector-url.js';
12
+ import { keyIdentity, type LocalCliDeps } from './local-cli-api.js';
13
+ import type { RelayApiDeps } from './relay.js';
14
+ import {
15
+ readLocalConnectors,
16
+ localImportConnectors,
17
+ localConnectorNamesByIds,
18
+ localCreateConnector,
19
+ localCreatePendingConnector,
20
+ localDeleteConnector,
21
+ localDisconnectConnector,
22
+ localGetConnector,
23
+ localListConnectors,
24
+ localReconnectConnector,
25
+ localRelayTarget,
26
+ localRenameConnector,
27
+ localVerifyConnector,
28
+ } from '../db/local-connectors.js';
29
+ import {
30
+ assertLocalOwner,
31
+ LOCAL_PROJECT_ID,
32
+ localAttachAccount,
33
+ localCreateAgent,
34
+ localDeleteAgent,
35
+ localDetachAccount,
36
+ localImportAgent,
37
+ localListAgents,
38
+ localOwnedAgentOrThrow,
39
+ localOwner,
40
+ localResetAgentKey,
41
+ readLocalAgentFile,
42
+ } from '../db/file-admin.js';
43
+ import { listAgentFiles, readAgentFile } from '../db/file-source.js';
44
+ import { readModelConfig } from '../gateway/model-config.js';
45
+ import type { StationName } from '../db/stations.js';
46
+
47
+ export interface LocalModeDeps {
48
+ syncStations: (station: StationName) => Promise<void>;
49
+ restart: () => void;
50
+ stop: () => void;
51
+ closeAgentSession: (id: string) => Promise<boolean>;
52
+ gatherAccounts: AgentApiDeps['gatherAccounts'];
53
+ capabilities: AgentApiDeps['capabilities'];
54
+ liveness: AgentApiDeps['liveness'];
55
+ prepareAccount: AccountApiDeps['prepareAccount'];
56
+ }
57
+
58
+ function attachSessions(deps: LocalModeDeps): AttachSessions {
59
+ return new AttachSessions({
60
+ authorize: async (owner) => {
61
+ await localOwnedAgentOrThrow(owner.subject, owner.agentId);
62
+ },
63
+ complete: async (owner, station, config) => {
64
+ const ref = await localAttachAccount(owner.subject, owner.agentId, station, config);
65
+ const activated = await deps.syncStations(station).then(
66
+ () => true,
67
+ (err: unknown) => {
68
+ log.warn(
69
+ { station, err: errMsg(err) },
70
+ 'attach-session: station reload failed, the change lands at the next boot',
71
+ );
72
+ return false;
73
+ },
74
+ );
75
+ return { accountId: ref.accountId, activated };
76
+ },
77
+ });
78
+ }
79
+
80
+ function agentApi(deps: LocalModeDeps): AgentApiDeps {
81
+ return {
82
+ attachSessions: attachSessions(deps),
83
+ listAgents: localListAgents,
84
+ createAgent: localCreateAgent,
85
+ deleteAgent: localDeleteAgent,
86
+ resetKey: async (subject, id) => {
87
+ const reset = await localResetAgentKey(subject, id);
88
+ const closed = await deps.closeAgentSession(id);
89
+ log.info({ agent: reset.name, id, sessionClosed: closed }, 'local: key rotated');
90
+ return reset;
91
+ },
92
+ gatherAccounts: deps.gatherAccounts,
93
+ capabilities: deps.capabilities,
94
+ attachable: ATTACHABLE.filter((s) => s !== 'webhook'),
95
+ liveness: deps.liveness,
96
+ connectorIds: connectorIdsOfLocalAgents,
97
+ prepareAccount: deps.prepareAccount,
98
+ attachAccount: localAttachAccount,
99
+ detachAccount: localDetachAccount,
100
+ syncStations: deps.syncStations,
101
+ };
102
+ }
103
+
104
+ const allConnectorIds = (): string[] => readLocalConnectors().map((c) => c.id);
105
+
106
+ function connectorIdsOfLocalAgents(ids: string[]): Promise<Map<string, string[]>> {
107
+ const all = allConnectorIds();
108
+ return Promise.resolve(new Map(ids.map((id) => [id, all] as const)));
109
+ }
110
+
111
+ const connectorApi: ConnectorApiDeps = {
112
+ listConnectors: localListConnectors,
113
+ createConnector: localCreateConnector,
114
+ verifyConnector: localVerifyConnector,
115
+ disconnectConnector: localDisconnectConnector,
116
+ renameConnector: localRenameConnector,
117
+ deleteConnector: localDeleteConnector,
118
+ createPendingConnector: localCreatePendingConnector,
119
+ reconnectConnector: localReconnectConnector,
120
+ getConnector: localGetConnector,
121
+ };
122
+
123
+ const relayApi: RelayApiDeps = {
124
+ target: (connectorId, force) => localRelayTarget(connectorId, force),
125
+ identify: keyIdentity,
126
+ };
127
+
128
+ function agentNameOf(agentId: string): string | null {
129
+ return listAgentFiles()
130
+ .map((path) => readAgentFile(path))
131
+ .find((file) => file.id === agentId)?.name ?? null;
132
+ }
133
+
134
+ const localCli: LocalCliDeps = {
135
+ agentName: agentNameOf,
136
+ connectorEntries: () => localConnectorNamesByIds(allConnectorIds()),
137
+ };
138
+
139
+ function bundleApi(deps: LocalModeDeps): BundleApiDeps {
140
+ return {
141
+ bundle: async (subject, agentId) => {
142
+ const { agent } = await localOwnedAgentOrThrow(subject, agentId);
143
+ const file = readLocalAgentFile(agentId);
144
+ const bundle: AgentBundle = {
145
+ version: 1,
146
+ agent: { id: file.id, name: file.name, key: file.key ?? '', stations: file.stations },
147
+ connectors: readLocalConnectors().map((c) => ({ id: c.id, name: c.name, url: c.url, transport: c.transport, config: { ...c.config } })),
148
+ };
149
+ if (bundle.agent.key === '') throw new ApiError(`agent '${agent.name}' has no key to bundle`, 400);
150
+ return bundle;
151
+ },
152
+ restore: async (subject, bundle) => {
153
+ assertLocalOwner(subject);
154
+ const made = await localImportAgent(subject, loadedAgentOf(bundle));
155
+ const connectors = localImportConnectors(bundle.connectors);
156
+ for (const station of new Set(bundle.agent.stations.map((a) => a.station)))
157
+ await deps.syncStations(station).catch((err: unknown) => {
158
+ log.warn({ station, err: errMsg(err) }, 'restore: station reload failed, the change lands at the next boot');
159
+ });
160
+ return { id: made.id, name: made.name, stations: made.stations, connectors };
161
+ },
162
+ };
163
+ }
164
+
165
+ function localModeInfo(): ModeInfo {
166
+ return { mode: 'local', owner: localOwner(), project: LOCAL_PROJECT_ID, version: METRO_VERSION };
167
+ }
168
+
169
+ export function localSessionApis(deps: LocalModeDeps): SessionApis {
170
+ allowLocalConnectors(true);
171
+ return {
172
+ agentApi: agentApi(deps),
173
+ bundleApi: bundleApi(deps),
174
+ connectorApi,
175
+ relayApi,
176
+ localCli,
177
+ claudeApi: { authorize: (subject) => { assertLocalOwner(subject); } },
178
+ updateApi: { authorize: (subject) => { assertLocalOwner(subject); }, restart: deps.restart },
179
+ controlApi: { authorize: (subject) => { assertLocalOwner(subject); }, restart: deps.restart, stop: deps.stop },
180
+ machineApi: { authorize: (subject) => { assertLocalOwner(subject); } },
181
+ modelApi: { authorize: (subject) => { assertLocalOwner(subject); } },
182
+ gateway: { config: readModelConfig },
183
+ terminalApi: { authorize: (subject) => { assertLocalOwner(subject); } },
184
+ identity: { owner: localOwner },
185
+ mode: localModeInfo,
186
+ };
187
+ }
@@ -0,0 +1,14 @@
1
+ import { localOwner, setLocalOwner } from '../db/file-admin.js';
2
+ import { log } from './log.js';
3
+
4
+ export function applyLocalOwner(): string | null {
5
+ const wanted = process.env.METRO_OWNER?.trim() ?? '';
6
+ if (wanted !== '') {
7
+ const address = setLocalOwner(wanted);
8
+ log.info({ owner: address }, 'local daemon: owner set from METRO_OWNER');
9
+ return address;
10
+ }
11
+ const stored = localOwner();
12
+ if (stored === null) log.warn('local daemon: no owner set, so no wallet can sign in; restart with metro serve --owner <address>');
13
+ return stored;
14
+ }
@@ -0,0 +1,42 @@
1
+ import { writeSync } from 'node:fs';
2
+ import pino from 'pino';
3
+ import pinoPretty from 'pino-pretty';
4
+
5
+ const stream = process.stderr.isTTY
6
+ ? pinoPretty({
7
+ colorize: true,
8
+ translateTime: 'HH:MM:ss',
9
+ ignore: 'pid,hostname,name',
10
+ destination: 2,
11
+ })
12
+ : pino.destination(2);
13
+
14
+ export const log = pino(
15
+ { base: { name: 'metro' }, level: process.env.METRO_LOG_LEVEL ?? 'info' },
16
+ stream,
17
+ );
18
+
19
+ export const logFatalSync = (
20
+ fields: Record<string, unknown>,
21
+ msg: string,
22
+ ): void => {
23
+ const line = JSON.stringify({
24
+ level: 60,
25
+ time: Date.now(),
26
+ name: 'metro',
27
+ ...fields,
28
+ msg,
29
+ });
30
+ try {
31
+ writeSync(2, `${line}\n`);
32
+ } catch {
33
+ return;
34
+ }
35
+ };
36
+
37
+ export const errMsg = (err: unknown): string => {
38
+ if (err instanceof Error) return err.message;
39
+ if (err && typeof err === 'object' && 'message' in err)
40
+ return String(err.message);
41
+ return String(err);
42
+ };
@@ -0,0 +1,61 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { hostname } from 'node:os';
3
+ import { ApiError } from './api-error.js';
4
+ import { apiFailure, apiSession, cors, sendJson } from './api-http.js';
5
+ import { publicBaseUrl } from './attach-serve.js';
6
+ import { claudeDir } from './claude-files.js';
7
+ import { webhookPort } from './tunnel.js';
8
+ import { METRO_VERSION } from './version.js';
9
+ import { agentsDir } from '../db/file-source.js';
10
+ import { localOwner } from '../db/file-admin.js';
11
+
12
+ const PATH = '/api/server';
13
+
14
+ export interface MachineApiDeps {
15
+ authorize: (subject: string) => void;
16
+ startedAt?: string;
17
+ }
18
+
19
+ const bootedAt = new Date(Date.now() - process.uptime() * 1000).toISOString();
20
+
21
+ export function machineInfo(startedAt = bootedAt): Record<string, unknown> {
22
+ const store = process.env.METRO_RUNTIME_STORE?.trim() ?? '';
23
+ return {
24
+ version: METRO_VERSION,
25
+ owner: localOwner(),
26
+ hostname: hostname(),
27
+ platform: process.platform,
28
+ arch: process.arch,
29
+ port: webhookPort(),
30
+ publicUrl: publicBaseUrl(),
31
+ uptimeSeconds: Math.round(process.uptime()),
32
+ startedAt,
33
+ bun: process.versions.bun ?? null,
34
+ agentsDir: agentsDir(),
35
+ claudeDir: claudeDir(),
36
+ runtimeStore: store === '' ? null : store,
37
+ };
38
+ }
39
+
40
+ export function handleMachineRequest(req: IncomingMessage, res: ServerResponse, deps: MachineApiDeps): boolean {
41
+ const path = (req.url ?? '').split('?')[0] ?? '';
42
+ if (path !== PATH) return false;
43
+ if (req.method === 'OPTIONS') {
44
+ res.writeHead(204, cors(req)).end();
45
+ return true;
46
+ }
47
+ if (req.method !== 'GET') {
48
+ sendJson(req, res, 405, { error: 'method not allowed' });
49
+ return true;
50
+ }
51
+ apiSession(req)
52
+ .then((session) => {
53
+ if (!session) throw new ApiError('unauthorized', 401);
54
+ deps.authorize(session.subject);
55
+ sendJson(req, res, 200, machineInfo(deps.startedAt));
56
+ })
57
+ .catch((err: unknown) => {
58
+ apiFailure(req, res, err, 'machine-api');
59
+ });
60
+ return true;
61
+ }
@@ -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' | 'linked' | '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,169 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { ApiError } from './api-error.js';
3
+ import { apiFailure, apiSession, cors, readJsonBody, sendJson } from './api-http.js';
4
+ import { isRecord } from './is-record.js';
5
+ import { log } from './log.js';
6
+ import { beginLogin, CodexAuthError, finishLogin, readCodexCliAuth } from '../gateway/codex-auth.js';
7
+ import { beginDeviceLogin, pollDeviceLogin } from '../gateway/codex-device.js';
8
+ import { codexModels, currentTokens, freshCodexState } from '../gateway/codex.js';
9
+ import type { CodexTokens } from '../gateway/codex-auth.js';
10
+ import { GatewayError } from '../gateway/forward.js';
11
+ import {
12
+ applyModelUpdate,
13
+ ModelConfigError,
14
+ publicModelConfig,
15
+ readModelConfig,
16
+ setCodexAuth,
17
+ writeModelConfig,
18
+ type ModelConfig,
19
+ } from '../gateway/model-config.js';
20
+
21
+ const PATH = '/api/model';
22
+ const CODEX = '/api/model/codex/';
23
+ const BODY_MAX = 16 * 1024;
24
+ const DEVICE_PREFIX = 'device/';
25
+ const DEVICE_ID_RE = /^[A-Za-z0-9_-]{16,64}$/;
26
+
27
+ export interface ModelApiDeps {
28
+ authorize: (subject: string) => void;
29
+ read?: () => ModelConfig;
30
+ write?: (cfg: ModelConfig) => void;
31
+ issuer?: string;
32
+ fetchImpl?: typeof fetch;
33
+ codexHome?: string;
34
+ codexBase?: string;
35
+ }
36
+
37
+ interface Store {
38
+ read: () => ModelConfig;
39
+ write: (cfg: ModelConfig) => void;
40
+ }
41
+
42
+ type Handler = (req: IncomingMessage, deps: ModelApiDeps, store: Store) => Promise<unknown>;
43
+
44
+ interface Route {
45
+ method: 'GET' | 'POST';
46
+ run: Handler;
47
+ }
48
+
49
+ function asApiError(err: unknown): never {
50
+ if (err instanceof ModelConfigError || err instanceof CodexAuthError) throw new ApiError(err.message, 400);
51
+ if (err instanceof GatewayError) throw new ApiError(err.message, 502);
52
+ throw err;
53
+ }
54
+
55
+ async function update(req: IncomingMessage, store: Store): Promise<unknown> {
56
+ const patch = await readJsonBody(req, BODY_MAX);
57
+ let next: ModelConfig;
58
+ try {
59
+ next = applyModelUpdate(store.read(), patch);
60
+ } catch (err) {
61
+ asApiError(err);
62
+ }
63
+ store.write(next);
64
+ log.info({ provider: next.provider }, 'model-api: route updated');
65
+ return publicModelConfig(next);
66
+ }
67
+
68
+ function saveCodex(store: Store, cfg: ModelConfig, note: string): unknown {
69
+ store.write(cfg);
70
+ log.info({ signedIn: cfg.codex.auth !== null, plan: cfg.codex.auth?.plan ?? null }, note);
71
+ return publicModelConfig(cfg);
72
+ }
73
+
74
+ const modelApiState = freshCodexState();
75
+
76
+ async function pollDevice(id: string, deps: ModelApiDeps, store: Store): Promise<unknown> {
77
+ const result = await pollDeviceLogin(id, deps.fetchImpl).catch(asApiError);
78
+ if (result.status !== 'done') return result;
79
+ return { status: 'done', settings: saveCodex(store, setCodexAuth(store.read(), result.tokens), 'model-api: Codex connected by device code') };
80
+ }
81
+
82
+ const CODEX_ROUTES: Record<string, Route> = {
83
+ device: {
84
+ method: 'POST',
85
+ run: async (_req, deps) => {
86
+ const login = await beginDeviceLogin(deps.issuer, deps.fetchImpl).catch(asApiError);
87
+ return { id: login.id, user_code: login.userCode, verify_url: login.verifyUrl, interval: login.interval };
88
+ },
89
+ },
90
+ login: {
91
+ method: 'POST',
92
+ run: (_req, deps) => Promise.resolve({ url: beginLogin(deps.issuer).url }),
93
+ },
94
+ callback: {
95
+ method: 'POST',
96
+ run: async (req, deps, store) => {
97
+ const body = await readJsonBody(req, BODY_MAX);
98
+ const raw = isRecord(body) && typeof body.url === 'string' ? body.url : '';
99
+ const tokens = await finishLogin(raw, deps.issuer, deps.fetchImpl).catch(asApiError);
100
+ return saveCodex(store, setCodexAuth(store.read(), tokens), 'model-api: Codex connected');
101
+ },
102
+ },
103
+ logout: {
104
+ method: 'POST',
105
+ run: (_req, _deps, store) => Promise.resolve(saveCodex(store, setCodexAuth(store.read(), null), 'model-api: Codex disconnected')),
106
+ },
107
+ import: {
108
+ method: 'POST',
109
+ run: (_req, deps, store) => {
110
+ let tokens;
111
+ try {
112
+ tokens = readCodexCliAuth(deps.codexHome);
113
+ } catch (err) {
114
+ asApiError(err);
115
+ }
116
+ return Promise.resolve(saveCodex(store, setCodexAuth(store.read(), tokens), 'model-api: Codex CLI login imported'));
117
+ },
118
+ },
119
+ models: {
120
+ method: 'GET',
121
+ run: async (_req, deps, store) => {
122
+ const cfg = store.read();
123
+ if (cfg.codex.auth === null) throw new ApiError('Codex is not connected: sign in with ChatGPT first', 400);
124
+ const codexDeps = { issuer: deps.issuer, fetchImpl: deps.fetchImpl, base: deps.codexBase, save: (t: CodexTokens) => { store.write(setCodexAuth(store.read(), t)); } };
125
+ const auth = await currentTokens(cfg, codexDeps, modelApiState).catch(asApiError);
126
+ const models = await codexModels(auth, codexDeps).catch(asApiError);
127
+ return { models };
128
+ },
129
+ },
130
+ };
131
+
132
+ function routeFor(path: string, method: string | undefined): Route | number {
133
+ if (path === PATH) {
134
+ if (method === 'GET') return { method: 'GET', run: (_req, _deps, store) => Promise.resolve(publicModelConfig(store.read())) };
135
+ if (method === 'PUT') return { method: 'POST', run: (req, _deps, store) => update(req, store) };
136
+ return 405;
137
+ }
138
+ const rest = path.slice(CODEX.length);
139
+ const route = CODEX_ROUTES[rest];
140
+ if (route !== undefined) return route.method === method ? route : 405;
141
+ const id = rest.startsWith(DEVICE_PREFIX) ? rest.slice(DEVICE_PREFIX.length) : '';
142
+ if (!DEVICE_ID_RE.test(id)) return 404;
143
+ return method === 'GET' ? { method: 'GET', run: (_req, deps, store) => pollDevice(id, deps, store) } : 405;
144
+ }
145
+
146
+ export function handleModelRequest(req: IncomingMessage, res: ServerResponse, deps: ModelApiDeps): boolean {
147
+ const path = (req.url ?? '').split('?')[0] ?? '';
148
+ if (path !== PATH && !path.startsWith(CODEX)) return false;
149
+ if (req.method === 'OPTIONS') {
150
+ res.writeHead(204, cors(req)).end();
151
+ return true;
152
+ }
153
+ const route = routeFor(path, req.method);
154
+ if (typeof route === 'number') {
155
+ sendJson(req, res, route, { error: route === 404 ? 'not found' : 'method not allowed' });
156
+ return true;
157
+ }
158
+ const store: Store = { read: deps.read ?? readModelConfig, write: deps.write ?? writeModelConfig };
159
+ apiSession(req)
160
+ .then(async (session) => {
161
+ if (!session) throw new ApiError('unauthorized', 401);
162
+ deps.authorize(session.subject);
163
+ sendJson(req, res, 200, await route.run(req, deps, store));
164
+ })
165
+ .catch((err: unknown) => {
166
+ apiFailure(req, res, err, 'model-api');
167
+ });
168
+ return true;
169
+ }
@@ -0,0 +1,159 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import {
3
+ ConnectorVerifyError,
4
+ type OAuthTokens,
5
+ } from './connector-verify.js';
6
+ import type { OAuthServer } from './oauth-discovery.js';
7
+ import { isRecord } from './is-record.js';
8
+
9
+ const TOKEN_TIMEOUT_MS = 15_000;
10
+ const CLIENT_NAME = 'Metro';
11
+
12
+ export interface OAuthClient {
13
+ clientId: string;
14
+ clientSecret?: string;
15
+ }
16
+
17
+ function refused(message: string): ConnectorVerifyError {
18
+ return new ConnectorVerifyError(message, 400);
19
+ }
20
+
21
+ export function newVerifier(): string {
22
+ return randomBytes(32).toString('base64url');
23
+ }
24
+
25
+ export function challengeOf(verifier: string): string {
26
+ return createHash('sha256').update(verifier).digest('base64url');
27
+ }
28
+
29
+ export async function registerClient(
30
+ server: OAuthServer,
31
+ redirectUri: string,
32
+ ): Promise<OAuthClient> {
33
+ if (server.registrationEndpoint === null)
34
+ throw refused(
35
+ 'That server requires OAuth but does not accept client registration, so Metro cannot sign in to it.',
36
+ );
37
+ let res: Response;
38
+ try {
39
+ res = await fetch(server.registrationEndpoint, {
40
+ method: 'POST',
41
+ redirect: 'manual',
42
+ signal: AbortSignal.timeout(TOKEN_TIMEOUT_MS),
43
+ headers: { 'content-type': 'application/json', accept: 'application/json' },
44
+ body: JSON.stringify({
45
+ client_name: CLIENT_NAME,
46
+ redirect_uris: [redirectUri],
47
+ grant_types: ['authorization_code', 'refresh_token'],
48
+ response_types: ['code'],
49
+ token_endpoint_auth_method: 'none',
50
+ }),
51
+ });
52
+ } catch {
53
+ throw refused('Metro could not reach that server to register.');
54
+ }
55
+ const body: unknown = await res.json().catch(() => null);
56
+ if (!res.ok || !isRecord(body) || typeof body.client_id !== 'string')
57
+ throw refused('That server refused to register Metro as a client.');
58
+ const secret = body.client_secret;
59
+ return {
60
+ clientId: body.client_id,
61
+ ...(typeof secret === 'string' ? { clientSecret: secret } : {}),
62
+ };
63
+ }
64
+
65
+ export interface AuthorizeInput {
66
+ server: OAuthServer;
67
+ client: OAuthClient;
68
+ redirectUri: string;
69
+ state: string;
70
+ verifier: string;
71
+ resource: string;
72
+ }
73
+
74
+ export function authorizeUrl(input: AuthorizeInput): string {
75
+ const url = new URL(input.server.authorizationEndpoint);
76
+ url.searchParams.set('response_type', 'code');
77
+ url.searchParams.set('client_id', input.client.clientId);
78
+ url.searchParams.set('redirect_uri', input.redirectUri);
79
+ url.searchParams.set('state', input.state);
80
+ url.searchParams.set('code_challenge', challengeOf(input.verifier));
81
+ url.searchParams.set('code_challenge_method', 'S256');
82
+ url.searchParams.set('resource', input.resource);
83
+ return url.toString();
84
+ }
85
+
86
+ function tokensFrom(body: Record<string, unknown>): OAuthTokens {
87
+ const access = body.access_token;
88
+ if (typeof access !== 'string' || access === '')
89
+ throw refused('That server did not return an access token.');
90
+ const refresh = body.refresh_token;
91
+ const expires = body.expires_in;
92
+ return {
93
+ accessToken: access,
94
+ ...(typeof refresh === 'string' ? { refreshToken: refresh } : {}),
95
+ ...(typeof expires === 'number'
96
+ ? { expiresAt: Date.now() + expires * 1000 }
97
+ : {}),
98
+ };
99
+ }
100
+
101
+ async function postToken(
102
+ server: OAuthServer,
103
+ client: OAuthClient,
104
+ form: Record<string, string>,
105
+ ): Promise<OAuthTokens> {
106
+ const params = new URLSearchParams({
107
+ ...form,
108
+ client_id: client.clientId,
109
+ ...(client.clientSecret === undefined
110
+ ? {}
111
+ : { client_secret: client.clientSecret }),
112
+ });
113
+ let res: Response;
114
+ try {
115
+ res = await fetch(server.tokenEndpoint, {
116
+ method: 'POST',
117
+ redirect: 'manual',
118
+ signal: AbortSignal.timeout(TOKEN_TIMEOUT_MS),
119
+ headers: {
120
+ 'content-type': 'application/x-www-form-urlencoded',
121
+ accept: 'application/json',
122
+ },
123
+ body: params.toString(),
124
+ });
125
+ } catch {
126
+ throw refused('Metro could not reach that server to finish signing in.');
127
+ }
128
+ const body: unknown = await res.json().catch(() => null);
129
+ if (!res.ok || !isRecord(body))
130
+ throw refused('That server refused to complete the sign-in.');
131
+ return tokensFrom(body);
132
+ }
133
+
134
+ export function exchangeCode(
135
+ server: OAuthServer,
136
+ client: OAuthClient,
137
+ input: { code: string; redirectUri: string; verifier: string; resource: string },
138
+ ): Promise<OAuthTokens> {
139
+ return postToken(server, client, {
140
+ grant_type: 'authorization_code',
141
+ code: input.code,
142
+ redirect_uri: input.redirectUri,
143
+ code_verifier: input.verifier,
144
+ resource: input.resource,
145
+ });
146
+ }
147
+
148
+ export function refreshTokens(
149
+ server: OAuthServer,
150
+ client: OAuthClient,
151
+ refreshToken: string,
152
+ resource: string,
153
+ ): Promise<OAuthTokens> {
154
+ return postToken(server, client, {
155
+ grant_type: 'refresh_token',
156
+ refresh_token: refreshToken,
157
+ resource,
158
+ });
159
+ }