@stage-labs/metro 0.1.0-beta.2 → 0.1.0-beta.200

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 (391) hide show
  1. package/dist/api.js +11 -0
  2. package/dist/claude-settings.js +40 -0
  3. package/dist/claude.js +163 -0
  4. package/dist/cli.js +77 -567
  5. package/dist/control.js +74 -0
  6. package/dist/hold.js +211 -0
  7. package/dist/local.js +43 -0
  8. package/dist/mcp-config.js +25 -0
  9. package/dist/node-name.js +71 -0
  10. package/dist/onboarding.js +44 -0
  11. package/dist/provider-flags.js +9 -0
  12. package/dist/route.js +58 -0
  13. package/dist/runtime-install.js +97 -0
  14. package/dist/runtime.js +74 -0
  15. package/dist/serve.js +158 -0
  16. package/dist/service.js +243 -0
  17. package/dist/tail.js +102 -144
  18. package/dist/update.js +45 -0
  19. package/dist/version.js +97 -0
  20. package/package.json +14 -23
  21. package/runtime/marketplace/.claude-plugin/marketplace.json +12 -0
  22. package/runtime/marketplace/plugin/.claude-plugin/plugin.json +17 -0
  23. package/runtime/marketplace/plugin/.mcp.json +1 -0
  24. package/runtime/marketplace/plugin/bin/guard.mjs +71 -0
  25. package/runtime/marketplace/plugin/bin/metro-plugin.mjs +30 -0
  26. package/runtime/marketplace/plugin/bin/policy.mjs +97 -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 +24 -0
  30. package/runtime/node_modules/@metro-labs/core/package.json +47 -0
  31. package/runtime/node_modules/@metro-labs/core/src/endpoints.ts +7 -0
  32. package/runtime/node_modules/@metro-labs/core/src/events.ts +119 -0
  33. package/runtime/node_modules/@metro-labs/core/src/identity.ts +7 -0
  34. package/runtime/node_modules/@metro-labs/core/src/ids.ts +24 -0
  35. package/runtime/node_modules/@metro-labs/core/src/is-record.ts +2 -0
  36. package/runtime/node_modules/@metro-labs/core/src/lines.ts +91 -0
  37. package/runtime/node_modules/@metro-labs/core/src/log.ts +42 -0
  38. package/runtime/node_modules/@metro-labs/core/src/protocol.ts +227 -0
  39. package/runtime/node_modules/@metro-labs/core/src/secure-fs.ts +74 -0
  40. package/runtime/node_modules/@metro-labs/core/src/station-names.ts +13 -0
  41. package/runtime/node_modules/@metro-labs/core/src/stations/account-files.ts +41 -0
  42. package/runtime/node_modules/@metro-labs/core/src/stations/account-store.ts +60 -0
  43. package/runtime/node_modules/@metro-labs/core/src/stations/attachments.ts +196 -0
  44. package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +88 -0
  45. package/runtime/node_modules/@metro-labs/core/src/stations/profile.ts +58 -0
  46. package/runtime/node_modules/@metro-labs/core/src/stations/sender-profile.ts +64 -0
  47. package/runtime/node_modules/@metro-labs/core/src/stations/station-runtime.ts +61 -0
  48. package/runtime/node_modules/@metro-labs/core/src/stations/train-boot.ts +42 -0
  49. package/runtime/node_modules/@metro-labs/core/src/stations/train-events.ts +89 -0
  50. package/runtime/node_modules/@metro-labs/core/src/stations/types.ts +123 -0
  51. package/runtime/node_modules/@metro-labs/core/src/str.ts +5 -0
  52. package/runtime/node_modules/@metro-labs/core/src/tickets.ts +37 -0
  53. package/runtime/node_modules/@metro-labs/core/src/train-error.ts +53 -0
  54. package/runtime/node_modules/@metro-labs/core/src/version.ts +2 -0
  55. package/runtime/node_modules/@metro-labs/daemon/package.json +48 -0
  56. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/agent-fs.ts +134 -0
  57. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/files-api.ts +12 -0
  58. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/files.ts +119 -0
  59. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/home-fs.ts +113 -0
  60. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/job-env.ts +51 -0
  61. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/provision.ts +91 -0
  62. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedule-detail.ts +100 -0
  63. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules-api.ts +27 -0
  64. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules.ts +252 -0
  65. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/unit-files.ts +61 -0
  66. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/user.ts +91 -0
  67. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/view.ts +87 -0
  68. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-attach.ts +19 -0
  69. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-lookups.ts +55 -0
  70. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-routes.ts +76 -0
  71. package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +357 -0
  72. package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +30 -0
  73. package/runtime/node_modules/@metro-labs/daemon/src/agents/allowlist.ts +59 -0
  74. package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +131 -0
  75. package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +132 -0
  76. package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +305 -0
  77. package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +168 -0
  78. package/runtime/node_modules/@metro-labs/daemon/src/agents/keys.ts +35 -0
  79. package/runtime/node_modules/@metro-labs/daemon/src/agents/map.ts +128 -0
  80. package/runtime/node_modules/@metro-labs/daemon/src/agents/policy-route.ts +27 -0
  81. package/runtime/node_modules/@metro-labs/daemon/src/agents/scope.ts +64 -0
  82. package/runtime/node_modules/@metro-labs/daemon/src/agents/sender-cards.ts +86 -0
  83. package/runtime/node_modules/@metro-labs/daemon/src/agents/senders.ts +38 -0
  84. package/runtime/node_modules/@metro-labs/daemon/src/approvals/api.ts +55 -0
  85. package/runtime/node_modules/@metro-labs/daemon/src/approvals/needs.ts +2 -0
  86. package/runtime/node_modules/@metro-labs/daemon/src/approvals/pending.ts +149 -0
  87. package/runtime/node_modules/@metro-labs/daemon/src/approvals/preview.ts +66 -0
  88. package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +221 -0
  89. package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +39 -0
  90. package/runtime/node_modules/@metro-labs/daemon/src/boot/crash-guard.ts +49 -0
  91. package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +21 -0
  92. package/runtime/node_modules/@metro-labs/daemon/src/boot/paths.ts +106 -0
  93. package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +26 -0
  94. package/runtime/node_modules/@metro-labs/daemon/src/channels/dedupe.ts +12 -0
  95. package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +370 -0
  96. package/runtime/node_modules/@metro-labs/daemon/src/channels/media-note.ts +90 -0
  97. package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +78 -0
  98. package/runtime/node_modules/@metro-labs/daemon/src/channels/react-note.ts +13 -0
  99. package/runtime/node_modules/@metro-labs/daemon/src/channels/relay.ts +129 -0
  100. package/runtime/node_modules/@metro-labs/daemon/src/channels/reply-id.ts +1 -0
  101. package/runtime/node_modules/@metro-labs/daemon/src/channels/webhook-note.ts +51 -0
  102. package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +355 -0
  103. package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +359 -0
  104. package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +208 -0
  105. package/runtime/node_modules/@metro-labs/daemon/src/claude/memory.ts +82 -0
  106. package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +60 -0
  107. package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +149 -0
  108. package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +75 -0
  109. package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +283 -0
  110. package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +109 -0
  111. package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +271 -0
  112. package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +120 -0
  113. package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +113 -0
  114. package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +306 -0
  115. package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +176 -0
  116. package/runtime/node_modules/@metro-labs/daemon/src/connectors/gates.ts +72 -0
  117. package/runtime/node_modules/@metro-labs/daemon/src/connectors/health.ts +18 -0
  118. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-client.ts +159 -0
  119. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-discovery.ts +135 -0
  120. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-routes.ts +158 -0
  121. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth.ts +166 -0
  122. package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +131 -0
  123. package/runtime/node_modules/@metro-labs/daemon/src/connectors/reach.ts +27 -0
  124. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-policy.ts +90 -0
  125. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-target.ts +29 -0
  126. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +348 -0
  127. package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +358 -0
  128. package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +81 -0
  129. package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +37 -0
  130. package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +106 -0
  131. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +99 -0
  132. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-owner.ts +32 -0
  133. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-reaper.ts +174 -0
  134. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +123 -0
  135. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +278 -0
  136. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-store.ts +181 -0
  137. package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +233 -0
  138. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +208 -0
  139. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
  140. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +173 -0
  141. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +194 -0
  142. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +137 -0
  143. package/runtime/node_modules/@metro-labs/daemon/src/gateway/effort.ts +77 -0
  144. package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +123 -0
  145. package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +237 -0
  146. package/runtime/node_modules/@metro-labs/daemon/src/gateway/frames.ts +118 -0
  147. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +274 -0
  148. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-auth.ts +160 -0
  149. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-client.ts +46 -0
  150. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-setup.ts +150 -0
  151. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-stream.ts +139 -0
  152. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-translate.ts +223 -0
  153. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini.ts +208 -0
  154. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +199 -0
  155. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +299 -0
  156. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-signin.ts +193 -0
  157. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-store.ts +70 -0
  158. package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +90 -0
  159. package/runtime/node_modules/@metro-labs/daemon/src/gateway/pkce.ts +36 -0
  160. package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +66 -0
  161. package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +18 -0
  162. package/runtime/node_modules/@metro-labs/daemon/src/gateway/subscription.ts +120 -0
  163. package/runtime/node_modules/@metro-labs/daemon/src/gateway/text.ts +15 -0
  164. package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +244 -0
  165. package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +156 -0
  166. package/runtime/node_modules/@metro-labs/daemon/src/mcp/attachment-schema.ts +91 -0
  167. package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +226 -0
  168. package/runtime/node_modules/@metro-labs/daemon/src/mcp/ctx.ts +62 -0
  169. package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +90 -0
  170. package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +129 -0
  171. package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +137 -0
  172. package/runtime/node_modules/@metro-labs/daemon/src/mcp/instructions.ts +19 -0
  173. package/runtime/node_modules/@metro-labs/daemon/src/mcp/member-tools.ts +72 -0
  174. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-prompt.ts +87 -0
  175. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +79 -0
  176. package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-gate.ts +83 -0
  177. package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-snapshot.ts +79 -0
  178. package/runtime/node_modules/@metro-labs/daemon/src/mcp/profile-lookup.ts +60 -0
  179. package/runtime/node_modules/@metro-labs/daemon/src/mcp/profile-tool.ts +96 -0
  180. package/runtime/node_modules/@metro-labs/daemon/src/mcp/raw-get-stream.ts +207 -0
  181. package/runtime/node_modules/@metro-labs/daemon/src/mcp/read-tool.ts +87 -0
  182. package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +46 -0
  183. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-slot.ts +48 -0
  184. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +202 -0
  185. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-catalog.ts +88 -0
  186. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-def.ts +9 -0
  187. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-dispatch.ts +225 -0
  188. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-schemas.ts +333 -0
  189. package/runtime/node_modules/@metro-labs/daemon/src/mcp/upload-tool.ts +61 -0
  190. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/api.ts +43 -0
  191. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/helper-script.ts +126 -0
  192. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/move.ts +165 -0
  193. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/privilege.ts +48 -0
  194. package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +132 -0
  195. package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +316 -0
  196. package/runtime/node_modules/@metro-labs/daemon/src/policy/policy.ts +118 -0
  197. package/runtime/node_modules/@metro-labs/daemon/src/routes/bearer.ts +35 -0
  198. package/runtime/node_modules/@metro-labs/daemon/src/routes/body.ts +24 -0
  199. package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +365 -0
  200. package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +208 -0
  201. package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +71 -0
  202. package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
  203. package/runtime/node_modules/@metro-labs/daemon/src/routes/threema-callback.ts +103 -0
  204. package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +39 -0
  205. package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +58 -0
  206. package/runtime/node_modules/@metro-labs/daemon/src/server/owner.ts +26 -0
  207. package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +90 -0
  208. package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
  209. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-driver.ts +42 -0
  210. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-inline.ts +128 -0
  211. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-interactive.ts +134 -0
  212. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-outlook.ts +63 -0
  213. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +225 -0
  214. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-session.ts +288 -0
  215. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-xmtp.ts +111 -0
  216. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +248 -0
  217. package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +265 -0
  218. package/runtime/node_modules/@metro-labs/daemon/src/stations/registry.ts +51 -0
  219. package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
  220. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor-io.ts +97 -0
  221. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +320 -0
  222. package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
  223. package/runtime/node_modules/@metro-labs/daemon/src/stations/train-call.ts +22 -0
  224. package/runtime/node_modules/@metro-labs/daemon/src/stations/webhook-endpoints.ts +51 -0
  225. package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +84 -0
  226. package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +124 -0
  227. package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +15 -0
  228. package/runtime/node_modules/@metro-labs/daemon/src/vault/api.ts +45 -0
  229. package/runtime/node_modules/@metro-labs/daemon/src/vault/config.ts +51 -0
  230. package/runtime/node_modules/@metro-labs/daemon/src/vault/firewall.ts +48 -0
  231. package/runtime/node_modules/@metro-labs/daemon/src/vault/index.ts +96 -0
  232. package/runtime/node_modules/@metro-labs/daemon/src/vault/install.ts +56 -0
  233. package/runtime/node_modules/@metro-labs/daemon/src/vault/paths.ts +26 -0
  234. package/runtime/node_modules/@metro-labs/daemon/src/vault/proxy.ts +148 -0
  235. package/runtime/node_modules/@metro-labs/daemon/src/vault/store.ts +130 -0
  236. package/runtime/node_modules/@metro-labs/daemon/src/vault/trust.ts +61 -0
  237. package/runtime/node_modules/@metro-labs/discord-bot/package.json +26 -0
  238. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +117 -0
  239. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +240 -0
  240. package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
  241. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
  242. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +174 -0
  243. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
  244. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
  245. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +122 -0
  246. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
  247. package/runtime/node_modules/@metro-labs/discord-bot/src/profile.ts +68 -0
  248. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
  249. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +22 -0
  250. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
  251. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +1 -0
  252. package/runtime/node_modules/@metro-labs/http/package.json +29 -0
  253. package/runtime/node_modules/@metro-labs/http/src/api-error.ts +8 -0
  254. package/runtime/node_modules/@metro-labs/http/src/api-http.ts +138 -0
  255. package/runtime/node_modules/@metro-labs/http/src/cors.ts +29 -0
  256. package/runtime/node_modules/@metro-labs/http/src/mode-api.ts +32 -0
  257. package/runtime/node_modules/@metro-labs/http/src/return-to.ts +21 -0
  258. package/runtime/node_modules/@metro-labs/http/src/workos-token.ts +149 -0
  259. package/runtime/node_modules/@metro-labs/outlook/package.json +25 -0
  260. package/runtime/node_modules/@metro-labs/outlook/src/accounts.ts +107 -0
  261. package/runtime/node_modules/@metro-labs/outlook/src/actions.ts +20 -0
  262. package/runtime/node_modules/@metro-labs/outlook/src/attachments.ts +97 -0
  263. package/runtime/node_modules/@metro-labs/outlook/src/auth.ts +150 -0
  264. package/runtime/node_modules/@metro-labs/outlook/src/browser.ts +53 -0
  265. package/runtime/node_modules/@metro-labs/outlook/src/config.ts +36 -0
  266. package/runtime/node_modules/@metro-labs/outlook/src/format.ts +141 -0
  267. package/runtime/node_modules/@metro-labs/outlook/src/graph.ts +60 -0
  268. package/runtime/node_modules/@metro-labs/outlook/src/html.ts +72 -0
  269. package/runtime/node_modules/@metro-labs/outlook/src/inbound.ts +104 -0
  270. package/runtime/node_modules/@metro-labs/outlook/src/index.ts +12 -0
  271. package/runtime/node_modules/@metro-labs/outlook/src/login.ts +150 -0
  272. package/runtime/node_modules/@metro-labs/outlook/src/me.ts +41 -0
  273. package/runtime/node_modules/@metro-labs/outlook/src/outbound.ts +116 -0
  274. package/runtime/node_modules/@metro-labs/outlook/src/query.ts +119 -0
  275. package/runtime/node_modules/@metro-labs/outlook/src/state.ts +43 -0
  276. package/runtime/node_modules/@metro-labs/outlook/src/station.ts +15 -0
  277. package/runtime/node_modules/@metro-labs/outlook/src/trust.ts +70 -0
  278. package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
  279. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
  280. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +256 -0
  281. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
  282. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
  283. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +128 -0
  284. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
  285. package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
  286. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +57 -0
  287. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +7 -0
  288. package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
  289. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
  290. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
  291. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
  292. package/runtime/node_modules/@metro-labs/telegram/src/profile.ts +70 -0
  293. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
  294. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +27 -0
  295. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +6 -0
  296. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
  297. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
  298. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +119 -0
  299. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +246 -0
  300. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
  301. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +173 -0
  302. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +103 -0
  303. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +95 -0
  304. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
  305. package/runtime/node_modules/@metro-labs/telegram-bot/src/profile.ts +65 -0
  306. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +20 -0
  307. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +56 -0
  308. package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
  309. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +1 -0
  310. package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
  311. package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +106 -0
  312. package/runtime/node_modules/@metro-labs/threema/src/actions.ts +134 -0
  313. package/runtime/node_modules/@metro-labs/threema/src/api.ts +152 -0
  314. package/runtime/node_modules/@metro-labs/threema/src/crypto.ts +100 -0
  315. package/runtime/node_modules/@metro-labs/threema/src/files.ts +58 -0
  316. package/runtime/node_modules/@metro-labs/threema/src/format.ts +169 -0
  317. package/runtime/node_modules/@metro-labs/threema/src/groups.ts +96 -0
  318. package/runtime/node_modules/@metro-labs/threema/src/ids.ts +21 -0
  319. package/runtime/node_modules/@metro-labs/threema/src/inbound.ts +89 -0
  320. package/runtime/node_modules/@metro-labs/threema/src/index.ts +24 -0
  321. package/runtime/node_modules/@metro-labs/threema/src/messages.ts +264 -0
  322. package/runtime/node_modules/@metro-labs/threema/src/outbound.ts +160 -0
  323. package/runtime/node_modules/@metro-labs/threema/src/station.ts +13 -0
  324. package/runtime/node_modules/@metro-labs/threema/src/verify.ts +117 -0
  325. package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
  326. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
  327. package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
  328. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
  329. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
  330. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +213 -0
  331. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
  332. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
  333. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +388 -0
  334. package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
  335. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +83 -0
  336. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +40 -0
  337. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +7 -0
  338. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
  339. package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
  340. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
  341. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
  342. package/runtime/node_modules/@metro-labs/whatsapp/src/names.ts +28 -0
  343. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
  344. package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
  345. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
  346. package/runtime/node_modules/@metro-labs/whatsapp/src/resolve.ts +52 -0
  347. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +24 -0
  348. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +94 -0
  349. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +5 -0
  350. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +1 -0
  351. package/runtime/node_modules/@metro-labs/xmtp/package.json +34 -0
  352. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +114 -0
  353. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
  354. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +101 -0
  355. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +118 -0
  356. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +244 -0
  357. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
  358. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
  359. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
  360. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +316 -0
  361. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +69 -0
  362. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
  363. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
  364. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +47 -0
  365. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +122 -0
  366. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +139 -0
  367. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
  368. package/runtime/node_modules/@metro-labs/xmtp/src/names.ts +61 -0
  369. package/runtime/node_modules/@metro-labs/xmtp/src/profile.ts +153 -0
  370. package/runtime/node_modules/@metro-labs/xmtp/src/sender.ts +112 -0
  371. package/runtime/node_modules/@metro-labs/xmtp/src/smart.ts +72 -0
  372. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +17 -0
  373. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +79 -0
  374. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +198 -0
  375. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +88 -0
  376. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +98 -0
  377. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +51 -0
  378. package/runtime/runtime.json +3 -0
  379. package/runtime/server.ts +1 -0
  380. package/runtime/stations.json +37 -0
  381. package/runtime/trains/.keep +0 -0
  382. package/LICENSE +0 -21
  383. package/README.md +0 -78
  384. package/dist/channels/discord.js +0 -148
  385. package/dist/channels/telegram.js +0 -137
  386. package/dist/lib/address.js +0 -21
  387. package/dist/lib/codex-rc.js +0 -274
  388. package/dist/lib/dotenv.js +0 -31
  389. package/dist/log.js +0 -12
  390. package/dist/paths.js +0 -45
  391. package/skills/metro/SKILL.md +0 -122
@@ -0,0 +1,128 @@
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 approversMap: AllowlistMap = {};
12
+ let disabledAccounts = new Set<string>();
13
+
14
+ export function setAgentMap(map: AgentMap, names: AgentNameMap): void {
15
+ agentMap = map;
16
+ agentNames = names;
17
+ }
18
+
19
+ export function setAllowlistMap(map: AllowlistMap): void {
20
+ allowlistMap = map;
21
+ }
22
+
23
+ export function setApproversMap(map: AllowlistMap): void {
24
+ approversMap = map;
25
+ }
26
+
27
+ export const approversForAccount = (station: string, accountId: string): string[] => approversMap[mapKey(station, accountId)] ?? [];
28
+
29
+ export function approversForLine(line: string): string[] {
30
+ const a = accountFromLine(line);
31
+ return a ? approversForAccount(a.station, a.accountId) : [];
32
+ }
33
+
34
+ export const mayApprove = (line: string, from: string): boolean => {
35
+ const approvers = approversForLine(line);
36
+ return approvers.length > 0 && senderMatchesAllowlist(approvers, from);
37
+ };
38
+
39
+ export function setDisabledAccounts(ids: Set<string>): void {
40
+ disabledAccounts = ids;
41
+ }
42
+
43
+ export const accountEnabled = (station: string, accountId: string): boolean => !disabledAccounts.has(mapKey(station, accountId));
44
+
45
+ export function accountFromLine(
46
+ line: string,
47
+ ): { station: string; accountId: string } | undefined {
48
+ const parts = line.split('/');
49
+ const station = parts[2];
50
+ const accountId = parts[3];
51
+ if (!station || !accountId) return undefined;
52
+ return { station, accountId };
53
+ }
54
+
55
+ export function agentIdForAccount(
56
+ station: string,
57
+ accountId: string,
58
+ ): string | undefined {
59
+ return agentMap[mapKey(station, accountId)];
60
+ }
61
+
62
+ export function agentIdForLine(line: string): string | undefined {
63
+ const a = accountFromLine(line);
64
+ return a ? agentIdForAccount(a.station, a.accountId) : undefined;
65
+ }
66
+
67
+ export function agentForLine(line: string): string | undefined {
68
+ const id = agentIdForLine(line);
69
+ return id === undefined ? undefined : agentNames[id];
70
+ }
71
+
72
+ export interface KnownAccount {
73
+ station: string;
74
+ id: string;
75
+ agentId: string;
76
+ }
77
+
78
+ export function knownAccounts(): KnownAccount[] {
79
+ return Object.entries(agentMap).flatMap(([key, agentId]) => {
80
+ const cut = key.indexOf('/');
81
+ return cut === -1 ? [] : [{ station: key.slice(0, cut), id: key.slice(cut + 1), agentId }];
82
+ });
83
+ }
84
+
85
+ export function stationAgentIds(station: string): string[] {
86
+ const prefix = `${station}/`;
87
+ return Object.entries(agentMap)
88
+ .filter(([key]) => key.startsWith(prefix))
89
+ .map(([, id]) => id);
90
+ }
91
+
92
+ export function allowlistForAccount(station: string, accountId: string): string[] | undefined {
93
+ return allowlistMap[mapKey(station, accountId)];
94
+ }
95
+
96
+ export function allowlistForLine(line: string): string[] | undefined {
97
+ const a = accountFromLine(line);
98
+ return a ? allowlistMap[mapKey(a.station, a.accountId)] : undefined;
99
+ }
100
+
101
+ export function senderPermitted(
102
+ allowlist: string[] | undefined,
103
+ from: string,
104
+ verified?: boolean,
105
+ ): boolean {
106
+ if (allowlist === undefined || allowlist.length === 0 || allowlist.includes('*')) return true;
107
+ return verified !== false && senderMatchesAllowlist(allowlist, from);
108
+ }
109
+
110
+ export function senderMatchesAllowlist(
111
+ allowlist: string[],
112
+ from: string,
113
+ ): boolean {
114
+ if (allowlist.length === 0 || allowlist.includes('*')) return true;
115
+ const f = (from ?? '').toLowerCase();
116
+ const id = f.split('/').pop() ?? f;
117
+ return allowlist.some((a) => {
118
+ const v = a.toLowerCase();
119
+ return v === f || v === id || domainMatches(v, id);
120
+ });
121
+ }
122
+
123
+ const EMAIL_RE = /^[^@\s/]+@([^@\s/]+)$/;
124
+
125
+ function domainMatches(entry: string, id: string): boolean {
126
+ if (!entry.startsWith('@') || entry.length < 2) return false;
127
+ return EMAIL_RE.exec(id)?.[1] === entry.slice(1);
128
+ }
@@ -0,0 +1,27 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { errMsg, log } from '@metro-labs/core/log';
3
+ import { bodyField, readJsonBody, sendJson } from '@metro-labs/http/api-http';
4
+ import type { StationName } from '@metro-labs/core/station-names';
5
+ import { normalizePolicy, type ToolPolicy } from '../policy/policy.js';
6
+
7
+ export type SetPolicy = (agentId: string, station: StationName, accountId: string, policy: ToolPolicy) => Promise<ToolPolicy>;
8
+
9
+ export async function handlePolicy(
10
+ req: IncomingMessage,
11
+ res: ServerResponse,
12
+ deps: { setPolicy: SetPolicy; reloadAgents: () => Promise<void> },
13
+ agentId: string,
14
+ target: { station: StationName; accountId: string },
15
+ ): Promise<void> {
16
+ const wanted = normalizePolicy(bodyField(await readJsonBody(req), 'policy'));
17
+ const policy = await deps.setPolicy(agentId, target.station, target.accountId, wanted);
18
+ log.info({ agentId, station: target.station, account: target.accountId, policy }, 'account-api: tool policy set');
19
+ const activated = await deps.reloadAgents().then(
20
+ () => true,
21
+ (err: unknown) => {
22
+ log.warn({ err: errMsg(err) }, 'account-api: policy reload failed, the change lands at the next boot');
23
+ return false;
24
+ },
25
+ );
26
+ sendJson(req, res, 200, { agentId, station: target.station, accountId: target.accountId, policy, activated });
27
+ }
@@ -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,86 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import type { StationName } from '@metro-labs/core/station-names';
3
+ import { errMsg, log } from '@metro-labs/core/log';
4
+ import { sendJson } from '@metro-labs/http/api-http';
5
+ import { isRecord } from '@metro-labs/core/is-record';
6
+ import { stationByName } from '../stations/registry.js';
7
+ import { allowlistForAccount } from './map.js';
8
+ import type { RecentSender } from './senders.js';
9
+
10
+ export interface SenderCard {
11
+ id: string;
12
+ name?: string;
13
+ handle?: string;
14
+ }
15
+
16
+ export interface SenderCardDeps {
17
+ recentSenders: (station: StationName, accountId: string) => RecentSender[];
18
+ accountCall: (station: StationName, action: string, args: Record<string, unknown>) => Promise<unknown>;
19
+ }
20
+
21
+ const MAX_LOOKUPS = 50;
22
+ const LOOKUP_MS = 8_000;
23
+
24
+ const filled = (value: unknown): string | undefined => (typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined);
25
+
26
+ function cardOf(id: string, profile: unknown, seenName: string | undefined): SenderCard {
27
+ const p = isRecord(profile) ? profile : {};
28
+ const display = filled(p.display_name);
29
+ const handle = filled(p.name);
30
+ const name = display ?? seenName ?? handle;
31
+ return {
32
+ id,
33
+ ...(name === undefined ? {} : { name }),
34
+ ...(handle === undefined || handle === name ? {} : { handle }),
35
+ };
36
+ }
37
+
38
+ async function lookup(deps: SenderCardDeps, station: StationName, accountId: string, user: string): Promise<unknown> {
39
+ let timer: ReturnType<typeof setTimeout> | undefined;
40
+ const late = new Promise<null>((resolve) => {
41
+ timer = setTimeout(() => {
42
+ resolve(null);
43
+ }, LOOKUP_MS);
44
+ });
45
+ try {
46
+ return await Promise.race([deps.accountCall(station, 'profile', { account: accountId, user }), late]);
47
+ } catch (err) {
48
+ log.info({ station, account: accountId, err: errMsg(err) }, 'account-api: could not read the profile of a listed sender');
49
+ return null;
50
+ } finally {
51
+ clearTimeout(timer);
52
+ }
53
+ }
54
+
55
+ export async function senderCards(deps: SenderCardDeps, station: StationName, accountId: string): Promise<SenderCard[]> {
56
+ const ids = (allowlistForAccount(station, accountId) ?? []).filter((id) => id !== '*' && !id.startsWith('@'));
57
+ const seen = new Map(deps.recentSenders(station, accountId).map((s) => [s.id.toLowerCase(), filled(s.name)]));
58
+ const reads = stationByName(station)?.readsProfiles === true;
59
+ return Promise.all(
60
+ ids.map(async (id, at) => {
61
+ const profile = reads && at < MAX_LOOKUPS ? await lookup(deps, station, accountId, id) : null;
62
+ return cardOf(id, profile, seen.get(id.toLowerCase()));
63
+ }),
64
+ );
65
+ }
66
+
67
+ const known = (profile: unknown): boolean =>
68
+ isRecord(profile) && ['name', 'display_name', 'about', 'avatar', 'address'].some((key) => filled(profile[key]) !== undefined);
69
+
70
+ export async function unconfirmedSenders(deps: SenderCardDeps, station: StationName, accountId: string, ids: string[]): Promise<string[]> {
71
+ if (stationByName(station)?.readsProfiles !== true || ids.length === 0) return [];
72
+ const seen = new Set(deps.recentSenders(station, accountId).map((s) => s.id.toLowerCase()));
73
+ const checks = await Promise.all(
74
+ ids.map(async (id) => (seen.has(id.toLowerCase()) || known(await lookup(deps, station, accountId, id)) ? null : id)),
75
+ );
76
+ return checks.filter((id): id is string => id !== null);
77
+ }
78
+
79
+ export async function handleSenderCards(
80
+ req: IncomingMessage,
81
+ res: ServerResponse,
82
+ deps: SenderCardDeps,
83
+ target: { station: StationName; accountId: string },
84
+ ): Promise<void> {
85
+ sendJson(req, res, 200, { senders: await senderCards(deps, target.station, target.accountId) });
86
+ }
@@ -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,55 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { ApiError } from '@metro-labs/http/api-error';
3
+ import { apiFailure, apiSession, bodyField, cors, readJsonBody, sendJson } from '@metro-labs/http/api-http';
4
+ import { answerPrompt, pendingPrompts, type Behavior, type PendingPrompt } from './pending.js';
5
+
6
+ const PREFIX = '/api/approvals';
7
+ const ID_RE = /^[a-km-z]{5}$/;
8
+
9
+ const approvalView = (p: PendingPrompt): Record<string, unknown> => ({
10
+ id: p.requestId,
11
+ tool: p.tool,
12
+ description: p.description,
13
+ preview: p.preview,
14
+ line: p.line ?? null,
15
+ requestedAt: new Date(p.at).toISOString(),
16
+ });
17
+
18
+ function decisionOf(body: unknown): Behavior {
19
+ const value = bodyField(body, 'decision');
20
+ if (value !== 'allow' && value !== 'deny') throw new ApiError("decision is 'allow' or 'deny'", 400);
21
+ return value;
22
+ }
23
+
24
+ async function answer(req: IncomingMessage, id: string | undefined, subject: string): Promise<unknown> {
25
+ if (id === undefined) {
26
+ if (req.method !== 'GET') throw new ApiError('method not allowed', 405);
27
+ return { approvals: pendingPrompts().map(approvalView) };
28
+ }
29
+ if (req.method !== 'POST') throw new ApiError('method not allowed', 405);
30
+ if (!ID_RE.test(id)) throw new ApiError('no such approval', 404);
31
+ const decision = decisionOf(await readJsonBody(req));
32
+ const answered = await answerPrompt(id, decision, 'page', subject);
33
+ if (answered === undefined) throw new ApiError('no such approval', 404);
34
+ return { approval: approvalView(answered), decision };
35
+ }
36
+
37
+ export function handleApprovalsRequest(req: IncomingMessage, res: ServerResponse): boolean {
38
+ const path = (req.url ?? '').split('?')[0] ?? '';
39
+ if (path !== PREFIX && !path.startsWith(`${PREFIX}/`)) return false;
40
+ const rest = path.slice(PREFIX.length + 1).split('/').filter(Boolean);
41
+ if (rest.length > 1) return false;
42
+ if (req.method === 'OPTIONS') {
43
+ res.writeHead(204, cors(req)).end();
44
+ return true;
45
+ }
46
+ apiSession(req)
47
+ .then(async (session) => {
48
+ if (!session) throw new ApiError('unauthorized', 401);
49
+ sendJson(req, res, 200, await answer(req, rest[0], session.subject));
50
+ })
51
+ .catch((err: unknown) => {
52
+ apiFailure(req, res, err, 'approvals-api');
53
+ });
54
+ return true;
55
+ }
@@ -0,0 +1,2 @@
1
+ export const NEEDS_APPROVAL = (where: string, tool: string): string =>
2
+ `Needs the owner's approval for ${where} (${tool}). Make this exact call from a background worker so Claude Code asks the owner in chat, and wait for the answer. An approval given only in the terminal does not count.`;
@@ -0,0 +1,149 @@
1
+ import { errMsg, log } from '@metro-labs/core/log';
2
+ import { previewMatches } from './preview.js';
3
+
4
+ export type Behavior = 'allow' | 'deny';
5
+
6
+ export type AnsweredVia = 'chat' | 'page' | 'expiry';
7
+
8
+ export interface PendingPrompt {
9
+ requestId: string;
10
+ tool: string;
11
+ description: string;
12
+ preview: string;
13
+ line: string | undefined;
14
+ at: number;
15
+ }
16
+
17
+ interface Held extends PendingPrompt {
18
+ owner: object;
19
+ send: (behavior: Behavior) => Promise<void>;
20
+ tell: ((text: string) => Promise<void>) | undefined;
21
+ }
22
+
23
+ const PENDING_MAX = 500;
24
+ const SWEEP_MS = 60_000;
25
+ const DEFAULT_TTL_H = 24;
26
+ const HOUR_MS = 3_600_000;
27
+
28
+ const held = new Map<string, Held>();
29
+
30
+ interface Grant {
31
+ tool: string;
32
+ preview: string;
33
+ at: number;
34
+ }
35
+
36
+ const GRANT_TTL_MS = 10 * 60_000;
37
+ const GRANTS_MAX = 200;
38
+ let grants: Grant[] = [];
39
+
40
+ function grant(entry: Held, now = Date.now()): void {
41
+ grants = [...grants.filter((g) => now - g.at < GRANT_TTL_MS), { tool: entry.tool, preview: entry.preview, at: now }].slice(-GRANTS_MAX);
42
+ }
43
+
44
+ export function takeGrant(toolMatches: (tool: string) => boolean, args: Record<string, unknown>, now = Date.now()): boolean {
45
+ const at = grants.findIndex((g) => now - g.at < GRANT_TTL_MS && toolMatches(g.tool) && previewMatches(g.preview, args));
46
+ if (at < 0) return false;
47
+ grants = grants.filter((_, i) => i !== at);
48
+ return true;
49
+ }
50
+
51
+ export function approvalTtlMs(env: NodeJS.ProcessEnv = process.env): number {
52
+ const hours = Number(env.METRO_APPROVAL_TTL_H);
53
+ return (Number.isFinite(hours) && hours > 0 ? hours : DEFAULT_TTL_H) * HOUR_MS;
54
+ }
55
+
56
+ const shown = ({ requestId, tool, description, preview, line, at }: Held): PendingPrompt => ({
57
+ requestId,
58
+ tool,
59
+ description,
60
+ preview,
61
+ line,
62
+ at,
63
+ });
64
+
65
+ export function holdPrompt(
66
+ prompt: PendingPrompt,
67
+ owner: object,
68
+ send: (behavior: Behavior) => Promise<void>,
69
+ tell?: (text: string) => Promise<void>,
70
+ ): void {
71
+ held.set(prompt.requestId, { ...prompt, owner, send, tell });
72
+ while (held.size > PENDING_MAX) {
73
+ const oldest = held.keys().next();
74
+ if (oldest.done) break;
75
+ held.delete(oldest.value);
76
+ }
77
+ }
78
+
79
+ export const pendingPrompts = (): PendingPrompt[] => [...held.values()].map(shown).sort((a, b) => b.at - a.at);
80
+
81
+ export const promptLine = (requestId: string): string | undefined => held.get(requestId)?.line;
82
+
83
+ function chatNotice(via: AnsweredVia, behavior: Behavior): string | undefined {
84
+ if (via === 'expiry') return 'Expired, denied.';
85
+ if (via === 'page') return behavior === 'allow' ? 'Approved on the page.' : 'Denied on the page.';
86
+ return undefined;
87
+ }
88
+
89
+ async function tellChat(entry: Held, via: AnsweredVia, behavior: Behavior): Promise<void> {
90
+ const text = chatNotice(via, behavior);
91
+ if (text === undefined || entry.tell === undefined) return;
92
+ await entry.tell(text).catch((err: unknown) => {
93
+ log.warn({ requestId: entry.requestId, err: errMsg(err) }, 'approvals: the chat could not be told the answer');
94
+ });
95
+ }
96
+
97
+ export async function answerPrompt(
98
+ requestId: string,
99
+ behavior: Behavior,
100
+ via: AnsweredVia,
101
+ who: string = via,
102
+ ): Promise<PendingPrompt | undefined> {
103
+ const entry = held.get(requestId);
104
+ if (entry === undefined) return undefined;
105
+ held.delete(requestId);
106
+ if (behavior === 'allow' && via !== 'expiry') grant(entry);
107
+ log.info({ requestId, tool: entry.tool, behavior, by: who === via ? via : `${via} ${who}` }, 'approvals: a Claude Code permission prompt answered');
108
+ await entry.send(behavior);
109
+ await tellChat(entry, via, behavior);
110
+ return shown(entry);
111
+ }
112
+
113
+ export function forgetPromptsOf(owner: object): void {
114
+ for (const [id, entry] of held) if (entry.owner === owner) held.delete(id);
115
+ }
116
+
117
+ export function settlePromptsFor(name: string, args: Record<string, unknown>): void {
118
+ for (const [id, entry] of held)
119
+ if (entry.tool.endsWith(`__${name}`) && previewMatches(entry.preview, args)) {
120
+ held.delete(id);
121
+ log.info({ requestId: id, tool: entry.tool }, 'approvals: the call ran, so the prompt was answered in Claude Code');
122
+ }
123
+ }
124
+
125
+ export async function expirePrompts(now = Date.now(), ttl = approvalTtlMs()): Promise<number> {
126
+ const overdue = [...held.values()].filter((entry) => now - entry.at >= ttl);
127
+ for (const entry of overdue)
128
+ await answerPrompt(entry.requestId, 'deny', 'expiry').catch((err: unknown) => {
129
+ log.warn({ requestId: entry.requestId, err: errMsg(err) }, 'approvals: the expiry answer could not be sent');
130
+ });
131
+ return overdue.length;
132
+ }
133
+
134
+ export function startPromptExpiry(): () => void {
135
+ const timer = setInterval(() => {
136
+ expirePrompts().catch((err: unknown) => {
137
+ log.warn({ err: errMsg(err) }, 'approvals: the expiry sweep failed');
138
+ });
139
+ }, SWEEP_MS);
140
+ timer.unref();
141
+ return () => {
142
+ clearInterval(timer);
143
+ };
144
+ }
145
+
146
+ export function forgetAllPrompts(): void {
147
+ held.clear();
148
+ grants = [];
149
+ }
@@ -0,0 +1,66 @@
1
+ import { isDeepStrictEqual } from 'node:util';
2
+ import { isRecord } from '@metro-labs/core/is-record';
3
+
4
+ export const ELIDED = '\u0000';
5
+
6
+ const CODE_POINTS = /(?:\\n|\n)⋯ \d+ code points elided ⋯(?:\\n|\n)/g;
7
+ const FIELD_BLOCK = /[\s,]*(?:\\n|\n)?\s*⋯[^]*$/;
8
+
9
+ export interface ReadPreview {
10
+ input: Record<string, unknown> | undefined;
11
+ partial: boolean;
12
+ }
13
+
14
+ function parsedRecord(text: string): Record<string, unknown> | undefined {
15
+ try {
16
+ const parsed: unknown = JSON.parse(text);
17
+ return isRecord(parsed) ? parsed : undefined;
18
+ } catch {
19
+ return undefined;
20
+ }
21
+ }
22
+
23
+ export function readPreview(preview: string): ReadPreview {
24
+ const text = preview.replace(CODE_POINTS, '\\u0000');
25
+ const whole = parsedRecord(text);
26
+ if (whole !== undefined) return { input: whole, partial: false };
27
+ if (!text.includes('⋯')) return { input: undefined, partial: false };
28
+ const head = text.replace(FIELD_BLOCK, '').replace(/[\s,]*$/, '');
29
+ const input = parsedRecord(`${head}}`) ?? parsedRecord(head);
30
+ return { input, partial: input !== undefined };
31
+ }
32
+
33
+ export const shownValue = (text: string): string => text.split(ELIDED).join('…');
34
+
35
+ function stringMatches(shown: string, real: string): boolean {
36
+ if (!shown.includes(ELIDED)) return shown === real;
37
+ const [first = '', ...rest] = shown.split(ELIDED);
38
+ const last = rest.pop() ?? '';
39
+ if (!real.startsWith(first) || !real.endsWith(last)) return false;
40
+ let at = first.length;
41
+ for (const part of rest) {
42
+ const found = real.indexOf(part, at);
43
+ if (found < 0) return false;
44
+ at = found + part.length;
45
+ }
46
+ return at <= real.length - last.length;
47
+ }
48
+
49
+ function valueMatches(shown: unknown, real: unknown): boolean {
50
+ if (typeof shown === 'string') return typeof real === 'string' && stringMatches(shown, real);
51
+ if (Array.isArray(shown))
52
+ return Array.isArray(real) && shown.length === real.length && shown.every((v, i) => valueMatches(v, real[i]));
53
+ if (isRecord(shown)) return isRecord(real) && recordMatches(shown, real, false);
54
+ return isDeepStrictEqual(shown, real);
55
+ }
56
+
57
+ function recordMatches(shown: Record<string, unknown>, real: Record<string, unknown>, partial: boolean): boolean {
58
+ const keys = Object.keys(shown);
59
+ if (!partial && keys.length !== Object.keys(real).length) return false;
60
+ return keys.every((key) => key in real && valueMatches(shown[key], real[key]));
61
+ }
62
+
63
+ export function previewMatches(preview: string, args: Record<string, unknown>): boolean {
64
+ const { input, partial } = readPreview(preview);
65
+ return input !== undefined && recordMatches(input, args, partial);
66
+ }