@stage-labs/metro 0.1.0-beta.16 → 0.1.0-beta.161

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 (309) hide show
  1. package/dist/api.js +8 -57
  2. package/dist/claude-settings.js +40 -0
  3. package/dist/claude.js +182 -0
  4. package/dist/cli.js +56 -39
  5. package/dist/control.js +74 -0
  6. package/dist/hold.js +211 -0
  7. package/dist/local.js +77 -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/provider-flags.js +9 -0
  12. package/dist/route.js +50 -0
  13. package/dist/runtime-install.js +97 -0
  14. package/dist/runtime.js +74 -0
  15. package/dist/serve.js +155 -0
  16. package/dist/service.js +242 -0
  17. package/dist/tail.js +119 -0
  18. package/dist/update.js +13 -1
  19. package/package.json +6 -5
  20. package/runtime/marketplace/.claude-plugin/marketplace.json +12 -0
  21. package/runtime/marketplace/plugin/.claude-plugin/plugin.json +18 -0
  22. package/runtime/marketplace/plugin/.mcp.json +1 -0
  23. package/runtime/marketplace/plugin/bin/guard.mjs +55 -0
  24. package/runtime/marketplace/plugin/bin/metro-plugin.mjs +40 -0
  25. package/runtime/marketplace/plugin/bin/session-start.mjs +31 -0
  26. package/runtime/marketplace/plugin/hooks/hooks.json +25 -0
  27. package/runtime/marketplace/plugin/orchestrator.md +16 -0
  28. package/runtime/node_modules/@metro-labs/core/package.json +44 -0
  29. package/runtime/node_modules/@metro-labs/core/src/address.ts +6 -0
  30. package/runtime/node_modules/@metro-labs/core/src/endpoints.ts +7 -0
  31. package/runtime/node_modules/@metro-labs/core/src/events.ts +120 -0
  32. package/runtime/node_modules/@metro-labs/core/src/identity.ts +7 -0
  33. package/runtime/node_modules/@metro-labs/core/src/ids.ts +21 -0
  34. package/runtime/node_modules/@metro-labs/core/src/is-record.ts +2 -0
  35. package/runtime/node_modules/@metro-labs/core/src/lines.ts +87 -0
  36. package/runtime/node_modules/@metro-labs/core/src/log.ts +42 -0
  37. package/runtime/node_modules/@metro-labs/core/src/protocol.ts +226 -0
  38. package/runtime/node_modules/@metro-labs/core/src/secure-fs.ts +57 -0
  39. package/runtime/node_modules/@metro-labs/core/src/station-names.ts +13 -0
  40. package/runtime/node_modules/@metro-labs/core/src/stations/account-store.ts +60 -0
  41. package/runtime/node_modules/@metro-labs/core/src/stations/attachments.ts +196 -0
  42. package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +88 -0
  43. package/runtime/node_modules/@metro-labs/core/src/stations/profile.ts +58 -0
  44. package/runtime/node_modules/@metro-labs/core/src/stations/station-runtime.ts +64 -0
  45. package/runtime/node_modules/@metro-labs/core/src/stations/types.ts +112 -0
  46. package/runtime/node_modules/@metro-labs/core/src/str.ts +5 -0
  47. package/runtime/node_modules/@metro-labs/core/src/train-error.ts +53 -0
  48. package/runtime/node_modules/@metro-labs/core/src/version.ts +2 -0
  49. package/runtime/node_modules/@metro-labs/daemon/package.json +47 -0
  50. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-attach.ts +19 -0
  51. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-routes.ts +72 -0
  52. package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +388 -0
  53. package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +39 -0
  54. package/runtime/node_modules/@metro-labs/daemon/src/agents/allowlist.ts +37 -0
  55. package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +225 -0
  56. package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +122 -0
  57. package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +364 -0
  58. package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +185 -0
  59. package/runtime/node_modules/@metro-labs/daemon/src/agents/keys.ts +40 -0
  60. package/runtime/node_modules/@metro-labs/daemon/src/agents/map.ts +95 -0
  61. package/runtime/node_modules/@metro-labs/daemon/src/agents/scope.ts +64 -0
  62. package/runtime/node_modules/@metro-labs/daemon/src/agents/senders.ts +38 -0
  63. package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +200 -0
  64. package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +39 -0
  65. package/runtime/node_modules/@metro-labs/daemon/src/boot/crash-guard.ts +49 -0
  66. package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +30 -0
  67. package/runtime/node_modules/@metro-labs/daemon/src/boot/paths.ts +106 -0
  68. package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +25 -0
  69. package/runtime/node_modules/@metro-labs/daemon/src/channels/dedupe.ts +24 -0
  70. package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +380 -0
  71. package/runtime/node_modules/@metro-labs/daemon/src/channels/media-note.ts +85 -0
  72. package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +75 -0
  73. package/runtime/node_modules/@metro-labs/daemon/src/channels/relay.ts +129 -0
  74. package/runtime/node_modules/@metro-labs/daemon/src/channels/webhook-note.ts +51 -0
  75. package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +354 -0
  76. package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +358 -0
  77. package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +169 -0
  78. package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +56 -0
  79. package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +140 -0
  80. package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +90 -0
  81. package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +253 -0
  82. package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +117 -0
  83. package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +263 -0
  84. package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +120 -0
  85. package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +109 -0
  86. package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +313 -0
  87. package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +157 -0
  88. package/runtime/node_modules/@metro-labs/daemon/src/connectors/health.ts +18 -0
  89. package/runtime/node_modules/@metro-labs/daemon/src/connectors/model.ts +69 -0
  90. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-client.ts +166 -0
  91. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-discovery.ts +139 -0
  92. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-pending.ts +65 -0
  93. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-routes.ts +171 -0
  94. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth.ts +155 -0
  95. package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +122 -0
  96. package/runtime/node_modules/@metro-labs/daemon/src/connectors/reach.ts +27 -0
  97. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-target.ts +39 -0
  98. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +304 -0
  99. package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +299 -0
  100. package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +108 -0
  101. package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +59 -0
  102. package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +101 -0
  103. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +99 -0
  104. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-owner.ts +32 -0
  105. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +121 -0
  106. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +294 -0
  107. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-store.ts +181 -0
  108. package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +259 -0
  109. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +231 -0
  110. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
  111. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +288 -0
  112. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +203 -0
  113. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +231 -0
  114. package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +123 -0
  115. package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +187 -0
  116. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +247 -0
  117. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-auth.ts +182 -0
  118. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-client.ts +46 -0
  119. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-setup.ts +150 -0
  120. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-stream.ts +148 -0
  121. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-translate.ts +229 -0
  122. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini.ts +283 -0
  123. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +362 -0
  124. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +232 -0
  125. package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +90 -0
  126. package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +65 -0
  127. package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +17 -0
  128. package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +242 -0
  129. package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +151 -0
  130. package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +216 -0
  131. package/runtime/node_modules/@metro-labs/daemon/src/mcp/channel-owner.ts +29 -0
  132. package/runtime/node_modules/@metro-labs/daemon/src/mcp/ctx.ts +62 -0
  133. package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +91 -0
  134. package/runtime/node_modules/@metro-labs/daemon/src/mcp/frame-scope.ts +21 -0
  135. package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +129 -0
  136. package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +186 -0
  137. package/runtime/node_modules/@metro-labs/daemon/src/mcp/member-tools.ts +72 -0
  138. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +66 -0
  139. package/runtime/node_modules/@metro-labs/daemon/src/mcp/profile-tool.ts +94 -0
  140. package/runtime/node_modules/@metro-labs/daemon/src/mcp/raw-get-stream.ts +207 -0
  141. package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +47 -0
  142. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-registry.ts +172 -0
  143. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-route.ts +43 -0
  144. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +215 -0
  145. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-dispatch.ts +219 -0
  146. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-schemas.ts +400 -0
  147. package/runtime/node_modules/@metro-labs/daemon/src/mcp/upload-tool.ts +66 -0
  148. package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +237 -0
  149. package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +389 -0
  150. package/runtime/node_modules/@metro-labs/daemon/src/routes/bearer.ts +35 -0
  151. package/runtime/node_modules/@metro-labs/daemon/src/routes/body.ts +24 -0
  152. package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +370 -0
  153. package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +208 -0
  154. package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +58 -0
  155. package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
  156. package/runtime/node_modules/@metro-labs/daemon/src/routes/threema-callback.ts +103 -0
  157. package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +65 -0
  158. package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +80 -0
  159. package/runtime/node_modules/@metro-labs/daemon/src/server/owner.ts +48 -0
  160. package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +117 -0
  161. package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
  162. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-inline.ts +100 -0
  163. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-interactive.ts +161 -0
  164. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +202 -0
  165. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-session.ts +280 -0
  166. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-xmtp.ts +111 -0
  167. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +248 -0
  168. package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +261 -0
  169. package/runtime/node_modules/@metro-labs/daemon/src/stations/registry.ts +37 -0
  170. package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
  171. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor-io.ts +97 -0
  172. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +320 -0
  173. package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
  174. package/runtime/node_modules/@metro-labs/daemon/src/stations/train-call.ts +22 -0
  175. package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +97 -0
  176. package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +123 -0
  177. package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +40 -0
  178. package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
  179. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -0
  180. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +390 -0
  181. package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
  182. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
  183. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +220 -0
  184. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
  185. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
  186. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
  187. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
  188. package/runtime/node_modules/@metro-labs/discord-bot/src/profile.ts +48 -0
  189. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
  190. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +21 -0
  191. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
  192. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
  193. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
  194. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
  195. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
  196. package/runtime/node_modules/@metro-labs/http/package.json +29 -0
  197. package/runtime/node_modules/@metro-labs/http/src/api-error.ts +8 -0
  198. package/runtime/node_modules/@metro-labs/http/src/api-http.ts +106 -0
  199. package/runtime/node_modules/@metro-labs/http/src/cors.ts +29 -0
  200. package/runtime/node_modules/@metro-labs/http/src/mode-api.ts +32 -0
  201. package/runtime/node_modules/@metro-labs/http/src/return-to.ts +21 -0
  202. package/runtime/node_modules/@metro-labs/http/src/workos-token.ts +150 -0
  203. package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
  204. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
  205. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +256 -0
  206. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
  207. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
  208. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +156 -0
  209. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
  210. package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
  211. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
  212. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
  213. package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
  214. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
  215. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
  216. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
  217. package/runtime/node_modules/@metro-labs/telegram/src/profile.ts +36 -0
  218. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
  219. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +26 -0
  220. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
  221. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
  222. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
  223. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
  224. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +297 -0
  225. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
  226. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +201 -0
  227. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +109 -0
  228. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
  229. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
  230. package/runtime/node_modules/@metro-labs/telegram-bot/src/profile.ts +33 -0
  231. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +19 -0
  232. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +58 -0
  233. package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
  234. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
  235. package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
  236. package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +107 -0
  237. package/runtime/node_modules/@metro-labs/threema/src/actions.ts +135 -0
  238. package/runtime/node_modules/@metro-labs/threema/src/api.ts +152 -0
  239. package/runtime/node_modules/@metro-labs/threema/src/crypto.ts +100 -0
  240. package/runtime/node_modules/@metro-labs/threema/src/files.ts +93 -0
  241. package/runtime/node_modules/@metro-labs/threema/src/format.ts +182 -0
  242. package/runtime/node_modules/@metro-labs/threema/src/groups.ts +95 -0
  243. package/runtime/node_modules/@metro-labs/threema/src/ids.ts +21 -0
  244. package/runtime/node_modules/@metro-labs/threema/src/inbound.ts +90 -0
  245. package/runtime/node_modules/@metro-labs/threema/src/index.ts +29 -0
  246. package/runtime/node_modules/@metro-labs/threema/src/messages.ts +264 -0
  247. package/runtime/node_modules/@metro-labs/threema/src/outbound.ts +160 -0
  248. package/runtime/node_modules/@metro-labs/threema/src/station.ts +10 -0
  249. package/runtime/node_modules/@metro-labs/threema/src/verify.ts +117 -0
  250. package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
  251. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
  252. package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
  253. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
  254. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
  255. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +203 -0
  256. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
  257. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
  258. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +359 -0
  259. package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
  260. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +138 -0
  261. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
  262. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
  263. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
  264. package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
  265. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
  266. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
  267. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
  268. package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
  269. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
  270. package/runtime/node_modules/@metro-labs/whatsapp/src/resolve.ts +52 -0
  271. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +20 -0
  272. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
  273. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
  274. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
  275. package/runtime/node_modules/@metro-labs/xmtp/package.json +35 -0
  276. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +115 -0
  277. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
  278. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
  279. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
  280. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
  281. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +380 -0
  282. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
  283. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
  284. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
  285. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
  286. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +335 -0
  287. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +80 -0
  288. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
  289. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
  290. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +47 -0
  291. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
  292. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
  293. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
  294. package/runtime/node_modules/@metro-labs/xmtp/src/names.ts +61 -0
  295. package/runtime/node_modules/@metro-labs/xmtp/src/profile.ts +153 -0
  296. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
  297. package/runtime/node_modules/@metro-labs/xmtp/src/smart.ts +72 -0
  298. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +16 -0
  299. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
  300. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
  301. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
  302. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +98 -0
  303. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +53 -0
  304. package/runtime/runtime.json +3 -0
  305. package/runtime/server.ts +1 -0
  306. package/runtime/stations.json +39 -0
  307. package/runtime/trains/.keep +0 -0
  308. package/dist/login.js +0 -102
  309. package/dist/store.js +0 -63
@@ -0,0 +1,185 @@
1
+ import { existsSync, readdirSync, readFileSync, renameSync, rmdirSync } from 'node:fs';
2
+ import { log } from '@metro-labs/core/log';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { AGENT_NAME_RE, ID_RE } from '@metro-labs/core/ids';
6
+ import { STATIONS, type StationName } from '@metro-labs/core/station-names';
7
+ import type { LoadedAccount, LoadedAgent, StationSource } from '../stations/materialize.js';
8
+ import { isRecord } from '@metro-labs/core/is-record';
9
+
10
+ export const AGENT_FILE = 'agent.json';
11
+
12
+ const KEY_RE = /^[A-Za-z0-9_-]{16,128}$/;
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 | null;
21
+ key: string | null;
22
+ owner: string | null;
23
+ stations: LoadedAccount[];
24
+ connectors: string[];
25
+ }
26
+
27
+ export const agentFilePath = (dir = agentsDir()): string => join(dir, AGENT_FILE);
28
+
29
+ export function agentsDir(): string {
30
+ const explicit = process.env.METRO_AGENTS_DIR?.trim();
31
+ return explicit !== undefined && explicit !== ''
32
+ ? explicit
33
+ : join(homedir(), '.metro', 'agents');
34
+ }
35
+
36
+ const OWNER_RE = /^(0x[0-9a-f]{40}|org_[A-Za-z0-9]{10,64})$/;
37
+
38
+ function fail(path: string, reason: string): never {
39
+ throw new AgentFileError(`${path}: ${reason}`);
40
+ }
41
+
42
+ function isStationName(value: unknown): value is StationName {
43
+ return typeof value === 'string' && STATION_NAMES.has(value);
44
+ }
45
+
46
+ function allowlistOf(raw: unknown, path: string, where: string): string[] | null {
47
+ if (raw === null || raw === undefined) return null;
48
+ if (!Array.isArray(raw)) fail(path, `${where}.allowlist is not a list of strings`);
49
+ const list = raw.filter((entry): entry is string => typeof entry === 'string');
50
+ if (list.length !== raw.length)
51
+ fail(path, `${where}.allowlist is not a list of strings`);
52
+ return list;
53
+ }
54
+
55
+ function stationOf(raw: unknown, path: string, index: number): LoadedAccount {
56
+ const where = `stations[${String(index)}]`;
57
+ if (!isRecord(raw)) fail(path, `${where} is not an object`);
58
+ const { station, id, config } = raw;
59
+ if (!isStationName(station)) fail(path, `${where}.station is not a known station`);
60
+ if (typeof id !== 'string' || !ID_RE.test(id))
61
+ fail(path, `${where}.id is not an 11-character id`);
62
+ if (!isRecord(config)) fail(path, `${where}.config is not an object`);
63
+ return { station, id, allowlist: allowlistOf(raw.allowlist, path, where), enabled: raw.enabled !== false, config };
64
+ }
65
+
66
+ function optionalMatch(
67
+ value: unknown,
68
+ re: RegExp,
69
+ path: string,
70
+ what: string,
71
+ ): string | null {
72
+ if (value === null || value === undefined) return null;
73
+ if (typeof value !== 'string' || !re.test(value)) fail(path, what);
74
+ return value;
75
+ }
76
+
77
+ const connectorsOf = (raw: unknown): string[] =>
78
+ Array.isArray(raw) ? raw.filter((id): id is string => typeof id === 'string' && ID_RE.test(id)) : [];
79
+
80
+ function nameOf(raw: unknown, path: string): string | null {
81
+ if (raw === undefined || raw === null) return null;
82
+ if (typeof raw !== 'string' || !AGENT_NAME_RE.test(raw)) fail(path, 'name is not a valid agent name');
83
+ return raw;
84
+ }
85
+
86
+ export function parseAgentFile(raw: string, path: string): AgentFile {
87
+ let parsed: unknown;
88
+ try {
89
+ parsed = JSON.parse(raw);
90
+ } catch {
91
+ fail(path, 'is not valid JSON');
92
+ }
93
+ if (!isRecord(parsed)) fail(path, 'is not a JSON object');
94
+ if (parsed.version !== 1) fail(path, 'version must be 1');
95
+ const { id, name, stations } = parsed;
96
+ if (typeof id !== 'string' || !ID_RE.test(id))
97
+ fail(path, 'id is not an 11-character id');
98
+ const label = nameOf(name, path);
99
+ const key = optionalMatch(parsed.key, KEY_RE, path, 'key is not an agent key');
100
+ const owner = optionalMatch(
101
+ parsed.owner,
102
+ OWNER_RE,
103
+ path,
104
+ 'owner is neither an organization id nor a lowercase Ethereum address',
105
+ );
106
+ if (!Array.isArray(stations)) fail(path, 'stations is not a list');
107
+ return {
108
+ version: 1,
109
+ id,
110
+ name: label,
111
+ key,
112
+ owner,
113
+ stations: stations.map((s, i) => stationOf(s, path, i)),
114
+ connectors: connectorsOf(parsed.connectors),
115
+ };
116
+ }
117
+
118
+ export function readAgentFile(path: string): AgentFile {
119
+ let raw: string;
120
+ try {
121
+ raw = readFileSync(path, 'utf8');
122
+ } catch {
123
+ fail(path, 'cannot be read');
124
+ }
125
+ return parseAgentFile(raw, path);
126
+ }
127
+
128
+ function legacyAgentFiles(dir: string): string[] {
129
+ if (!existsSync(dir)) return [];
130
+ return readdirSync(dir, { withFileTypes: true })
131
+ .filter((entry) => entry.isDirectory())
132
+ .map((entry) => join(dir, entry.name, AGENT_FILE))
133
+ .filter((path) => existsSync(path))
134
+ .sort();
135
+ }
136
+
137
+ export function listAgentFiles(dir = agentsDir()): string[] {
138
+ const fixed = agentFilePath(dir);
139
+ return existsSync(fixed) ? [fixed] : legacyAgentFiles(dir);
140
+ }
141
+
142
+ export function migrateAgentLayout(dir = agentsDir()): 'moved' | 'kept' | 'none' {
143
+ const fixed = agentFilePath(dir);
144
+ if (existsSync(fixed)) return 'kept';
145
+ const [first, ...rest] = legacyAgentFiles(dir);
146
+ if (first === undefined) return 'none';
147
+ renameSync(first, fixed);
148
+ try {
149
+ rmdirSync(join(first, '..'));
150
+ } catch {
151
+ log.warn({ folder: join(first, '..') }, 'agent layout: the old agent folder is not empty and was left in place');
152
+ }
153
+ for (const extra of rest) log.warn({ path: extra }, 'agent layout: a second agent file is ignored; a box holds one agent');
154
+ log.info({ from: first, to: fixed }, 'agent layout: moved the agent file to its fixed place');
155
+ return 'moved';
156
+ }
157
+
158
+ function assertUnique(agents: AgentFile[], paths: string[]): void {
159
+ const ids = new Map<string, string>();
160
+ const keys = new Map<string, string>();
161
+ agents.forEach((agent, i) => {
162
+ const path = paths[i] ?? '';
163
+ const sameId = ids.get(agent.id);
164
+ if (sameId !== undefined) fail(path, `id ${agent.id} is already used by ${sameId}`);
165
+ ids.set(agent.id, path);
166
+ if (agent.key === null) return;
167
+ const sameKey = keys.get(agent.key);
168
+ if (sameKey !== undefined) fail(path, `its key is already used by ${sameKey}`);
169
+ keys.set(agent.key, path);
170
+ });
171
+ }
172
+
173
+ export function loadFileAgents(dir = agentsDir()): LoadedAgent[] {
174
+ const paths = listAgentFiles(dir);
175
+ const agents = paths.map(readAgentFile);
176
+ assertUnique(agents, paths);
177
+ return agents.map((agent) => ({
178
+ id: agent.id,
179
+ name: agent.name ?? agent.id,
180
+ key: agent.key,
181
+ accounts: agent.stations,
182
+ }));
183
+ }
184
+
185
+ export const fileSource: StationSource = () => Promise.resolve(loadFileAgents());
@@ -0,0 +1,40 @@
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 agentIdForKey(token: string): string | undefined {
34
+ if (token === '') return undefined;
35
+ return agentIdByKeyHash.get(digest(token));
36
+ }
37
+
38
+ export function hasAnyKey(): boolean {
39
+ return agentIdByKeyHash.size > 0;
40
+ }
@@ -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
+ }
@@ -0,0 +1,64 @@
1
+ import {
2
+ accountFromLine,
3
+ agentIdForAccount,
4
+ stationAgentIds,
5
+ } from './map.js';
6
+ import { STATIONS } from '@metro-labs/core/station-names';
7
+
8
+ const ACCOUNT_STATIONS = new Set<string>(STATIONS);
9
+
10
+ const stationHasAccounts = (station: string): boolean =>
11
+ ACCOUNT_STATIONS.has(station);
12
+
13
+ const accountInScope = (
14
+ allowed: Set<string>,
15
+ station: string,
16
+ accountId: string,
17
+ ): boolean => {
18
+ const agentId = agentIdForAccount(station, accountId);
19
+ return agentId !== undefined && allowed.has(agentId);
20
+ };
21
+
22
+ export function lineTargetDenied(
23
+ allowed: Set<string>,
24
+ args: Record<string, unknown>,
25
+ station?: string,
26
+ ): boolean {
27
+ const line = typeof args.line === 'string' ? args.line : undefined;
28
+ if (line === undefined) return false;
29
+ const acct = accountFromLine(line);
30
+ if (!acct) return true;
31
+ if (station !== undefined && acct.station !== station) return true;
32
+ if (!accountInScope(allowed, acct.station, acct.accountId)) return true;
33
+ const override = typeof args.account === 'string' ? args.account : undefined;
34
+ return override !== undefined
35
+ ? !accountInScope(allowed, acct.station, override)
36
+ : false;
37
+ }
38
+
39
+ export function stationFullyScoped(
40
+ allowed: Set<string>,
41
+ station: string,
42
+ ): boolean {
43
+ const ids = stationAgentIds(station);
44
+ return ids.length > 0 && ids.every((id) => allowed.has(id));
45
+ }
46
+
47
+ export function callTargetDenied(
48
+ allowed: Set<string>,
49
+ station: string,
50
+ args: Record<string, unknown>,
51
+ ): boolean {
52
+ if (typeof args.line === 'string')
53
+ return lineTargetDenied(allowed, args, station);
54
+ if (typeof args.account === 'string')
55
+ return !accountInScope(allowed, station, args.account);
56
+ return !stationFullyScoped(allowed, station);
57
+ }
58
+
59
+ export function eventInScope(allowed: Set<string>, line: string): boolean {
60
+ const acct = accountFromLine(line);
61
+ if (!acct) return false;
62
+ if (!stationHasAccounts(acct.station)) return true;
63
+ return accountInScope(allowed, acct.station, acct.accountId);
64
+ }
@@ -0,0 +1,38 @@
1
+ import { bufferedSince, type MetroEvent } from '@metro-labs/core/events';
2
+ import { accountFromLine } from './map.js';
3
+
4
+ export interface RecentSender {
5
+ id: string;
6
+ name: string;
7
+ at: string;
8
+ }
9
+
10
+ const DEFAULT_LIMIT = 20;
11
+
12
+ const SKIP = new Set(['', 'self', 'unknown']);
13
+
14
+ function sameAccount(from: string, station: string, accountId: string): boolean {
15
+ const account = accountFromLine(from);
16
+ return account?.station === station && account.accountId === accountId;
17
+ }
18
+
19
+ function senderOf(event: MetroEvent, station: string, accountId: string): { id: string; name: string } | null {
20
+ const from = String(event.from);
21
+ if (event.station !== station || !sameAccount(from, station, accountId)) return null;
22
+ const id = from.split('/').pop() ?? '';
23
+ if (SKIP.has(id)) return null;
24
+ return { id, name: event.fromDisplayName ?? event.fromName ?? '' };
25
+ }
26
+
27
+ export function recentSenders(station: string, accountId: string, limit = DEFAULT_LIMIT): RecentSender[] {
28
+ const seen = new Set<string>();
29
+ const out: RecentSender[] = [];
30
+ for (const { event } of bufferedSince(0).reverse()) {
31
+ const sender = senderOf(event, station, accountId);
32
+ if (sender === null || seen.has(sender.id.toLowerCase())) continue;
33
+ seen.add(sender.id.toLowerCase());
34
+ out.push({ ...sender, at: event.ts });
35
+ if (out.length >= limit) break;
36
+ }
37
+ return out;
38
+ }
@@ -0,0 +1,200 @@
1
+ import { join } from 'node:path';
2
+ import { type Server } from 'node:http';
3
+ import { userSelf } from '@metro-labs/core/events';
4
+ import { setTrainCallBackend } from '../stations/train-call.js';
5
+ import { errMsg, log, logFatalSync } from '@metro-labs/core/log';
6
+ import { acquireLock, STATE_DIR, trainsDir } from './paths.js';
7
+ import { installCrashGuard, markDaemonReady } from './crash-guard.js';
8
+ import { METRO_VERSION } from '@metro-labs/core/version';
9
+ import { funnelDriver, Tunnel, tunnelWanted, webhookPort } from '../net/tunnel.js';
10
+ import {
11
+ localConnectHint,
12
+ publicConnectHint,
13
+ tunnelPendingHint,
14
+ } from './connect-hint.js';
15
+ import { TrainSupervisor } from '../stations/supervisor.js';
16
+ import {
17
+ makeEmit,
18
+ startWebhookServer,
19
+ trainEventToMetroEvent,
20
+ } from '../routes/http.js';
21
+ import { localAgentKey } from '../stations/materialize.js';
22
+ import { agentsDir, fileSource } from '../agents/files.js';
23
+ import { syncPluginServers } from '../connectors/plugin-sync.js';
24
+ import { readLocalConnectors } from '../connectors/store.js';
25
+ import { ensureMetroPlugin } from '../claude/plugin-install.js';
26
+ import { unwatchSession, watchSession } from '../claude/session.js';
27
+ import { tryClaudeSetup } from '../claude/setup.js';
28
+ import { applyLocalOwner } from './local-owner.js';
29
+ import { installBearerSessions } from '../routes/bearer.js';
30
+ import { ensureLocalAgent, localOwner } from '../agents/file-admin.js';
31
+ import { migrateAgentLayout } from '../agents/files.js';
32
+ import { ensureStationDeps } from '../stations/runtime-deps.js';
33
+ import { localSessionApis } from '../routes/local-mode.js';
34
+ import type { SessionApis } from '../routes/session-apis.js';
35
+ import {
36
+ agentLiveness,
37
+ createMetroMcp,
38
+ } from '../mcp/index.js';
39
+ import { metroCall } from '../mcp/ctx.js';
40
+ import { gatherAccountsForAgents } from '../mcp/accounts.js';
41
+ import {
42
+ accountStationCapabilities,
43
+ stationByName,
44
+ } from '../stations/registry.js';
45
+ import { prepareAccount } from '../stations/attach.js';
46
+ import { materializeFrom, reloadFrom } from '../stations/materialize.js';
47
+ import type { StationName } from '@metro-labs/core/station-names';
48
+ import { startUploadReaper } from '../files/upload-store.js';
49
+
50
+ installCrashGuard();
51
+ acquireLock(join(STATE_DIR, '.tail-lock'));
52
+
53
+ log.info({ self: userSelf() }, 'user identity');
54
+
55
+ process.stdout.on('error', (err) => {
56
+ if ((err as NodeJS.ErrnoException).code !== 'EPIPE')
57
+ log.warn({ err: errMsg(err) }, 'stdout error');
58
+ });
59
+
60
+ const supervisor = new TrainSupervisor();
61
+ const emit = makeEmit();
62
+
63
+ supervisor.onTrainEvent((env, train) => {
64
+ const entry = trainEventToMetroEvent(env, train);
65
+ if (entry) emit(entry);
66
+ });
67
+
68
+ let webhookServer: Server | null = null;
69
+ const tunnel = tunnelWanted() ? new Tunnel(funnelDriver(webhookPort()), announcePublic) : null;
70
+
71
+ function announcePublic(url: string): void {
72
+ process.stderr.write(`\n${publicConnectHint(url, localOwner())}`);
73
+ }
74
+
75
+ setTrainCallBackend((train, action, args) =>
76
+ supervisor.call(train, action, args),
77
+ );
78
+
79
+ function announceLocalEndpoint(): void {
80
+ process.stderr.write(
81
+ `\n${tunnel === null ? localConnectHint(webhookPort(), localOwner()) : tunnelPendingHint()}`,
82
+ );
83
+ const key = localAgentKey();
84
+ if (key === null) {
85
+ log.info('no agent on this machine yet');
86
+ return;
87
+ }
88
+ const url = `http://127.0.0.1:${String(webhookPort())}/mcp?token=${key}`;
89
+ process.stderr.write(
90
+ `\nConnect an agent on this machine:\n\n claude mcp add --transport http metro "${url}"\n\n`,
91
+ );
92
+ }
93
+
94
+ async function syncStations(station: StationName): Promise<void> {
95
+ const { removed } = await reloadFrom(fileSource);
96
+ if (stationByName(station)?.hasTrain === false) return;
97
+ if (removed.includes(station)) await supervisor.stopTrain(station);
98
+ else {
99
+ ensureStationDeps(station);
100
+ supervisor.requestReload(station);
101
+ }
102
+ }
103
+
104
+ function sessionApis(): SessionApis {
105
+ return localSessionApis({
106
+ syncStations,
107
+ reloadAgents: async () => {
108
+ await reloadFrom(fileSource);
109
+ },
110
+ restart: () => {
111
+ exitCode = RESTART_EXIT;
112
+ onShutdown();
113
+ },
114
+ stop: () => {
115
+ exitCode = HOLD_EXIT;
116
+ onShutdown();
117
+ },
118
+ gatherAccounts: gatherAccountsForAgents,
119
+ capabilities: accountStationCapabilities,
120
+ liveness: agentLiveness,
121
+ prepareAccount,
122
+ });
123
+ }
124
+
125
+
126
+ async function main(): Promise<void> {
127
+ applyLocalOwner();
128
+ installBearerSessions(agentsDir(), localOwner);
129
+ migrateAgentLayout();
130
+ log.info({ agent: await ensureLocalAgent() }, 'local daemon: agent');
131
+ await materializeFrom(fileSource, { allowEmpty: true });
132
+ supervisor.start();
133
+ const metroMcp = await createMetroMcp();
134
+ webhookServer = await startWebhookServer(
135
+ emit,
136
+ sessionApis(),
137
+ metroMcp.httpHandler,
138
+ metroCall,
139
+ );
140
+ metroMcp.startInbound();
141
+ syncPluginServers(readLocalConnectors());
142
+ startUploadReaper();
143
+ announceLocalEndpoint();
144
+ tunnel?.start();
145
+ log.info(
146
+ { tunnel: !!tunnel, trainsDir: trainsDir(), mcp: '/', version: METRO_VERSION },
147
+ 'dispatcher ready',
148
+ );
149
+ markDaemonReady();
150
+ ensureMetroPlugin()
151
+ .then((outcome) => {
152
+ log.info({ outcome }, 'plugin: metro plugin for Claude Code');
153
+ if (outcome !== 'skipped') syncPluginServers(readLocalConnectors());
154
+ })
155
+ .catch((err: unknown) => {
156
+ log.warn({ err: errMsg(err) }, 'plugin: could not ensure the Claude Code plugin');
157
+ });
158
+ tryClaudeSetup();
159
+ watchSession();
160
+ }
161
+
162
+ const SHUTDOWN_TIMEOUT_MS = 3_000;
163
+ const RESTART_EXIT = 75;
164
+ const HOLD_EXIT = 76;
165
+ let exitCode = 0;
166
+ let shuttingDown = false;
167
+ async function shutdown(): Promise<void> {
168
+ if (shuttingDown) return;
169
+ shuttingDown = true;
170
+ log.info('dispatcher shutting down');
171
+ unwatchSession();
172
+ tunnel?.stop();
173
+ if (webhookServer) {
174
+ const server = webhookServer;
175
+ await Promise.race([
176
+ new Promise<void>((r) => {
177
+ server.close(() => {
178
+ r();
179
+ });
180
+ }),
181
+ new Promise<void>((r) => {
182
+ setTimeout(r, SHUTDOWN_TIMEOUT_MS).unref();
183
+ }),
184
+ ]);
185
+ }
186
+ await supervisor.stop();
187
+ process.exit(exitCode);
188
+ }
189
+ const onShutdown = (): void => {
190
+ shutdown().catch((err: unknown) => {
191
+ log.error({ err: errMsg(err) }, 'dispatcher: shutdown failed');
192
+ process.exit(1);
193
+ });
194
+ };
195
+ for (const sig of ['SIGINT', 'SIGTERM'] as const) process.on(sig, onShutdown);
196
+
197
+ await main().catch((err: unknown) => {
198
+ logFatalSync({ err: errMsg(err) }, 'dispatcher failed to start');
199
+ process.exit(1);
200
+ });
@@ -0,0 +1,39 @@
1
+ import { hostname } from 'node:os';
2
+
3
+ const uiBase = (): string => process.env.METRO_UI_URL ?? 'https://metro.box';
4
+
5
+ const connectLink = (base: string): string =>
6
+ `${uiBase()}/#/${new URL(base).host}`;
7
+
8
+ const ownerLine = (owner: string | null): string =>
9
+ owner === null
10
+ ? 'No owner is set, so no wallet can sign in. Restart with: metro serve --owner <address>'
11
+ : `Only ${owner} can sign in.`;
12
+
13
+ export function localConnectHint(port: number, owner: string | null): string {
14
+ const here = `http://127.0.0.1:${String(port)}`;
15
+ const forward = `ssh -L ${String(port)}:127.0.0.1:${String(port)} ${hostname()}`;
16
+ return [
17
+ 'Manage this machine from the web UI:',
18
+ '',
19
+ ` ${connectLink(here)}`,
20
+ '',
21
+ `From another computer, forward the port first: ${forward}`,
22
+ ownerLine(owner),
23
+ '',
24
+ ].join('\n');
25
+ }
26
+
27
+ export function publicConnectHint(url: string, owner: string | null): string {
28
+ return [
29
+ 'Manage this machine from the web UI, from anywhere:',
30
+ '',
31
+ ` ${connectLink(url)}`,
32
+ '',
33
+ ownerLine(owner),
34
+ '',
35
+ ].join('\n');
36
+ }
37
+
38
+ export const tunnelPendingHint = (): string =>
39
+ 'Publishing this daemon through Tailscale Funnel… (if Tailscale asks to enable Funnel, its link is in the log)\n';