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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (329) hide show
  1. package/dist/api.js +14 -0
  2. package/dist/claude-settings.js +40 -0
  3. package/dist/claude.js +176 -0
  4. package/dist/cli.js +101 -0
  5. package/dist/control.js +74 -0
  6. package/dist/hold.js +211 -0
  7. package/dist/local.js +93 -0
  8. package/dist/mcp-config.js +25 -0
  9. package/dist/node-name.js +71 -0
  10. package/dist/onboarding.js +43 -0
  11. package/dist/plugin.js +120 -0
  12. package/dist/provider-flags.js +9 -0
  13. package/dist/route.js +38 -0
  14. package/dist/runtime-install.js +97 -0
  15. package/dist/runtime.js +74 -0
  16. package/dist/serve.js +150 -0
  17. package/dist/service.js +242 -0
  18. package/dist/tail.js +119 -0
  19. package/dist/update.js +45 -0
  20. package/dist/version.js +97 -0
  21. package/package.json +14 -27
  22. package/runtime/marketplace/.claude-plugin/marketplace.json +12 -0
  23. package/runtime/marketplace/plugin/.claude-plugin/plugin.json +18 -0
  24. package/runtime/marketplace/plugin/.mcp.json +1 -0
  25. package/runtime/marketplace/plugin/bin/guard.mjs +55 -0
  26. package/runtime/marketplace/plugin/bin/metro-plugin.mjs +122 -0
  27. package/runtime/marketplace/plugin/bin/session-start.mjs +31 -0
  28. package/runtime/marketplace/plugin/hooks/hooks.json +25 -0
  29. package/runtime/marketplace/plugin/orchestrator.md +16 -0
  30. package/runtime/marketplace/plugin/skills/refresh/SKILL.md +15 -0
  31. package/runtime/node_modules/@metro-labs/core/package.json +43 -0
  32. package/runtime/node_modules/@metro-labs/core/src/address.ts +6 -0
  33. package/runtime/node_modules/@metro-labs/core/src/endpoints.ts +7 -0
  34. package/runtime/node_modules/@metro-labs/core/src/events.ts +120 -0
  35. package/runtime/node_modules/@metro-labs/core/src/identity.ts +7 -0
  36. package/runtime/node_modules/@metro-labs/core/src/ids.ts +21 -0
  37. package/runtime/node_modules/@metro-labs/core/src/is-record.ts +2 -0
  38. package/runtime/node_modules/@metro-labs/core/src/lines.ts +87 -0
  39. package/runtime/node_modules/@metro-labs/core/src/log.ts +42 -0
  40. package/runtime/node_modules/@metro-labs/core/src/protocol.ts +226 -0
  41. package/runtime/node_modules/@metro-labs/core/src/secure-fs.ts +57 -0
  42. package/runtime/node_modules/@metro-labs/core/src/station-names.ts +13 -0
  43. package/runtime/node_modules/@metro-labs/core/src/stations/account-store.ts +60 -0
  44. package/runtime/node_modules/@metro-labs/core/src/stations/attachments.ts +196 -0
  45. package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +88 -0
  46. package/runtime/node_modules/@metro-labs/core/src/stations/station-runtime.ts +64 -0
  47. package/runtime/node_modules/@metro-labs/core/src/stations/types.ts +108 -0
  48. package/runtime/node_modules/@metro-labs/core/src/str.ts +5 -0
  49. package/runtime/node_modules/@metro-labs/core/src/train-error.ts +53 -0
  50. package/runtime/node_modules/@metro-labs/core/src/version.ts +2 -0
  51. package/runtime/node_modules/@metro-labs/daemon/package.json +48 -0
  52. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-attach.ts +19 -0
  53. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-routes.ts +70 -0
  54. package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +365 -0
  55. package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +43 -0
  56. package/runtime/node_modules/@metro-labs/daemon/src/agents/allowlist.ts +37 -0
  57. package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +300 -0
  58. package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +122 -0
  59. package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +371 -0
  60. package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +155 -0
  61. package/runtime/node_modules/@metro-labs/daemon/src/agents/keys.ts +45 -0
  62. package/runtime/node_modules/@metro-labs/daemon/src/agents/map.ts +95 -0
  63. package/runtime/node_modules/@metro-labs/daemon/src/agents/scope.ts +64 -0
  64. package/runtime/node_modules/@metro-labs/daemon/src/agents/seed.ts +33 -0
  65. package/runtime/node_modules/@metro-labs/daemon/src/agents/senders.ts +38 -0
  66. package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +209 -0
  67. package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +39 -0
  68. package/runtime/node_modules/@metro-labs/daemon/src/boot/crash-guard.ts +49 -0
  69. package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +14 -0
  70. package/runtime/node_modules/@metro-labs/daemon/src/boot/paths.ts +106 -0
  71. package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +25 -0
  72. package/runtime/node_modules/@metro-labs/daemon/src/channels/dedupe.ts +24 -0
  73. package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +377 -0
  74. package/runtime/node_modules/@metro-labs/daemon/src/channels/media-note.ts +85 -0
  75. package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +75 -0
  76. package/runtime/node_modules/@metro-labs/daemon/src/channels/relay.ts +129 -0
  77. package/runtime/node_modules/@metro-labs/daemon/src/channels/webhook-note.ts +51 -0
  78. package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +309 -0
  79. package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +358 -0
  80. package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +169 -0
  81. package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +56 -0
  82. package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +140 -0
  83. package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +90 -0
  84. package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +253 -0
  85. package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +117 -0
  86. package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +239 -0
  87. package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +154 -0
  88. package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +109 -0
  89. package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +310 -0
  90. package/runtime/node_modules/@metro-labs/daemon/src/connectors/cli-api.ts +63 -0
  91. package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +175 -0
  92. package/runtime/node_modules/@metro-labs/daemon/src/connectors/json.ts +21 -0
  93. package/runtime/node_modules/@metro-labs/daemon/src/connectors/model.ts +77 -0
  94. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-client.ts +166 -0
  95. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-discovery.ts +139 -0
  96. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-pending.ts +65 -0
  97. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-routes.ts +171 -0
  98. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth.ts +155 -0
  99. package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +122 -0
  100. package/runtime/node_modules/@metro-labs/daemon/src/connectors/reach.ts +27 -0
  101. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-target.ts +39 -0
  102. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +296 -0
  103. package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +286 -0
  104. package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +98 -0
  105. package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +70 -0
  106. package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +332 -0
  107. package/runtime/node_modules/@metro-labs/daemon/src/connectors/watch.ts +54 -0
  108. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +99 -0
  109. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-owner.ts +32 -0
  110. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +121 -0
  111. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +294 -0
  112. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-store.ts +181 -0
  113. package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +259 -0
  114. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +231 -0
  115. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
  116. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +288 -0
  117. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +203 -0
  118. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +231 -0
  119. package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +123 -0
  120. package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +187 -0
  121. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +229 -0
  122. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +269 -0
  123. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +207 -0
  124. package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +90 -0
  125. package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +65 -0
  126. package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +17 -0
  127. package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +229 -0
  128. package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +151 -0
  129. package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +216 -0
  130. package/runtime/node_modules/@metro-labs/daemon/src/mcp/channel-owner.ts +29 -0
  131. package/runtime/node_modules/@metro-labs/daemon/src/mcp/ctx.ts +62 -0
  132. package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +91 -0
  133. package/runtime/node_modules/@metro-labs/daemon/src/mcp/frame-scope.ts +21 -0
  134. package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +129 -0
  135. package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +191 -0
  136. package/runtime/node_modules/@metro-labs/daemon/src/mcp/member-tools.ts +72 -0
  137. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +66 -0
  138. package/runtime/node_modules/@metro-labs/daemon/src/mcp/raw-get-stream.ts +207 -0
  139. package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +47 -0
  140. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-registry.ts +172 -0
  141. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-route.ts +43 -0
  142. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +215 -0
  143. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-dispatch.ts +215 -0
  144. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-schemas.ts +400 -0
  145. package/runtime/node_modules/@metro-labs/daemon/src/mcp/upload-tool.ts +66 -0
  146. package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +237 -0
  147. package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +389 -0
  148. package/runtime/node_modules/@metro-labs/daemon/src/routes/body.ts +24 -0
  149. package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +372 -0
  150. package/runtime/node_modules/@metro-labs/daemon/src/routes/identity.ts +54 -0
  151. package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +223 -0
  152. package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +60 -0
  153. package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
  154. package/runtime/node_modules/@metro-labs/daemon/src/routes/threema-callback.ts +103 -0
  155. package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +64 -0
  156. package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +61 -0
  157. package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +116 -0
  158. package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
  159. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-inline.ts +100 -0
  160. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-interactive.ts +161 -0
  161. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +202 -0
  162. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-session.ts +280 -0
  163. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-xmtp.ts +111 -0
  164. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +248 -0
  165. package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +261 -0
  166. package/runtime/node_modules/@metro-labs/daemon/src/stations/registry.ts +37 -0
  167. package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
  168. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor-io.ts +97 -0
  169. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +320 -0
  170. package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
  171. package/runtime/node_modules/@metro-labs/daemon/src/stations/train-call.ts +22 -0
  172. package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +96 -0
  173. package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +123 -0
  174. package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +40 -0
  175. package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
  176. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -0
  177. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
  178. package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
  179. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
  180. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +220 -0
  181. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
  182. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
  183. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
  184. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
  185. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
  186. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
  187. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
  188. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
  189. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
  190. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
  191. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
  192. package/runtime/node_modules/@metro-labs/http/package.json +33 -0
  193. package/runtime/node_modules/@metro-labs/http/src/api-error.ts +8 -0
  194. package/runtime/node_modules/@metro-labs/http/src/api-http.ts +95 -0
  195. package/runtime/node_modules/@metro-labs/http/src/cors.ts +29 -0
  196. package/runtime/node_modules/@metro-labs/http/src/identity-key.ts +33 -0
  197. package/runtime/node_modules/@metro-labs/http/src/identity-registry.ts +13 -0
  198. package/runtime/node_modules/@metro-labs/http/src/mode-api.ts +32 -0
  199. package/runtime/node_modules/@metro-labs/http/src/return-to.ts +21 -0
  200. package/runtime/node_modules/@metro-labs/http/src/signed-identity.ts +36 -0
  201. package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
  202. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
  203. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +254 -0
  204. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
  205. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
  206. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +156 -0
  207. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
  208. package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
  209. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
  210. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
  211. package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
  212. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
  213. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
  214. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
  215. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
  216. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
  217. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
  218. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
  219. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
  220. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
  221. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
  222. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
  223. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +201 -0
  224. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +109 -0
  225. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
  226. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
  227. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
  228. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +58 -0
  229. package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
  230. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
  231. package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
  232. package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +100 -0
  233. package/runtime/node_modules/@metro-labs/threema/src/actions.ts +219 -0
  234. package/runtime/node_modules/@metro-labs/threema/src/api.ts +130 -0
  235. package/runtime/node_modules/@metro-labs/threema/src/crypto.ts +132 -0
  236. package/runtime/node_modules/@metro-labs/threema/src/format.ts +123 -0
  237. package/runtime/node_modules/@metro-labs/threema/src/ids.ts +21 -0
  238. package/runtime/node_modules/@metro-labs/threema/src/index.ts +29 -0
  239. package/runtime/node_modules/@metro-labs/threema/src/station.ts +10 -0
  240. package/runtime/node_modules/@metro-labs/threema/src/verify.ts +117 -0
  241. package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
  242. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
  243. package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
  244. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
  245. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
  246. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +190 -0
  247. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
  248. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
  249. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +347 -0
  250. package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
  251. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +138 -0
  252. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
  253. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
  254. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
  255. package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
  256. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
  257. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
  258. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
  259. package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
  260. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
  261. package/runtime/node_modules/@metro-labs/whatsapp/src/resolve.ts +52 -0
  262. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +18 -0
  263. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
  264. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
  265. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
  266. package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
  267. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +112 -0
  268. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
  269. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
  270. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
  271. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
  272. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
  273. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
  274. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
  275. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
  276. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
  277. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +335 -0
  278. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +80 -0
  279. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
  280. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
  281. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
  282. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
  283. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
  284. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
  285. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
  286. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
  287. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
  288. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
  289. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
  290. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
  291. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +53 -0
  292. package/runtime/runtime.json +3 -0
  293. package/runtime/server.ts +1 -0
  294. package/runtime/stations.json +39 -0
  295. package/runtime/trains/.keep +0 -0
  296. package/LICENSE +0 -21
  297. package/README.md +0 -252
  298. package/dist/broker.js +0 -202
  299. package/dist/cache.js +0 -69
  300. package/dist/cli/actions.js +0 -206
  301. package/dist/cli/config.js +0 -185
  302. package/dist/cli/index.js +0 -188
  303. package/dist/cli/skill.js +0 -62
  304. package/dist/cli/tail.js +0 -164
  305. package/dist/cli/util.js +0 -72
  306. package/dist/cli/webhook.js +0 -81
  307. package/dist/codex-rc.js +0 -245
  308. package/dist/dispatcher.js +0 -128
  309. package/dist/history.js +0 -121
  310. package/dist/ipc.js +0 -72
  311. package/dist/log.js +0 -15
  312. package/dist/monitor.js +0 -194
  313. package/dist/paths.js +0 -68
  314. package/dist/registry.js +0 -48
  315. package/dist/stations/claude.js +0 -45
  316. package/dist/stations/codex.js +0 -68
  317. package/dist/stations/discord.js +0 -216
  318. package/dist/stations/index.js +0 -129
  319. package/dist/stations/telegram-md.js +0 -34
  320. package/dist/stations/telegram-upload.js +0 -113
  321. package/dist/stations/telegram.js +0 -234
  322. package/dist/stations/webhook.js +0 -103
  323. package/dist/tunnel.js +0 -64
  324. package/dist/webhooks.js +0 -41
  325. package/docs/broker.md +0 -214
  326. package/docs/monitor.md +0 -163
  327. package/docs/uri-scheme.md +0 -124
  328. package/docs/users.md +0 -226
  329. package/skills/metro/SKILL.md +0 -256
package/dist/tunnel.js DELETED
@@ -1,64 +0,0 @@
1
- /** Cloudflared tunnel manager. Prefers token-from-env so a missing local credentials JSON does not block startup. */
2
- import { spawn, spawnSync } from 'node:child_process';
3
- import { existsSync, readFileSync, writeFileSync } from 'node:fs';
4
- import { join } from 'node:path';
5
- import { STATE_DIR } from './paths.js';
6
- import { errMsg, log } from './log.js';
7
- const FILE = join(STATE_DIR, 'tunnel.json');
8
- const RESTART_DELAY_MS = 2_000;
9
- export const loadTunnelConfig = () => existsSync(FILE)
10
- ? JSON.parse(readFileSync(FILE, 'utf8'))
11
- : null;
12
- export function saveTunnelConfig(c) { writeFileSync(FILE, JSON.stringify(c, null, 2)); }
13
- /** Fetch the tunnel's auth token. Null when CLI is unavailable, not logged in, or no such tunnel. */
14
- function fetchTunnelToken(name) {
15
- const r = spawnSync('cloudflared', ['tunnel', 'token', name], { encoding: 'utf8' });
16
- if (r.status !== 0)
17
- return null;
18
- const token = r.stdout.trim();
19
- return token.length > 0 ? token : null;
20
- }
21
- export class Tunnel {
22
- cfg;
23
- port;
24
- child = null;
25
- closed = false;
26
- /** `undefined` = unresolved; `null` = resolved but unavailable (CLI missing / not logged in / no tunnel). */
27
- token = undefined;
28
- constructor(cfg, port) {
29
- this.cfg = cfg;
30
- this.port = port;
31
- }
32
- get hostname() { return this.cfg.hostname; }
33
- start() {
34
- if (this.closed)
35
- return;
36
- if (this.token === undefined)
37
- this.token = fetchTunnelToken(this.cfg.name);
38
- const mode = this.token ? 'token' : 'named';
39
- log.info({ name: this.cfg.name, hostname: this.cfg.hostname, port: this.port, mode }, 'cloudflared tunnel starting');
40
- /** `--no-autoupdate` is a global cloudflared flag — must come before the `tunnel` subcommand. */
41
- const args = ['--no-autoupdate', 'tunnel', 'run', '--url', `http://127.0.0.1:${this.port}`];
42
- /** Token form resolves the tunnel from TUNNEL_TOKEN so the trailing name arg must be omitted. */
43
- if (!this.token)
44
- args.push(this.cfg.name);
45
- const env = this.token
46
- ? { ...process.env, TUNNEL_TOKEN: this.token }
47
- : process.env;
48
- this.child = spawn('cloudflared', args, { stdio: ['ignore', 'pipe', 'pipe'], env });
49
- this.child.stderr?.on('data', d => log.debug({ cloudflared: d.toString().trim() }, 'cloudflared'));
50
- this.child.on('exit', code => {
51
- this.child = null;
52
- if (this.closed)
53
- return;
54
- log.warn({ code }, 'cloudflared exited; restarting');
55
- setTimeout(() => this.start(), RESTART_DELAY_MS);
56
- });
57
- this.child.on('error', err => log.warn({ err: errMsg(err) }, 'cloudflared spawn error'));
58
- }
59
- stop() {
60
- this.closed = true;
61
- this.child?.kill();
62
- this.child = null;
63
- }
64
- }
package/dist/webhooks.js DELETED
@@ -1,41 +0,0 @@
1
- /** Webhook endpoint registry: persists `(id, label, secret?)` for each receive endpoint. */
2
- import { randomBytes } from 'node:crypto';
3
- import { existsSync, readFileSync, writeFileSync } from 'node:fs';
4
- import { join } from 'node:path';
5
- import { STATE_DIR } from './paths.js';
6
- const FILE = join(STATE_DIR, 'webhooks.json');
7
- /** Local listener port — `127.0.0.1` only; expose publicly via Cloudflare tunnel. */
8
- export const webhookPort = () => Number(process.env.METRO_WEBHOOK_PORT) || 8420;
9
- function read() {
10
- if (!existsSync(FILE))
11
- return { endpoints: [] };
12
- try {
13
- return JSON.parse(readFileSync(FILE, 'utf8'));
14
- }
15
- catch {
16
- return { endpoints: [] };
17
- }
18
- }
19
- function write(s) { writeFileSync(FILE, JSON.stringify(s, null, 2)); }
20
- export const listEndpoints = () => read().endpoints;
21
- export const findEndpoint = (id) => read().endpoints.find(e => e.id === id);
22
- export function addEndpoint(label, secret) {
23
- const s = read();
24
- /** 16-char URL-safe id (~96 bits — collision-proof for any reasonable count). */
25
- const ep = {
26
- id: randomBytes(12).toString('base64url'), label, createdAt: new Date().toISOString(),
27
- ...(secret ? { secret } : {}),
28
- };
29
- s.endpoints.push(ep);
30
- write(s);
31
- return ep;
32
- }
33
- export function removeEndpoint(id) {
34
- const s = read();
35
- const before = s.endpoints.length;
36
- s.endpoints = s.endpoints.filter(e => e.id !== id);
37
- if (s.endpoints.length === before)
38
- return false;
39
- write(s);
40
- return true;
41
- }
package/docs/broker.md DELETED
@@ -1,214 +0,0 @@
1
- # Metro broker
2
-
3
- Multi-user event routing. Turns metro from "one daemon → one stdout consumer" into "one daemon → N independently-subscribed users (Claude Code, Codex, anything) with durable, replayable delivery".
4
-
5
- ## Why
6
-
7
- Today the dispatcher writes every inbound event to **its own stdout**, which only the parent process (one Claude Code, monitoring the daemon via `Monitor`) can read. Consequences:
8
-
9
- - **Throughput bottleneck**: bursts of inbound messages serialize behind whatever the single user is currently doing.
10
- - **No real sub-users**: `Agent`-tool sub-users can call `metro send` (IPC works from anywhere), but they cannot *receive* events — they have no stdout subscription.
11
- - **No multi-instance**: a second `claude` window or a separate `codex` process can't join in; the stream has one reader.
12
- - **No durability**: a user crashes mid-conversation → events emitted during the gap are lost; on restart it starts deaf.
13
-
14
- The fix is to treat metro as a tiny **durable message broker** over the event log that already exists ([history.ts](../src/history.ts), [user-registry.json](../src/registry.ts)).
15
-
16
- ## Core idea
17
-
18
- One concept — a **claim** — and three on-disk files you can `cat`:
19
-
20
- | Concern | File | Role |
21
- |----------------------|-----------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------|
22
- | Event log | `$METRO_STATE_DIR/history.jsonl` | Append-only JSONL — every inbound/outbound/edit/react. Already exists. The single source of truth. |
23
- | Claims | `$METRO_STATE_DIR/claims.json` | `{ <line>: <user-id> }` — flat map. A line in here is *exclusively* owned by that user. Absence = broadcast. New. |
24
- | Per-mode cursor | `$METRO_STATE_DIR/cursors/<key>` | Byte offset into `history.jsonl` — last-emitted position for one tail mode. New. Updated atomically after each emit. |
25
-
26
- Cursor keys are derived from the *effective mode* (not from `userSelf()`), so `--all` and `--unclaimed` don't collide with a personal `--as=<id>` tail:
27
-
28
- | Tail invocation | Cursor key |
29
- |----------------------------------|------------------------------------|
30
- | `metro tail --as=<id>` | `<userSlug(id)>` |
31
- | `metro tail --as=<id> --strict` | `<userSlug(id)>--strict` |
32
- | `metro tail --as=<id> --include-webhooks` | `<userSlug(id)>--with-webhooks` (or `…--strict--with-webhooks`) |
33
- | `metro tail --unclaimed` | `_unclaimed` |
34
- | `metro tail --all` | `_all` |
35
-
36
- The `_` prefix on the mode-keys can't collide with a real `userSelf()` slug (which always contains a station name like `claude-user-…`). Switching modes mid-stream keeps each cursor independent — a `tail --all` from a `CLAUDECODE=1` shell does **not** advance the personal `--as=<me>` cursor.
37
-
38
- `--chat=<line>` and `--station=<name>` are post-filters applied **after** cursor advancement, so they don't need their own cursor keys.
39
-
40
- Subscribers do not register with the daemon. They tail the log; the broker semantics emerge from one filtering rule applied at read time:
41
-
42
- > An event is delivered to a user when its `line` is **claimed by that user** *or* **claimed by no one**.
43
-
44
- That single rule covers every case the design needs to handle:
45
-
46
- - **Chat with one responder** — user claims the chat; other tailing users stop receiving it. No race.
47
- - **Webhook fan-out** — nobody claims; every user tailing a matching filter sees it.
48
- - **Operator observability** — `metro tail` with no `--as` (or `--all`) shows everything regardless of claims; doesn't take ownership.
49
- - **Sub-user onboarding** — sub-user claims its assigned chat before reading; parent stops receiving that chat without any coordination.
50
-
51
- There is no separate concept for "subscription" or "fan-out mode" — claims and their absence cover both. The dispatcher writes; tails filter; claims gate exclusivity. Three primitives, one rule.
52
-
53
- ```
54
- ┌──────────────────────────┐
55
- inbound (Discord/TG/web) ──► │ dispatcher │ ──► history.jsonl ◄── metro tail --as claude-A
56
- │ (writes log, no routing)│ ◄── metro tail --as codex-B
57
- └──────────────────────────┘ ◄── metro tail --as claude-sub-1
58
- (each holds its own cursor)
59
- ```
60
-
61
- ## CLI surface
62
-
63
- ```bash
64
- # Tail the event log. --follow streams new entries via fs.watch.
65
- metro tail [--as <user-id>] [--follow] [--strict | --unclaimed | --all] [--include-webhooks]
66
- [--chat <line>] [--station <name>] [--since <offset|tail>] [--limit <n>]
67
-
68
- # Claims: assert/release exclusive ownership of a line. Updates claims.json.
69
- metro claim <line> [--as <user-id>] # add/overwrite — last writer wins
70
- metro release <line> # remove (line returns to broadcast)
71
- metro claims # print current claims.json
72
-
73
- # Outbound actions auto-claim the line on first contact when topology is 1:1 (DM, claude/codex line).
74
- # Group / public / webhook lines are skipped by default — pass --claim to force.
75
- metro send <line> <text> [--no-claim] [--claim]
76
- metro reply <line> <msg-id> <text> [--no-claim] [--claim]
77
- metro edit <line> <msg-id> <text> [--no-claim] [--claim]
78
- metro react <line> <msg-id> <emoji> [--no-claim] [--claim]
79
- # Or disable globally: METRO_NO_AUTO_CLAIM=1
80
-
81
- # Lease/ack — optional, v2. When enabled, an event is "in flight" with the
82
- # claimant; if no ack in N seconds the cursor isn't advanced and the next
83
- # `metro tail` re-emits.
84
- metro ack <event-id> --as <user-id>
85
- ```
86
-
87
- `--as <user-id>` defaults to `userSelf()` ([history.ts:121](../src/history.ts#L121)) — the same stable identity already used in routing-aware code.
88
-
89
- ### Subscription modes
90
-
91
- The same `metro tail` command serves four distinct callers — a working user, a strict worker, a router, and a human observer. Each maps to one mutually-exclusive flag controlling the claim-aware filter:
92
-
93
- | Mode | Flag | Predicate | Who uses it |
94
- |--------------------|----------------------------|---------------------------------------------------------------------------------|------------------------------------------------------------|
95
- | **Mine + free** | `--as <id>` (default) | `(claims[line] == <id> ∨ line ∉ claims) ∧ station ≠ 'webhook'` | Default working user. Zero-config single-user setup. |
96
- | **Mine only** | `--as <id> --strict` | `claims[line] == <id> ∧ station ≠ 'webhook'` | Disciplined worker that won't race on unclaimed events. |
97
- | **Unclaimed only** | `--unclaimed` | `line ∉ claims` | Router/first-responder user that finds work to claim. |
98
- | **All** | (no `--as`) or `--all` | `true` | Operator/auditor/debugger; never takes ownership. |
99
-
100
- Webhooks (`station == 'webhook'`) are excluded from the personal modes by default — they're broadcast traffic (GitHub pushes, Intercom pings, etc.) that should flow to the *router* (`--unclaimed`) or *operator* (`--all`) feed, not firehose into every `--as <id>` tail. Opt back in with `metro tail --as <id> --include-webhooks` when you genuinely want a worker to see them.
101
-
102
- Two UX defaults worth being explicit about:
103
-
104
- 1. **`--as <id>` with no mode flag = "mine + free".** Single-user setups (the common case) get zero-config metro: nothing claimed yet, so the only tail sees everything. Adding a second user means claiming first — surfaced in docs, not enforced by the daemon. `--strict` is the opt-in for setups that want stricter separation.
105
- 2. **No `--as` = "all".** Matches the unix `tail -f` mental model. Operators just want to read the log without registering an identity or accidentally taking ownership of anything.
106
-
107
- `--unclaimed` is the genuinely new primitive: it enables a "router" user pattern where one process watches for ownerless events and either responds directly or claims and delegates. It works with or without `--as` — with `--as`, outbound replies are still attributed correctly.
108
-
109
- Direct messages between users (`event.to == user-line`) always pass the filter regardless of mode — they're inherently 1:1 and can't be "claimed" by someone else.
110
-
111
- ### Auto-claim on outbound
112
-
113
- `metro send`, `reply`, `edit`, and `react` claim the target `<line>` for the actor (`userSelf()`) the first time they touch it, atomically — same lockfile as `metro claim`. The intent: when a user picks up a conversation by replying, subsequent inbound events on that line route to them without any explicit `metro claim` call.
114
-
115
- Auto-claim only fires when **the line topology is 1:1** (DM, or a Claude/Codex cross-user line). Shared lines — group chats, public channels, webhook streams — would lock out other workers, so they're skipped by default:
116
-
117
- | Line | Classification | Auto-claim default? | How |
118
- |-------------------------------------------------|----------------|---------------------|--------------------------------------------------------------|
119
- | `metro://telegram/<positive-id>` (incl. topics) | DM | Yes | Telegram chat-id > 0 ⇒ private chat |
120
- | `metro://telegram/<negative-id>` / `-100…` | group | **No** | Telegram chat-id < 0 ⇒ group/supergroup |
121
- | `metro://discord/<channel-id>` (no guild) | DM | Yes | Recent inbound payload `guildId == null` |
122
- | `metro://discord/<channel-id>` (in guild) | group | **No** | Recent inbound payload `guildId != null` |
123
- | `metro://discord/<channel-id>` (no inbound) | unknown | Yes (conservative) | No metadata cached — treat as DM-eligible until proven group |
124
- | `metro://claude/...` / `metro://codex/...` | 1:1 | Yes | Cross-user notify is inherently 1:1 by construction |
125
- | `metro://webhook/<id>` | broadcast | **Never** | Webhook lines are conceptually a stream, not a conversation |
126
-
127
- - If the line is already claimed by **someone else** (and topology check passed), the action still proceeds (sending doesn't require ownership) but the claim is **not overwritten**. A single-line stderr note (`auto-claim skipped: line owned by <other-id>`) signals the no-op.
128
- - On a group-line skip you'll see `auto-claim skipped: <line> is a group/public line; pass --claim to take it explicitly` on stderr.
129
- - Opt-out per command with `--no-claim`, or globally with the env var `METRO_NO_AUTO_CLAIM=1`.
130
- - Opt-IN for groups: `--claim` forces auto-claim even on a group/public line (operator explicitly takes responsibility).
131
- - Cross-user sends (`metro send metro://claude/... ...` from a different user) auto-claim the target line too — the sender is taking ownership of the conversation.
132
-
133
- This default plus the webhook-exclusion above means: a webhook or a busy group channel flowing through the daemon won't auto-claim under any worker, so the router pattern (`--unclaimed`) can still see them.
134
-
135
- ### `metro tail` mechanics
136
-
137
- - Reads `history.jsonl`, applies the mode predicate + any `--chat`/`--station` filters (AND), prints one JSONL line per event to stdout.
138
- - With `--follow`: stays open, watches the file via `fs.watch`, emits new matching lines as they're appended.
139
- - Maintains a per-user cursor (byte offset) at `cursors/<user-id>`. On startup, resumes from cursor; on each emitted line, the offset is advanced *after* the write succeeds. Byte offsets give O(1) resume — no file scan.
140
- - `--since <offset>` overrides the cursor; `--since=tail` starts from EOF, ignoring backlog. Useful for fresh-start without losing the persisted cursor.
141
- - Claim lookups read `claims.json` once per emitted event. The file is small (a few KB) and OS-cached; cost is sub-microsecond per event.
142
-
143
- ### `metro claim` semantics
144
-
145
- - Pure metadata edit on `claims.json`. Does **not** notify the daemon — claims are read by tails, not the dispatcher (see "Dispatcher changes" below).
146
- - Re-claiming a line re-assigns it (last writer wins). `metro claims` prints the current map so a human can audit.
147
- - Releasing a line returns it to broadcast — every matching tail picks it up again.
148
- - Writes to `claims.json` are wrapped in an `O_EXCL` lockfile to serialize concurrent `metro claim` invocations on the same host.
149
-
150
- ## Dispatcher changes
151
-
152
- Almost none. `emit()` still appends to history, pushes to codex-rc, and writes to stdout. The broker model lives entirely on the read side — claims and cursors are consulted by `metro tail`, not by the dispatcher. The dispatcher doesn't need to know who's listening or who's claimed what.
153
-
154
- This is the design's key simplification: **the daemon stays dumb**. It's still a single-writer to a JSONL file. All the routing intelligence is in `metro tail`'s filter, which reads two small files (`claims.json` and its own cursor) on each event.
155
-
156
- No new sockets. No fan-out bookkeeping. No coupling between subscriber count and daemon state.
157
-
158
- ## What this enables
159
-
160
- - **Sub-users that actually receive events**: `Agent` spawns a sub-user whose first action is `metro tail --as <its-id> --chat <line> --follow &` — it then `Monitor`s that background process and gets *only* its assigned chat's events.
161
- - **Two manual Claude Code windows**: each runs `metro tail --as claude-A` / `claude-B`, claims disjoint chats. No coordination beyond `metro claim`.
162
- - **Codex alongside Claude**: same model — `metro tail --as codex-1 --station telegram` etc. The codex-rc push becomes optional: a Codex worker can subscribe via `metro tail` directly and bypass the rc file.
163
- - **Crash recovery**: process dies → restarts → `metro tail` resumes from cursor → backlog replays in order. No double-replies (the cursor is advanced on emit, not on reply).
164
- - **Replay for new joiners**: `metro tail --as new-user --since <offset-from-5-min-ago>` lets a freshly-spawned process backfill recent history before going live.
165
-
166
- ## Concurrency
167
-
168
- Multiple processes already write `history.jsonl` today: the daemon's `emit()` and every short-lived CLI invocation (`metro send`/`reply`/`react` — see [actions.ts](../src/cli/actions.ts)). It works because `appendFileSync` opens with `O_APPEND`, and POSIX guarantees that `O_APPEND` writes atomically seek-to-end-and-write in one operation — concurrent writers produce whole lines in some order, never interleaved halves. Node issues one `write(2)` per `appendFileSync` call, and our entries (even fat webhook payloads) stay well under per-syscall atomicity limits on both Linux (~2GB) and macOS (`INT_MAX`). The broker model adds **only readers**, so the existing safety property is preserved.
169
-
170
- `claims.json` is read on every event by every tail, but writes are infrequent (`metro claim`/`release`). An `O_EXCL` lockfile around writes is enough; tails do an unlocked read with a malformed-JSON retry (one read can race with one write; the retry resolves it).
171
-
172
- ## Isolation
173
-
174
- `METRO_STATE_DIR` isolates state-dir-scoped artifacts (`history.jsonl`, `claims.json`, `cursors/`, `lines.json`, `bot-ids.json`, the daemon socket, the webhook port). It does **not** isolate platform credentials: `metro send`, `reply`, `edit`, and `react` always read bot tokens from `$XDG_CONFIG_HOME/metro/.env` (defaulting to `~/.config/metro/.env`) and post directly to Discord/Telegram regardless of where `METRO_STATE_DIR` points.
175
-
176
- This means a test invocation with `METRO_STATE_DIR=/tmp/metro-test metro send …` will hit the **production** Discord/Telegram bot with production tokens. To avoid leaking real messages from a test/sandbox:
177
-
178
- - Use lines whose channel/chat IDs you know don't exist (the platform will 4xx before any side-effect).
179
- - Or unset/move `~/.config/metro/.env` for the test process — `metro send` will fail fast with a missing-token error.
180
- - Or use `metro tail` + manual `history.jsonl` seeding to exercise the read path without any platform contact.
181
-
182
- The auto-claim write happens **after** platform-API success, so a failed `metro send` never writes to `claims.json`. (Tests can rely on this: a failing send leaves the test state dir unchanged apart from the `history.jsonl` line the daemon would emit, if one were running.)
183
-
184
- ## Failure modes & guardrails
185
-
186
- | Failure | Behavior |
187
- |-------------------------------------|---------------------------------------------------------------------------------------------------|
188
- | Process crashes mid-event | Cursor not advanced → event redelivered on next `metro tail`. At-least-once. |
189
- | Two users claim same line | `claims.json` last-write-wins. `metro claims` shows current owner; humans resolve. |
190
- | No user claims a chat | Event broadcasts to every tail whose filters match. Two tails without filters → both reply (operator error — claim should have been set first). |
191
- | User silently slow (no ack) | v1: not detected. v2: `metro ack` + lease TTL — cursor doesn't advance, next `metro tail` re-emits, can surface "X went dark on chat Y" via an inbound event from another user. |
192
- | `history.jsonl` grows unboundedly | Existing concern; out of scope for this doc. (Rotate by date, prune by age.) |
193
-
194
- ## Migration
195
-
196
- All changes are additive. With no subscribers, the dispatcher behaves exactly as today (parent reads stdout, single-user throughput, no routing). The broker model layers on top:
197
-
198
- 1. Ship `metro tail` (read-only, no daemon changes, no claim file). Users can subscribe and filter; multi-cast works for everything.
199
- 2. Ship `metro claim`/`release`/`claims` + claim-aware filtering in `metro tail`. Exclusivity works.
200
- 3. Optional v2: lease/ack — only if silent drops become a real problem.
201
-
202
- Each step is independently shippable.
203
-
204
- ## Open questions
205
-
206
- - **Routing-key granularity**: claims map to `user-id` (orgId-level — same across sessions/devices) rather than `user-line` (`<user-id>/<session-id>`). This means two Claude Code windows logged into the same account share claims. The session-scoped alternative is more flexible but requires the claimant to write its current `selfLine()` into `claims.json` and refresh it when the session changes. **Default: user-id.** Override per-claim with `metro claim <line> --as <full-line>` if needed.
207
- - **codex-rc deprecation**: today the dispatcher mirrors every event into a codex-rc file so Codex sees them. Once `metro tail` exists, Codex workers could subscribe directly. The rc-push stays for compatibility; the next major version can drop it.
208
-
209
- ## Non-goals
210
-
211
- - **Strict ordering across chats**: events within one `line` are ordered by JSONL append order; cross-chat ordering is best-effort. Subscribers shouldn't rely on it.
212
- - **Exactly-once delivery**: at-least-once via cursor + redelivery. Idempotency is the subscriber's problem (the daemon already mints stable `msg_*` ids).
213
- - **Authn between users**: any process with filesystem access to `$METRO_STATE_DIR` can tail and claim. Same trust model as today.
214
- - **Remote users**: broker is local-only. Cross-host fan-out is a separate problem (likely solved by running metro on each host and bridging at the chat layer).
package/docs/monitor.md DELETED
@@ -1,163 +0,0 @@
1
- # Metro monitor endpoints
2
-
3
- Read-only HTTP endpoints for an external observer (the `apps/app` mobile app, an admin
4
- dashboard, a curl one-liner) to view live daemon state without touching the JSONL files
5
- directly.
6
-
7
- These endpoints mount on the **existing** webhook HTTP server (default port `8420`).
8
- There is no separate daemon, no separate port, no extra process to launch.
9
-
10
- ## Routes
11
-
12
- | Method | Path | Returns |
13
- |--------|--------------|------------------------------------------------------------------------------------------|
14
- | GET | `/api/state` | JSON snapshot — `{ claims, lines, recent_history (last 100), bot_ids }`. |
15
- | GET | `/api/tail` | Server-Sent Events stream — `history.jsonl` entries, claim-aware filtered. |
16
-
17
- Both routes are **read-only**. The daemon never mutates state on receipt. The handlers
18
- read the same files the broker reads (`history.jsonl`, `claims.json`, `bot-ids.json`)
19
- under whatever `METRO_STATE_DIR` resolves to.
20
-
21
- ## Authentication
22
-
23
- Bearer token in env: `METRO_MONITOR_TOKEN`.
24
-
25
- - If unset, both routes respond **503** with `{"error":"monitor endpoints not configured (METRO_MONITOR_TOKEN unset)"}`. Anonymous access is never allowed by accident.
26
- - If set, requests **must** carry `Authorization: Bearer <token>`. Missing/wrong/malformed → **401**. The comparison is constant-time (`crypto.timingSafeEqual`).
27
-
28
- Set in `~/.config/metro/.env`:
29
-
30
- ```
31
- METRO_MONITOR_TOKEN=<a long random string — `openssl rand -base64 32`>
32
- ```
33
-
34
- The daemon picks up the env var on next start.
35
-
36
- ## `GET /api/state`
37
-
38
- Returns a one-shot JSON snapshot:
39
-
40
- ```jsonc
41
- {
42
- "claims": {
43
- "metro://discord/123456789": "metro://claude/user/abc"
44
- },
45
- "lines": [
46
- "metro://discord/123456789",
47
- "metro://telegram/-100…"
48
- ],
49
- "recent_history": [/* most-recent-first, up to 100 HistoryEntry objects */],
50
- "bot_ids": { "discord": "1234567890", "telegram": "987654321" }
51
- }
52
- ```
53
-
54
- - `claims` — verbatim contents of `claims.json`.
55
- - `lines` — the set of conversation URIs seen across recent history and current claims (good-enough proxy for "what lines exist right now"). Subject to refinement; not authoritative.
56
- - `recent_history` — same shape as `HistoryEntry` in `src/history.ts`, ordered most-recent-first, capped at 100 entries.
57
- - `bot_ids` — verbatim contents of `bot-ids.json`.
58
-
59
- ### Example
60
-
61
- ```bash
62
- curl -H "Authorization: Bearer $METRO_MONITOR_TOKEN" \
63
- https://monitor.metro.box/api/state | jq
64
- ```
65
-
66
- ## `GET /api/tail` (SSE)
67
-
68
- Server-Sent Events stream of new `history.jsonl` entries. Each event has:
69
-
70
- ```
71
- id: <metro-msg-id>
72
- event: history
73
- data: <one HistoryEntry as JSON>
74
-
75
- ```
76
-
77
- The stream stays open until the client disconnects. A `: keepalive` comment is emitted
78
- every 25 seconds to keep proxies happy.
79
-
80
- ### Query parameters
81
-
82
- All optional. Mirror the `metro tail` CLI flags.
83
-
84
- | Param | Default | Effect |
85
- |--------------------|-------------|-------------------------------------------------------------------------|
86
- | `as=<line>` | (none) | Self URI — enables "mine + free" claim-aware filtering. |
87
- | `mode=strict\|unclaimed\|all` | derived | Override the mode (`strict` needs `as=`). |
88
- | `chat=<line>` | (none) | Only emit events matching this exact `line`. |
89
- | `station=<name>` | (none) | Only emit events matching this station (`discord`, `telegram`, …). |
90
- | `include_webhooks=true` | `false` | Include webhook-station events in personal modes. |
91
- | `since=tail\|0\|<offset>` | `tail` | Where to start in `history.jsonl`. `tail` = EOF; `0` = full replay; or a byte offset. |
92
-
93
- ### Filter rule
94
-
95
- Same predicate as `metro tail --as=<id> [--strict|--unclaimed|--all] [--include-webhooks]`:
96
-
97
- > An event is delivered when its `line` is **claimed by `as=`** *or* **claimed by no one**,
98
- > minus webhooks (unless `include_webhooks=true`), minus anything failing `chat=`/`station=`.
99
-
100
- See [broker.md](./broker.md) for the underlying broker semantics.
101
-
102
- ### Example: live tail for a claude user
103
-
104
- ```bash
105
- curl -N \
106
- -H "Authorization: Bearer $METRO_MONITOR_TOKEN" \
107
- "https://monitor.metro.box/api/tail?as=metro://claude/user/abc&include_webhooks=true"
108
- ```
109
-
110
- The `-N` flag disables curl's output buffering so SSE frames appear as they arrive.
111
-
112
- ### Example: full backlog replay for debugging
113
-
114
- ```bash
115
- curl -N \
116
- -H "Authorization: Bearer $METRO_MONITOR_TOKEN" \
117
- "https://monitor.metro.box/api/tail?since=0"
118
- ```
119
-
120
- ## Exposing publicly via Cloudflare tunnel
121
-
122
- The daemon listens on `127.0.0.1:8420` only. To reach `/api/*` from a phone or a
123
- remote machine, route a public hostname through the existing `webhook.metro.box`
124
- cloudflared tunnel.
125
-
126
- Add a second hostname route to your `cloudflared` config (typically
127
- `~/.cloudflared/config.yml`):
128
-
129
- ```yaml
130
- ingress:
131
- - hostname: webhook.metro.box
132
- service: http://127.0.0.1:8420
133
- - hostname: monitor.metro.box # new — same backing service
134
- service: http://127.0.0.1:8420
135
- - service: http_status:404
136
- ```
137
-
138
- Then create the DNS record:
139
-
140
- ```bash
141
- cloudflared tunnel route dns <tunnel-name> monitor.metro.box
142
- ```
143
-
144
- Restart the tunnel; both hostnames now reach the same metro daemon. `webhook.metro.box`
145
- keeps serving inbound webhooks; `monitor.metro.box` serves the bearer-token-gated
146
- monitor routes.
147
-
148
- There's no harm in serving `/api/*` from `webhook.metro.box` too — the bearer-token
149
- gate is the same either way. The separate hostname is purely a routing convenience
150
- (and lets you put different access policies in front of each, e.g., Cloudflare Access
151
- on `monitor.metro.box` only).
152
-
153
- ## Failure modes
154
-
155
- | Condition | Response |
156
- |------------------------------------|-------------------------------------------------------------------|
157
- | `METRO_MONITOR_TOKEN` not set | 503 `{"error":"monitor endpoints not configured (...)"}` |
158
- | Missing `Authorization` header | 401 `{"error":"unauthorized"}` |
159
- | Wrong / malformed token | 401 `{"error":"unauthorized"}` |
160
- | Unknown `/api/*` path | 404 `{"error":"not found"}` |
161
- | `POST` (or any non-`GET`) to `/api/*` | 405 `{"error":"method not allowed"}` |
162
- | `history.jsonl` doesn't exist yet | 200 with empty `recent_history` / SSE stream w/ no events. |
163
- | Client disconnects mid-SSE | Handler clears its interval + closes the file watcher cleanly. |
@@ -1,124 +0,0 @@
1
- # Metro URI scheme
2
-
3
- Universal identifier for every conversational scope in metro. Lines pass around as opaque strings; only the owning station parses its own paths.
4
-
5
- ## Grammar
6
-
7
- ```
8
- line = "metro://" station "/" path
9
- station = lowercase identifier (claude | codex | discord | telegram | webhook | …)
10
- path = station-specific, "/"-separated segments
11
- ```
12
-
13
- The URI parses cleanly with the WHATWG `URL` parser: `new URL(line)` gives `protocol="metro:"`, `host=<station>`, `pathname="/<path>"`.
14
-
15
- ## Registered stations
16
-
17
- | Station | Pattern | Example |
18
- |------------|----------------------------------------------|------------------------------------------------------------------------|
19
- | `discord` | `metro://discord/<channel-id>` | `metro://discord/1234567890123456789` |
20
- | `telegram` | `metro://telegram/<chat-id>[/<topic-id>]` | `metro://telegram/-1001234567890/42` |
21
- | `claude` | `metro://claude/<user-id>/<session-id>` | `metro://claude/9bfc7af0-…/50b00d11-…` |
22
- | `codex` | `metro://codex/<user-id>/<session-id>` | `metro://codex/8119ecb1-…/01997d4b-…` |
23
- | `webhook` | `metro://webhook/<endpoint-id>` | `metro://webhook/fwaCgTKJuLAjS2K0` |
24
-
25
- Claude / Codex lines mirror the `<root>/<sub>` structure of `metro://telegram/<chat-id>/<topic-id>`: `<user-id>` (the stable account id — same across devices) plays the role of `<chat-id>`, and `<session-id>` (one conversation) plays the role of `<topic-id>`. Both segments are derived per station (see [participants](#participants) below).
26
-
27
- ## Participants
28
-
29
- Every chat station also exposes participant URIs — used as `from` on inbound/outbound events and history rows.
30
-
31
- | Kind | Pattern | Example |
32
- |--------|----------------------------------|---------------------------------------------------------------|
33
- | user | `metro://<station>/user/<id>` | `metro://discord/user/87654321` |
34
- | claude | `metro://claude/user/<orgId>` | `metro://claude/user/9bfc7af0-2117-44c5-baf2-d22ba382d065` |
35
- | codex | `metro://codex/user/<accountId>` | `metro://codex/user/8119ecb1-b05e-48db-aa80-434584439df9` |
36
- | webhook | `metro://webhook/<endpointId>` | `metro://webhook/fwaCgTKJuLAjS2K0` (line + `from` are the same — no HTTP-side user identity) |
37
-
38
- `from` and `to` on history entries are always participant URIs. Discord/Telegram inbounds set `from` to the user URI; the daemon sets `to` to the local user identity:
39
-
40
- - **Claude Code** (`$CLAUDECODE` set) — `metro://claude/user/<orgId>`. `<orgId>` is the stable Anthropic-account UUID, resolved by shelling out to `claude auth status --json`.
41
- - **Codex** (`$METRO_CODEX_RC` or `$CODEX_HOME` set) — `metro://codex/user/<accountId>`. `<accountId>` is the ChatGPT-account UUID, read from `$CODEX_HOME/auth.json` (default `~/.codex/auth.json`) at the `tokens.account_id` field. Requires `auth_mode=chatgpt`; API-key-only Codex sessions have no account id and metro will error.
42
- - **Neither** — `to` is the generic `metro://user`.
43
-
44
- Same account on any machine yields the same URI. Switching accounts via `claude auth login` / `codex login` flips the URI within ~5 s for the long-lived daemon (5 s TTL cache); one-shot CLI invocations re-resolve every run. On outbound, `from` = the same user identity; `to` = the original sender for replies/reacts (looked up from history), or the channel `line` for fresh group sends. A `fromName` field carries the display name (`@alice`, `bonustrack_`).
45
-
46
- Override with `--from=<uri>` on any write command, or set `$METRO_FROM` to pin a custom identity for the whole session.
47
-
48
- Chat lines identify a Discord channel / Telegram chat (with optional forum topic). Claude / Codex lines identify a *specific session* of a specific user (`<user-id>/<session-id>`) — posting to one re-emits the message on the daemon's stdout stream and (if configured) pushes it to the Codex app-server. They have no inherent "messages"; only events.
49
-
50
- ### Session derivation per station
51
-
52
- | Station | `<user-id>` | `<session-id>` |
53
- |----------|----------------------------|-----------------------------------------------------------------|
54
- | `claude` | `orgId` from `claude auth status --json` | `$CLAUDE_CODE_SESSION_ID` (set by Claude Code; stable across `--resume`)|
55
- | `codex` | `tokens.account_id` from `$CODEX_HOME/auth.json` | codex-rc thread id from the JSON-RPC handshake (`thread/loaded/list` → `thread/start`) |
56
-
57
- Override either segment with `METRO_USER_ID` / `METRO_USER_SESSION_ID` env vars.
58
-
59
- ### User registry
60
-
61
- The daemon persists every `(station, user-id, session)` tuple it sees to `$METRO_STATE_DIR/user-registry.json`. `metro stations` prints the count of seen users and sessions per station. Run it to discover what's reachable rather than guessing topic names.
62
-
63
- ## Webhook station
64
-
65
- Receive-only HTTP endpoint for third-party services (GitHub, Intercom, Fireflies, …). Each registered endpoint is one `metro://webhook/<endpoint-id>` line.
66
-
67
- - **Register:** `metro webhook add <label> [--secret=<shared-secret>]` mints a 16-char endpoint id (96 bits of entropy, persisted to `$METRO_STATE_DIR/webhooks.json`) and prints the receiving URL. `metro webhook list` / `remove <id>` for the obvious.
68
- - **Listener:** the dispatcher binds `127.0.0.1:8420` (override with `METRO_WEBHOOK_PORT`) when ≥1 endpoint is registered. Routes `POST /wh/<endpoint-id>` to an inbound event with `payload: { headers, body }` — `body` is parsed JSON when the request `Content-Type` is JSON, raw string otherwise. `GET /wh/<endpoint-id>` returns 200 (for provider ping checks).
69
- - **Envelope:** `messageId` falls back to `X-GitHub-Delivery` / `X-Request-ID` / a generated UUID for idempotency tracking. `text` is synthesized from `X-GitHub-Event` / `X-Intercom-Topic` plus method + path for at-a-glance routing; consumers narrow on `payload.body` for full event details.
70
- - **HMAC verification:** if `--secret` was set on `metro webhook add`, requests must include a matching `X-Hub-Signature-256: sha256=<hex>` (GitHub/Intercom format) — mismatches are rejected with 401 before reaching the stream.
71
- - **Public reachability:** provided by a Cloudflare named tunnel — see [Tunneling](#tunneling) below. Without one, the listener stays loopback-only (useful for `curl` testing).
72
-
73
- ## Tunneling
74
-
75
- Webhook providers need a public URL. Metro integrates with **Cloudflare named tunnels** (free, stable, account-scoped):
76
-
77
- ```bash
78
- cloudflared tunnel login # one-time OAuth (browser)
79
- metro tunnel setup metro webhook.yourdomain.com # creates the tunnel + DNS route
80
- metro # daemon spawns `cloudflared tunnel run`
81
- ```
82
-
83
- After setup, `metro webhook list` prints `https://webhook.yourdomain.com/wh/<id>` for each endpoint. The URL is stable across restarts (bound to the tunnel UUID in `~/.cloudflared/<uuid>.json`, not the cloudflared process). Tunnel config persists at `$METRO_STATE_DIR/tunnel.json`. Without setup, endpoints fall back to `http://127.0.0.1:8420/wh/<id>` (local-only, useful for curl testing).
84
-
85
- ## Message addressing
86
-
87
- Messages on chat lines are referenced by **line + message id** (two args), not as part of the URI. So:
88
-
89
- ```bash
90
- metro reply metro://discord/123… 4567 "ack"
91
- metro edit metro://discord/123… 9876 "fixed typo"
92
- metro react metro://telegram/-100…/42 4567 👍
93
- ```
94
-
95
- ## Properties
96
-
97
- - **Stable**: a Line is valid for the lifetime of the scope.
98
- - **Self-describing**: the station name is encoded; the dispatcher routes by station prefix.
99
- - **Persistable**: safe as a JSON key on disk (used by `lines.json`).
100
- - **Branded**: TypeScript type `Line` prevents mixing with arbitrary strings.
101
-
102
- ## API
103
-
104
- ```ts
105
- import { Line } from './stations/index.js'; // value namespace + type
106
-
107
- const l: Line = Line.discord('1234567890'); // typed Line
108
- Line.parse(l); // { station: 'discord', path: ['1234567890'] } | null
109
- Line.station(l); // 'discord'
110
- Line.claude(orgId, sessionId); // metro://claude/<orgId>/<sessionId>
111
- Line.codex(accountId, threadId); // metro://codex/<accountId>/<threadId>
112
- Line.parseClaude(l); // { userId, sessionId } | null
113
- Line.parseCodex(l); // { userId, sessionId } | null
114
- Line.webhook(endpointId); // metro://webhook/<endpointId>
115
- Line.parseWebhook(l); // string | null (the endpoint id)
116
- Line.user(station, id); // metro://<station>/user/<id>
117
- Line.isLocal(l); // true for any metro://{claude,codex}/...
118
- ```
119
-
120
- ## Adding a new station
121
-
122
- 1. Pick a lowercase station name (`slack`, `matrix`, …).
123
- 2. Add a `Line.<station>(...)` formatter and a parser that returns your typed payload.
124
- 3. Document the path grammar in the table above.