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

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 (328) 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/senders.ts +38 -0
  65. package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +207 -0
  66. package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +39 -0
  67. package/runtime/node_modules/@metro-labs/daemon/src/boot/crash-guard.ts +49 -0
  68. package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +14 -0
  69. package/runtime/node_modules/@metro-labs/daemon/src/boot/paths.ts +106 -0
  70. package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +25 -0
  71. package/runtime/node_modules/@metro-labs/daemon/src/channels/dedupe.ts +24 -0
  72. package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +377 -0
  73. package/runtime/node_modules/@metro-labs/daemon/src/channels/media-note.ts +85 -0
  74. package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +75 -0
  75. package/runtime/node_modules/@metro-labs/daemon/src/channels/relay.ts +129 -0
  76. package/runtime/node_modules/@metro-labs/daemon/src/channels/webhook-note.ts +51 -0
  77. package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +309 -0
  78. package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +358 -0
  79. package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +169 -0
  80. package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +56 -0
  81. package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +140 -0
  82. package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +90 -0
  83. package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +253 -0
  84. package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +117 -0
  85. package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +239 -0
  86. package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +154 -0
  87. package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +109 -0
  88. package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +310 -0
  89. package/runtime/node_modules/@metro-labs/daemon/src/connectors/cli-api.ts +63 -0
  90. package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +175 -0
  91. package/runtime/node_modules/@metro-labs/daemon/src/connectors/json.ts +21 -0
  92. package/runtime/node_modules/@metro-labs/daemon/src/connectors/model.ts +77 -0
  93. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-client.ts +166 -0
  94. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-discovery.ts +139 -0
  95. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-pending.ts +65 -0
  96. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-routes.ts +171 -0
  97. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth.ts +155 -0
  98. package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +122 -0
  99. package/runtime/node_modules/@metro-labs/daemon/src/connectors/reach.ts +27 -0
  100. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-target.ts +39 -0
  101. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +296 -0
  102. package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +286 -0
  103. package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +98 -0
  104. package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +70 -0
  105. package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +332 -0
  106. package/runtime/node_modules/@metro-labs/daemon/src/connectors/watch.ts +54 -0
  107. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +99 -0
  108. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-owner.ts +32 -0
  109. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +121 -0
  110. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +294 -0
  111. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-store.ts +181 -0
  112. package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +259 -0
  113. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +231 -0
  114. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
  115. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +288 -0
  116. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +203 -0
  117. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +231 -0
  118. package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +123 -0
  119. package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +187 -0
  120. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +229 -0
  121. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +269 -0
  122. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +207 -0
  123. package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +90 -0
  124. package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +65 -0
  125. package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +17 -0
  126. package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +229 -0
  127. package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +151 -0
  128. package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +216 -0
  129. package/runtime/node_modules/@metro-labs/daemon/src/mcp/channel-owner.ts +29 -0
  130. package/runtime/node_modules/@metro-labs/daemon/src/mcp/ctx.ts +62 -0
  131. package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +91 -0
  132. package/runtime/node_modules/@metro-labs/daemon/src/mcp/frame-scope.ts +21 -0
  133. package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +129 -0
  134. package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +191 -0
  135. package/runtime/node_modules/@metro-labs/daemon/src/mcp/member-tools.ts +72 -0
  136. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +66 -0
  137. package/runtime/node_modules/@metro-labs/daemon/src/mcp/raw-get-stream.ts +207 -0
  138. package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +47 -0
  139. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-registry.ts +172 -0
  140. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-route.ts +43 -0
  141. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +215 -0
  142. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-dispatch.ts +215 -0
  143. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-schemas.ts +400 -0
  144. package/runtime/node_modules/@metro-labs/daemon/src/mcp/upload-tool.ts +66 -0
  145. package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +237 -0
  146. package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +389 -0
  147. package/runtime/node_modules/@metro-labs/daemon/src/routes/body.ts +24 -0
  148. package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +372 -0
  149. package/runtime/node_modules/@metro-labs/daemon/src/routes/identity.ts +54 -0
  150. package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +223 -0
  151. package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +60 -0
  152. package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
  153. package/runtime/node_modules/@metro-labs/daemon/src/routes/threema-callback.ts +103 -0
  154. package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +64 -0
  155. package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +61 -0
  156. package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +116 -0
  157. package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
  158. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-inline.ts +100 -0
  159. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-interactive.ts +161 -0
  160. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +202 -0
  161. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-session.ts +280 -0
  162. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-xmtp.ts +111 -0
  163. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +248 -0
  164. package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +261 -0
  165. package/runtime/node_modules/@metro-labs/daemon/src/stations/registry.ts +37 -0
  166. package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
  167. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor-io.ts +97 -0
  168. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +320 -0
  169. package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
  170. package/runtime/node_modules/@metro-labs/daemon/src/stations/train-call.ts +22 -0
  171. package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +96 -0
  172. package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +123 -0
  173. package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +40 -0
  174. package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
  175. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -0
  176. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
  177. package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
  178. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
  179. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +220 -0
  180. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
  181. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
  182. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
  183. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
  184. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
  185. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
  186. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
  187. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
  188. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
  189. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
  190. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
  191. package/runtime/node_modules/@metro-labs/http/package.json +33 -0
  192. package/runtime/node_modules/@metro-labs/http/src/api-error.ts +8 -0
  193. package/runtime/node_modules/@metro-labs/http/src/api-http.ts +95 -0
  194. package/runtime/node_modules/@metro-labs/http/src/cors.ts +29 -0
  195. package/runtime/node_modules/@metro-labs/http/src/identity-key.ts +33 -0
  196. package/runtime/node_modules/@metro-labs/http/src/identity-registry.ts +13 -0
  197. package/runtime/node_modules/@metro-labs/http/src/mode-api.ts +32 -0
  198. package/runtime/node_modules/@metro-labs/http/src/return-to.ts +21 -0
  199. package/runtime/node_modules/@metro-labs/http/src/signed-identity.ts +36 -0
  200. package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
  201. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
  202. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +254 -0
  203. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
  204. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
  205. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +156 -0
  206. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
  207. package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
  208. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
  209. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
  210. package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
  211. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
  212. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
  213. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
  214. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
  215. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
  216. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
  217. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
  218. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
  219. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
  220. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
  221. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
  222. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +201 -0
  223. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +109 -0
  224. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
  225. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
  226. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
  227. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +58 -0
  228. package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
  229. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
  230. package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
  231. package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +100 -0
  232. package/runtime/node_modules/@metro-labs/threema/src/actions.ts +219 -0
  233. package/runtime/node_modules/@metro-labs/threema/src/api.ts +130 -0
  234. package/runtime/node_modules/@metro-labs/threema/src/crypto.ts +132 -0
  235. package/runtime/node_modules/@metro-labs/threema/src/format.ts +123 -0
  236. package/runtime/node_modules/@metro-labs/threema/src/ids.ts +21 -0
  237. package/runtime/node_modules/@metro-labs/threema/src/index.ts +29 -0
  238. package/runtime/node_modules/@metro-labs/threema/src/station.ts +10 -0
  239. package/runtime/node_modules/@metro-labs/threema/src/verify.ts +117 -0
  240. package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
  241. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
  242. package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
  243. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
  244. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
  245. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +190 -0
  246. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
  247. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
  248. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +347 -0
  249. package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
  250. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +138 -0
  251. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
  252. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
  253. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
  254. package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
  255. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
  256. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
  257. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
  258. package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
  259. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
  260. package/runtime/node_modules/@metro-labs/whatsapp/src/resolve.ts +52 -0
  261. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +18 -0
  262. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
  263. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
  264. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
  265. package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
  266. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +112 -0
  267. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
  268. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
  269. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
  270. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
  271. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
  272. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
  273. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
  274. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
  275. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
  276. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +335 -0
  277. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +80 -0
  278. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
  279. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
  280. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
  281. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
  282. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
  283. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
  284. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
  285. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
  286. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
  287. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
  288. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
  289. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
  290. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +53 -0
  291. package/runtime/runtime.json +3 -0
  292. package/runtime/server.ts +1 -0
  293. package/runtime/stations.json +39 -0
  294. package/runtime/trains/.keep +0 -0
  295. package/LICENSE +0 -21
  296. package/README.md +0 -252
  297. package/dist/broker.js +0 -202
  298. package/dist/cache.js +0 -69
  299. package/dist/cli/actions.js +0 -206
  300. package/dist/cli/config.js +0 -185
  301. package/dist/cli/index.js +0 -188
  302. package/dist/cli/skill.js +0 -62
  303. package/dist/cli/tail.js +0 -164
  304. package/dist/cli/util.js +0 -72
  305. package/dist/cli/webhook.js +0 -81
  306. package/dist/codex-rc.js +0 -245
  307. package/dist/dispatcher.js +0 -128
  308. package/dist/history.js +0 -121
  309. package/dist/ipc.js +0 -72
  310. package/dist/log.js +0 -15
  311. package/dist/monitor.js +0 -194
  312. package/dist/paths.js +0 -68
  313. package/dist/registry.js +0 -48
  314. package/dist/stations/claude.js +0 -45
  315. package/dist/stations/codex.js +0 -68
  316. package/dist/stations/discord.js +0 -216
  317. package/dist/stations/index.js +0 -129
  318. package/dist/stations/telegram-md.js +0 -34
  319. package/dist/stations/telegram-upload.js +0 -113
  320. package/dist/stations/telegram.js +0 -234
  321. package/dist/stations/webhook.js +0 -103
  322. package/dist/tunnel.js +0 -64
  323. package/dist/webhooks.js +0 -41
  324. package/docs/broker.md +0 -214
  325. package/docs/monitor.md +0 -163
  326. package/docs/uri-scheme.md +0 -124
  327. package/docs/users.md +0 -226
  328. package/skills/metro/SKILL.md +0 -256
@@ -0,0 +1,371 @@
1
+ import { existsSync, readFileSync, rmdirSync, rmSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { ApiError } from '@metro-labs/http/api-error';
4
+ import { ensureSecureDir, writeSecure } from '@metro-labs/core/secure-fs';
5
+ import {
6
+ AgentAdminError,
7
+ newApiKey,
8
+ normalizeAgentName,
9
+ type AgentSummary,
10
+ type CreatedAgent,
11
+ type DeletedAgent,
12
+ type OwnedAgent,
13
+ type ResetAgentKey,
14
+ } from './admin.js';
15
+ import type { AccountRef } from './account-attach.js';
16
+ import {
17
+ AGENT_FILE,
18
+ AgentFileError,
19
+ agentsDir,
20
+ listAgentFiles,
21
+ parseAgentFile,
22
+ readAgentFile,
23
+ type AgentFile,
24
+ } from './files.js';
25
+ import { newId } from '@metro-labs/core/ids';
26
+ import { registerKey, rotateAgentKey, unregisterAgentKey } from './keys.js';
27
+ import { MOVABLE_STATIONS, type LoadedAgent } from '../stations/materialize.js';
28
+ import type { StationName } from '@metro-labs/core/station-names';
29
+ import { normalizeAddress } from '@metro-labs/core/address';
30
+
31
+ export const LOCAL_PROJECT_ID = 'localdaemon';
32
+ const OWNER_FILE = '.owner';
33
+
34
+ interface Stored {
35
+ path: string;
36
+ file: AgentFile;
37
+ }
38
+
39
+ const missing = (): AgentAdminError => new AgentAdminError('no such agent', 404);
40
+
41
+ export function localOwner(dir = agentsDir()): string | null {
42
+ try {
43
+ return normalizeAddress(readFileSync(join(dir, OWNER_FILE), 'utf8'));
44
+ } catch {
45
+ return null;
46
+ }
47
+ }
48
+
49
+ export function setLocalOwner(raw: string, dir = agentsDir()): string {
50
+ const address = normalizeAddress(raw);
51
+ if (address === null) throw new ApiError(`'${raw}' is not an Ethereum address`, 400);
52
+ ensureSecureDir(dir);
53
+ writeSecure(join(dir, OWNER_FILE), `${address}\n`);
54
+ return address;
55
+ }
56
+
57
+
58
+ function isOwner(subject: string, dir: string): boolean {
59
+ const owner = localOwner(dir);
60
+ return owner !== null && owner === normalizeAddress(subject);
61
+ }
62
+
63
+ function storedAgents(dir: string): Stored[] {
64
+ return listAgentFiles(dir).map((path) => ({ path, file: readAgentFile(path) }));
65
+ }
66
+
67
+ function save(stored: Stored): void {
68
+ writeSecure(stored.path, `${JSON.stringify(stored.file, null, 2)}\n`);
69
+ }
70
+
71
+ function ownedOrThrow(subject: string, id: string, dir: string): Stored {
72
+ if (!isOwner(subject, dir)) throw missing();
73
+ const found = storedAgents(dir).find((s) => s.file.id === id);
74
+ if (found === undefined) throw missing();
75
+ return found;
76
+ }
77
+
78
+ export async function localOwnedAgentOrThrow(
79
+ subject: string,
80
+ id: string,
81
+ dir = agentsDir(),
82
+ ): Promise<{ agent: OwnedAgent }> {
83
+ const { file } = ownedOrThrow(subject, id, dir);
84
+ return Promise.resolve({ agent: { id: file.id, name: file.name } });
85
+ }
86
+
87
+ export async function localListAgents(
88
+ subject: string,
89
+ project: string,
90
+ dir = agentsDir(),
91
+ ): Promise<AgentSummary[]> {
92
+ if (project !== LOCAL_PROJECT_ID || !isOwner(subject, dir))
93
+ throw new AgentAdminError('no such project', 404);
94
+ return Promise.resolve(
95
+ storedAgents(dir)
96
+ .map(({ file }) => ({ id: file.id, name: file.name, owned: true, key: file.key }))
97
+ .sort((a, b) => a.name.localeCompare(b.name) || a.id.localeCompare(b.id)),
98
+ );
99
+ }
100
+
101
+ function freshId(taken: Set<string>): string {
102
+ for (let attempt = 0; attempt < 5; attempt += 1) {
103
+ const id = newId();
104
+ if (!taken.has(id)) return id;
105
+ }
106
+ throw new AgentAdminError('could not allocate a free id', 500);
107
+ }
108
+
109
+ export async function localCreateAgent(
110
+ subject: string,
111
+ project: string,
112
+ rawName: string,
113
+ dir = agentsDir(),
114
+ ): Promise<CreatedAgent> {
115
+ if (project !== LOCAL_PROJECT_ID || !isOwner(subject, dir))
116
+ throw new AgentAdminError('no such project', 404);
117
+ const name = normalizeAgentName(rawName);
118
+ const folder = join(dir, name);
119
+ if (existsSync(join(folder, AGENT_FILE)))
120
+ throw new AgentAdminError(
121
+ `an agent named '${name}' already exists on this machine`,
122
+ 409,
123
+ );
124
+ const existing = storedAgents(dir);
125
+ const id = freshId(new Set(existing.map((s) => s.file.id)));
126
+ const key = newApiKey();
127
+ ensureSecureDir(folder);
128
+ save({
129
+ path: join(folder, AGENT_FILE),
130
+ file: { version: 1, id, name, key, owner: localOwner(dir), stations: [], connectors: [] },
131
+ });
132
+ registerKey(key, id);
133
+ return Promise.resolve({ id, name, key });
134
+ }
135
+
136
+ export async function localResetAgentKey(
137
+ subject: string,
138
+ id: string,
139
+ dir = agentsDir(),
140
+ ): Promise<ResetAgentKey> {
141
+ const stored = ownedOrThrow(subject, id, dir);
142
+ const key = newApiKey();
143
+ stored.file.key = key;
144
+ save(stored);
145
+ rotateAgentKey(id, key);
146
+ return Promise.resolve({ id, name: stored.file.name, key });
147
+ }
148
+
149
+ export async function localDeleteAgent(
150
+ subject: string,
151
+ id: string,
152
+ dir = agentsDir(),
153
+ ): Promise<DeletedAgent> {
154
+ const stored = ownedOrThrow(subject, id, dir);
155
+ const attached = stored.file.stations.length;
156
+ if (attached > 0)
157
+ throw new AgentAdminError(
158
+ `agent '${stored.file.name}' still has ${String(attached)} station account(s) attached — detach them first`,
159
+ 409,
160
+ );
161
+ rmSync(stored.path);
162
+ removeIfEmpty(join(stored.path, '..'));
163
+ unregisterAgentKey(id);
164
+ return Promise.resolve({ id, name: stored.file.name });
165
+ }
166
+
167
+ function assertUnclaimed(existing: Stored[], agent: LoadedAgent): void {
168
+ for (const { file } of existing) {
169
+ if (file.id === agent.id)
170
+ throw new AgentAdminError(`agent ${agent.id} is already on this machine`, 409);
171
+ if (agent.key !== null && file.key === agent.key)
172
+ throw new AgentAdminError('that agent key is already on this machine', 409);
173
+ }
174
+ }
175
+
176
+ export function assertLocalOwner(subject: string, dir = agentsDir()): void {
177
+ if (!isOwner(subject, dir)) throw new AgentAdminError('no such project', 404);
178
+ }
179
+
180
+ function assertImportable(agent: LoadedAgent): asserts agent is LoadedAgent & { key: string } {
181
+ const immovable = agent.accounts.find((a) => !MOVABLE_STATIONS.has(a.station));
182
+ if (immovable !== undefined)
183
+ throw new AgentAdminError(
184
+ `a ${immovable.station} endpoint needs a public url and cannot live on a local daemon`,
185
+ 400,
186
+ );
187
+ if (agent.key === null)
188
+ throw new AgentAdminError('that agent has no key; reset it on metro.box first', 400);
189
+ }
190
+
191
+ interface ImportTarget {
192
+ path: string;
193
+ previous: AgentFile | undefined;
194
+ }
195
+
196
+ function importTarget(dir: string, agent: LoadedAgent & { key: string }): ImportTarget {
197
+ const existing = storedAgents(dir);
198
+ const same = existing.find((s) => s.file.id === agent.id);
199
+ const fresh = join(dir, agent.name, AGENT_FILE);
200
+ if (same === undefined && existsSync(fresh))
201
+ throw new AgentAdminError(
202
+ `an agent named '${agent.name}' already exists on this machine`,
203
+ 409,
204
+ );
205
+ assertUnclaimed(existing.filter((s) => s !== same), agent);
206
+ return { path: same?.path ?? fresh, previous: same?.file };
207
+ }
208
+
209
+ export type ImportMode = 'append' | 'overwrite';
210
+
211
+ export async function localImportAgent(
212
+ subject: string,
213
+ agent: LoadedAgent,
214
+ dir = agentsDir(),
215
+ mode: ImportMode = 'overwrite',
216
+ ): Promise<{ id: string; name: string; key: string; stations: number }> {
217
+ assertLocalOwner(subject, dir);
218
+ assertImportable(agent);
219
+ const { path, previous } = importTarget(dir, agent);
220
+ const file = fileFor(agent, localOwner(dir), path, previous, mode);
221
+ ensureSecureDir(join(path, '..'));
222
+ save({ path, file });
223
+ if (previous !== undefined && previous.key !== agent.key) unregisterAgentKey(agent.id);
224
+ registerKey(agent.key, agent.id);
225
+ return Promise.resolve({
226
+ id: agent.id,
227
+ name: agent.name,
228
+ key: agent.key,
229
+ stations: file.stations.length,
230
+ });
231
+ }
232
+
233
+ function mergedStations(
234
+ agent: LoadedAgent,
235
+ previous: AgentFile | undefined,
236
+ mode: ImportMode,
237
+ ): AgentFile['stations'] {
238
+ const before = previous?.stations ?? [];
239
+ const here = (station: string, id: string): boolean => before.some((s) => s.station === station && s.id === id);
240
+ const fromMetro = agent.accounts
241
+ .map((a) => ({ ...a, allowlist: a.allowlist ?? ['*'] }))
242
+ .filter((m) => mode === 'overwrite' || !here(m.station, m.id));
243
+ const kept = before.filter((s) => !fromMetro.some((m) => m.station === s.station && m.id === s.id));
244
+ return [...fromMetro, ...kept];
245
+ }
246
+
247
+ function fileFor(
248
+ agent: LoadedAgent,
249
+ owner: string | null,
250
+ path: string,
251
+ previous: AgentFile | undefined,
252
+ mode: ImportMode,
253
+ ): AgentFile {
254
+ try {
255
+ return parseAgentFile(
256
+ JSON.stringify({
257
+ version: 1,
258
+ id: agent.id,
259
+ name: previous?.name ?? agent.name,
260
+ key: agent.key,
261
+ owner,
262
+ stations: mergedStations(agent, previous, mode),
263
+ connectors: previous?.connectors ?? [],
264
+ }),
265
+ path,
266
+ );
267
+ } catch (err) {
268
+ if (err instanceof AgentFileError)
269
+ throw new AgentAdminError(`metro's copy of this agent cannot be written here: ${err.message}`, 400);
270
+ throw err;
271
+ }
272
+ }
273
+
274
+ export function readLocalAgentFile(agentId: string, dir = agentsDir()): AgentFile {
275
+ const found = storedAgents(dir).find((s) => s.file.id === agentId);
276
+ if (found === undefined) throw missing();
277
+ return found.file;
278
+ }
279
+
280
+ function removeIfEmpty(folder: string): void {
281
+ try {
282
+ rmdirSync(folder);
283
+ } catch {
284
+ return;
285
+ }
286
+ }
287
+
288
+ function assertTokenFree(all: Stored[], station: StationName, token: string): void {
289
+ const taken = all.some((s) =>
290
+ s.file.stations.some(
291
+ (a) => a.station === station && a.config.token === token,
292
+ ),
293
+ );
294
+ if (taken)
295
+ throw new AgentAdminError(
296
+ 'that bot token is already attached to an agent on this machine',
297
+ 409,
298
+ );
299
+ }
300
+
301
+ export async function localAttachAccount(
302
+ subject: string,
303
+ agentId: string,
304
+ station: StationName,
305
+ config: Record<string, unknown>,
306
+ dir = agentsDir(),
307
+ ): Promise<AccountRef> {
308
+ const stored = ownedOrThrow(subject, agentId, dir);
309
+ if (!MOVABLE_STATIONS.has(station))
310
+ throw new AgentAdminError(
311
+ `a ${station} endpoint needs a public url and cannot live on a local daemon`,
312
+ 400,
313
+ );
314
+ const token = config.token;
315
+ if (typeof token === 'string') assertTokenFree(storedAgents(dir), station, token);
316
+ const taken = new Set(stored.file.stations.map((a) => a.id));
317
+ const accountId = freshId(taken);
318
+ stored.file.stations.push({ station, id: accountId, allowlist: ['*'], enabled: true, config });
319
+ save(stored);
320
+ return Promise.resolve({ agentId, station, accountId });
321
+ }
322
+
323
+ export async function localSetAllowlist(
324
+ subject: string,
325
+ agentId: string,
326
+ station: StationName,
327
+ accountId: string,
328
+ allowlist: string[],
329
+ dir = agentsDir(),
330
+ ): Promise<string[]> {
331
+ const stored = ownedOrThrow(subject, agentId, dir);
332
+ const account = stored.file.stations.find((a) => a.station === station && a.id === accountId);
333
+ if (account === undefined) throw new AgentAdminError('no such account on this agent', 404);
334
+ account.allowlist = allowlist;
335
+ save(stored);
336
+ return Promise.resolve(allowlist);
337
+ }
338
+
339
+ export async function localSetAccountEnabled(
340
+ subject: string,
341
+ agentId: string,
342
+ station: StationName,
343
+ accountId: string,
344
+ enabled: boolean,
345
+ dir = agentsDir(),
346
+ ): Promise<boolean> {
347
+ const stored = ownedOrThrow(subject, agentId, dir);
348
+ const account = stored.file.stations.find((a) => a.station === station && a.id === accountId);
349
+ if (account === undefined) throw new AgentAdminError('no such account on this agent', 404);
350
+ account.enabled = enabled;
351
+ save(stored);
352
+ return Promise.resolve(enabled);
353
+ }
354
+
355
+ export async function localDetachAccount(
356
+ subject: string,
357
+ agentId: string,
358
+ station: StationName,
359
+ accountId: string,
360
+ dir = agentsDir(),
361
+ ): Promise<AccountRef> {
362
+ const stored = ownedOrThrow(subject, agentId, dir);
363
+ const before = stored.file.stations.length;
364
+ stored.file.stations = stored.file.stations.filter(
365
+ (a) => !(a.station === station && a.id === accountId),
366
+ );
367
+ if (stored.file.stations.length === before)
368
+ throw new AgentAdminError('no such account on this agent', 404);
369
+ save(stored);
370
+ return Promise.resolve({ agentId, station, accountId });
371
+ }
@@ -0,0 +1,155 @@
1
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { AGENT_NAME_RE, ID_RE } from '@metro-labs/core/ids';
5
+ import { STATIONS, type StationName } from '@metro-labs/core/station-names';
6
+ import type { LoadedAccount, LoadedAgent, StationSource } from '../stations/materialize.js';
7
+ import { isRecord } from '@metro-labs/core/is-record';
8
+
9
+ export const AGENT_FILE = 'agent.json';
10
+
11
+ const KEY_RE = /^[A-Za-z0-9_-]{16,128}$/;
12
+ const ADDRESS_RE = /^0x[0-9a-f]{40}$/;
13
+ const STATION_NAMES = new Set<string>(STATIONS);
14
+
15
+ export class AgentFileError extends Error {}
16
+
17
+ export interface AgentFile {
18
+ version: 1;
19
+ id: string;
20
+ name: string;
21
+ key: string | null;
22
+ owner: string | null;
23
+ stations: LoadedAccount[];
24
+ connectors: string[];
25
+ }
26
+
27
+ export function agentsDir(): string {
28
+ const explicit = process.env.METRO_AGENTS_DIR?.trim();
29
+ return explicit !== undefined && explicit !== ''
30
+ ? explicit
31
+ : join(homedir(), '.metro', 'agents');
32
+ }
33
+
34
+ function fail(path: string, reason: string): never {
35
+ throw new AgentFileError(`${path}: ${reason}`);
36
+ }
37
+
38
+ function isStationName(value: unknown): value is StationName {
39
+ return typeof value === 'string' && STATION_NAMES.has(value);
40
+ }
41
+
42
+ function allowlistOf(raw: unknown, path: string, where: string): string[] | null {
43
+ if (raw === null || raw === undefined) return null;
44
+ if (!Array.isArray(raw)) fail(path, `${where}.allowlist is not a list of strings`);
45
+ const list = raw.filter((entry): entry is string => typeof entry === 'string');
46
+ if (list.length !== raw.length)
47
+ fail(path, `${where}.allowlist is not a list of strings`);
48
+ return list;
49
+ }
50
+
51
+ function stationOf(raw: unknown, path: string, index: number): LoadedAccount {
52
+ const where = `stations[${String(index)}]`;
53
+ if (!isRecord(raw)) fail(path, `${where} is not an object`);
54
+ const { station, id, config } = raw;
55
+ if (!isStationName(station)) fail(path, `${where}.station is not a known station`);
56
+ if (typeof id !== 'string' || !ID_RE.test(id))
57
+ fail(path, `${where}.id is not an 11-character id`);
58
+ if (!isRecord(config)) fail(path, `${where}.config is not an object`);
59
+ return { station, id, allowlist: allowlistOf(raw.allowlist, path, where), enabled: raw.enabled !== false, config };
60
+ }
61
+
62
+ function optionalMatch(
63
+ value: unknown,
64
+ re: RegExp,
65
+ path: string,
66
+ what: string,
67
+ ): string | null {
68
+ if (value === null || value === undefined) return null;
69
+ if (typeof value !== 'string' || !re.test(value)) fail(path, what);
70
+ return value;
71
+ }
72
+
73
+ const connectorsOf = (raw: unknown): string[] =>
74
+ Array.isArray(raw) ? raw.filter((id): id is string => typeof id === 'string' && ID_RE.test(id)) : [];
75
+
76
+ export function parseAgentFile(raw: string, path: string): AgentFile {
77
+ let parsed: unknown;
78
+ try {
79
+ parsed = JSON.parse(raw);
80
+ } catch {
81
+ fail(path, 'is not valid JSON');
82
+ }
83
+ if (!isRecord(parsed)) fail(path, 'is not a JSON object');
84
+ if (parsed.version !== 1) fail(path, 'version must be 1');
85
+ const { id, name, stations } = parsed;
86
+ if (typeof id !== 'string' || !ID_RE.test(id))
87
+ fail(path, 'id is not an 11-character id');
88
+ if (typeof name !== 'string' || !AGENT_NAME_RE.test(name))
89
+ fail(path, 'name is not a valid agent name');
90
+ const key = optionalMatch(parsed.key, KEY_RE, path, 'key is not an agent key');
91
+ const owner = optionalMatch(
92
+ parsed.owner,
93
+ ADDRESS_RE,
94
+ path,
95
+ 'owner is not a lowercase Ethereum address',
96
+ );
97
+ if (!Array.isArray(stations)) fail(path, 'stations is not a list');
98
+ return {
99
+ version: 1,
100
+ id,
101
+ name,
102
+ key,
103
+ owner,
104
+ stations: stations.map((s, i) => stationOf(s, path, i)),
105
+ connectors: connectorsOf(parsed.connectors),
106
+ };
107
+ }
108
+
109
+ export function readAgentFile(path: string): AgentFile {
110
+ let raw: string;
111
+ try {
112
+ raw = readFileSync(path, 'utf8');
113
+ } catch {
114
+ fail(path, 'cannot be read');
115
+ }
116
+ return parseAgentFile(raw, path);
117
+ }
118
+
119
+ export function listAgentFiles(dir = agentsDir()): string[] {
120
+ if (!existsSync(dir)) return [];
121
+ return readdirSync(dir, { withFileTypes: true })
122
+ .filter((entry) => entry.isDirectory())
123
+ .map((entry) => join(dir, entry.name, AGENT_FILE))
124
+ .filter((path) => existsSync(path))
125
+ .sort();
126
+ }
127
+
128
+ function assertUnique(agents: AgentFile[], paths: string[]): void {
129
+ const ids = new Map<string, string>();
130
+ const keys = new Map<string, string>();
131
+ agents.forEach((agent, i) => {
132
+ const path = paths[i] ?? '';
133
+ const sameId = ids.get(agent.id);
134
+ if (sameId !== undefined) fail(path, `id ${agent.id} is already used by ${sameId}`);
135
+ ids.set(agent.id, path);
136
+ if (agent.key === null) return;
137
+ const sameKey = keys.get(agent.key);
138
+ if (sameKey !== undefined) fail(path, `its key is already used by ${sameKey}`);
139
+ keys.set(agent.key, path);
140
+ });
141
+ }
142
+
143
+ export function loadFileAgents(dir = agentsDir()): LoadedAgent[] {
144
+ const paths = listAgentFiles(dir);
145
+ const agents = paths.map(readAgentFile);
146
+ assertUnique(agents, paths);
147
+ return agents.map((agent) => ({
148
+ id: agent.id,
149
+ name: agent.name,
150
+ key: agent.key,
151
+ accounts: agent.stations,
152
+ }));
153
+ }
154
+
155
+ export const fileSource: StationSource = () => Promise.resolve(loadFileAgents());
@@ -0,0 +1,45 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { ID_RE } from '@metro-labs/core/ids';
3
+
4
+ const digest = (token: string): string =>
5
+ createHash('sha256').update(token).digest('hex');
6
+
7
+ let agentIdByKeyHash = new Map<string, string>();
8
+
9
+ export interface KeyOwner {
10
+ key: string;
11
+ agentId: string;
12
+ }
13
+
14
+ const usable = (e: KeyOwner): boolean =>
15
+ e.key !== '' && ID_RE.test(e.agentId);
16
+
17
+ export function setKeyMap(entries: KeyOwner[]): void {
18
+ agentIdByKeyHash = new Map(
19
+ entries.filter(usable).map((e) => [digest(e.key), e.agentId]),
20
+ );
21
+ }
22
+
23
+ export function registerKey(key: string, agentId: string): void {
24
+ if (!usable({ key, agentId })) return;
25
+ agentIdByKeyHash.set(digest(key), agentId);
26
+ }
27
+
28
+ export function unregisterAgentKey(agentId: string): void {
29
+ for (const [hash, id] of agentIdByKeyHash)
30
+ if (id === agentId) agentIdByKeyHash.delete(hash);
31
+ }
32
+
33
+ export function rotateAgentKey(agentId: string, key: string | null): void {
34
+ unregisterAgentKey(agentId);
35
+ if (key !== null) registerKey(key, agentId);
36
+ }
37
+
38
+ export function agentIdForKey(token: string): string | undefined {
39
+ if (token === '') return undefined;
40
+ return agentIdByKeyHash.get(digest(token));
41
+ }
42
+
43
+ export function hasAnyKey(): boolean {
44
+ return agentIdByKeyHash.size > 0;
45
+ }
@@ -0,0 +1,95 @@
1
+ export type AgentMap = Record<string, string>;
2
+ export type AgentNameMap = Record<string, string>;
3
+ export type AllowlistMap = Record<string, string[]>;
4
+
5
+ const mapKey = (station: string, accountId: string): string =>
6
+ `${station}/${accountId}`;
7
+
8
+ let agentMap: AgentMap = {};
9
+ let agentNames: AgentNameMap = {};
10
+ let allowlistMap: AllowlistMap = {};
11
+ let disabledAccounts = new Set<string>();
12
+
13
+ export function setAgentMap(map: AgentMap, names: AgentNameMap): void {
14
+ agentMap = map;
15
+ agentNames = names;
16
+ }
17
+
18
+ export function setAllowlistMap(map: AllowlistMap): void {
19
+ allowlistMap = map;
20
+ }
21
+
22
+ export function setDisabledAccounts(ids: Set<string>): void {
23
+ disabledAccounts = ids;
24
+ }
25
+
26
+ export const accountEnabled = (station: string, accountId: string): boolean => !disabledAccounts.has(mapKey(station, accountId));
27
+
28
+ export function accountFromLine(
29
+ line: string,
30
+ ): { station: string; accountId: string } | undefined {
31
+ const parts = line.split('/');
32
+ const station = parts[2];
33
+ const accountId = parts[3];
34
+ if (!station || !accountId) return undefined;
35
+ return { station, accountId };
36
+ }
37
+
38
+ export function agentIdForAccount(
39
+ station: string,
40
+ accountId: string,
41
+ ): string | undefined {
42
+ return agentMap[mapKey(station, accountId)];
43
+ }
44
+
45
+ export function agentIdForLine(line: string): string | undefined {
46
+ const a = accountFromLine(line);
47
+ return a ? agentIdForAccount(a.station, a.accountId) : undefined;
48
+ }
49
+
50
+ export function agentForLine(line: string): string | undefined {
51
+ const id = agentIdForLine(line);
52
+ return id === undefined ? undefined : agentNames[id];
53
+ }
54
+
55
+ export interface KnownAccount {
56
+ station: string;
57
+ id: string;
58
+ agentId: string;
59
+ }
60
+
61
+ export function knownAccounts(): KnownAccount[] {
62
+ return Object.entries(agentMap).flatMap(([key, agentId]) => {
63
+ const cut = key.indexOf('/');
64
+ return cut === -1 ? [] : [{ station: key.slice(0, cut), id: key.slice(cut + 1), agentId }];
65
+ });
66
+ }
67
+
68
+ export function stationAgentIds(station: string): string[] {
69
+ const prefix = `${station}/`;
70
+ return Object.entries(agentMap)
71
+ .filter(([key]) => key.startsWith(prefix))
72
+ .map(([, id]) => id);
73
+ }
74
+
75
+ export function allowlistForAccount(station: string, accountId: string): string[] | undefined {
76
+ return allowlistMap[mapKey(station, accountId)];
77
+ }
78
+
79
+ export function allowlistForLine(line: string): string[] | undefined {
80
+ const a = accountFromLine(line);
81
+ return a ? allowlistMap[mapKey(a.station, a.accountId)] : undefined;
82
+ }
83
+
84
+ export function senderMatchesAllowlist(
85
+ allowlist: string[],
86
+ from: string,
87
+ ): boolean {
88
+ if (allowlist.length === 0 || allowlist.includes('*')) return true;
89
+ const f = (from ?? '').toLowerCase();
90
+ const id = f.split('/').pop() ?? f;
91
+ return allowlist.some((a) => {
92
+ const v = a.toLowerCase();
93
+ return v === f || v === id;
94
+ });
95
+ }