@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,274 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { errMsg, log } from '@metro-labs/core/log';
3
+ import { agentIdForKey } from '../agents/keys.js';
4
+ import {
5
+ assertBedrockReady,
6
+ bedrockBase,
7
+ bedrockCount,
8
+ bedrockMessages,
9
+ freshAdaptations,
10
+ type Adaptations,
11
+ } from './bedrock.js';
12
+ import { addBeta, anthropicHeaders, forwardedHeaders, GatewayError, parseJson, pipeResponse, readBody, sendError, watchUpstream } from './forward.js';
13
+ import { BINDING_BETA, cappedEffort, effortToApply, plannedEffort, withBlockBinding, withEffort, withThinkingFor } from './effort.js';
14
+ import { notReady, readModelConfig, resolveRoute, routeLabel, setCodexAuth, setGeminiAuth, writeModelConfig, type Connection, type ModelConfig, type Route } from './model-config.js';
15
+ import { codexMessages, sharedCodexState } from './codex.js';
16
+ import { geminiMessages, sharedGeminiState } from './gemini.js';
17
+ import { countTokens } from './subscription.js';
18
+ import type { GeminiDeps } from './gemini.js';
19
+ import type { CodexDeps } from './codex.js';
20
+ import type { GeminiTokens } from './gemini-auth.js';
21
+ import { OPENROUTER_BASE } from './openrouter.js';
22
+ import { isRecord } from '@metro-labs/core/is-record';
23
+ import { forgetServed, noteServed } from './served.js';
24
+ import { forgetUsage, noteUsageHeaders, UsageScanner } from './usage.js';
25
+ import type { CodexTokens } from './codex-auth.js';
26
+
27
+ export const GATEWAY_PREFIX = '/gateway';
28
+ export const ANTHROPIC_BASE = 'https://api.anthropic.com';
29
+ const MESSAGES = '/v1/messages';
30
+ const COUNT = '/v1/messages/count_tokens';
31
+ const MODELS = '/v1/models';
32
+ const HELLO = '/api/hello';
33
+
34
+ export interface GatewayDeps {
35
+ config: () => ModelConfig;
36
+ identify?: (key: string) => boolean;
37
+ anthropicBase?: string;
38
+ bedrockBase?: string;
39
+ openrouterBase?: string;
40
+ codex?: Partial<CodexDeps>;
41
+ gemini?: Partial<GeminiDeps>;
42
+ }
43
+
44
+ const learned: Adaptations = freshAdaptations();
45
+
46
+ export function resetGatewayState(): void {
47
+ forgetServed();
48
+ forgetUsage();
49
+ learned.fields.clear();
50
+ learned.dropBetas = false;
51
+ sharedCodexState.clear();
52
+ sharedGeminiState.clear();
53
+ }
54
+
55
+ const saveCodexTokens = (id: string, tokens: CodexTokens): void => {
56
+ writeModelConfig(setCodexAuth(readModelConfig(), id, tokens));
57
+ };
58
+
59
+ const saveGeminiTokens = (id: string, tokens: GeminiTokens): void => {
60
+ writeModelConfig(setGeminiAuth(readModelConfig(), id, tokens));
61
+ };
62
+
63
+ const keyOf = (req: IncomingMessage): string => {
64
+ const raw = req.headers['x-metro-key'];
65
+ return (Array.isArray(raw) ? raw[0] : raw)?.trim() ?? '';
66
+ };
67
+
68
+ const defaultIdentify = (key: string): boolean => key !== '' && agentIdForKey(key) !== undefined;
69
+
70
+ function modelsBody(cfg: ModelConfig): Record<string, unknown> {
71
+ const data = cfg.connections
72
+ .filter((c) => c.provider !== 'anthropic' && c.model !== '')
73
+ .map((c) => ({ id: `${c.provider}:${c.model}`, display_name: `${c.label} · ${c.model}`, description: 'Through metro' }));
74
+ return { data, has_more: false, first_id: data[0]?.id ?? null, last_id: data.at(-1)?.id ?? null };
75
+ }
76
+
77
+ const PASSTHROUGH: Connection = { id: 'passthrough', provider: 'anthropic', label: 'Claude Code login', model: '', apiKey: '', region: '', zdr: false, codex: null, gemini: null };
78
+
79
+ const passthrough = (body: Record<string, unknown>): Route => ({ connection: PASSTHROUGH, model: requestedModel(body) });
80
+
81
+ function requestedModel(body: Record<string, unknown>): string {
82
+ const model = body.model;
83
+ if (typeof model !== 'string' || model === '') throw new GatewayError(400, 'invalid_request_error', 'model is required');
84
+ return model;
85
+ }
86
+
87
+ const standsInFor = (req: IncomingMessage): boolean => {
88
+ const metroKey = req.headers['x-metro-key'];
89
+ return typeof metroKey === 'string' && metroKey !== '' && req.headers.authorization === `Bearer ${metroKey}`;
90
+ };
91
+
92
+ interface Payloads {
93
+ metro: Buffer;
94
+ asSent: Buffer | null;
95
+ bound: boolean;
96
+ }
97
+
98
+ function anthropicPayloads(raw: Buffer, sent: Record<string, unknown>, shaped: Record<string, unknown>, model: string): Payloads {
99
+ const rewrite = typeof sent.model === 'string' && sent.model !== model;
100
+ const asSent = rewrite ? Buffer.from(JSON.stringify({ ...sent, model })) : raw;
101
+ const bound = withBlockBinding(withThinkingFor(shaped, model));
102
+ if (bound === sent) return { metro: asSent, asSent: null, bound: false };
103
+ return { metro: Buffer.from(JSON.stringify(rewrite ? { ...bound, model } : bound)), asSent, bound: bound !== shaped };
104
+ }
105
+
106
+ type Send = (payload: Buffer, headers: Record<string, string>) => Promise<Response>;
107
+
108
+ async function asAnthropicWants(send: Send, base: Record<string, string>, payloads: Payloads, connection: string, model: string): Promise<Response> {
109
+ const { metro, asSent, bound } = payloads;
110
+ const upstream = await send(metro, bound ? addBeta(base, BINDING_BETA) : base);
111
+ if (upstream.status !== 400 || asSent === null) return upstream;
112
+ await upstream.body?.cancel();
113
+ log.warn({ connection, model }, 'gateway: Anthropic refused the request metro shaped, so it was sent again as Claude Code wrote it');
114
+ return send(asSent, base);
115
+ }
116
+
117
+ async function toAnthropic(
118
+ req: IncomingMessage,
119
+ res: ServerResponse,
120
+ raw: Buffer,
121
+ sent: Record<string, unknown>,
122
+ shaped: Record<string, unknown>,
123
+ route: Route,
124
+ deps: GatewayDeps,
125
+ ): Promise<void> {
126
+ const conn = route.connection;
127
+ const url = `${deps.anthropicBase ?? ANTHROPIC_BASE}${(req.url ?? '').slice(GATEWAY_PREFIX.length)}`;
128
+ const key = conn.apiKey;
129
+ if (key === '' && standsInFor(req))
130
+ throw new GatewayError(
131
+ 403,
132
+ 'permission_error',
133
+ 'Claude Code on this machine has no Anthropic login of its own; choose Bedrock, OpenRouter, Codex or Gemini on the Model page, or sign in on the Claude tab',
134
+ );
135
+ const watch = watchUpstream(res);
136
+ const base = key === '' ? forwardedHeaders(req) : anthropicHeaders(req, key);
137
+ const send = (payload: Buffer, headers: Record<string, string>): Promise<Response> =>
138
+ fetch(url, { method: 'POST', headers, body: new Uint8Array(payload), signal: watch.signal, redirect: 'manual' });
139
+ const upstream = await asAnthropicWants(send, base, anthropicPayloads(raw, sent, shaped, route.model), conn.label, route.model);
140
+ noteRefusal(conn.label, route.model, upstream);
141
+ noteUsageHeaders('anthropic', conn.id, upstream.headers);
142
+ const scanner = new UsageScanner(conn.id);
143
+ await pipeResponse(upstream, res, watch, key === '' ? { scanner } : { ownCredential: true, scanner });
144
+ }
145
+
146
+ async function toOpenRouter(
147
+ req: IncomingMessage,
148
+ res: ServerResponse,
149
+ body: Record<string, unknown>,
150
+ route: Route,
151
+ cfg: ModelConfig,
152
+ deps: GatewayDeps,
153
+ ): Promise<void> {
154
+ const conn = route.connection;
155
+ const reason = notReady(cfg, conn);
156
+ if (reason !== null) throw new GatewayError(400, 'invalid_request_error', reason);
157
+ const headers: Record<string, string> = {
158
+ 'content-type': 'application/json',
159
+ accept: req.headers.accept ?? 'application/json',
160
+ authorization: `Bearer ${conn.apiKey}`,
161
+ 'anthropic-version': typeof req.headers['anthropic-version'] === 'string' ? req.headers['anthropic-version'] : '2023-06-01',
162
+ 'http-referer': 'https://metro.box',
163
+ 'x-title': 'metro',
164
+ };
165
+ const beta = req.headers['anthropic-beta'];
166
+ if (typeof beta === 'string' && beta !== '') headers['anthropic-beta'] = beta;
167
+ const watch = watchUpstream(res);
168
+ const upstream = await fetch(`${deps.openrouterBase ?? OPENROUTER_BASE}${MESSAGES}`, {
169
+ method: 'POST',
170
+ headers,
171
+ body: JSON.stringify(openrouterBody(body, route.model, conn.zdr)),
172
+ signal: watch.signal,
173
+ redirect: 'manual',
174
+ });
175
+ noteRefusal(conn.label, route.model, upstream);
176
+ await pipeResponse(upstream, res, watch, { keepalive: true, ownCredential: true, scanner: new UsageScanner(conn.id) });
177
+ }
178
+
179
+ const thinkingOff = (body: Record<string, unknown>): boolean => isRecord(body.thinking) && body.thinking.type === 'disabled';
180
+
181
+ export function openrouterBody(body: Record<string, unknown>, model: string, zdr: boolean): Record<string, unknown> {
182
+ const sent: Record<string, unknown> = { ...body, model };
183
+ if (thinkingOff(body)) delete sent.thinking;
184
+ const effort = effortToApply(body);
185
+ if (effort !== null) sent.reasoning = { ...(isRecord(body.reasoning) ? body.reasoning : {}), effort: cappedEffort(effort) };
186
+ if (!zdr) return sent;
187
+ const provider = isRecord(body.provider) ? body.provider : {};
188
+ return { ...sent, provider: { ...provider, zdr: true } };
189
+ }
190
+
191
+ function noteRefusal(connection: string, model: string, upstream: Response): void {
192
+ if (!upstream.ok) log.warn({ connection, model, status: upstream.status }, 'gateway: the provider refused the request');
193
+ }
194
+
195
+ async function toSubscription(req: IncomingMessage, res: ServerResponse, path: string, body: Record<string, unknown>, route: Route, deps: GatewayDeps): Promise<void> {
196
+ const conn = route.connection;
197
+ if (path === COUNT) countTokens(res, body);
198
+ else if (conn.provider === 'gemini') await geminiMessages(req, res, body, route.model, conn, { save: saveGeminiTokens, ...deps.gemini }, sharedGeminiState, watchUpstream(res));
199
+ else await codexMessages(req, res, body, route.model, conn, { save: saveCodexTokens, ...deps.codex }, sharedCodexState, watchUpstream(res));
200
+ }
201
+
202
+ function shapedFor(req: IncomingMessage, sent: Record<string, unknown>): Record<string, unknown> {
203
+ const effort = plannedEffort(req, sent);
204
+ return effort === null ? sent : withEffort(sent, effort);
205
+ }
206
+
207
+ async function dispatch(req: IncomingMessage, res: ServerResponse, path: string, deps: GatewayDeps): Promise<void> {
208
+ const cfg = deps.config();
209
+ const raw = await readBody(req);
210
+ const sent = parseJson(raw);
211
+ const body = shapedFor(req, sent);
212
+ const route = resolveRoute(requestedModel(body), cfg) ?? passthrough(body);
213
+ const conn = route.connection;
214
+ log.info({ route: routeLabel(route), connection: conn.label, path }, 'gateway: routing');
215
+ if (path === MESSAGES) noteServed({ connection: conn.id, provider: conn.provider, model: route.model, at: new Date().toISOString() });
216
+ if (conn.provider === 'bedrock') {
217
+ assertBedrockReady(conn);
218
+ const up = { settings: conn, base: deps.bedrockBase ?? bedrockBase(conn.region), learned, watch: watchUpstream(res) };
219
+ if (path === COUNT) await bedrockCount(req, res, body, route.model, up);
220
+ else await bedrockMessages(req, res, body, route.model, up);
221
+ return;
222
+ }
223
+ if (conn.provider === 'openrouter') {
224
+ if (path === COUNT) throw new GatewayError(404, 'not_found_error', 'OpenRouter does not count tokens');
225
+ await toOpenRouter(req, res, body, route, cfg, deps);
226
+ return;
227
+ }
228
+ if (conn.provider === 'codex' || conn.provider === 'gemini') {
229
+ await toSubscription(req, res, path, body, route, deps);
230
+ return;
231
+ }
232
+ await toAnthropic(req, res, raw, sent, body, route, deps);
233
+ }
234
+
235
+ function failed(res: ServerResponse, err: unknown): void {
236
+ if (res.headersSent) {
237
+ res.end();
238
+ return;
239
+ }
240
+ if (err instanceof GatewayError) {
241
+ sendError(res, err.status, err.kind, err.message);
242
+ return;
243
+ }
244
+ log.warn({ err: errMsg(err) }, 'gateway: request failed');
245
+ sendError(res, 502, 'api_error', `metro gateway: ${errMsg(err)}`);
246
+ }
247
+
248
+ function answer(req: IncomingMessage, res: ServerResponse, path: string, deps: GatewayDeps): void {
249
+ if (req.method === 'GET' && path === MODELS) {
250
+ res.writeHead(200, { 'content-type': 'application/json' });
251
+ res.end(JSON.stringify(modelsBody(deps.config())));
252
+ return;
253
+ }
254
+ if (req.method === 'POST' && (path === MESSAGES || path === COUNT)) {
255
+ dispatch(req, res, path, deps).catch((err: unknown) => {
256
+ failed(res, err);
257
+ });
258
+ return;
259
+ }
260
+ sendError(res, 404, 'not_found_error', 'not found');
261
+ }
262
+
263
+ export function handleGatewayRequest(req: IncomingMessage, res: ServerResponse, deps: GatewayDeps): boolean {
264
+ const full = req.url ?? '';
265
+ if (full !== GATEWAY_PREFIX && !full.startsWith(`${GATEWAY_PREFIX}/`)) return false;
266
+ const path = (full.split('?')[0] ?? '').slice(GATEWAY_PREFIX.length);
267
+ if (req.method === 'HEAD' && path === HELLO) {
268
+ res.writeHead(200).end();
269
+ return true;
270
+ }
271
+ if ((deps.identify ?? defaultIdentify)(keyOf(req))) answer(req, res, path, deps);
272
+ else sendError(res, 401, 'authentication_error', 'metro gateway: no agent key (x-metro-key); start Claude Code with metro claude');
273
+ return true;
274
+ }
@@ -0,0 +1,160 @@
1
+ import { isRecord } from '@metro-labs/core/is-record';
2
+ import { PendingLogins } from './pkce.js';
3
+ import { nonEmpty } from './text.js';
4
+
5
+ export const GEMINI_CLIENT_ID = '1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com';
6
+ export const GEMINI_CLIENT_SECRET = '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';
7
+ export const GEMINI_AUTH_BASE = 'https://accounts.google.com';
8
+ export const GEMINI_TOKEN_BASE = 'https://oauth2.googleapis.com';
9
+ export const GEMINI_REDIRECT = 'http://localhost:51121/oauth-callback';
10
+ const SCOPE = [
11
+ 'https://www.googleapis.com/auth/cloud-platform',
12
+ 'https://www.googleapis.com/auth/userinfo.email',
13
+ 'https://www.googleapis.com/auth/userinfo.profile',
14
+ 'https://www.googleapis.com/auth/cclog',
15
+ 'https://www.googleapis.com/auth/experimentsandconfigs',
16
+ ].join(' ');
17
+ const EXPIRY_MARGIN_MS = 5 * 60_000;
18
+ const DEFAULT_TTL_MS = 55 * 60_000;
19
+
20
+ export interface GeminiTokens {
21
+ accessToken: string;
22
+ refreshToken: string;
23
+ expiresAt: number;
24
+ email: string | null;
25
+ project: string;
26
+ tier: string | null;
27
+ savedAt: string;
28
+ }
29
+
30
+ export class GeminiAuthError extends Error {}
31
+
32
+ const pending = new PendingLogins(16);
33
+
34
+ export function authorizeUrl(state: string, challenge: string, base = GEMINI_AUTH_BASE): string {
35
+ const params = new URLSearchParams({
36
+ client_id: GEMINI_CLIENT_ID,
37
+ redirect_uri: GEMINI_REDIRECT,
38
+ response_type: 'code',
39
+ scope: SCOPE,
40
+ access_type: 'offline',
41
+ prompt: 'consent',
42
+ code_challenge: challenge,
43
+ code_challenge_method: 'S256',
44
+ state,
45
+ });
46
+ return `${base}/o/oauth2/v2/auth?${params.toString()}`;
47
+ }
48
+
49
+ export function beginLogin(base = GEMINI_AUTH_BASE, now = Date.now()): { url: string; state: string } {
50
+ const { state, challenge } = pending.begin(now);
51
+ return { url: authorizeUrl(state, challenge, base), state };
52
+ }
53
+
54
+
55
+ const refusalIn = (body: Record<string, unknown>): string => nonEmpty(body.error_description) ?? nonEmpty(body.error) ?? 'Google answered without an access token';
56
+
57
+ function grantedTokens(body: unknown, previous: GeminiTokens | null): { accessToken: string; refreshToken: string; ttl: number } {
58
+ if (!isRecord(body)) throw new GeminiAuthError('Google answered without tokens');
59
+ const accessToken = nonEmpty(body.access_token);
60
+ if (accessToken === null) throw new GeminiAuthError(refusalIn(body));
61
+ const refreshToken = nonEmpty(body.refresh_token) ?? previous?.refreshToken ?? '';
62
+ if (refreshToken === '') throw new GeminiAuthError('Google issued no refresh token; sign in again');
63
+ return { accessToken, refreshToken, ttl: typeof body.expires_in === 'number' ? body.expires_in * 1000 : DEFAULT_TTL_MS };
64
+ }
65
+
66
+ function tokensOf(body: unknown, previous: GeminiTokens | null, now: number): GeminiTokens {
67
+ const granted = grantedTokens(body, previous);
68
+ return {
69
+ accessToken: granted.accessToken,
70
+ refreshToken: granted.refreshToken,
71
+ expiresAt: now + granted.ttl,
72
+ email: previous?.email ?? null,
73
+ project: previous?.project ?? '',
74
+ tier: previous?.tier ?? null,
75
+ savedAt: new Date(now).toISOString(),
76
+ };
77
+ }
78
+
79
+ async function tokenCall(form: Record<string, string>, base: string, fetchImpl: typeof fetch): Promise<unknown> {
80
+ let res: Response;
81
+ try {
82
+ res = await fetchImpl(`${base}/token`, {
83
+ method: 'POST',
84
+ headers: { 'content-type': 'application/x-www-form-urlencoded' },
85
+ body: new URLSearchParams(form).toString(),
86
+ signal: AbortSignal.timeout(30_000),
87
+ });
88
+ } catch (err) {
89
+ throw new GeminiAuthError(`could not reach Google: ${err instanceof Error ? err.message : String(err)}`);
90
+ }
91
+ const body: unknown = await res.json().catch(() => null);
92
+ if (!res.ok) throw new GeminiAuthError(isRecord(body) ? (nonEmpty(body.error_description) ?? nonEmpty(body.error) ?? `Google answered ${String(res.status)}`) : `Google answered ${String(res.status)}`);
93
+ return body;
94
+ }
95
+
96
+ export function codeIn(pasted: string, state: string): string {
97
+ const trimmed = pasted.trim();
98
+ if (trimmed === '') throw new GeminiAuthError('paste the address the browser landed on, or the code in it');
99
+ if (!/^https?:\/\//i.test(trimmed)) return trimmed;
100
+ let url: URL;
101
+ try {
102
+ url = new URL(trimmed);
103
+ } catch {
104
+ throw new GeminiAuthError('that is not a full address');
105
+ }
106
+ const code = url.searchParams.get('code') ?? '';
107
+ const carried = url.searchParams.get('state');
108
+ if (code === '') throw new GeminiAuthError(`that address carries no code${url.searchParams.get('error') === null ? '' : ` (Google said ${url.searchParams.get('error') ?? ''})`}`);
109
+ if (carried !== null && carried !== state) throw new GeminiAuthError('that address belongs to another sign-in; start again and paste the new one');
110
+ return code;
111
+ }
112
+
113
+ export async function exchangeCode(pasted: string, state: string, base = GEMINI_TOKEN_BASE, fetchImpl: typeof fetch = fetch, now = Date.now()): Promise<GeminiTokens> {
114
+ const verifier = pending.take(state, now);
115
+ if (verifier === null) throw new GeminiAuthError('that sign-in has expired; start it again');
116
+ const code = codeIn(pasted, state);
117
+ const body = await tokenCall(
118
+ { client_id: GEMINI_CLIENT_ID, client_secret: GEMINI_CLIENT_SECRET, grant_type: 'authorization_code', code, code_verifier: verifier, redirect_uri: GEMINI_REDIRECT },
119
+ base,
120
+ fetchImpl,
121
+ );
122
+ return tokensOf(body, null, now);
123
+ }
124
+
125
+ export async function refreshTokens(previous: GeminiTokens, base = GEMINI_TOKEN_BASE, fetchImpl: typeof fetch = fetch, now = Date.now()): Promise<GeminiTokens> {
126
+ const body = await tokenCall(
127
+ { client_id: GEMINI_CLIENT_ID, client_secret: GEMINI_CLIENT_SECRET, grant_type: 'refresh_token', refresh_token: previous.refreshToken },
128
+ base,
129
+ fetchImpl,
130
+ );
131
+ return tokensOf(body, previous, now);
132
+ }
133
+
134
+ export const tokensStale = (tokens: GeminiTokens, now = Date.now()): boolean => tokens.expiresAt - EXPIRY_MARGIN_MS <= now;
135
+
136
+ export async function userEmail(tokens: GeminiTokens, base = 'https://www.googleapis.com', fetchImpl: typeof fetch = fetch): Promise<string | null> {
137
+ try {
138
+ const res = await fetchImpl(`${base}/oauth2/v2/userinfo`, { headers: { authorization: `Bearer ${tokens.accessToken}` }, signal: AbortSignal.timeout(15_000) });
139
+ const body: unknown = await res.json().catch(() => null);
140
+ return res.ok && isRecord(body) ? nonEmpty(body.email) : null;
141
+ } catch {
142
+ return null;
143
+ }
144
+ }
145
+
146
+ export function tokensFromDisk(raw: unknown): GeminiTokens | null {
147
+ if (!isRecord(raw)) return null;
148
+ const accessToken = nonEmpty(raw.accessToken);
149
+ const refreshToken = nonEmpty(raw.refreshToken);
150
+ if (accessToken === null || refreshToken === null) return null;
151
+ return {
152
+ accessToken,
153
+ refreshToken,
154
+ expiresAt: typeof raw.expiresAt === 'number' ? raw.expiresAt : 0,
155
+ email: nonEmpty(raw.email),
156
+ project: nonEmpty(raw.project) ?? '',
157
+ tier: nonEmpty(raw.tier),
158
+ savedAt: nonEmpty(raw.savedAt) ?? '',
159
+ };
160
+ }
@@ -0,0 +1,46 @@
1
+ import { randomUUID } from 'node:crypto';
2
+
3
+ export const CODE_ASSIST_BASE = 'https://cloudcode-pa.googleapis.com';
4
+ export const CODE_ASSIST_DAILY = 'https://daily-cloudcode-pa.googleapis.com';
5
+ export const API = 'v1internal';
6
+ export const APP_VERSION = '2.0.3';
7
+ export const CLIENT_VERSION = '1.110.0';
8
+ export const CLIENT_NAME = 'antigravity';
9
+ export const IDE_TYPE = 9;
10
+ export const PLUGIN_TYPE = 2;
11
+ export const SYSTEM_PREFIX =
12
+ 'You are Antigravity, a powerful agentic AI coding assistant designed by the Google Deepmind team working on Advanced Agentic Coding.You are pair programming with a USER to solve their coding task. The task may require creating a new codebase, modifying or debugging an existing codebase, or simply answering a question.**Absolute paths only****Proactiveness**';
13
+
14
+ const PLATFORMS: Record<string, number> = { 'darwin-x64': 1, 'darwin-arm64': 2, 'linux-x64': 3, 'linux-arm64': 4, 'win32-x64': 5 };
15
+
16
+ const osName = (): string => (process.platform === 'darwin' ? 'darwin' : process.platform === 'win32' ? 'win32' : 'linux');
17
+
18
+ export const platformCode = (): number => PLATFORMS[`${process.platform}-${process.arch}`] ?? 0;
19
+
20
+ export const clientMetadata = (project: string | null): Record<string, unknown> => ({
21
+ ideType: IDE_TYPE,
22
+ platform: platformCode(),
23
+ pluginType: PLUGIN_TYPE,
24
+ ...(project === null ? {} : { duetProject: project }),
25
+ });
26
+
27
+ export const clientHeaders = (): Record<string, string> => ({
28
+ 'user-agent': `${CLIENT_NAME}/${APP_VERSION} ${osName()}/${process.arch}`,
29
+ 'x-client-name': CLIENT_NAME,
30
+ 'x-client-version': CLIENT_VERSION,
31
+ 'x-goog-api-client': 'gl-node/18.18.2 fire/0.8.6 grpc/1.10.x',
32
+ });
33
+
34
+ export type Bases = string | string[];
35
+
36
+ const listed = (override: Bases): string[] => (typeof override === 'string' ? [override] : override);
37
+
38
+ export const generateBases = (override?: Bases): string[] => (override === undefined ? [CODE_ASSIST_DAILY, CODE_ASSIST_BASE] : listed(override));
39
+
40
+ export const setupBases = (override?: Bases): string[] => (override === undefined ? [CODE_ASSIST_BASE, CODE_ASSIST_DAILY] : listed(override));
41
+
42
+ export const requestId = (): string => `agent-${randomUUID()}`;
43
+
44
+ const sessionId = randomUUID();
45
+
46
+ export const machineSessionId = (): string => sessionId;
@@ -0,0 +1,150 @@
1
+ import { isRecord } from '@metro-labs/core/is-record';
2
+ import { log } from '@metro-labs/core/log';
3
+ import { GeminiAuthError, type GeminiTokens } from './gemini-auth.js';
4
+ import { API, clientHeaders, clientMetadata, setupBases, type Bases } from './gemini-client.js';
5
+ import { nonEmpty } from './text.js';
6
+
7
+ const FREE_TIER = 'free-tier';
8
+ const PROJECT_RE = /^[a-z][a-z0-9-]{4,28}[a-z0-9]$/;
9
+ const POLL_MS = 5000;
10
+ const POLL_MAX = 24;
11
+
12
+ export interface Onboarded {
13
+ project: string;
14
+ tier: string | null;
15
+ }
16
+
17
+
18
+ export function parseGeminiProject(value: unknown): string | null {
19
+ const project = typeof value === 'string' ? value.trim() : '';
20
+ if (project === '') return null;
21
+ if (!PROJECT_RE.test(project)) throw new GeminiAuthError('that is not a Google Cloud project id: 6 to 30 lowercase letters, digits and dashes, starting with a letter');
22
+ return project;
23
+ }
24
+
25
+ class Retryable extends Error {}
26
+
27
+ async function callOne(base: string, method: string, token: string, body: unknown, fetchImpl: typeof fetch): Promise<Record<string, unknown>> {
28
+ let res: Response;
29
+ try {
30
+ res = await fetchImpl(`${base}/${API}:${method}`, {
31
+ method: 'POST',
32
+ headers: { authorization: `Bearer ${token}`, 'content-type': 'application/json', ...clientHeaders() },
33
+ body: JSON.stringify(body),
34
+ signal: AbortSignal.timeout(30_000),
35
+ });
36
+ } catch (err) {
37
+ throw new Retryable(`could not reach Google Code Assist: ${err instanceof Error ? err.message : String(err)}`);
38
+ }
39
+ const answer: unknown = await res.json().catch(() => null);
40
+ if (!res.ok) {
41
+ const detail = (isRecord(answer) && isRecord(answer.error) ? nonEmpty(answer.error.message) : null) ?? `Google Code Assist answered ${String(res.status)} on ${method}`;
42
+ throw res.status >= 500 ? new Retryable(detail) : new GeminiAuthError(detail);
43
+ }
44
+ return isRecord(answer) ? answer : {};
45
+ }
46
+
47
+ async function call(bases: string[], method: string, token: string, body: unknown, fetchImpl: typeof fetch): Promise<Record<string, unknown>> {
48
+ let last = 'no Code Assist endpoint to call';
49
+ for (const base of bases) {
50
+ try {
51
+ return await callOne(base, method, token, body, fetchImpl);
52
+ } catch (err) {
53
+ if (!(err instanceof Retryable)) throw err;
54
+ last = err.message;
55
+ }
56
+ }
57
+ throw new GeminiAuthError(last);
58
+ }
59
+
60
+ const tierOf = (load: Record<string, unknown>): { id: string | null; name: string | null } => {
61
+ const tier = isRecord(load.paidTier) ? load.paidTier : isRecord(load.currentTier) ? load.currentTier : null;
62
+ return { id: tier === null ? null : nonEmpty(tier.id), name: tier === null ? null : nonEmpty(tier.name) };
63
+ };
64
+
65
+ async function waitOperation(base: string, token: string, first: Record<string, unknown>, fetchImpl: typeof fetch): Promise<Record<string, unknown>> {
66
+ let op = first;
67
+ for (let i = 0; op.done !== true && i < POLL_MAX; i += 1) {
68
+ const name = nonEmpty(op.name);
69
+ if (name === null) break;
70
+ await new Promise((r) => setTimeout(r, POLL_MS));
71
+ const res = await fetchImpl(`${base}/${API}/${name}`, { headers: { authorization: `Bearer ${token}`, ...clientHeaders() }, signal: AbortSignal.timeout(30_000) });
72
+ const answer: unknown = await res.json().catch(() => null);
73
+ op = isRecord(answer) ? answer : {};
74
+ }
75
+ return op;
76
+ }
77
+
78
+ const REASONS: Record<string, string> = {
79
+ DASHER_USER: 'this is a Google Workspace account; the individual tier needs a personal Google account',
80
+ NON_USER_ACCOUNT: 'this is not a user account',
81
+ RESTRICTED_AGE: 'the account does not meet the age requirement',
82
+ UNSUPPORTED_LOCATION: 'Gemini Code Assist for individuals is not offered where this account is located',
83
+ UNKNOWN_LOCATION: 'Google could not tell where this account is located',
84
+ RESTRICTED_NETWORK: 'the network this box is on is restricted',
85
+ INELIGIBLE_ACCOUNT: 'Google marks this account as not eligible',
86
+ UNSUPPORTED_CLIENT: 'Google no longer serves the client metro presents; nothing on this box can fix that',
87
+ };
88
+
89
+ function refuseIneligible(load: Record<string, unknown>): void {
90
+ if (isRecord(load.currentTier) || !Array.isArray(load.ineligibleTiers)) return;
91
+ const tiers = load.ineligibleTiers.filter(isRecord);
92
+ if (tiers.length === 0) return;
93
+ const validation = tiers.find((t) => t.reasonCode === 'VALIDATION_REQUIRED' && nonEmpty(t.validationUrl) !== null);
94
+ if (validation !== undefined) throw new GeminiAuthError(`Google asks you to validate the account first: open ${nonEmpty(validation.validationUrl) ?? ''} and then connect again`);
95
+ const said = tiers.map((t) => `${nonEmpty(t.reasonMessage) ?? 'not eligible'} [${nonEmpty(t.reasonCode) ?? 'UNKNOWN'}: ${REASONS[nonEmpty(t.reasonCode) ?? ''] ?? 'no known reason'}]`);
96
+ throw new GeminiAuthError(said.join('; '));
97
+ }
98
+
99
+ const NO_PROJECT = 'this account holds a Code Assist licence but Google named no project for it: enter the id of the Google Cloud project that carries the licence and connect again';
100
+
101
+ function tierToOnboard(load: Record<string, unknown>): { id: string; name: string | null } {
102
+ const allowed = Array.isArray(load.allowedTiers) ? load.allowedTiers.filter(isRecord) : [];
103
+ const chosen = allowed.find((t) => t.isDefault === true);
104
+ return { id: nonEmpty(chosen?.id) ?? FREE_TIER, name: nonEmpty(chosen?.name) };
105
+ }
106
+
107
+ const summary = (load: Record<string, unknown>): Record<string, unknown> => ({
108
+ current: isRecord(load.currentTier) ? nonEmpty(load.currentTier.id) : null,
109
+ paid: isRecord(load.paidTier) ? nonEmpty(load.paidTier.id) : null,
110
+ allowed: Array.isArray(load.allowedTiers) ? load.allowedTiers.filter(isRecord).map((t) => `${nonEmpty(t.id) ?? '?'}${t.isDefault === true ? '*' : ''}`) : [],
111
+ ineligible: Array.isArray(load.ineligibleTiers) ? load.ineligibleTiers.filter(isRecord).map((t) => `${nonEmpty(t.tierId) ?? '?'}:${nonEmpty(t.reasonCode) ?? '?'}`) : [],
112
+ project: nonEmpty(load.cloudaicompanionProject),
113
+ });
114
+
115
+ function onboardBody(load: Record<string, unknown>, project: string | null): { body: Record<string, unknown>; name: string | null } {
116
+ const wanted = tierToOnboard(load);
117
+ const paid = wanted.id !== FREE_TIER && project !== null;
118
+ return { body: { tierId: wanted.id, metadata: clientMetadata(paid ? project : null) }, name: wanted.name ?? wanted.id };
119
+ }
120
+
121
+ const NO_ASSIGNMENT = 'Google did not assign a Code Assist project to this account; enter the id of the Google Cloud project that carries your licence and connect again';
122
+
123
+ const withProject = (project: string | null): Record<string, unknown> => (project === null ? {} : { cloudaicompanionProject: project });
124
+
125
+ const assignedProject = (op: Record<string, unknown>): string | null => {
126
+ const response = isRecord(op.response) ? op.response : {};
127
+ return isRecord(response.cloudaicompanionProject) ? nonEmpty(response.cloudaicompanionProject.id) : null;
128
+ };
129
+
130
+ async function onboardNew(load: Record<string, unknown>, tokens: GeminiTokens, project: string | null, bases: string[], fetchImpl: typeof fetch): Promise<Onboarded> {
131
+ const { body, name } = onboardBody(load, project);
132
+ const first = await call(bases, 'onboardUser', tokens.accessToken, body, fetchImpl);
133
+ const op = await waitOperation(bases[0] ?? '', tokens.accessToken, first, fetchImpl);
134
+ const assigned = assignedProject(op) ?? project;
135
+ if (assigned === null) throw new GeminiAuthError(NO_ASSIGNMENT);
136
+ return { project: assigned, tier: name };
137
+ }
138
+
139
+ export async function onboard(tokens: GeminiTokens, project: string | null = null, base?: Bases, fetchImpl: typeof fetch = fetch): Promise<Onboarded> {
140
+ const bases = setupBases(base);
141
+ const asked = { metadata: clientMetadata(project), mode: 1, ...withProject(project) };
142
+ const load = await call(bases, 'loadCodeAssist', tokens.accessToken, asked, fetchImpl);
143
+ log.info(summary(load), 'gemini: Code Assist answered loadCodeAssist');
144
+ refuseIneligible(load);
145
+ if (!isRecord(load.currentTier)) return onboardNew(load, tokens, project, [...bases].reverse(), fetchImpl);
146
+ const known = nonEmpty(load.cloudaicompanionProject) ?? project;
147
+ if (known === null) throw new GeminiAuthError(NO_PROJECT);
148
+ const tier = tierOf(load);
149
+ return { project: known, tier: tier.name ?? tier.id };
150
+ }