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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (279) hide show
  1. package/dist/api.js +14 -0
  2. package/dist/bedrock-eventstream.js +120 -0
  3. package/dist/bedrock-proxy.js +260 -0
  4. package/dist/bedrock-upstream.js +72 -0
  5. package/dist/bedrock.js +37 -0
  6. package/dist/claude-settings.js +40 -0
  7. package/dist/claude.js +107 -0
  8. package/dist/cli.js +101 -0
  9. package/dist/control.js +74 -0
  10. package/dist/hold.js +211 -0
  11. package/dist/local.js +93 -0
  12. package/dist/node-name.js +51 -0
  13. package/dist/plugin.js +80 -0
  14. package/dist/provider-flags.js +9 -0
  15. package/dist/runtime-install.js +84 -0
  16. package/dist/runtime.js +70 -0
  17. package/dist/serve.js +147 -0
  18. package/dist/service.js +224 -0
  19. package/dist/tail.js +119 -0
  20. package/dist/update.js +45 -0
  21. package/dist/version.js +97 -0
  22. package/package.json +14 -25
  23. package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
  24. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -0
  25. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
  26. package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
  27. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
  28. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +220 -0
  29. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
  30. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
  31. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
  32. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
  33. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
  34. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
  35. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
  36. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
  37. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
  38. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
  39. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
  40. package/runtime/node_modules/@metro-labs/mcp/package.json +64 -0
  41. package/runtime/node_modules/@metro-labs/mcp/src/channels/addressed.ts +25 -0
  42. package/runtime/node_modules/@metro-labs/mcp/src/channels/dedupe.ts +24 -0
  43. package/runtime/node_modules/@metro-labs/mcp/src/channels/inbound.ts +377 -0
  44. package/runtime/node_modules/@metro-labs/mcp/src/channels/media-note.ts +85 -0
  45. package/runtime/node_modules/@metro-labs/mcp/src/channels/pending.ts +75 -0
  46. package/runtime/node_modules/@metro-labs/mcp/src/channels/relay.ts +129 -0
  47. package/runtime/node_modules/@metro-labs/mcp/src/channels/webhook-note.ts +51 -0
  48. package/runtime/node_modules/@metro-labs/mcp/src/daemon/account-api.ts +311 -0
  49. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +302 -0
  50. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-error.ts +8 -0
  51. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-http.ts +95 -0
  52. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-grant.ts +99 -0
  53. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-owner.ts +32 -0
  54. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-serve.ts +128 -0
  55. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-session.ts +280 -0
  56. package/runtime/node_modules/@metro-labs/mcp/src/daemon/boot.ts +204 -0
  57. package/runtime/node_modules/@metro-labs/mcp/src/daemon/bundle-api.ts +111 -0
  58. package/runtime/node_modules/@metro-labs/mcp/src/daemon/claude-api.ts +97 -0
  59. package/runtime/node_modules/@metro-labs/mcp/src/daemon/claude-files.ts +329 -0
  60. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connect-hint.ts +39 -0
  61. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-api.ts +307 -0
  62. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-json.ts +21 -0
  63. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth-routes.ts +157 -0
  64. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth.ts +127 -0
  65. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-tools.ts +98 -0
  66. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-url.ts +70 -0
  67. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-verify.ts +330 -0
  68. package/runtime/node_modules/@metro-labs/mcp/src/daemon/control-api.ts +64 -0
  69. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cors.ts +29 -0
  70. package/runtime/node_modules/@metro-labs/mcp/src/daemon/crash-guard.ts +49 -0
  71. package/runtime/node_modules/@metro-labs/mcp/src/daemon/events.ts +120 -0
  72. package/runtime/node_modules/@metro-labs/mcp/src/daemon/http.ts +390 -0
  73. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity-key.ts +33 -0
  74. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity-registry.ts +13 -0
  75. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity-routes.ts +54 -0
  76. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +7 -0
  77. package/runtime/node_modules/@metro-labs/mcp/src/daemon/is-record.ts +2 -0
  78. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-cli-api.ts +63 -0
  79. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-mode.ts +187 -0
  80. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-owner.ts +14 -0
  81. package/runtime/node_modules/@metro-labs/mcp/src/daemon/log.ts +42 -0
  82. package/runtime/node_modules/@metro-labs/mcp/src/daemon/machine-api.ts +61 -0
  83. package/runtime/node_modules/@metro-labs/mcp/src/daemon/mode-api.ts +32 -0
  84. package/runtime/node_modules/@metro-labs/mcp/src/daemon/model-api.ts +169 -0
  85. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-client.ts +159 -0
  86. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-discovery.ts +117 -0
  87. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-pending.ts +65 -0
  88. package/runtime/node_modules/@metro-labs/mcp/src/daemon/paths.ts +109 -0
  89. package/runtime/node_modules/@metro-labs/mcp/src/daemon/protocol.ts +226 -0
  90. package/runtime/node_modules/@metro-labs/mcp/src/daemon/relay.ts +295 -0
  91. package/runtime/node_modules/@metro-labs/mcp/src/daemon/return-to.ts +21 -0
  92. package/runtime/node_modules/@metro-labs/mcp/src/daemon/runtime-deps.ts +82 -0
  93. package/runtime/node_modules/@metro-labs/mcp/src/daemon/secure-fs.ts +57 -0
  94. package/runtime/node_modules/@metro-labs/mcp/src/daemon/server-types.ts +21 -0
  95. package/runtime/node_modules/@metro-labs/mcp/src/daemon/servers-api.ts +80 -0
  96. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-api.ts +29 -0
  97. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-apis.ts +66 -0
  98. package/runtime/node_modules/@metro-labs/mcp/src/daemon/signed-identity.ts +36 -0
  99. package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor-io.ts +97 -0
  100. package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor.ts +320 -0
  101. package/runtime/node_modules/@metro-labs/mcp/src/daemon/terminal-api.ts +71 -0
  102. package/runtime/node_modules/@metro-labs/mcp/src/daemon/terminal-tickets.ts +40 -0
  103. package/runtime/node_modules/@metro-labs/mcp/src/daemon/terminal-ws.ts +114 -0
  104. package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-call.ts +22 -0
  105. package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-error.ts +53 -0
  106. package/runtime/node_modules/@metro-labs/mcp/src/daemon/tunnel.ts +352 -0
  107. package/runtime/node_modules/@metro-labs/mcp/src/daemon/update-api.ts +116 -0
  108. package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-api.ts +294 -0
  109. package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-store.ts +181 -0
  110. package/runtime/node_modules/@metro-labs/mcp/src/daemon/vault-api.ts +78 -0
  111. package/runtime/node_modules/@metro-labs/mcp/src/daemon/vault-types.ts +12 -0
  112. package/runtime/node_modules/@metro-labs/mcp/src/daemon/version.ts +2 -0
  113. package/runtime/node_modules/@metro-labs/mcp/src/db/account-attach.ts +19 -0
  114. package/runtime/node_modules/@metro-labs/mcp/src/db/address.ts +6 -0
  115. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-admin.ts +43 -0
  116. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-map.ts +84 -0
  117. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-scope.ts +64 -0
  118. package/runtime/node_modules/@metro-labs/mcp/src/db/client.ts +32 -0
  119. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-config.ts +144 -0
  120. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-relay.ts +39 -0
  121. package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +69 -0
  122. package/runtime/node_modules/@metro-labs/mcp/src/db/file-admin.ts +329 -0
  123. package/runtime/node_modules/@metro-labs/mcp/src/db/file-source.ts +155 -0
  124. package/runtime/node_modules/@metro-labs/mcp/src/db/ids.ts +21 -0
  125. package/runtime/node_modules/@metro-labs/mcp/src/db/key-map.ts +45 -0
  126. package/runtime/node_modules/@metro-labs/mcp/src/db/local-connectors.ts +277 -0
  127. package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +253 -0
  128. package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +33 -0
  129. package/runtime/node_modules/@metro-labs/mcp/src/db/servers.ts +83 -0
  130. package/runtime/node_modules/@metro-labs/mcp/src/db/stations.ts +12 -0
  131. package/runtime/node_modules/@metro-labs/mcp/src/db/vault.ts +109 -0
  132. package/runtime/node_modules/@metro-labs/mcp/src/gateway/bedrock.ts +252 -0
  133. package/runtime/node_modules/@metro-labs/mcp/src/gateway/codex-auth.ts +231 -0
  134. package/runtime/node_modules/@metro-labs/mcp/src/gateway/codex-device.ts +91 -0
  135. package/runtime/node_modules/@metro-labs/mcp/src/gateway/codex-stream.ts +288 -0
  136. package/runtime/node_modules/@metro-labs/mcp/src/gateway/codex-translate.ts +203 -0
  137. package/runtime/node_modules/@metro-labs/mcp/src/gateway/codex.ts +211 -0
  138. package/runtime/node_modules/@metro-labs/mcp/src/gateway/eventstream.ts +123 -0
  139. package/runtime/node_modules/@metro-labs/mcp/src/gateway/forward.ts +169 -0
  140. package/runtime/node_modules/@metro-labs/mcp/src/gateway/gateway.ts +193 -0
  141. package/runtime/node_modules/@metro-labs/mcp/src/gateway/model-config.ts +180 -0
  142. package/runtime/node_modules/@metro-labs/mcp/src/mcp/accounts.ts +141 -0
  143. package/runtime/node_modules/@metro-labs/mcp/src/mcp/call-tools.ts +216 -0
  144. package/runtime/node_modules/@metro-labs/mcp/src/mcp/channel-owner.ts +29 -0
  145. package/runtime/node_modules/@metro-labs/mcp/src/mcp/ctx.ts +62 -0
  146. package/runtime/node_modules/@metro-labs/mcp/src/mcp/event-store.ts +91 -0
  147. package/runtime/node_modules/@metro-labs/mcp/src/mcp/frame-scope.ts +21 -0
  148. package/runtime/node_modules/@metro-labs/mcp/src/mcp/group-tools.ts +129 -0
  149. package/runtime/node_modules/@metro-labs/mcp/src/mcp/index.ts +191 -0
  150. package/runtime/node_modules/@metro-labs/mcp/src/mcp/member-tools.ts +72 -0
  151. package/runtime/node_modules/@metro-labs/mcp/src/mcp/permission-relay.ts +66 -0
  152. package/runtime/node_modules/@metro-labs/mcp/src/mcp/raw-get-stream.ts +207 -0
  153. package/runtime/node_modules/@metro-labs/mcp/src/mcp/request-identity.ts +47 -0
  154. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-registry.ts +172 -0
  155. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-route.ts +43 -0
  156. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session.ts +215 -0
  157. package/runtime/node_modules/@metro-labs/mcp/src/mcp/str.ts +5 -0
  158. package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-dispatch.ts +215 -0
  159. package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-schemas.ts +400 -0
  160. package/runtime/node_modules/@metro-labs/mcp/src/mcp/upload-tool.ts +66 -0
  161. package/runtime/node_modules/@metro-labs/mcp/src/monitor/api.ts +237 -0
  162. package/runtime/node_modules/@metro-labs/mcp/src/server.ts +2 -0
  163. package/runtime/node_modules/@metro-labs/mcp/src/stations/account-store.ts +60 -0
  164. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-inline.ts +100 -0
  165. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-interactive.ts +161 -0
  166. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-resolve.ts +200 -0
  167. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-xmtp.ts +111 -0
  168. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach.ts +184 -0
  169. package/runtime/node_modules/@metro-labs/mcp/src/stations/attachments.ts +196 -0
  170. package/runtime/node_modules/@metro-labs/mcp/src/stations/lines.ts +82 -0
  171. package/runtime/node_modules/@metro-labs/mcp/src/stations/messaging-normalize.ts +88 -0
  172. package/runtime/node_modules/@metro-labs/mcp/src/stations/registry.ts +35 -0
  173. package/runtime/node_modules/@metro-labs/mcp/src/stations/station-runtime.ts +64 -0
  174. package/runtime/node_modules/@metro-labs/mcp/src/stations/types.ts +107 -0
  175. package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
  176. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
  177. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +254 -0
  178. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
  179. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
  180. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +156 -0
  181. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
  182. package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
  183. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
  184. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
  185. package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
  186. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
  187. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
  188. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
  189. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
  190. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
  191. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
  192. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
  193. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
  194. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
  195. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
  196. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
  197. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +201 -0
  198. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +109 -0
  199. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
  200. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
  201. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
  202. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +58 -0
  203. package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
  204. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
  205. package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
  206. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
  207. package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
  208. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
  209. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
  210. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +178 -0
  211. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
  212. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
  213. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +327 -0
  214. package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
  215. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +138 -0
  216. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
  217. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
  218. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
  219. package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
  220. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
  221. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
  222. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
  223. package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
  224. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
  225. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +17 -0
  226. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
  227. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
  228. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
  229. package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
  230. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +112 -0
  231. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
  232. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
  233. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
  234. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
  235. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
  236. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
  237. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
  238. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
  239. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
  240. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +335 -0
  241. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +80 -0
  242. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
  243. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
  244. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
  245. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
  246. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
  247. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
  248. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
  249. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
  250. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
  251. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
  252. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
  253. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
  254. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +53 -0
  255. package/runtime/runtime.json +3 -0
  256. package/runtime/stations.json +36 -0
  257. package/runtime/trains/.keep +0 -0
  258. package/LICENSE +0 -21
  259. package/README.md +0 -193
  260. package/dist/cache.js +0 -75
  261. package/dist/cli/actions.js +0 -139
  262. package/dist/cli/config.js +0 -185
  263. package/dist/cli/index.js +0 -159
  264. package/dist/cli/skill.js +0 -62
  265. package/dist/cli/util.js +0 -72
  266. package/dist/codex-rc.js +0 -236
  267. package/dist/dispatcher.js +0 -95
  268. package/dist/history.js +0 -84
  269. package/dist/ipc.js +0 -72
  270. package/dist/log.js +0 -15
  271. package/dist/paths.js +0 -68
  272. package/dist/stations/discord.js +0 -200
  273. package/dist/stations/index.js +0 -73
  274. package/dist/stations/telegram-md.js +0 -34
  275. package/dist/stations/telegram-upload.js +0 -113
  276. package/dist/stations/telegram.js +0 -213
  277. package/docs/agents.md +0 -185
  278. package/docs/uri-scheme.md +0 -71
  279. package/skills/metro/SKILL.md +0 -241
@@ -0,0 +1,329 @@
1
+ import {
2
+ closeSync,
3
+ createReadStream,
4
+ existsSync,
5
+ openSync,
6
+ readdirSync,
7
+ readSync,
8
+ rmSync,
9
+ statSync,
10
+ } from 'node:fs';
11
+ import { homedir } from 'node:os';
12
+ import { join } from 'node:path';
13
+ import { createInterface } from 'node:readline';
14
+ import { ApiError } from './api-error.js';
15
+ import { isRecord } from './is-record.js';
16
+
17
+ const PROJECT_RE = /^[A-Za-z0-9._-]{1,200}$/;
18
+ const SESSION_RE = /^[A-Za-z0-9][A-Za-z0-9-]{7,63}$/;
19
+ const MEMORY_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,119}\.md$/;
20
+ const EDGE_BYTES = 64 * 1024;
21
+ const TEXT_CAP = 20_000;
22
+ const RESULT_CAP = 8_000;
23
+ const INPUT_CAP = 4_000;
24
+ const FILE_CAP = 1_000_000;
25
+ const NOISE = ['<task-notification>', '<system-reminder>', '<local-command', '<command-name>'];
26
+
27
+ export function claudeDir(): string {
28
+ const explicit = process.env.METRO_CLAUDE_DIR?.trim();
29
+ if (explicit !== undefined && explicit !== '') return explicit;
30
+ const cfg = process.env.CLAUDE_CONFIG_DIR?.trim();
31
+ if (cfg !== undefined && cfg !== '') return cfg;
32
+ return join(homedir(), '.claude');
33
+ }
34
+
35
+ const str = (v: unknown): string | null => (typeof v === 'string' && v !== '' ? v : null);
36
+
37
+ function safeName(value: string, re: RegExp, what: string): string {
38
+ if (!re.test(value) || value === '.' || value === '..')
39
+ throw new ApiError(`that is not a ${what}`, 400);
40
+ return value;
41
+ }
42
+
43
+ function projectDir(project: string, dir: string): string {
44
+ const path = join(dir, 'projects', safeName(project, PROJECT_RE, 'Claude project id'));
45
+ if (!existsSync(path)) throw new ApiError('no such Claude project', 404);
46
+ return path;
47
+ }
48
+
49
+ function parseLines(text: string): Record<string, unknown>[] {
50
+ const out: Record<string, unknown>[] = [];
51
+ for (const line of text.split('\n')) {
52
+ if (line.trim() === '') continue;
53
+ try {
54
+ const parsed: unknown = JSON.parse(line);
55
+ if (isRecord(parsed)) out.push(parsed);
56
+ } catch {
57
+ continue;
58
+ }
59
+ }
60
+ return out;
61
+ }
62
+
63
+ function readEdge(path: string, fromEnd: boolean): Record<string, unknown>[] {
64
+ const size = statSync(path).size;
65
+ const length = Math.min(EDGE_BYTES, size);
66
+ const fd = openSync(path, 'r');
67
+ try {
68
+ const buffer = Buffer.alloc(length);
69
+ readSync(fd, buffer, 0, length, fromEnd ? size - length : 0);
70
+ const text = buffer.toString('utf8');
71
+ const cut = fromEnd && length < size ? text.indexOf('\n') + 1 : 0;
72
+ return parseLines(text.slice(cut));
73
+ } finally {
74
+ closeSync(fd);
75
+ }
76
+ }
77
+
78
+ export interface ClaudeProject {
79
+ id: string;
80
+ cwd: string | null;
81
+ sessions: number;
82
+ lastActiveAt: string | null;
83
+ hasMemory: boolean;
84
+ }
85
+
86
+ function sessionFiles(path: string): { name: string; mtime: number; size: number }[] {
87
+ return readdirSync(path)
88
+ .filter((name) => name.endsWith('.jsonl'))
89
+ .map((name) => {
90
+ const s = statSync(join(path, name));
91
+ return { name, mtime: s.mtimeMs, size: s.size };
92
+ })
93
+ .sort((a, b) => b.mtime - a.mtime);
94
+ }
95
+
96
+ function cwdOf(path: string): string | null {
97
+ for (const line of readEdge(path, false)) {
98
+ const cwd = str(line.cwd);
99
+ if (cwd !== null) return cwd;
100
+ }
101
+ return null;
102
+ }
103
+
104
+ export function listClaudeProjects(dir = claudeDir()): ClaudeProject[] {
105
+ const root = join(dir, 'projects');
106
+ if (!existsSync(root)) return [];
107
+ return readdirSync(root, { withFileTypes: true })
108
+ .filter((entry) => entry.isDirectory() && PROJECT_RE.test(entry.name))
109
+ .map((entry) => {
110
+ const path = join(root, entry.name);
111
+ const files = sessionFiles(path);
112
+ const newest = files[0];
113
+ return {
114
+ id: entry.name,
115
+ cwd: newest === undefined ? null : cwdOf(join(path, newest.name)),
116
+ sessions: files.length,
117
+ lastActiveAt: newest === undefined ? null : new Date(newest.mtime).toISOString(),
118
+ hasMemory: existsSync(join(path, 'memory')),
119
+ };
120
+ })
121
+ .sort((a, b) => (b.lastActiveAt ?? '').localeCompare(a.lastActiveAt ?? ''));
122
+ }
123
+
124
+ export interface ClaudeSession {
125
+ id: string;
126
+ title: string;
127
+ startedAt: string | null;
128
+ lastAt: string;
129
+ bytes: number;
130
+ gitBranch: string | null;
131
+ version: string | null;
132
+ }
133
+
134
+ function firstPrompt(lines: Record<string, unknown>[]): string | null {
135
+ for (const line of lines) {
136
+ if (line.type !== 'user' || line.isSidechain === true) continue;
137
+ const message = isRecord(line.message) ? line.message : {};
138
+ const text = promptText(message.content);
139
+ if (text !== null && !NOISE.some((n) => text.startsWith(n))) return text.slice(0, 120);
140
+ }
141
+ return null;
142
+ }
143
+
144
+ function promptText(content: unknown): string | null {
145
+ if (typeof content === 'string') return content.trim() === '' ? null : content.trim();
146
+ if (!Array.isArray(content)) return null;
147
+ for (const block of content)
148
+ if (isRecord(block) && block.type === 'text') {
149
+ const text = str(block.text);
150
+ if (text !== null) return text.trim();
151
+ }
152
+ return null;
153
+ }
154
+
155
+ function sessionOf(path: string, id: string, mtime: number, size: number): ClaudeSession {
156
+ const head = readEdge(path, false);
157
+ const tail = readEdge(path, true);
158
+ const titled = [...tail].reverse().find((l) => l.type === 'ai-title' && str(l.aiTitle) !== null);
159
+ const meta = head.find((l) => l.type === 'user' || l.type === 'assistant') ?? {};
160
+ return {
161
+ id,
162
+ title: (titled === undefined ? null : str(titled.aiTitle)) ?? firstPrompt(head) ?? 'Untitled session',
163
+ startedAt: str(head.find((l) => str(l.timestamp) !== null)?.timestamp) ?? null,
164
+ lastAt: new Date(mtime).toISOString(),
165
+ bytes: size,
166
+ gitBranch: str(meta.gitBranch),
167
+ version: str(meta.version),
168
+ };
169
+ }
170
+
171
+ export function listClaudeSessions(project: string, dir = claudeDir()): ClaudeSession[] {
172
+ const path = projectDir(project, dir);
173
+ return sessionFiles(path).map((f) =>
174
+ sessionOf(join(path, f.name), f.name.slice(0, -'.jsonl'.length), f.mtime, f.size),
175
+ );
176
+ }
177
+
178
+ export type Block =
179
+ | { kind: 'text'; text: string }
180
+ | { kind: 'tool_use'; name: string; input: string }
181
+ | { kind: 'tool_result'; text: string; isError: boolean }
182
+ | { kind: 'thinking' }
183
+ | { kind: 'image' };
184
+
185
+ export interface TranscriptEntry {
186
+ uuid: string;
187
+ at: string | null;
188
+ role: 'user' | 'assistant';
189
+ blocks: Block[];
190
+ }
191
+
192
+ export interface TranscriptPage {
193
+ entries: TranscriptEntry[];
194
+ total: number;
195
+ next: number | null;
196
+ }
197
+
198
+ const cap = (text: string, max: number): string =>
199
+ text.length > max ? `${text.slice(0, max)}\n… (${String(text.length - max)} more characters)` : text;
200
+
201
+ function resultText(content: unknown): string {
202
+ if (typeof content === 'string') return content;
203
+ if (!Array.isArray(content)) return '';
204
+ return content
205
+ .map((b) => (isRecord(b) && b.type === 'text' ? (str(b.text) ?? '') : ''))
206
+ .filter((t) => t !== '')
207
+ .join('\n');
208
+ }
209
+
210
+ function textBlock(raw: Record<string, unknown>): Block | null {
211
+ const text = str(raw.text);
212
+ if (text === null || NOISE.some((n) => text.startsWith(n))) return null;
213
+ return { kind: 'text', text: cap(text, TEXT_CAP) };
214
+ }
215
+
216
+ const BLOCKS: Record<string, (raw: Record<string, unknown>) => Block | null> = {
217
+ text: textBlock,
218
+ tool_use: (raw) => ({
219
+ kind: 'tool_use',
220
+ name: str(raw.name) ?? 'tool',
221
+ input: cap(JSON.stringify(raw.input ?? {}, null, 1), INPUT_CAP),
222
+ }),
223
+ tool_result: (raw) => ({
224
+ kind: 'tool_result',
225
+ text: cap(resultText(raw.content), RESULT_CAP),
226
+ isError: raw.is_error === true,
227
+ }),
228
+ thinking: () => ({ kind: 'thinking' }),
229
+ image: () => ({ kind: 'image' }),
230
+ };
231
+
232
+ function blockOf(raw: unknown): Block | null {
233
+ if (!isRecord(raw) || typeof raw.type !== 'string') return null;
234
+ const make = BLOCKS[raw.type];
235
+ return make === undefined ? null : make(raw);
236
+ }
237
+
238
+ function entryOf(line: Record<string, unknown>): TranscriptEntry | null {
239
+ if ((line.type !== 'user' && line.type !== 'assistant') || line.isSidechain === true) return null;
240
+ const message = isRecord(line.message) ? line.message : {};
241
+ const content = message.content;
242
+ const blocks =
243
+ typeof content === 'string'
244
+ ? [blockOf({ type: 'text', text: content })]
245
+ : Array.isArray(content)
246
+ ? content.map(blockOf)
247
+ : [];
248
+ const kept = blocks.filter((b): b is Block => b !== null);
249
+ if (kept.length === 0) return null;
250
+ return { uuid: str(line.uuid) ?? '', at: str(line.timestamp), role: line.type, blocks: kept };
251
+ }
252
+
253
+ export async function readTranscript(
254
+ project: string,
255
+ session: string,
256
+ offset: number,
257
+ limit: number,
258
+ dir = claudeDir(),
259
+ ): Promise<TranscriptPage> {
260
+ const path = join(projectDir(project, dir), `${safeName(session, SESSION_RE, 'session id')}.jsonl`);
261
+ if (!existsSync(path)) throw new ApiError('no such session', 404);
262
+ const entries: TranscriptEntry[] = [];
263
+ let total = 0;
264
+ const lines = createInterface({ input: createReadStream(path, { encoding: 'utf8' }), crlfDelay: Infinity });
265
+ for await (const line of lines) {
266
+ const parsed = parseLines(line)[0];
267
+ const entry = parsed === undefined ? null : entryOf(parsed);
268
+ if (entry === null) continue;
269
+ if (total >= offset && entries.length < limit) entries.push(entry);
270
+ total += 1;
271
+ }
272
+ return { entries, total, next: offset + limit < total ? offset + limit : null };
273
+ }
274
+
275
+ export function deleteClaudeSession(project: string, session: string, dir = claudeDir()): void {
276
+ const root = projectDir(project, dir);
277
+ const id = safeName(session, SESSION_RE, 'session id');
278
+ const file = join(root, `${id}.jsonl`);
279
+ if (!existsSync(file)) throw new ApiError('no such session', 404);
280
+ rmSync(file);
281
+ rmSync(join(root, id), { recursive: true, force: true });
282
+ }
283
+
284
+ export interface MemoryFile {
285
+ name: string;
286
+ bytes: number;
287
+ modifiedAt: string;
288
+ }
289
+
290
+ export interface MemoryListing {
291
+ files: MemoryFile[];
292
+ index: string | null;
293
+ }
294
+
295
+ function memoryDir(project: string, dir: string): string {
296
+ return join(projectDir(project, dir), 'memory');
297
+ }
298
+
299
+ function readCapped(path: string): string {
300
+ const size = statSync(path).size;
301
+ const fd = openSync(path, 'r');
302
+ try {
303
+ const buffer = Buffer.alloc(Math.min(size, FILE_CAP));
304
+ readSync(fd, buffer, 0, buffer.length, 0);
305
+ return size > FILE_CAP ? `${buffer.toString('utf8')}\n\n… truncated` : buffer.toString('utf8');
306
+ } finally {
307
+ closeSync(fd);
308
+ }
309
+ }
310
+
311
+ export function listMemory(project: string, dir = claudeDir()): MemoryListing {
312
+ const path = memoryDir(project, dir);
313
+ if (!existsSync(path)) return { files: [], index: null };
314
+ const files = readdirSync(path)
315
+ .filter((name) => MEMORY_RE.test(name) && name !== 'MEMORY.md')
316
+ .map((name) => {
317
+ const s = statSync(join(path, name));
318
+ return { name, bytes: s.size, modifiedAt: s.mtime.toISOString() };
319
+ })
320
+ .sort((a, b) => a.name.localeCompare(b.name));
321
+ const index = join(path, 'MEMORY.md');
322
+ return { files, index: existsSync(index) ? readCapped(index) : null };
323
+ }
324
+
325
+ export function readMemoryFile(project: string, name: string, dir = claudeDir()): string {
326
+ const path = join(memoryDir(project, dir), safeName(name, MEMORY_RE, 'memory file name'));
327
+ if (!existsSync(path)) throw new ApiError('no such memory file', 404);
328
+ return readCapped(path);
329
+ }
@@ -0,0 +1,39 @@
1
+ import { hostname } from 'node:os';
2
+
3
+ const uiBase = (): string => process.env.METRO_UI_URL ?? 'https://metro.box';
4
+
5
+ const connectLink = (base: string): string =>
6
+ `${uiBase()}/#/${new URL(base).host}`;
7
+
8
+ const ownerLine = (owner: string | null): string =>
9
+ owner === null
10
+ ? 'No owner is set, so no wallet can sign in. Restart with: metro serve --owner <address>'
11
+ : `Only ${owner} can sign in.`;
12
+
13
+ export function localConnectHint(port: number, owner: string | null): string {
14
+ const here = `http://127.0.0.1:${String(port)}`;
15
+ const forward = `ssh -L ${String(port)}:127.0.0.1:${String(port)} ${hostname()}`;
16
+ return [
17
+ 'Manage this machine from the web UI:',
18
+ '',
19
+ ` ${connectLink(here)}`,
20
+ '',
21
+ `From another computer, forward the port first: ${forward}`,
22
+ ownerLine(owner),
23
+ '',
24
+ ].join('\n');
25
+ }
26
+
27
+ export function publicConnectHint(url: string, owner: string | null): string {
28
+ return [
29
+ 'Manage this machine from the web UI, from anywhere:',
30
+ '',
31
+ ` ${connectLink(url)}`,
32
+ '',
33
+ ownerLine(owner),
34
+ '',
35
+ ].join('\n');
36
+ }
37
+
38
+ export const tunnelPendingHint = (): string =>
39
+ 'Publishing this daemon through Tailscale Funnel… (if Tailscale asks to enable Funnel, its link is in the log)\n';
@@ -0,0 +1,307 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { errMsg, log } from './log.js';
3
+ import {
4
+ apiFailure,
5
+ apiSession,
6
+ projectParam,
7
+ bodyField,
8
+ cors,
9
+ readJsonBody,
10
+ sendJson,
11
+ type ApiSession,
12
+ } from './api-http.js';
13
+ import { parseId } from '../db/ids.js';
14
+ import {
15
+ handleCallback,
16
+ handleConnect,
17
+ hostOf,
18
+ startOAuth,
19
+ type OAuthRouteDeps,
20
+ } from './connector-oauth-routes.js';
21
+ import { ConnectorUnauthorized } from './connector-verify.js';
22
+ import type {
23
+ Connector,
24
+ ConnectorCheck,
25
+ ConnectorInput,
26
+ DeletedConnector,
27
+ } from '../db/connectors.js';
28
+
29
+ const PREFIX = '/api/connectors';
30
+
31
+ const asText = (value: unknown): string =>
32
+ typeof value === 'string' ? value : '';
33
+
34
+ export interface ConnectorApiDeps extends OAuthRouteDeps {
35
+ listConnectors: (subject: string, project: string) => Promise<Connector[]>;
36
+ createConnector: (
37
+ subject: string,
38
+ project: string,
39
+ input: ConnectorInput,
40
+ ) => Promise<Connector>;
41
+ verifyConnector: (subject: string, id: string) => Promise<ConnectorCheck>;
42
+ disconnectConnector: (subject: string, id: string) => Promise<Connector>;
43
+ renameConnector: (
44
+ subject: string,
45
+ id: string,
46
+ name: string,
47
+ ) => Promise<Connector>;
48
+ deleteConnector: (subject: string, id: string) => Promise<DeletedConnector>;
49
+ }
50
+
51
+ type Routable =
52
+ | { kind: 'collection' }
53
+ | { kind: 'callback' }
54
+ | { kind: 'connector'; id: string }
55
+ | { kind: 'verify'; id: string }
56
+ | { kind: 'connect'; id: string }
57
+ | { kind: 'disconnect'; id: string }
58
+ | { kind: 'rename'; id: string };
59
+
60
+ type Target = Routable | { kind: 'unknown' } | null;
61
+
62
+ function subTarget(id: string, rest: string[]): Target {
63
+ if (rest.length === 0) return { kind: 'connector', id };
64
+ if (rest.length > 1) return { kind: 'unknown' };
65
+ const head = rest[0];
66
+ if (head === 'verify') return { kind: 'verify', id };
67
+ if (head === 'connect') return { kind: 'connect', id };
68
+ if (head === 'disconnect') return { kind: 'disconnect', id };
69
+ if (head === 'rename') return { kind: 'rename', id };
70
+ return { kind: 'unknown' };
71
+ }
72
+
73
+ function target(path: string): Target {
74
+ if (path === PREFIX || path === `${PREFIX}/`) return { kind: 'collection' };
75
+ if (!path.startsWith(`${PREFIX}/`)) return null;
76
+ const segments = path.slice(PREFIX.length + 1).split('/').filter(Boolean);
77
+ const head = segments[0];
78
+ if (head === undefined) return { kind: 'collection' };
79
+ if (head === 'callback' && segments.length === 1) return { kind: 'callback' };
80
+ const id = parseId(head);
81
+ return id === null ? { kind: 'unknown' } : subTarget(id, segments.slice(1));
82
+ }
83
+
84
+ function connectorPayload(
85
+ row: Connector,
86
+ detail = false,
87
+ ): Record<string, unknown> {
88
+ const { catalog, ...summary } = row.verified;
89
+ return {
90
+ id: row.id,
91
+ name: row.name,
92
+ url: row.url,
93
+ transport: row.transport,
94
+ auth: row.auth,
95
+ header: row.header,
96
+ signIn: row.signIn,
97
+ verified: detail ? { ...summary, catalog } : summary,
98
+ };
99
+ }
100
+
101
+ async function handleList(
102
+ req: IncomingMessage,
103
+ res: ServerResponse,
104
+ deps: ConnectorApiDeps,
105
+ session: ApiSession,
106
+ ): Promise<void> {
107
+ const project = projectParam(req);
108
+ if (project === null) {
109
+ sendJson(req, res, 400, { error: 'a project is required' });
110
+ return;
111
+ }
112
+ const rows = await deps.listConnectors(session.subject, project);
113
+ sendJson(req, res, 200, {
114
+ connectors: rows.map((row) => connectorPayload(row)),
115
+ });
116
+ }
117
+
118
+ async function handleCreate(
119
+ req: IncomingMessage,
120
+ res: ServerResponse,
121
+ deps: ConnectorApiDeps,
122
+ session: ApiSession,
123
+ ): Promise<void> {
124
+ const body = await readJsonBody(req);
125
+ const project = projectParam(req);
126
+ if (project === null) {
127
+ sendJson(req, res, 400, { error: 'a project is required' });
128
+ return;
129
+ }
130
+ const offered = asText(bodyField(body, 'value')).trim() !== '';
131
+ try {
132
+ const created = await deps.createConnector(session.subject, project, {
133
+ name: bodyField(body, 'name'),
134
+ url: bodyField(body, 'url'),
135
+ header: bodyField(body, 'header'),
136
+ value: bodyField(body, 'value'),
137
+ });
138
+ log.info(
139
+ { id: created.id, name: created.name, host: hostOf(created.url) },
140
+ 'connector-api: created connector',
141
+ );
142
+ sendJson(req, res, 201, connectorPayload(created));
143
+ } catch (err) {
144
+ if (offered || !(err instanceof ConnectorUnauthorized)) throw err;
145
+ await startOAuth(req, res, deps, session, project, body, (row) =>
146
+ connectorPayload(row),
147
+ );
148
+ }
149
+ }
150
+
151
+ async function handleVerify(
152
+ req: IncomingMessage,
153
+ res: ServerResponse,
154
+ deps: ConnectorApiDeps,
155
+ session: ApiSession,
156
+ id: string,
157
+ ): Promise<void> {
158
+ const check = await deps.verifyConnector(session.subject, id);
159
+ log.info(
160
+ { id: check.id, name: check.name, ok: check.ok },
161
+ 'connector-api: re-verified connector',
162
+ );
163
+ sendJson(req, res, 200, check);
164
+ }
165
+
166
+ async function handleDisconnect(
167
+ req: IncomingMessage,
168
+ res: ServerResponse,
169
+ deps: ConnectorApiDeps,
170
+ session: ApiSession,
171
+ id: string,
172
+ ): Promise<void> {
173
+ const row = await deps.disconnectConnector(session.subject, id);
174
+ log.info(
175
+ { id: row.id, name: row.name },
176
+ 'connector-api: signed the connector out',
177
+ );
178
+ sendJson(req, res, 200, connectorPayload(row));
179
+ }
180
+
181
+ async function handleDelete(
182
+ req: IncomingMessage,
183
+ res: ServerResponse,
184
+ deps: ConnectorApiDeps,
185
+ session: ApiSession,
186
+ id: string,
187
+ ): Promise<void> {
188
+ const gone = await deps.deleteConnector(session.subject, id);
189
+ log.info(
190
+ { id: gone.id, name: gone.name },
191
+ 'connector-api: deleted connector',
192
+ );
193
+ sendJson(req, res, 200, { id: gone.id, name: gone.name, deleted: true });
194
+ }
195
+
196
+ async function handleConnector(
197
+ req: IncomingMessage,
198
+ res: ServerResponse,
199
+ deps: ConnectorApiDeps,
200
+ session: ApiSession,
201
+ id: string,
202
+ ): Promise<void> {
203
+ if (req.method !== 'GET') {
204
+ await handleDelete(req, res, deps, session, id);
205
+ return;
206
+ }
207
+ const row = await deps.getConnector(session.subject, id);
208
+ sendJson(req, res, 200, connectorPayload(row, true));
209
+ }
210
+
211
+ async function handleRename(
212
+ req: IncomingMessage,
213
+ res: ServerResponse,
214
+ deps: ConnectorApiDeps,
215
+ session: ApiSession,
216
+ id: string,
217
+ ): Promise<void> {
218
+ const name = bodyField(await readJsonBody(req), 'name');
219
+ if (typeof name !== 'string') {
220
+ sendJson(req, res, 400, { error: 'name is required' });
221
+ return;
222
+ }
223
+ const row = await deps.renameConnector(session.subject, id, name);
224
+ log.info({ id: row.id, name: row.name }, 'connector-api: renamed connector');
225
+ sendJson(req, res, 200, connectorPayload(row));
226
+ }
227
+
228
+ async function route(
229
+ req: IncomingMessage,
230
+ res: ServerResponse,
231
+ deps: ConnectorApiDeps,
232
+ session: ApiSession,
233
+ tgt: Routable,
234
+ ): Promise<void> {
235
+ try {
236
+ if (tgt.kind === 'callback') return;
237
+ if (tgt.kind === 'verify')
238
+ await handleVerify(req, res, deps, session, tgt.id);
239
+ else if (tgt.kind === 'connect')
240
+ await handleConnect(req, res, deps, session, tgt.id);
241
+ else if (tgt.kind === 'disconnect')
242
+ await handleDisconnect(req, res, deps, session, tgt.id);
243
+ else if (tgt.kind === 'rename')
244
+ await handleRename(req, res, deps, session, tgt.id);
245
+ else if (tgt.kind === 'connector')
246
+ await handleConnector(req, res, deps, session, tgt.id);
247
+ else if (req.method === 'GET') await handleList(req, res, deps, session);
248
+ else await handleCreate(req, res, deps, session);
249
+ } catch (err) {
250
+ apiFailure(req, res, err, 'connector-api');
251
+ }
252
+ }
253
+
254
+ const ALLOWED: Record<Routable['kind'], string[]> = {
255
+ collection: ['GET', 'POST'],
256
+ callback: ['GET'],
257
+ connector: ['GET', 'DELETE'],
258
+ verify: ['POST'],
259
+ connect: ['POST'],
260
+ disconnect: ['POST'],
261
+ rename: ['POST'],
262
+ };
263
+
264
+ async function dispatch(
265
+ req: IncomingMessage,
266
+ res: ServerResponse,
267
+ deps: ConnectorApiDeps,
268
+ tgt: Routable,
269
+ ): Promise<void> {
270
+ if (tgt.kind === 'callback') {
271
+ handleCallback(req, res, deps);
272
+ return;
273
+ }
274
+ const session = await apiSession(req);
275
+ if (!session) {
276
+ sendJson(req, res, 401, { error: 'unauthorized' });
277
+ return;
278
+ }
279
+ await route(req, res, deps, session, tgt);
280
+ }
281
+
282
+ export function handleConnectorApiRequest(
283
+ req: IncomingMessage,
284
+ res: ServerResponse,
285
+ deps: ConnectorApiDeps,
286
+ ): boolean {
287
+ const tgt = target((req.url ?? '').split('?')[0] ?? '');
288
+ if (tgt === null) return false;
289
+ if (req.method === 'OPTIONS') {
290
+ res.writeHead(204, cors(req)).end();
291
+ return true;
292
+ }
293
+ if (tgt.kind === 'unknown') {
294
+ sendJson(req, res, 404, { error: 'no such connector' });
295
+ return true;
296
+ }
297
+ if (!ALLOWED[tgt.kind].includes(req.method ?? '')) {
298
+ sendJson(req, res, 405, { error: 'method not allowed' });
299
+ return true;
300
+ }
301
+ dispatch(req, res, deps, tgt).catch((err: unknown) => {
302
+ log.warn({ err: errMsg(err) }, 'connector-api: unhandled error');
303
+ if (!res.headersSent)
304
+ sendJson(req, res, 500, { error: 'connector api failed' });
305
+ });
306
+ return true;
307
+ }
@@ -0,0 +1,21 @@
1
+ export interface RelayServerEntry {
2
+ id: string;
3
+ name: string;
4
+ }
5
+
6
+ export const NAME_PREFIX = 'metro.box ';
7
+
8
+ export function relayServersJson(
9
+ entries: RelayServerEntry[],
10
+ base: string,
11
+ cliToken: string,
12
+ ): string {
13
+ const mcpServers: Record<string, unknown> = {};
14
+ for (const entry of entries)
15
+ mcpServers[`${NAME_PREFIX}${entry.name}`] = {
16
+ type: 'http',
17
+ url: `${base}/relay/${entry.id}`,
18
+ headers: { Authorization: `Bearer ${cliToken}` },
19
+ };
20
+ return JSON.stringify({ mcpServers }, null, 2);
21
+ }