@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,277 @@
1
+ import { join } from 'node:path';
2
+ import { readJson, writeSecure } from '../daemon/secure-fs.js';
3
+ import { errMsg, log } from '../daemon/log.js';
4
+ import {
5
+ connectorUrlText,
6
+ ConnectorVerifyError,
7
+ parseConnectorUrl,
8
+ verifyRemoteMcp,
9
+ type ConnectorAuth,
10
+ type OAuthAuth,
11
+ } from '../daemon/connector-verify.js';
12
+ import { oauthExpired, refreshOAuth } from '../daemon/connector-oauth.js';
13
+ import { advertisesOAuth } from '../daemon/oauth-discovery.js';
14
+ import {
15
+ ConnectorError,
16
+ connectorAuth,
17
+ connectorName,
18
+ readConfig,
19
+ stamp,
20
+ type ConnectorConfig,
21
+ } from './connector-config.js';
22
+ import {
23
+ bearerHeaders,
24
+ fixedTarget,
25
+ unrefreshedTarget,
26
+ type RelayTarget,
27
+ } from './connector-relay.js';
28
+ import {
29
+ connectorFromRow,
30
+ UNVERIFIED,
31
+ type Connector,
32
+ type ConnectorCheck,
33
+ type ConnectorInput,
34
+ type DeletedConnector,
35
+ } from './connectors.js';
36
+ import { agentsDir } from './file-source.js';
37
+ import { assertLocalOwner, LOCAL_PROJECT_ID } from './file-admin.js';
38
+ import { newId } from './ids.js';
39
+ import type { LoadedConnector } from './materialize.js';
40
+
41
+ export interface LocalConnectorRow {
42
+ id: string;
43
+ name: string;
44
+ url: string;
45
+ transport: 'http';
46
+ config: ConnectorConfig;
47
+ }
48
+
49
+ const FILE = 'connectors.json';
50
+ const filePath = (dir: string): string => join(dir, FILE);
51
+
52
+ const isRow = (v: unknown): v is { id: string; name: string; url: string; config?: unknown } =>
53
+ typeof v === 'object' &&
54
+ v !== null &&
55
+ typeof (v as { id?: unknown }).id === 'string' &&
56
+ typeof (v as { name?: unknown }).name === 'string' &&
57
+ typeof (v as { url?: unknown }).url === 'string';
58
+
59
+ const asList = (value: unknown): unknown[] =>
60
+ Array.isArray(value) ? value.map((item: unknown) => item) : [];
61
+
62
+ export function readLocalConnectors(dir = agentsDir()): LocalConnectorRow[] {
63
+ const raw = readJson<{ connectors?: unknown }>(filePath(dir), {}, {
64
+ warn: 'connectors.json: malformed, ignoring',
65
+ });
66
+ return asList(raw.connectors).filter(isRow).map((r) => ({
67
+ id: r.id,
68
+ name: r.name,
69
+ url: r.url,
70
+ transport: 'http' as const,
71
+ config: readConfig(r.config),
72
+ }));
73
+ }
74
+
75
+ function writeRows(dir: string, rows: LocalConnectorRow[]): void {
76
+ writeSecure(filePath(dir), `${JSON.stringify({ version: 1, connectors: rows }, null, 2)}\n`);
77
+ }
78
+
79
+ const missing = (): ConnectorError => new ConnectorError('no such connector', 404);
80
+
81
+ function ownedRows(subject: string, project: string, dir: string): LocalConnectorRow[] {
82
+ assertLocalOwner(subject, dir);
83
+ if (project !== LOCAL_PROJECT_ID) throw new ConnectorError('no such project', 404);
84
+ return readLocalConnectors(dir);
85
+ }
86
+
87
+ function rowOrThrow(subject: string, id: string, dir: string): LocalConnectorRow {
88
+ assertLocalOwner(subject, dir);
89
+ const row = readLocalConnectors(dir).find((r) => r.id === id);
90
+ if (row === undefined) throw missing();
91
+ return row;
92
+ }
93
+
94
+ function replace(dir: string, row: LocalConnectorRow): Connector {
95
+ writeRows(dir, readLocalConnectors(dir).map((r) => (r.id === row.id ? row : r)));
96
+ return connectorFromRow(row);
97
+ }
98
+
99
+ async function oauthCapable(url: URL, auth: ConnectorAuth): Promise<boolean> {
100
+ if (auth.kind === 'oauth') return true;
101
+ if (auth.kind === 'header') return false;
102
+ return advertisesOAuth(url);
103
+ }
104
+
105
+ export function localImportConnectors(rows: LoadedConnector[], dir = agentsDir()): number {
106
+ const current = readLocalConnectors(dir);
107
+ for (const row of rows) {
108
+ const other = current.find((r) => r.id !== row.id && r.name === row.name);
109
+ if (other !== undefined)
110
+ throw new ConnectorError(`a connector named '${row.name}' already exists on this daemon; rename it first`, 409);
111
+ }
112
+ const imported: LocalConnectorRow[] = rows.map((row) => ({
113
+ id: row.id,
114
+ name: row.name,
115
+ url: row.url,
116
+ transport: 'http',
117
+ config: readConfig(row.config),
118
+ }));
119
+ const ids = new Set(imported.map((r) => r.id));
120
+ writeRows(dir, [...current.filter((r) => !ids.has(r.id)), ...imported]);
121
+ return imported.length;
122
+ }
123
+
124
+ export async function localListConnectors(
125
+ subject: string,
126
+ project: string,
127
+ dir = agentsDir(),
128
+ ): Promise<Connector[]> {
129
+ return Promise.resolve(ownedRows(subject, project, dir).map(connectorFromRow));
130
+ }
131
+
132
+ export async function localGetConnector(subject: string, id: string, dir = agentsDir()): Promise<Connector> {
133
+ return Promise.resolve(connectorFromRow(rowOrThrow(subject, id, dir)));
134
+ }
135
+
136
+ function assertNameFree(name: string, exceptId: string | null, dir: string): void {
137
+ if (readLocalConnectors(dir).some((r) => r.id !== exceptId && r.name === name))
138
+ throw new ConnectorError(`a connector named '${name}' already exists on this daemon`, 409);
139
+ }
140
+
141
+ function insert(dir: string, name: string, url: URL, config: ConnectorConfig): Connector {
142
+ assertNameFree(name, null, dir);
143
+ const row: LocalConnectorRow = { id: newId(), name, url: connectorUrlText(url), transport: 'http', config };
144
+ writeRows(dir, [...readLocalConnectors(dir), row]);
145
+ return connectorFromRow(row);
146
+ }
147
+
148
+ export async function localCreateConnector(
149
+ subject: string,
150
+ project: string,
151
+ input: ConnectorInput,
152
+ dir = agentsDir(),
153
+ ): Promise<Connector> {
154
+ ownedRows(subject, project, dir);
155
+ const name = connectorName(input.name);
156
+ const url = parseConnectorUrl(input.url);
157
+ const auth = connectorAuth(input.header, input.value);
158
+ const verified = stamp(await verifyRemoteMcp(url, auth));
159
+ return insert(dir, name, url, {
160
+ auth,
161
+ createdAt: new Date().toISOString(),
162
+ verified,
163
+ oauth: await oauthCapable(url, auth),
164
+ });
165
+ }
166
+
167
+ export async function localCreatePendingConnector(
168
+ subject: string,
169
+ project: string,
170
+ input: { name: unknown; url: unknown },
171
+ dir = agentsDir(),
172
+ ): Promise<Connector> {
173
+ ownedRows(subject, project, dir);
174
+ const name = connectorName(input.name);
175
+ const url = parseConnectorUrl(input.url);
176
+ return Promise.resolve(
177
+ insert(dir, name, url, { auth: { kind: 'none' }, createdAt: new Date().toISOString(), verified: UNVERIFIED, oauth: true }),
178
+ );
179
+ }
180
+
181
+ export async function localReconnectConnector(
182
+ subject: string,
183
+ id: string,
184
+ auth: OAuthAuth,
185
+ dir = agentsDir(),
186
+ ): Promise<Connector> {
187
+ const row = rowOrThrow(subject, id, dir);
188
+ const verified = stamp(await verifyRemoteMcp(parseConnectorUrl(row.url), auth));
189
+ return replace(dir, { ...row, config: { ...row.config, auth, verified, oauth: true } });
190
+ }
191
+
192
+ export async function localDisconnectConnector(subject: string, id: string, dir = agentsDir()): Promise<Connector> {
193
+ const row = rowOrThrow(subject, id, dir);
194
+ if (row.config.auth.kind !== 'oauth') throw new ConnectorError('that connector is not signed in', 400);
195
+ return Promise.resolve(replace(dir, { ...row, config: { ...row.config, auth: { kind: 'none' }, oauth: true } }));
196
+ }
197
+
198
+ async function freshAuth(auth: ConnectorAuth, resource: string): Promise<ConnectorAuth> {
199
+ if (auth.kind !== 'oauth' || !oauthExpired(auth)) return auth;
200
+ return refreshOAuth(auth, resource);
201
+ }
202
+
203
+ export async function localVerifyConnector(subject: string, id: string, dir = agentsDir()): Promise<ConnectorCheck> {
204
+ const row = rowOrThrow(subject, id, dir);
205
+ try {
206
+ const url = parseConnectorUrl(row.url);
207
+ const auth = await freshAuth(row.config.auth, url.toString());
208
+ const verified = stamp(await verifyRemoteMcp(url, auth));
209
+ const oauth = row.config.oauth || (await oauthCapable(url, auth));
210
+ replace(dir, { ...row, config: { ...row.config, auth, verified, oauth } });
211
+ return { id: row.id, name: row.name, ok: true, verified };
212
+ } catch (err) {
213
+ if (!(err instanceof ConnectorVerifyError)) throw err;
214
+ return { id: row.id, name: row.name, ok: false, reason: err.message };
215
+ }
216
+ }
217
+
218
+ export async function localRenameConnector(subject: string, id: string, raw: string, dir = agentsDir()): Promise<Connector> {
219
+ const name = connectorName(raw);
220
+ const row = rowOrThrow(subject, id, dir);
221
+ assertNameFree(name, id, dir);
222
+ return Promise.resolve(replace(dir, { ...row, name }));
223
+ }
224
+
225
+ export async function localDeleteConnector(subject: string, id: string, dir = agentsDir()): Promise<DeletedConnector> {
226
+ const row = rowOrThrow(subject, id, dir);
227
+ writeRows(dir, readLocalConnectors(dir).filter((r) => r.id !== id));
228
+ return Promise.resolve({ id: row.id, name: row.name });
229
+ }
230
+
231
+ const byIds = (ids: string[], dir: string): LocalConnectorRow[] =>
232
+ readLocalConnectors(dir).filter((r) => ids.includes(r.id));
233
+
234
+ export async function localConnectorNamesByIds(ids: string[], dir = agentsDir()): Promise<{ id: string; name: string }[]> {
235
+ return Promise.resolve(byIds(ids, dir).map((r) => ({ id: r.id, name: r.name })));
236
+ }
237
+
238
+ const inflight = new Map<string, Promise<OAuthAuth>>();
239
+
240
+ function refreshOnce(row: LocalConnectorRow, auth: OAuthAuth, dir: string): Promise<OAuthAuth> {
241
+ const running = inflight.get(row.id);
242
+ if (running !== undefined) return running;
243
+ const job = refreshOAuth(auth, parseConnectorUrl(row.url).toString())
244
+ .then((fresh) => {
245
+ replace(dir, { ...row, config: { ...row.config, auth: fresh } });
246
+ return fresh;
247
+ })
248
+ .finally(() => {
249
+ inflight.delete(row.id);
250
+ });
251
+ inflight.set(row.id, job);
252
+ return job;
253
+ }
254
+
255
+ async function oauthTarget(row: LocalConnectorRow, auth: OAuthAuth, force: boolean, dir: string): Promise<RelayTarget> {
256
+ if (!force && !oauthExpired(auth)) return { kind: 'ok', url: row.url, headers: bearerHeaders(auth.accessToken) };
257
+ try {
258
+ const fresh = await refreshOnce(row, auth, dir);
259
+ return { kind: 'ok', url: row.url, headers: bearerHeaders(fresh.accessToken) };
260
+ } catch (err) {
261
+ log.warn({ id: row.id, err: errMsg(err) }, 'local relay: token refresh failed');
262
+ return unrefreshedTarget(row.url, auth, force);
263
+ }
264
+ }
265
+
266
+ export async function localRelayTarget(
267
+ connectorId: string,
268
+ force: boolean,
269
+ dir = agentsDir(),
270
+ ): Promise<RelayTarget> {
271
+ const row = readLocalConnectors(dir).find((r) => r.id === connectorId);
272
+ if (row === undefined) return { kind: 'missing' };
273
+ parseConnectorUrl(row.url);
274
+ const auth = row.config.auth;
275
+ if (auth.kind === 'oauth') return oauthTarget(row, auth, force, dir);
276
+ return fixedTarget(row.url, auth, force);
277
+ }
@@ -0,0 +1,253 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ readFileSync,
5
+ rmSync,
6
+ writeFileSync,
7
+ } from 'node:fs';
8
+ import { homedir } from 'node:os';
9
+ import { join } from 'node:path';
10
+ import { log } from '../daemon/log.js';
11
+ import { writeSecure } from '../daemon/secure-fs.js';
12
+ import { isLocalMode, trainsDir } from '../daemon/paths.js';
13
+ import {
14
+ setAgentMap,
15
+ setAllowlistMap,
16
+ type AgentMap,
17
+ type AgentNameMap,
18
+ type AllowlistMap,
19
+ } from './agent-map.js';
20
+ import { setKeyMap } from './key-map.js';
21
+ import { STATIONS, type StationName } from './stations.js';
22
+
23
+ interface StationTarget {
24
+ file: string;
25
+ fileEnv: string;
26
+ trainImport: string | null;
27
+ }
28
+
29
+ export interface LoadedAccount {
30
+ station: StationName;
31
+ id: string;
32
+ allowlist: string[] | null;
33
+ config: Record<string, unknown>;
34
+ }
35
+
36
+ export interface LoadedConnector {
37
+ id: string;
38
+ name: string;
39
+ url: string;
40
+ transport: 'http';
41
+ config: Record<string, unknown>;
42
+ }
43
+
44
+ export interface LoadedAgent {
45
+ id: string;
46
+ name: string;
47
+ accounts: LoadedAccount[];
48
+ key: string | null;
49
+ connectors?: LoadedConnector[];
50
+ }
51
+
52
+ const METRO_DIR = join(homedir(), '.metro');
53
+
54
+ const STATION_TARGETS: Record<StationName, StationTarget> = {
55
+ xmtp: {
56
+ file: 'xmtp-accounts.json',
57
+ fileEnv: 'XMTP_ACCOUNTS_FILE',
58
+ trainImport: '@metro-labs/xmtp/train',
59
+ },
60
+ 'telegram-bot': {
61
+ file: 'telegram-bot-accounts.json',
62
+ fileEnv: 'TELEGRAM_BOT_ACCOUNTS_FILE',
63
+ trainImport: '@metro-labs/telegram-bot/train',
64
+ },
65
+ 'telegram': {
66
+ file: 'telegram-accounts.json',
67
+ fileEnv: 'TELEGRAM_ACCOUNTS_FILE',
68
+ trainImport: '@metro-labs/telegram/train',
69
+ },
70
+ 'discord-bot': {
71
+ file: 'discord-bot-accounts.json',
72
+ fileEnv: 'DISCORD_BOT_ACCOUNTS_FILE',
73
+ trainImport: '@metro-labs/discord-bot/train',
74
+ },
75
+ whatsapp: {
76
+ file: 'whatsapp-accounts.json',
77
+ fileEnv: 'WHATSAPP_ACCOUNTS_FILE',
78
+ trainImport: '@metro-labs/whatsapp/train',
79
+ },
80
+ webhook: {
81
+ file: 'webhook-accounts.json',
82
+ fileEnv: 'WEBHOOK_ACCOUNTS_FILE',
83
+ trainImport: null,
84
+ },
85
+ };
86
+
87
+ function accountFilePath(station: StationName): string {
88
+ const target = STATION_TARGETS[station];
89
+ return process.env[target.fileEnv] ?? join(METRO_DIR, target.file);
90
+ }
91
+
92
+ export type StationSource = () => Promise<LoadedAgent[]>;
93
+
94
+ export const MOVABLE_STATIONS = new Set<StationName>(
95
+ STATIONS.filter((s) => s !== 'webhook'),
96
+ );
97
+
98
+ function trainStubPath(station: StationName): string {
99
+ return join(trainsDir(), `${station}.ts`);
100
+ }
101
+
102
+ function currentText(path: string): string | null {
103
+ try {
104
+ return readFileSync(path, 'utf8');
105
+ } catch {
106
+ return null;
107
+ }
108
+ }
109
+
110
+ export function writeIfChanged(path: string, content: string): boolean {
111
+ if (currentText(path) === content) return false;
112
+ writeFileSync(path, content);
113
+ return true;
114
+ }
115
+
116
+ export const stationRunsHere = (station: StationName): boolean =>
117
+ isLocalMode() || !MOVABLE_STATIONS.has(station);
118
+
119
+ interface WrittenStations {
120
+ active: Map<StationName, number>;
121
+ changed: StationName[];
122
+ }
123
+
124
+ function writeStations(list: LoadedAgent[]): WrittenStations {
125
+ mkdirSync(METRO_DIR, { recursive: true });
126
+ mkdirSync(trainsDir(), { recursive: true });
127
+
128
+ const byStation = new Map<StationName, LoadedAccount[]>();
129
+ const map: AgentMap = {};
130
+ const names: AgentNameMap = {};
131
+ const allow: AllowlistMap = {};
132
+ for (const agent of list) {
133
+ names[agent.id] = agent.name;
134
+ for (const a of agent.accounts) {
135
+ if (!(a.station in STATION_TARGETS)) {
136
+ log.error(
137
+ { station: a.station, id: a.id },
138
+ 'unknown station — this metro is older or newer than the row; skipping it',
139
+ );
140
+ continue;
141
+ }
142
+ map[`${a.station}/${a.id}`] = agent.id;
143
+ if (a.allowlist) allow[`${a.station}/${a.id}`] = a.allowlist;
144
+ if (stationRunsHere(a.station)) {
145
+ const cur = byStation.get(a.station);
146
+ if (cur) cur.push(a);
147
+ else byStation.set(a.station, [a]);
148
+ }
149
+ }
150
+ }
151
+ setAgentMap(map, names);
152
+ setAllowlistMap(allow);
153
+
154
+ const active = new Map<StationName, number>();
155
+ const changed: StationName[] = [];
156
+ for (const [station, accts] of byStation) {
157
+ const records = accts.map((a) => ({ id: a.id, ...a.config }));
158
+ const path = accountFilePath(station);
159
+ const content = JSON.stringify(records, null, 2);
160
+ if (currentText(path) !== content) {
161
+ writeSecure(path, content);
162
+ changed.push(station);
163
+ }
164
+ const { trainImport } = STATION_TARGETS[station];
165
+ if (trainImport !== null)
166
+ writeIfChanged(trainStubPath(station), `import '${trainImport}';\n`);
167
+ active.set(station, accts.length);
168
+ }
169
+ return { active, changed };
170
+ }
171
+
172
+ const BLANKED = '[]\n';
173
+
174
+ function pruneStations(present: Map<StationName, number>): StationName[] {
175
+ const removed: StationName[] = [];
176
+ for (const station of Object.keys(STATION_TARGETS) as StationName[]) {
177
+ if (present.has(station)) continue;
178
+ const hasTrain = STATION_TARGETS[station].trainImport !== null;
179
+ const stub = trainStubPath(station);
180
+ const stubGone = !hasTrain || !existsSync(stub);
181
+ const file = currentText(accountFilePath(station));
182
+ const fileInert = file === null || file === BLANKED;
183
+ if (stubGone && fileInert) continue;
184
+ if (!fileInert) writeSecure(accountFilePath(station), BLANKED);
185
+ if (hasTrain) rmSync(stub, { force: true });
186
+ removed.push(station);
187
+ }
188
+ return removed;
189
+ }
190
+
191
+ const stationLabels = (m: Map<StationName, number>): string[] =>
192
+ [...m].map(([station, n]) => `${station}(${n})`);
193
+
194
+ function applyKeyMap(list: LoadedAgent[]): void {
195
+ lastKey = list.find((a) => a.key !== null)?.key ?? null;
196
+ setKeyMap(
197
+ list.flatMap((agent) =>
198
+ agent.key === null ? [] : [{ key: agent.key, agentId: agent.id }],
199
+ ),
200
+ );
201
+ }
202
+
203
+ let lastKey: string | null = null;
204
+
205
+ export function localAgentKey(): string | null {
206
+ return lastKey;
207
+ }
208
+
209
+ async function loadAndWrite(
210
+ source: StationSource,
211
+ ): Promise<WrittenStations & { agents: number }> {
212
+ const list = await source();
213
+ applyKeyMap(list);
214
+ return { ...writeStations(list), agents: list.length };
215
+ }
216
+
217
+ export interface MaterializeOptions {
218
+ allowEmpty?: boolean;
219
+ }
220
+
221
+ export async function materializeFrom(
222
+ source: StationSource,
223
+ opts: MaterializeOptions = {},
224
+ ): Promise<void> {
225
+ const { active, agents: found } = await loadAndWrite(source);
226
+
227
+ if (found === 0 && opts.allowEmpty !== true)
228
+ throw new Error('no agents found — nothing to materialize');
229
+ const removed = pruneStations(active);
230
+ log.info(
231
+ { stations: stationLabels(active), agents: found, removed },
232
+ 'materialized station accounts',
233
+ );
234
+ }
235
+
236
+ export interface ReloadedStations {
237
+ active: StationName[];
238
+ removed: StationName[];
239
+ changed: StationName[];
240
+ }
241
+
242
+ export async function reloadFrom(
243
+ source: StationSource,
244
+ ): Promise<ReloadedStations> {
245
+ const { active, changed } = await loadAndWrite(source);
246
+ const removed = pruneStations(active);
247
+ if (removed.length > 0 || changed.length > 0)
248
+ log.info(
249
+ { stations: stationLabels(active), removed, changed },
250
+ 'reloaded station accounts',
251
+ );
252
+ return { active: [...active.keys()], removed, changed };
253
+ }
@@ -0,0 +1,33 @@
1
+ import { index, jsonb, pgTable, text, uniqueIndex } from 'drizzle-orm/pg-core';
2
+
3
+ import { STATIONS, type ConnectorTransport, type StationName } from './stations.js';
4
+
5
+ export { STATIONS, type ConnectorTransport, type StationName };
6
+
7
+ export type ProjectRole = 'admin' | 'member';
8
+
9
+
10
+ export const vaults = pgTable(
11
+ 'vaults',
12
+ {
13
+ id: text('id').primaryKey(),
14
+ owner: text('owner').notNull(),
15
+ name: text('name').notNull(),
16
+ stations: jsonb('stations').notNull(),
17
+ envelope: jsonb('envelope').notNull(),
18
+ syncedAt: text('synced_at').notNull(),
19
+ },
20
+ (t) => [index('vaults_owner_idx').on(t.owner)],
21
+ );
22
+
23
+ export const servers = pgTable(
24
+ 'servers',
25
+ {
26
+ id: text('id').primaryKey(),
27
+ owner: text('owner').notNull(),
28
+ host: text('host').notNull(),
29
+ name: text('name'),
30
+ addedAt: text('added_at').notNull(),
31
+ },
32
+ (t) => [uniqueIndex('servers_owner_host_idx').on(t.owner, t.host), index('servers_owner_idx').on(t.owner)],
33
+ );
@@ -0,0 +1,83 @@
1
+ import { and, asc, eq } from 'drizzle-orm';
2
+ import { ApiError } from '../daemon/api-error.js';
3
+ import { isRecord } from '../daemon/is-record.js';
4
+ import { getDb } from './client.js';
5
+ import { newId, parseId } from './ids.js';
6
+ import { servers } from './schema.js';
7
+ import { normalizeAddress } from './address.js';
8
+ import { parseServerHost, parseServerName, type ServerEntry } from '../daemon/server-types.js';
9
+
10
+ export class ServerListError extends ApiError {}
11
+
12
+ const missing = (): ServerListError => new ServerListError('no such server', 404);
13
+
14
+ function ownerOf(subject: string): string {
15
+ const address = normalizeAddress(subject);
16
+ if (address === null) throw new ServerListError('the server list needs a signed identity', 403);
17
+ return address;
18
+ }
19
+
20
+ function idOf(raw: string): string {
21
+ const id = parseId(raw);
22
+ if (id === null) throw missing();
23
+ return id;
24
+ }
25
+
26
+ const entryOf = (row: { id: string; host: string; name: string | null; addedAt: string }): ServerEntry => ({
27
+ id: row.id,
28
+ host: row.host,
29
+ name: row.name,
30
+ addedAt: row.addedAt,
31
+ });
32
+
33
+ const columns = { id: servers.id, host: servers.host, name: servers.name, addedAt: servers.addedAt };
34
+
35
+ export async function listServersForOwner(subject: string): Promise<ServerEntry[]> {
36
+ const owner = ownerOf(subject);
37
+ const rows = await getDb().select(columns).from(servers).where(eq(servers.owner, owner)).orderBy(asc(servers.addedAt));
38
+ return rows.map(entryOf);
39
+ }
40
+
41
+ export async function addServerForOwner(subject: string, body: unknown): Promise<ServerEntry> {
42
+ const owner = ownerOf(subject);
43
+ const host = parseServerHost(isRecord(body) && typeof body.host === 'string' ? body.host : '');
44
+ if (host === null) throw new ServerListError('host is not a server address', 400);
45
+ const name = parseServerName(isRecord(body) ? body.name : undefined);
46
+ const db = getDb();
47
+ const held = await db.select(columns).from(servers).where(and(eq(servers.owner, owner), eq(servers.host, host)));
48
+ const row = held[0];
49
+ if (row !== undefined) {
50
+ if (name === null) return entryOf(row);
51
+ await db.update(servers).set({ name }).where(eq(servers.id, row.id));
52
+ return entryOf({ ...row, name });
53
+ }
54
+ const next = { id: newId(), owner, host, name, addedAt: new Date().toISOString() };
55
+ await db.insert(servers).values(next);
56
+ return entryOf(next);
57
+ }
58
+
59
+ export async function renameServerForOwner(subject: string, rawId: string, body: unknown): Promise<ServerEntry> {
60
+ const owner = ownerOf(subject);
61
+ const id = idOf(rawId);
62
+ const name = parseServerName(isRecord(body) ? body.name : undefined);
63
+ const rows = await getDb()
64
+ .update(servers)
65
+ .set({ name })
66
+ .where(and(eq(servers.id, id), eq(servers.owner, owner)))
67
+ .returning(columns);
68
+ const row = rows[0];
69
+ if (row === undefined) throw missing();
70
+ return entryOf(row);
71
+ }
72
+
73
+ export async function deleteServerForOwner(subject: string, rawId: string): Promise<{ id: string; host: string }> {
74
+ const owner = ownerOf(subject);
75
+ const id = idOf(rawId);
76
+ const gone = await getDb()
77
+ .delete(servers)
78
+ .where(and(eq(servers.id, id), eq(servers.owner, owner)))
79
+ .returning({ id: servers.id, host: servers.host });
80
+ const row = gone[0];
81
+ if (row === undefined) throw missing();
82
+ return row;
83
+ }
@@ -0,0 +1,12 @@
1
+ export const STATIONS = [
2
+ 'xmtp',
3
+ 'telegram-bot',
4
+ 'telegram',
5
+ 'discord-bot',
6
+ 'whatsapp',
7
+ 'webhook',
8
+ ] as const;
9
+
10
+ export type StationName = (typeof STATIONS)[number];
11
+
12
+ export type ConnectorTransport = 'http' | 'sse';