@stage-labs/metro 0.1.0-beta.13 → 0.1.0-beta.131

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 (329) hide show
  1. package/dist/api.js +14 -0
  2. package/dist/claude-settings.js +40 -0
  3. package/dist/claude.js +176 -0
  4. package/dist/cli.js +101 -0
  5. package/dist/control.js +74 -0
  6. package/dist/hold.js +211 -0
  7. package/dist/local.js +93 -0
  8. package/dist/mcp-config.js +25 -0
  9. package/dist/node-name.js +71 -0
  10. package/dist/onboarding.js +43 -0
  11. package/dist/plugin.js +120 -0
  12. package/dist/provider-flags.js +9 -0
  13. package/dist/route.js +38 -0
  14. package/dist/runtime-install.js +97 -0
  15. package/dist/runtime.js +74 -0
  16. package/dist/serve.js +150 -0
  17. package/dist/service.js +242 -0
  18. package/dist/tail.js +119 -0
  19. package/dist/update.js +45 -0
  20. package/dist/version.js +97 -0
  21. package/package.json +14 -27
  22. package/runtime/marketplace/.claude-plugin/marketplace.json +12 -0
  23. package/runtime/marketplace/plugin/.claude-plugin/plugin.json +18 -0
  24. package/runtime/marketplace/plugin/.mcp.json +1 -0
  25. package/runtime/marketplace/plugin/bin/guard.mjs +55 -0
  26. package/runtime/marketplace/plugin/bin/metro-plugin.mjs +122 -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 +16 -0
  30. package/runtime/marketplace/plugin/skills/refresh/SKILL.md +15 -0
  31. package/runtime/node_modules/@metro-labs/core/package.json +43 -0
  32. package/runtime/node_modules/@metro-labs/core/src/address.ts +6 -0
  33. package/runtime/node_modules/@metro-labs/core/src/endpoints.ts +7 -0
  34. package/runtime/node_modules/@metro-labs/core/src/events.ts +120 -0
  35. package/runtime/node_modules/@metro-labs/core/src/identity.ts +7 -0
  36. package/runtime/node_modules/@metro-labs/core/src/ids.ts +21 -0
  37. package/runtime/node_modules/@metro-labs/core/src/is-record.ts +2 -0
  38. package/runtime/node_modules/@metro-labs/core/src/lines.ts +87 -0
  39. package/runtime/node_modules/@metro-labs/core/src/log.ts +42 -0
  40. package/runtime/node_modules/@metro-labs/core/src/protocol.ts +226 -0
  41. package/runtime/node_modules/@metro-labs/core/src/secure-fs.ts +57 -0
  42. package/runtime/node_modules/@metro-labs/core/src/station-names.ts +13 -0
  43. package/runtime/node_modules/@metro-labs/core/src/stations/account-store.ts +60 -0
  44. package/runtime/node_modules/@metro-labs/core/src/stations/attachments.ts +196 -0
  45. package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +88 -0
  46. package/runtime/node_modules/@metro-labs/core/src/stations/station-runtime.ts +64 -0
  47. package/runtime/node_modules/@metro-labs/core/src/stations/types.ts +108 -0
  48. package/runtime/node_modules/@metro-labs/core/src/str.ts +5 -0
  49. package/runtime/node_modules/@metro-labs/core/src/train-error.ts +53 -0
  50. package/runtime/node_modules/@metro-labs/core/src/version.ts +2 -0
  51. package/runtime/node_modules/@metro-labs/daemon/package.json +48 -0
  52. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-attach.ts +19 -0
  53. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-routes.ts +70 -0
  54. package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +365 -0
  55. package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +43 -0
  56. package/runtime/node_modules/@metro-labs/daemon/src/agents/allowlist.ts +37 -0
  57. package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +300 -0
  58. package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +122 -0
  59. package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +371 -0
  60. package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +155 -0
  61. package/runtime/node_modules/@metro-labs/daemon/src/agents/keys.ts +45 -0
  62. package/runtime/node_modules/@metro-labs/daemon/src/agents/map.ts +95 -0
  63. package/runtime/node_modules/@metro-labs/daemon/src/agents/scope.ts +64 -0
  64. package/runtime/node_modules/@metro-labs/daemon/src/agents/seed.ts +33 -0
  65. package/runtime/node_modules/@metro-labs/daemon/src/agents/senders.ts +38 -0
  66. package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +209 -0
  67. package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +39 -0
  68. package/runtime/node_modules/@metro-labs/daemon/src/boot/crash-guard.ts +49 -0
  69. package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +14 -0
  70. package/runtime/node_modules/@metro-labs/daemon/src/boot/paths.ts +106 -0
  71. package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +25 -0
  72. package/runtime/node_modules/@metro-labs/daemon/src/channels/dedupe.ts +24 -0
  73. package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +377 -0
  74. package/runtime/node_modules/@metro-labs/daemon/src/channels/media-note.ts +85 -0
  75. package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +75 -0
  76. package/runtime/node_modules/@metro-labs/daemon/src/channels/relay.ts +129 -0
  77. package/runtime/node_modules/@metro-labs/daemon/src/channels/webhook-note.ts +51 -0
  78. package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +309 -0
  79. package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +358 -0
  80. package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +169 -0
  81. package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +56 -0
  82. package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +140 -0
  83. package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +90 -0
  84. package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +253 -0
  85. package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +117 -0
  86. package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +239 -0
  87. package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +154 -0
  88. package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +109 -0
  89. package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +310 -0
  90. package/runtime/node_modules/@metro-labs/daemon/src/connectors/cli-api.ts +63 -0
  91. package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +175 -0
  92. package/runtime/node_modules/@metro-labs/daemon/src/connectors/json.ts +21 -0
  93. package/runtime/node_modules/@metro-labs/daemon/src/connectors/model.ts +77 -0
  94. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-client.ts +166 -0
  95. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-discovery.ts +139 -0
  96. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-pending.ts +65 -0
  97. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-routes.ts +171 -0
  98. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth.ts +155 -0
  99. package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +122 -0
  100. package/runtime/node_modules/@metro-labs/daemon/src/connectors/reach.ts +27 -0
  101. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-target.ts +39 -0
  102. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +296 -0
  103. package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +286 -0
  104. package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +98 -0
  105. package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +70 -0
  106. package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +332 -0
  107. package/runtime/node_modules/@metro-labs/daemon/src/connectors/watch.ts +54 -0
  108. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +99 -0
  109. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-owner.ts +32 -0
  110. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +121 -0
  111. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +294 -0
  112. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-store.ts +181 -0
  113. package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +259 -0
  114. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +231 -0
  115. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
  116. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +288 -0
  117. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +203 -0
  118. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +231 -0
  119. package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +123 -0
  120. package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +187 -0
  121. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +229 -0
  122. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +269 -0
  123. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +207 -0
  124. package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +90 -0
  125. package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +65 -0
  126. package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +17 -0
  127. package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +229 -0
  128. package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +151 -0
  129. package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +216 -0
  130. package/runtime/node_modules/@metro-labs/daemon/src/mcp/channel-owner.ts +29 -0
  131. package/runtime/node_modules/@metro-labs/daemon/src/mcp/ctx.ts +62 -0
  132. package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +91 -0
  133. package/runtime/node_modules/@metro-labs/daemon/src/mcp/frame-scope.ts +21 -0
  134. package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +129 -0
  135. package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +191 -0
  136. package/runtime/node_modules/@metro-labs/daemon/src/mcp/member-tools.ts +72 -0
  137. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +66 -0
  138. package/runtime/node_modules/@metro-labs/daemon/src/mcp/raw-get-stream.ts +207 -0
  139. package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +47 -0
  140. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-registry.ts +172 -0
  141. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-route.ts +43 -0
  142. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +215 -0
  143. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-dispatch.ts +215 -0
  144. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-schemas.ts +400 -0
  145. package/runtime/node_modules/@metro-labs/daemon/src/mcp/upload-tool.ts +66 -0
  146. package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +237 -0
  147. package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +389 -0
  148. package/runtime/node_modules/@metro-labs/daemon/src/routes/body.ts +24 -0
  149. package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +372 -0
  150. package/runtime/node_modules/@metro-labs/daemon/src/routes/identity.ts +54 -0
  151. package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +223 -0
  152. package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +60 -0
  153. package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
  154. package/runtime/node_modules/@metro-labs/daemon/src/routes/threema-callback.ts +103 -0
  155. package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +64 -0
  156. package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +61 -0
  157. package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +116 -0
  158. package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
  159. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-inline.ts +100 -0
  160. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-interactive.ts +161 -0
  161. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +202 -0
  162. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-session.ts +280 -0
  163. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-xmtp.ts +111 -0
  164. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +248 -0
  165. package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +261 -0
  166. package/runtime/node_modules/@metro-labs/daemon/src/stations/registry.ts +37 -0
  167. package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
  168. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor-io.ts +97 -0
  169. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +320 -0
  170. package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
  171. package/runtime/node_modules/@metro-labs/daemon/src/stations/train-call.ts +22 -0
  172. package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +96 -0
  173. package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +123 -0
  174. package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +40 -0
  175. package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
  176. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -0
  177. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
  178. package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
  179. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
  180. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +220 -0
  181. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
  182. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
  183. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
  184. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
  185. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
  186. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
  187. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
  188. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
  189. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
  190. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
  191. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
  192. package/runtime/node_modules/@metro-labs/http/package.json +33 -0
  193. package/runtime/node_modules/@metro-labs/http/src/api-error.ts +8 -0
  194. package/runtime/node_modules/@metro-labs/http/src/api-http.ts +95 -0
  195. package/runtime/node_modules/@metro-labs/http/src/cors.ts +29 -0
  196. package/runtime/node_modules/@metro-labs/http/src/identity-key.ts +33 -0
  197. package/runtime/node_modules/@metro-labs/http/src/identity-registry.ts +13 -0
  198. package/runtime/node_modules/@metro-labs/http/src/mode-api.ts +32 -0
  199. package/runtime/node_modules/@metro-labs/http/src/return-to.ts +21 -0
  200. package/runtime/node_modules/@metro-labs/http/src/signed-identity.ts +36 -0
  201. package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
  202. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
  203. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +254 -0
  204. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
  205. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
  206. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +156 -0
  207. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
  208. package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
  209. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
  210. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
  211. package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
  212. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
  213. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
  214. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
  215. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
  216. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
  217. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
  218. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
  219. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
  220. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
  221. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
  222. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
  223. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +201 -0
  224. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +109 -0
  225. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
  226. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
  227. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
  228. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +58 -0
  229. package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
  230. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
  231. package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
  232. package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +100 -0
  233. package/runtime/node_modules/@metro-labs/threema/src/actions.ts +219 -0
  234. package/runtime/node_modules/@metro-labs/threema/src/api.ts +130 -0
  235. package/runtime/node_modules/@metro-labs/threema/src/crypto.ts +132 -0
  236. package/runtime/node_modules/@metro-labs/threema/src/format.ts +123 -0
  237. package/runtime/node_modules/@metro-labs/threema/src/ids.ts +21 -0
  238. package/runtime/node_modules/@metro-labs/threema/src/index.ts +29 -0
  239. package/runtime/node_modules/@metro-labs/threema/src/station.ts +10 -0
  240. package/runtime/node_modules/@metro-labs/threema/src/verify.ts +117 -0
  241. package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
  242. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
  243. package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
  244. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
  245. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
  246. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +190 -0
  247. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
  248. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
  249. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +347 -0
  250. package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
  251. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +138 -0
  252. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
  253. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
  254. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
  255. package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
  256. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
  257. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
  258. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
  259. package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
  260. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
  261. package/runtime/node_modules/@metro-labs/whatsapp/src/resolve.ts +52 -0
  262. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +18 -0
  263. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
  264. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
  265. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
  266. package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
  267. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +112 -0
  268. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
  269. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
  270. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
  271. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
  272. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
  273. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
  274. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
  275. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
  276. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
  277. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +335 -0
  278. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +80 -0
  279. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
  280. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
  281. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
  282. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
  283. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
  284. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
  285. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
  286. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
  287. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
  288. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
  289. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
  290. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
  291. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +53 -0
  292. package/runtime/runtime.json +3 -0
  293. package/runtime/server.ts +1 -0
  294. package/runtime/stations.json +39 -0
  295. package/runtime/trains/.keep +0 -0
  296. package/LICENSE +0 -21
  297. package/README.md +0 -252
  298. package/dist/broker.js +0 -202
  299. package/dist/cache.js +0 -69
  300. package/dist/cli/actions.js +0 -206
  301. package/dist/cli/config.js +0 -185
  302. package/dist/cli/index.js +0 -188
  303. package/dist/cli/skill.js +0 -62
  304. package/dist/cli/tail.js +0 -164
  305. package/dist/cli/util.js +0 -72
  306. package/dist/cli/webhook.js +0 -81
  307. package/dist/codex-rc.js +0 -245
  308. package/dist/dispatcher.js +0 -128
  309. package/dist/history.js +0 -121
  310. package/dist/ipc.js +0 -72
  311. package/dist/log.js +0 -15
  312. package/dist/monitor.js +0 -194
  313. package/dist/paths.js +0 -68
  314. package/dist/registry.js +0 -48
  315. package/dist/stations/claude.js +0 -45
  316. package/dist/stations/codex.js +0 -68
  317. package/dist/stations/discord.js +0 -216
  318. package/dist/stations/index.js +0 -129
  319. package/dist/stations/telegram-md.js +0 -34
  320. package/dist/stations/telegram-upload.js +0 -113
  321. package/dist/stations/telegram.js +0 -234
  322. package/dist/stations/webhook.js +0 -103
  323. package/dist/tunnel.js +0 -64
  324. package/dist/webhooks.js +0 -41
  325. package/docs/broker.md +0 -214
  326. package/docs/monitor.md +0 -163
  327. package/docs/uri-scheme.md +0 -124
  328. package/docs/users.md +0 -226
  329. package/skills/metro/SKILL.md +0 -256
@@ -0,0 +1,259 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { log } from '@metro-labs/core/log';
3
+ import { EventStreamDecoder, type EventStreamMessage } from './eventstream.js';
4
+ import { errorFrame, GatewayError, idleMessage, providerStatus, sendError, upstreamMessage, type Watch } from './forward.js';
5
+ import type { BedrockSettings } from './model-config.js';
6
+ import { UsageScanner } from './usage.js';
7
+
8
+ const ANTHROPIC_VERSION = 'bedrock-2023-05-31';
9
+ const EXTRA_INPUT_RE = /^([A-Za-z0-9_]+)(?:\.[^:]*)?: Extra inputs are not permitted/;
10
+ const MAX_REPAIRS = 4;
11
+ const PING_MS = 25_000;
12
+
13
+ export interface Adaptations {
14
+ fields: Set<string>;
15
+ dropBetas: boolean;
16
+ }
17
+
18
+ export const freshAdaptations = (): Adaptations => ({ fields: new Set(), dropBetas: false });
19
+
20
+ export interface BedrockUpstream {
21
+ settings: BedrockSettings;
22
+ base: string;
23
+ learned: Adaptations;
24
+ watch: Watch;
25
+ }
26
+
27
+ interface Rewritten {
28
+ modelId: string;
29
+ stream: boolean;
30
+ body: Record<string, unknown>;
31
+ betas: string[];
32
+ }
33
+
34
+ interface Attempt {
35
+ body: Record<string, unknown>;
36
+ betas: string[];
37
+ }
38
+
39
+ const PREFIXES: [RegExp, string][] = [
40
+ [/^us-gov-/, 'us-gov'],
41
+ [/^us-/, 'us'],
42
+ [/^eu-/, 'eu'],
43
+ [/^ap-/, 'apac'],
44
+ ];
45
+
46
+ export function regionPrefix(region: string): string {
47
+ for (const [pattern, prefix] of PREFIXES) if (pattern.test(region)) return prefix;
48
+ return 'global';
49
+ }
50
+
51
+ export function bedrockModelId(requested: string, region: string): string {
52
+ if (requested.includes('anthropic.')) return requested;
53
+ return `${regionPrefix(region)}.anthropic.${requested}`;
54
+ }
55
+
56
+ export const bedrockBase = (region: string): string => `https://bedrock-runtime.${region}.amazonaws.com`;
57
+
58
+ function splitBetas(header: string | string[] | undefined): string[] {
59
+ const raw = Array.isArray(header) ? header.join(',') : (header ?? '');
60
+ return raw
61
+ .split(',')
62
+ .map((b) => b.trim())
63
+ .filter((b) => b !== '');
64
+ }
65
+
66
+ export function rewriteForBedrock(
67
+ body: Record<string, unknown>,
68
+ model: string,
69
+ betaHeader: string | string[] | undefined,
70
+ region: string,
71
+ ): Rewritten {
72
+ const betas = splitBetas(betaHeader);
73
+ const out: Record<string, unknown> = {
74
+ ...Object.fromEntries(Object.entries(body).filter(([key]) => key !== 'model' && key !== 'stream')),
75
+ anthropic_version: ANTHROPIC_VERSION,
76
+ };
77
+ if (betas.length > 0) out.anthropic_beta = betas;
78
+ return { modelId: bedrockModelId(model, region), stream: body.stream === true, body: out, betas };
79
+ }
80
+
81
+ export function errorKind(status: number, errorType: string | null): string {
82
+ if (status === 429 || errorType === 'ThrottlingException') return 'rate_limit_error';
83
+ if (status === 400) return 'invalid_request_error';
84
+ if (status === 401 || status === 403) return 'permission_error';
85
+ if (status === 404) return 'not_found_error';
86
+ if (status === 503) return 'overloaded_error';
87
+ return 'api_error';
88
+ }
89
+
90
+ const invokeUrl = (up: BedrockUpstream, modelId: string, action: string): string =>
91
+ `${up.base}/model/${encodeURIComponent(modelId)}/${action}`;
92
+
93
+ function callBedrock(up: BedrockUpstream, url: string, body: unknown, accept: string): Promise<Response> {
94
+ return fetch(url, {
95
+ method: 'POST',
96
+ headers: {
97
+ authorization: `Bearer ${up.settings.apiKey}`,
98
+ 'content-type': 'application/json',
99
+ accept,
100
+ },
101
+ body: JSON.stringify(body),
102
+ signal: up.watch.signal,
103
+ });
104
+ }
105
+
106
+ const without = (body: Record<string, unknown>, keys: Set<string>): Record<string, unknown> =>
107
+ Object.fromEntries(Object.entries(body).filter(([key]) => !keys.has(key)));
108
+
109
+ function applyLearned(req: Rewritten, learned: Adaptations): Attempt {
110
+ const dropped = new Set(learned.fields);
111
+ if (learned.dropBetas) dropped.add('anthropic_beta');
112
+ return { body: without(req.body, dropped), betas: learned.dropBetas ? [] : req.betas };
113
+ }
114
+
115
+ function repair(attempt: Attempt, learned: Adaptations, message: string): Attempt | null {
116
+ const named = EXTRA_INPUT_RE.exec(message)?.[1];
117
+ if (named !== undefined && named in attempt.body) {
118
+ learned.fields.add(named);
119
+ log.warn({ field: named }, 'gateway: Bedrock refused an extra input; dropping it from every request');
120
+ return { body: without(attempt.body, new Set([named])), betas: attempt.betas };
121
+ }
122
+ if (attempt.betas.length > 0) {
123
+ learned.dropBetas = true;
124
+ log.warn({ betas: attempt.betas }, 'gateway: retrying without anthropic_beta');
125
+ return { body: without(attempt.body, new Set(['anthropic_beta'])), betas: [] };
126
+ }
127
+ return null;
128
+ }
129
+
130
+ async function withRepairs(up: BedrockUpstream, req: Rewritten, send: (attempt: Attempt) => Promise<Response>): Promise<Response> {
131
+ let attempt = applyLearned(req, up.learned);
132
+ for (let repairs = 0; ; repairs += 1) {
133
+ const res = await send(attempt);
134
+ if (res.status !== 400 || repairs >= MAX_REPAIRS) return res;
135
+ const text = await res.text();
136
+ const next = repair(attempt, up.learned, upstreamMessage(text, 'Bedrock returned no body'));
137
+ if (next === null) return new Response(text, { status: 400, headers: res.headers });
138
+ attempt = next;
139
+ }
140
+ }
141
+
142
+ function invoke(up: BedrockUpstream, req: Rewritten): Promise<Response> {
143
+ const action = req.stream ? 'invoke-with-response-stream' : 'invoke';
144
+ const accept = req.stream ? 'application/vnd.amazon.eventstream' : 'application/json';
145
+ const url = invokeUrl(up, req.modelId, action);
146
+ return withRepairs(up, req, (attempt) => callBedrock(up, url, attempt.body, accept));
147
+ }
148
+
149
+ function eventTypeOf(event: string): string {
150
+ try {
151
+ const parsed = JSON.parse(event) as { type?: unknown };
152
+ return typeof parsed.type === 'string' ? parsed.type : 'message';
153
+ } catch {
154
+ return 'message';
155
+ }
156
+ }
157
+
158
+ function writeEvent(res: ServerResponse, message: EventStreamMessage, scanner: UsageScanner): void {
159
+ if (message.headers[':message-type'] === 'event') {
160
+ const parsed = JSON.parse(message.payload.toString('utf8')) as { bytes?: unknown };
161
+ if (typeof parsed.bytes !== 'string') return;
162
+ const event = Buffer.from(parsed.bytes, 'base64').toString('utf8');
163
+ scanner.feed(`${event}\n`);
164
+ res.write(`event: ${eventTypeOf(event)}\ndata: ${event}\n\n`);
165
+ return;
166
+ }
167
+ const errorType = message.headers[':exception-type'] ?? message.headers[':error-code'] ?? null;
168
+ const body = {
169
+ type: 'error',
170
+ error: { type: errorKind(500, errorType), message: upstreamMessage(message.payload.toString('utf8'), 'Bedrock stream error') },
171
+ };
172
+ res.write(`event: error\ndata: ${JSON.stringify(body)}\n\n`);
173
+ }
174
+
175
+ async function relayStream(upstream: Response, res: ServerResponse, watch: Watch): Promise<void> {
176
+ res.writeHead(200, { 'content-type': 'text/event-stream', 'cache-control': 'no-store', connection: 'keep-alive' });
177
+ const body = upstream.body;
178
+ if (body === null) {
179
+ res.end();
180
+ return;
181
+ }
182
+ const ping = setInterval(() => res.write('event: ping\ndata: {"type":"ping"}\n\n'), PING_MS);
183
+ const scanner = new UsageScanner('bedrock');
184
+ try {
185
+ const decoder = new EventStreamDecoder();
186
+ const reader = body.getReader();
187
+ for (;;) {
188
+ const { done, value } = await reader.read();
189
+ if (done) break;
190
+ watch.touch();
191
+ for (const message of decoder.push(Buffer.from(value))) writeEvent(res, message, scanner);
192
+ }
193
+ } catch (err) {
194
+ if (!watch.idle()) throw err;
195
+ res.write(errorFrame('api_error', idleMessage(watch.ms)));
196
+ } finally {
197
+ clearInterval(ping);
198
+ watch.stop();
199
+ res.end();
200
+ scanner.done();
201
+ }
202
+ }
203
+
204
+ async function relayFailure(upstream: Response, res: ServerResponse): Promise<void> {
205
+ const text = await upstream.text();
206
+ sendError(res, providerStatus(upstream.status), errorKind(upstream.status, upstream.headers.get('x-amzn-errortype')), upstreamMessage(text, 'Bedrock returned no body'));
207
+ }
208
+
209
+ export async function bedrockMessages(
210
+ req: IncomingMessage,
211
+ res: ServerResponse,
212
+ body: Record<string, unknown>,
213
+ model: string,
214
+ up: BedrockUpstream,
215
+ ): Promise<void> {
216
+ const rewritten = rewriteForBedrock(body, model, req.headers['anthropic-beta'], up.settings.region);
217
+ const upstream = await invoke(up, rewritten);
218
+ if (!upstream.ok) {
219
+ await relayFailure(upstream, res);
220
+ return;
221
+ }
222
+ if (rewritten.stream) {
223
+ await relayStream(upstream, res, up.watch);
224
+ return;
225
+ }
226
+ const text = await upstream.text();
227
+ const scanner = new UsageScanner('bedrock');
228
+ scanner.feed(text);
229
+ scanner.done();
230
+ res.writeHead(200, { 'content-type': 'application/json' });
231
+ res.end(text);
232
+ }
233
+
234
+ const estimateTokens = (body: Record<string, unknown>): number => Math.ceil(JSON.stringify(body).length / 4);
235
+
236
+ export async function bedrockCount(
237
+ req: IncomingMessage,
238
+ res: ServerResponse,
239
+ body: Record<string, unknown>,
240
+ model: string,
241
+ up: BedrockUpstream,
242
+ ): Promise<void> {
243
+ const rewritten = rewriteForBedrock(body, model, req.headers['anthropic-beta'], up.settings.region);
244
+ const url = invokeUrl(up, rewritten.modelId, 'count-tokens');
245
+ const upstream = await withRepairs(up, rewritten, (attempt) => callBedrock(up, url, { input: { invokeModel: { body: JSON.stringify(attempt.body) } } }, 'application/json'));
246
+ const text = await upstream.text();
247
+ let counted: number | null = null;
248
+ if (upstream.ok) {
249
+ const parsed = JSON.parse(text) as { inputTokens?: unknown };
250
+ if (typeof parsed.inputTokens === 'number') counted = parsed.inputTokens;
251
+ }
252
+ res.writeHead(200, { 'content-type': 'application/json' });
253
+ res.end(JSON.stringify({ input_tokens: counted ?? estimateTokens(rewritten.body) }));
254
+ }
255
+
256
+ export function assertBedrockReady(settings: BedrockSettings): void {
257
+ if (settings.apiKey === '' || settings.region === '')
258
+ throw new GatewayError(400, 'invalid_request_error', 'Bedrock needs an API key and a region: add them on the Model page.');
259
+ }
@@ -0,0 +1,231 @@
1
+ import { createHash, randomBytes } from 'node:crypto';
2
+ import { readFileSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { isRecord } from '@metro-labs/core/is-record';
6
+
7
+ export const CODEX_CLIENT_ID = 'app_EMoamEEZ73f0CkXaXp7hrann';
8
+ export const CODEX_ISSUER = 'https://auth.openai.com';
9
+ export const CODEX_REDIRECT = 'http://localhost:1455/auth/callback';
10
+ const SCOPE = 'openid profile email offline_access api.connectors.read api.connectors.invoke';
11
+ const AUTH_CLAIM = 'https://api.openai.com/auth';
12
+ const PENDING_TTL_MS = 10 * 60_000;
13
+ export const ACCESS_TOKEN_TTL_MS = 55 * 60_000;
14
+
15
+ export interface CodexTokens {
16
+ accessToken: string;
17
+ refreshToken: string;
18
+ idToken: string;
19
+ accountId: string;
20
+ email: string | null;
21
+ plan: string | null;
22
+ savedAt: string;
23
+ }
24
+
25
+ export class CodexAuthError extends Error {}
26
+
27
+ interface Pending {
28
+ verifier: string;
29
+ at: number;
30
+ }
31
+
32
+ const pending = new Map<string, Pending>();
33
+
34
+ const b64url = (buf: Buffer): string => buf.toString('base64url');
35
+
36
+ export function newPkce(): { verifier: string; challenge: string } {
37
+ const verifier = b64url(randomBytes(32));
38
+ const challenge = b64url(createHash('sha256').update(verifier).digest());
39
+ return { verifier, challenge };
40
+ }
41
+
42
+ export function authorizeUrl(state: string, challenge: string, issuer = CODEX_ISSUER): string {
43
+ const query = new URLSearchParams({
44
+ response_type: 'code',
45
+ client_id: CODEX_CLIENT_ID,
46
+ redirect_uri: CODEX_REDIRECT,
47
+ scope: SCOPE,
48
+ code_challenge: challenge,
49
+ code_challenge_method: 'S256',
50
+ id_token_add_organizations: 'true',
51
+ codex_cli_simplified_flow: 'true',
52
+ state,
53
+ originator: 'codex_cli_rs',
54
+ });
55
+ return `${issuer}/oauth/authorize?${query.toString()}`;
56
+ }
57
+
58
+ function sweep(now: number): void {
59
+ for (const [state, entry] of pending) if (now - entry.at > PENDING_TTL_MS) pending.delete(state);
60
+ }
61
+
62
+ export function beginLogin(issuer = CODEX_ISSUER, now = Date.now()): { url: string; state: string } {
63
+ sweep(now);
64
+ const { verifier, challenge } = newPkce();
65
+ const state = b64url(randomBytes(24));
66
+ pending.set(state, { verifier, at: now });
67
+ return { url: authorizeUrl(state, challenge, issuer), state };
68
+ }
69
+
70
+ export function parseCallback(raw: string): { code: string; state: string } {
71
+ const text = raw.trim();
72
+ let query: URLSearchParams;
73
+ try {
74
+ query = new URL(text).searchParams;
75
+ } catch {
76
+ query = new URLSearchParams(text.replace(/^\?/, ''));
77
+ }
78
+ const code = query.get('code')?.trim() ?? '';
79
+ const state = query.get('state')?.trim() ?? '';
80
+ if (code === '' || state === '')
81
+ throw new CodexAuthError('paste the whole address the browser landed on; it carries code= and state=');
82
+ return { code, state };
83
+ }
84
+
85
+ function decodeJwtPayload(jwt: string): Record<string, unknown> {
86
+ const part = jwt.split('.')[1] ?? '';
87
+ try {
88
+ const parsed: unknown = JSON.parse(Buffer.from(part, 'base64url').toString('utf8'));
89
+ return isRecord(parsed) ? parsed : {};
90
+ } catch {
91
+ return {};
92
+ }
93
+ }
94
+
95
+ const text = (value: unknown): string | null => (typeof value === 'string' && value !== '' ? value : null);
96
+
97
+ export function claimsOf(idToken: string): { accountId: string | null; email: string | null; plan: string | null } {
98
+ const payload = decodeJwtPayload(idToken);
99
+ const auth = isRecord(payload[AUTH_CLAIM]) ? payload[AUTH_CLAIM] : {};
100
+ return { accountId: text(auth.chatgpt_account_id), email: text(payload.email), plan: text(auth.chatgpt_plan_type) };
101
+ }
102
+
103
+ interface TokenResponse {
104
+ id_token?: unknown;
105
+ access_token?: unknown;
106
+ refresh_token?: unknown;
107
+ }
108
+
109
+ const EMPTY_TOKENS: CodexTokens = { accessToken: '', refreshToken: '', idToken: '', accountId: '', email: null, plan: null, savedAt: '' };
110
+
111
+ function assertUsable(next: CodexTokens): CodexTokens {
112
+ if (next.accessToken === '' || next.refreshToken === '') throw new CodexAuthError('the token endpoint returned no usable tokens');
113
+ if (next.accountId === '') throw new CodexAuthError('the id token carries no ChatGPT account id; sign in with a ChatGPT account that has Codex');
114
+ return next;
115
+ }
116
+
117
+ const pick = (fresh: string | null, kept: string | null): string | null => fresh ?? kept;
118
+
119
+ export function tokensFrom(body: unknown, previous: CodexTokens | null, now = new Date()): CodexTokens {
120
+ const res = (isRecord(body) ? body : {}) as TokenResponse;
121
+ const kept = previous ?? EMPTY_TOKENS;
122
+ const idToken = pick(text(res.id_token), kept.idToken) ?? '';
123
+ const claims = claimsOf(idToken);
124
+ return assertUsable({
125
+ accessToken: text(res.access_token) ?? '',
126
+ refreshToken: pick(text(res.refresh_token), kept.refreshToken) ?? '',
127
+ idToken,
128
+ accountId: pick(claims.accountId, kept.accountId) ?? '',
129
+ email: pick(claims.email, kept.email),
130
+ plan: pick(claims.plan, kept.plan),
131
+ savedAt: now.toISOString(),
132
+ });
133
+ }
134
+
135
+ async function tokenCall(issuer: string, init: RequestInit, fetchImpl: typeof fetch): Promise<unknown> {
136
+ const res = await fetchImpl(`${issuer}/oauth/token`, init);
137
+ const raw = await res.text();
138
+ if (!res.ok) {
139
+ let detail = raw.slice(0, 300);
140
+ try {
141
+ const parsed: unknown = JSON.parse(raw);
142
+ if (isRecord(parsed)) detail = text(parsed.error_description) ?? text(parsed.error) ?? detail;
143
+ } catch {
144
+ detail = raw.slice(0, 300);
145
+ }
146
+ throw new CodexAuthError(`OpenAI refused the token request (${String(res.status)}): ${detail}`);
147
+ }
148
+ return JSON.parse(raw) as unknown;
149
+ }
150
+
151
+ export async function finishLogin(
152
+ callback: string,
153
+ issuer = CODEX_ISSUER,
154
+ fetchImpl: typeof fetch = fetch,
155
+ now = Date.now(),
156
+ ): Promise<CodexTokens> {
157
+ sweep(now);
158
+ const { code, state } = parseCallback(callback);
159
+ const entry = pending.get(state);
160
+ if (entry === undefined) throw new CodexAuthError('this sign-in link has expired or was started elsewhere; press Connect again');
161
+ pending.delete(state);
162
+ return exchangeCode(issuer, code, entry.verifier, CODEX_REDIRECT, fetchImpl, now);
163
+ }
164
+
165
+ export async function exchangeCode(
166
+ issuer: string,
167
+ code: string,
168
+ verifier: string,
169
+ redirectUri: string,
170
+ fetchImpl: typeof fetch,
171
+ now = Date.now(),
172
+ ): Promise<CodexTokens> {
173
+ const body = new URLSearchParams({
174
+ grant_type: 'authorization_code',
175
+ code,
176
+ redirect_uri: redirectUri,
177
+ client_id: CODEX_CLIENT_ID,
178
+ code_verifier: verifier,
179
+ });
180
+ const res = await tokenCall(
181
+ issuer,
182
+ { method: 'POST', headers: { 'content-type': 'application/x-www-form-urlencoded' }, body: body.toString() },
183
+ fetchImpl,
184
+ );
185
+ return tokensFrom(res, null, new Date(now));
186
+ }
187
+
188
+ export async function refreshTokens(previous: CodexTokens, issuer = CODEX_ISSUER, fetchImpl: typeof fetch = fetch): Promise<CodexTokens> {
189
+ const res = await tokenCall(
190
+ issuer,
191
+ {
192
+ method: 'POST',
193
+ headers: { 'content-type': 'application/json' },
194
+ body: JSON.stringify({ client_id: CODEX_CLIENT_ID, grant_type: 'refresh_token', refresh_token: previous.refreshToken }),
195
+ },
196
+ fetchImpl,
197
+ );
198
+ return tokensFrom(res, previous);
199
+ }
200
+
201
+ export const tokensStale = (tokens: CodexTokens, now = Date.now()): boolean =>
202
+ now - Date.parse(tokens.savedAt) > ACCESS_TOKEN_TTL_MS;
203
+
204
+ function readAuthFile(home: string): Record<string, unknown> {
205
+ let raw: unknown;
206
+ try {
207
+ raw = JSON.parse(readFileSync(join(home, '.codex', 'auth.json'), 'utf8'));
208
+ } catch {
209
+ throw new CodexAuthError('no Codex CLI login on this machine (~/.codex/auth.json); run codex login there first, or connect here');
210
+ }
211
+ return isRecord(raw) ? raw : {};
212
+ }
213
+
214
+ export function readCodexCliAuth(home = homedir()): CodexTokens {
215
+ const raw = readAuthFile(home);
216
+ if (!isRecord(raw.tokens)) throw new CodexAuthError('~/.codex/auth.json holds no ChatGPT tokens (an API-key login cannot be reused here)');
217
+ const stored = raw.tokens;
218
+ const idToken = text(stored.id_token) ?? '';
219
+ const claims = claimsOf(idToken);
220
+ const found: CodexTokens = {
221
+ accessToken: text(stored.access_token) ?? '',
222
+ refreshToken: text(stored.refresh_token) ?? '',
223
+ idToken,
224
+ accountId: text(stored.account_id) ?? claims.accountId ?? '',
225
+ email: claims.email,
226
+ plan: claims.plan,
227
+ savedAt: text(raw.last_refresh) ?? new Date(0).toISOString(),
228
+ };
229
+ if ([found.accessToken, found.refreshToken, found.accountId].includes('')) throw new CodexAuthError('~/.codex/auth.json is missing a token or the account id');
230
+ return found;
231
+ }
@@ -0,0 +1,91 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { isRecord } from '@metro-labs/core/is-record';
3
+ import { errMsg } from '@metro-labs/core/log';
4
+ import { CODEX_CLIENT_ID, CODEX_ISSUER, CodexAuthError, exchangeCode, type CodexTokens } from './codex-auth.js';
5
+
6
+ const VERIFY_PATH = '/codex/device';
7
+ const DEVICE_TTL_MS = 15 * 60_000;
8
+ const DEFAULT_INTERVAL_S = 5;
9
+ const ID_BYTES = 18;
10
+
11
+ export interface DeviceLogin {
12
+ id: string;
13
+ userCode: string;
14
+ verifyUrl: string;
15
+ interval: number;
16
+ }
17
+
18
+ export type DeviceStatus = { status: 'pending' } | { status: 'done'; tokens: CodexTokens } | { status: 'failed'; error: string };
19
+
20
+ interface Entry {
21
+ deviceAuthId: string;
22
+ userCode: string;
23
+ interval: number;
24
+ issuer: string;
25
+ startedAt: number;
26
+ polledAt: number | null;
27
+ }
28
+
29
+ const logins = new Map<string, Entry>();
30
+
31
+ const str = (value: unknown): string => (typeof value === 'string' ? value : '');
32
+
33
+ function intervalOf(value: unknown): number {
34
+ const n = typeof value === 'number' ? value : typeof value === 'string' ? Number(value) : NaN;
35
+ return Number.isFinite(n) && n >= 1 ? Math.round(n) : DEFAULT_INTERVAL_S;
36
+ }
37
+
38
+ function sweep(now: number): void {
39
+ for (const [id, entry] of logins) if (now - entry.startedAt > DEVICE_TTL_MS) logins.delete(id);
40
+ }
41
+
42
+ const post = (url: string, body: unknown, fetchImpl: typeof fetch): Promise<Response> =>
43
+ fetchImpl(url, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(body) });
44
+
45
+ export async function beginDeviceLogin(issuer = CODEX_ISSUER, fetchImpl: typeof fetch = fetch, now = Date.now()): Promise<DeviceLogin> {
46
+ sweep(now);
47
+ const res = await post(`${issuer}/api/accounts/deviceauth/usercode`, { client_id: CODEX_CLIENT_ID }, fetchImpl);
48
+ if (res.status === 404) throw new CodexAuthError('OpenAI is not offering the device-code sign-in right now; use the browser sign-in instead');
49
+ if (!res.ok) throw new CodexAuthError(`OpenAI refused to start the device-code sign-in (${String(res.status)})`);
50
+ const body: unknown = await res.json();
51
+ const parsed = isRecord(body) ? body : {};
52
+ const deviceAuthId = str(parsed.device_auth_id);
53
+ const userCode = str(parsed.user_code) || str(parsed.usercode);
54
+ if (deviceAuthId === '' || userCode === '') throw new CodexAuthError('OpenAI answered without a device code');
55
+ const id = randomBytes(ID_BYTES).toString('base64url');
56
+ const interval = intervalOf(parsed.interval);
57
+ logins.set(id, { deviceAuthId, userCode, interval, issuer, startedAt: now, polledAt: null });
58
+ return { id, userCode, verifyUrl: `${issuer}${VERIFY_PATH}`, interval };
59
+ }
60
+
61
+ async function claim(entry: Entry, fetchImpl: typeof fetch, now: number): Promise<DeviceStatus> {
62
+ const res = await post(`${entry.issuer}/api/accounts/deviceauth/token`, { device_auth_id: entry.deviceAuthId, user_code: entry.userCode }, fetchImpl);
63
+ if (res.status === 403 || res.status === 404) {
64
+ await res.body?.cancel();
65
+ return { status: 'pending' };
66
+ }
67
+ if (!res.ok) return { status: 'failed', error: `OpenAI ended the device-code sign-in (${String(res.status)})` };
68
+ const body: unknown = await res.json();
69
+ const parsed = isRecord(body) ? body : {};
70
+ const code = str(parsed.authorization_code);
71
+ const verifier = str(parsed.code_verifier);
72
+ if (code === '' || verifier === '') return { status: 'failed', error: 'OpenAI approved the sign-in but sent no code back' };
73
+ const tokens = await exchangeCode(entry.issuer, code, verifier, `${entry.issuer}/deviceauth/callback`, fetchImpl, now);
74
+ return { status: 'done', tokens };
75
+ }
76
+
77
+ export async function pollDeviceLogin(id: string, fetchImpl: typeof fetch = fetch, now = Date.now()): Promise<DeviceStatus> {
78
+ sweep(now);
79
+ const entry = logins.get(id);
80
+ if (entry === undefined) throw new CodexAuthError('this sign-in has expired or already finished; press Connect again');
81
+ if (entry.polledAt !== null && now - entry.polledAt < entry.interval * 1000) return { status: 'pending' };
82
+ entry.polledAt = now;
83
+ let result: DeviceStatus;
84
+ try {
85
+ result = await claim(entry, fetchImpl, now);
86
+ } catch (err) {
87
+ result = { status: 'failed', error: errMsg(err) };
88
+ }
89
+ if (result.status !== 'pending') logins.delete(id);
90
+ return result;
91
+ }