@stage-labs/metro 0.1.0-beta.15 → 0.1.0-beta.151

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 (327) hide show
  1. package/dist/api.js +9 -0
  2. package/dist/claude-settings.js +40 -0
  3. package/dist/claude.js +182 -0
  4. package/dist/cli.js +86 -0
  5. package/dist/control.js +74 -0
  6. package/dist/hold.js +211 -0
  7. package/dist/local.js +77 -0
  8. package/dist/mcp-config.js +25 -0
  9. package/dist/node-name.js +71 -0
  10. package/dist/onboarding.js +43 -0
  11. package/dist/provider-flags.js +9 -0
  12. package/dist/route.js +50 -0
  13. package/dist/runtime-install.js +97 -0
  14. package/dist/runtime.js +74 -0
  15. package/dist/serve.js +155 -0
  16. package/dist/service.js +242 -0
  17. package/dist/tail.js +119 -0
  18. package/dist/update.js +45 -0
  19. package/dist/version.js +97 -0
  20. package/package.json +14 -28
  21. package/runtime/marketplace/.claude-plugin/marketplace.json +12 -0
  22. package/runtime/marketplace/plugin/.claude-plugin/plugin.json +18 -0
  23. package/runtime/marketplace/plugin/.mcp.json +1 -0
  24. package/runtime/marketplace/plugin/bin/guard.mjs +55 -0
  25. package/runtime/marketplace/plugin/bin/metro-plugin.mjs +40 -0
  26. package/runtime/marketplace/plugin/bin/session-start.mjs +31 -0
  27. package/runtime/marketplace/plugin/hooks/hooks.json +25 -0
  28. package/runtime/marketplace/plugin/orchestrator.md +16 -0
  29. package/runtime/node_modules/@metro-labs/core/package.json +43 -0
  30. package/runtime/node_modules/@metro-labs/core/src/address.ts +6 -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 +120 -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 +21 -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 +87 -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 +226 -0
  39. package/runtime/node_modules/@metro-labs/core/src/secure-fs.ts +57 -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-store.ts +60 -0
  42. package/runtime/node_modules/@metro-labs/core/src/stations/attachments.ts +196 -0
  43. package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +88 -0
  44. package/runtime/node_modules/@metro-labs/core/src/stations/station-runtime.ts +64 -0
  45. package/runtime/node_modules/@metro-labs/core/src/stations/types.ts +108 -0
  46. package/runtime/node_modules/@metro-labs/core/src/str.ts +5 -0
  47. package/runtime/node_modules/@metro-labs/core/src/train-error.ts +53 -0
  48. package/runtime/node_modules/@metro-labs/core/src/version.ts +2 -0
  49. package/runtime/node_modules/@metro-labs/daemon/package.json +47 -0
  50. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-attach.ts +19 -0
  51. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-routes.ts +70 -0
  52. package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +365 -0
  53. package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +39 -0
  54. package/runtime/node_modules/@metro-labs/daemon/src/agents/allowlist.ts +37 -0
  55. package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +225 -0
  56. package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +122 -0
  57. package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +364 -0
  58. package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +185 -0
  59. package/runtime/node_modules/@metro-labs/daemon/src/agents/keys.ts +40 -0
  60. package/runtime/node_modules/@metro-labs/daemon/src/agents/map.ts +95 -0
  61. package/runtime/node_modules/@metro-labs/daemon/src/agents/scope.ts +64 -0
  62. package/runtime/node_modules/@metro-labs/daemon/src/agents/senders.ts +38 -0
  63. package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +200 -0
  64. package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +39 -0
  65. package/runtime/node_modules/@metro-labs/daemon/src/boot/crash-guard.ts +49 -0
  66. package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +30 -0
  67. package/runtime/node_modules/@metro-labs/daemon/src/boot/paths.ts +106 -0
  68. package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +25 -0
  69. package/runtime/node_modules/@metro-labs/daemon/src/channels/dedupe.ts +24 -0
  70. package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +380 -0
  71. package/runtime/node_modules/@metro-labs/daemon/src/channels/media-note.ts +85 -0
  72. package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +75 -0
  73. package/runtime/node_modules/@metro-labs/daemon/src/channels/relay.ts +129 -0
  74. package/runtime/node_modules/@metro-labs/daemon/src/channels/webhook-note.ts +51 -0
  75. package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +354 -0
  76. package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +358 -0
  77. package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +169 -0
  78. package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +56 -0
  79. package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +140 -0
  80. package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +90 -0
  81. package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +253 -0
  82. package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +117 -0
  83. package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +263 -0
  84. package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +118 -0
  85. package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +109 -0
  86. package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +313 -0
  87. package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +157 -0
  88. package/runtime/node_modules/@metro-labs/daemon/src/connectors/health.ts +18 -0
  89. package/runtime/node_modules/@metro-labs/daemon/src/connectors/model.ts +69 -0
  90. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-client.ts +166 -0
  91. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-discovery.ts +139 -0
  92. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-pending.ts +65 -0
  93. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-routes.ts +171 -0
  94. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth.ts +155 -0
  95. package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +122 -0
  96. package/runtime/node_modules/@metro-labs/daemon/src/connectors/reach.ts +27 -0
  97. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-target.ts +39 -0
  98. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +304 -0
  99. package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +299 -0
  100. package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +108 -0
  101. package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +59 -0
  102. package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +101 -0
  103. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +99 -0
  104. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-owner.ts +32 -0
  105. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +121 -0
  106. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +294 -0
  107. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-store.ts +181 -0
  108. package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +259 -0
  109. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +231 -0
  110. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
  111. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +288 -0
  112. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +203 -0
  113. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +231 -0
  114. package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +123 -0
  115. package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +187 -0
  116. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +247 -0
  117. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-auth.ts +182 -0
  118. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-client.ts +46 -0
  119. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-setup.ts +150 -0
  120. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-stream.ts +148 -0
  121. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-translate.ts +201 -0
  122. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini.ts +257 -0
  123. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +353 -0
  124. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +232 -0
  125. package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +90 -0
  126. package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +65 -0
  127. package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +17 -0
  128. package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +242 -0
  129. package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +151 -0
  130. package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +216 -0
  131. package/runtime/node_modules/@metro-labs/daemon/src/mcp/channel-owner.ts +29 -0
  132. package/runtime/node_modules/@metro-labs/daemon/src/mcp/ctx.ts +62 -0
  133. package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +91 -0
  134. package/runtime/node_modules/@metro-labs/daemon/src/mcp/frame-scope.ts +21 -0
  135. package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +129 -0
  136. package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +186 -0
  137. package/runtime/node_modules/@metro-labs/daemon/src/mcp/member-tools.ts +72 -0
  138. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +66 -0
  139. package/runtime/node_modules/@metro-labs/daemon/src/mcp/raw-get-stream.ts +207 -0
  140. package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +47 -0
  141. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-registry.ts +172 -0
  142. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-route.ts +43 -0
  143. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +215 -0
  144. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-dispatch.ts +215 -0
  145. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-schemas.ts +400 -0
  146. package/runtime/node_modules/@metro-labs/daemon/src/mcp/upload-tool.ts +66 -0
  147. package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +237 -0
  148. package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +389 -0
  149. package/runtime/node_modules/@metro-labs/daemon/src/routes/bearer.ts +35 -0
  150. package/runtime/node_modules/@metro-labs/daemon/src/routes/body.ts +24 -0
  151. package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +370 -0
  152. package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +210 -0
  153. package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +58 -0
  154. package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
  155. package/runtime/node_modules/@metro-labs/daemon/src/routes/threema-callback.ts +103 -0
  156. package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +65 -0
  157. package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +80 -0
  158. package/runtime/node_modules/@metro-labs/daemon/src/server/owner.ts +48 -0
  159. package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +117 -0
  160. package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
  161. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-inline.ts +100 -0
  162. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-interactive.ts +161 -0
  163. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +202 -0
  164. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-session.ts +280 -0
  165. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-xmtp.ts +111 -0
  166. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +248 -0
  167. package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +261 -0
  168. package/runtime/node_modules/@metro-labs/daemon/src/stations/registry.ts +37 -0
  169. package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
  170. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor-io.ts +97 -0
  171. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +320 -0
  172. package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
  173. package/runtime/node_modules/@metro-labs/daemon/src/stations/train-call.ts +22 -0
  174. package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +97 -0
  175. package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +123 -0
  176. package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +40 -0
  177. package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
  178. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -0
  179. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
  180. package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
  181. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
  182. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +220 -0
  183. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
  184. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
  185. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
  186. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
  187. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
  188. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
  189. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
  190. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
  191. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
  192. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
  193. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
  194. package/runtime/node_modules/@metro-labs/http/package.json +29 -0
  195. package/runtime/node_modules/@metro-labs/http/src/api-error.ts +8 -0
  196. package/runtime/node_modules/@metro-labs/http/src/api-http.ts +106 -0
  197. package/runtime/node_modules/@metro-labs/http/src/cors.ts +29 -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/workos-token.ts +150 -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 +223 -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 +37 -0
  295. package/runtime/trains/.keep +0 -0
  296. package/LICENSE +0 -21
  297. package/README.md +0 -139
  298. package/dist/broker/claims.js +0 -144
  299. package/dist/broker/history-stream.js +0 -149
  300. package/dist/cli/config.js +0 -179
  301. package/dist/cli/index.js +0 -175
  302. package/dist/cli/messenger-api.js +0 -214
  303. package/dist/cli/messenger-transcribe.js +0 -43
  304. package/dist/cli/messenger-uploads.js +0 -116
  305. package/dist/cli/monitor-api.js +0 -205
  306. package/dist/cli/tail.js +0 -95
  307. package/dist/cli/util.js +0 -72
  308. package/dist/cli/webhook.js +0 -181
  309. package/dist/codex-rc/client.js +0 -225
  310. package/dist/codex-rc/protocol.js +0 -38
  311. package/dist/dispatcher/server.js +0 -122
  312. package/dist/dispatcher.js +0 -97
  313. package/dist/history.js +0 -143
  314. package/dist/ipc.js +0 -90
  315. package/dist/lines.js +0 -54
  316. package/dist/local-identity.js +0 -80
  317. package/dist/log.js +0 -15
  318. package/dist/paths.js +0 -114
  319. package/dist/trains/protocol.js +0 -99
  320. package/dist/trains/supervisor.js +0 -210
  321. package/dist/tunnel.js +0 -102
  322. package/docs/broker.md +0 -166
  323. package/docs/monitor.md +0 -241
  324. package/docs/uri-scheme.md +0 -127
  325. package/examples/README.md +0 -32
  326. package/examples/telegram.ts +0 -121
  327. package/skills/metro/SKILL.md +0 -110
@@ -0,0 +1,225 @@
1
+ import { webhookPort } from '../net/tunnel.js';
2
+ import type { IncomingMessage, ServerResponse } from 'node:http';
3
+ import { parseId } from '@metro-labs/core/ids';
4
+ import { log } from '@metro-labs/core/log';
5
+ import {
6
+ apiFailure,
7
+ apiSession, requireAdmin,
8
+ projectParam,
9
+ cors,
10
+ sendJson,
11
+ type ApiSession,
12
+ } from '@metro-labs/http/api-http';
13
+ import { ATTACHABLE, handleAccountRoute, type AccountApiDeps } from './accounts-api.js';
14
+ import {
15
+ accountRoute,
16
+ accountRouteAllows,
17
+ type AccountRoute,
18
+ } from './account-routes.js';
19
+ import {
20
+ type AgentSummary,
21
+ type DeletedAgent,
22
+ } from './admin.js';
23
+
24
+ const PREFIX = '/api/agents';
25
+
26
+ export interface AgentApiDeps extends AccountApiDeps {
27
+ listAgents: (subject: string, project: string) => Promise<AgentSummary[]>;
28
+ deleteAgent: (
29
+ subject: string,
30
+ id: string,
31
+ ) => Promise<DeletedAgent>;
32
+ gatherAccounts: (allowed: Set<string>) => Promise<{
33
+ accounts: Record<string, unknown[]>;
34
+ unavailable: string[];
35
+ }>;
36
+ capabilities: () => Record<string, string[]>;
37
+ attachable?: string[];
38
+ liveness: () => Map<string, { connected: boolean; lastSeenAt: number }>;
39
+ connectorIds: (agentIds: string[]) => Promise<Map<string, string[]>>;
40
+ }
41
+
42
+ type Routable =
43
+ | { kind: 'collection' }
44
+ | { kind: 'agent'; id: string }
45
+ | { kind: 'accounts'; id: string; route: AccountRoute };
46
+
47
+ type Target = Routable | { kind: 'unknown' } | null;
48
+
49
+ function subTarget(id: string, rest: string[]): Target {
50
+ if (rest.length === 0) return { kind: 'agent', id };
51
+ if (rest[0] !== 'accounts') return { kind: 'unknown' };
52
+ const route = accountRoute(rest.slice(1));
53
+ return route === null ? { kind: 'unknown' } : { kind: 'accounts', id, route };
54
+ }
55
+
56
+ export function target(path: string): Target {
57
+ if (path === PREFIX || path === `${PREFIX}/`) return { kind: 'collection' };
58
+ if (!path.startsWith(`${PREFIX}/`)) return null;
59
+ const segments = path.slice(PREFIX.length + 1).split('/').filter(Boolean);
60
+ const head = segments[0];
61
+ if (head === undefined) return { kind: 'collection' };
62
+ const id = parseId(head);
63
+ return id === null ? { kind: 'unknown' } : subTarget(id, segments.slice(1));
64
+ }
65
+
66
+ const localMcpEndpoint = (): string => `http://127.0.0.1:${String(webhookPort())}/mcp`;
67
+
68
+ interface KeyPayload {
69
+ key: string | null;
70
+ endpoint: string | null;
71
+ }
72
+
73
+ const credentials = (key: string): KeyPayload => ({ key, endpoint: `${localMcpEndpoint()}?token=${key}` });
74
+
75
+ function keyPayload(agent: AgentSummary): KeyPayload {
76
+ const value = agent.owned ? agent.key : null;
77
+ return value === null ? { key: null, endpoint: null } : credentials(value);
78
+ }
79
+
80
+ function livenessPayload(
81
+ agent: AgentSummary,
82
+ live: Map<string, { connected: boolean; lastSeenAt: number }>,
83
+ ): Record<string, unknown> {
84
+ const found = agent.owned ? live.get(agent.id) : undefined;
85
+ if (found === undefined) return { connected: false, last_seen: null };
86
+ return {
87
+ connected: found.connected,
88
+ last_seen: new Date(found.lastSeenAt).toISOString(),
89
+ };
90
+ }
91
+
92
+ function agentPayload(
93
+ agent: AgentSummary,
94
+ live: Map<string, { connected: boolean; lastSeenAt: number }>,
95
+ connectors: Map<string, string[]>,
96
+ ): Record<string, unknown> {
97
+ return {
98
+ id: agent.id,
99
+ name: agent.name,
100
+ owned: agent.owned,
101
+ connector_ids: connectors.get(agent.id) ?? [],
102
+ ...livenessPayload(agent, live),
103
+ ...keyPayload(agent),
104
+ };
105
+ }
106
+
107
+ function wantsAccounts(req: IncomingMessage): boolean {
108
+ const query = (req.url ?? '').split('?')[1] ?? '';
109
+ return new URLSearchParams(query).get('accounts') === '1';
110
+ }
111
+
112
+ async function handleList(
113
+ req: IncomingMessage,
114
+ res: ServerResponse,
115
+ deps: AgentApiDeps,
116
+ session: ApiSession,
117
+ ): Promise<void> {
118
+ const project = projectParam(req);
119
+ if (project === null) {
120
+ sendJson(req, res, 400, { error: 'a project is required' });
121
+ return;
122
+ }
123
+ const list = await deps.listAgents(session.subject, project);
124
+ const live = deps.liveness();
125
+ const connectors = await deps.connectorIds(list.map((a) => a.id));
126
+ const base = {
127
+ agents: list.map((a) => agentPayload(a, live, connectors)),
128
+ capabilities: deps.capabilities(),
129
+ attachable: deps.attachable ?? ATTACHABLE,
130
+ };
131
+ if (!wantsAccounts(req)) {
132
+ sendJson(req, res, 200, base);
133
+ return;
134
+ }
135
+ const { accounts, unavailable } = await deps.gatherAccounts(
136
+ new Set(list.map((a) => a.id)),
137
+ );
138
+ sendJson(req, res, 200, { ...base, accounts, unavailable });
139
+ }
140
+
141
+ async function handleDelete(
142
+ req: IncomingMessage,
143
+ res: ServerResponse,
144
+ deps: AgentApiDeps,
145
+ session: ApiSession,
146
+ id: string,
147
+ ): Promise<void> {
148
+ const gone = await deps.deleteAgent(session.subject, id);
149
+ log.info(
150
+ { agent: gone.name, id: gone.id, owner: session.subject },
151
+ 'agent-api: deleted agent',
152
+ );
153
+ sendJson(req, res, 200, { id: gone.id, name: gone.name, deleted: true });
154
+ }
155
+
156
+ type AgentTarget = Exclude<Routable, { kind: 'accounts' }>;
157
+
158
+ async function routeAgent(
159
+ req: IncomingMessage,
160
+ res: ServerResponse,
161
+ deps: AgentApiDeps,
162
+ session: ApiSession,
163
+ tgt: AgentTarget,
164
+ ): Promise<void> {
165
+ try {
166
+ if (tgt.kind === 'agent') {
167
+ requireAdmin(session);
168
+ await handleDelete(req, res, deps, session, tgt.id);
169
+ } else await handleList(req, res, deps, session);
170
+ } catch (err) {
171
+ apiFailure(req, res, err);
172
+ }
173
+ }
174
+
175
+ const ALLOWED: Record<AgentTarget['kind'], string[]> = {
176
+ collection: ['GET'],
177
+ agent: ['DELETE'],
178
+ };
179
+
180
+ function methodAllowed(tgt: Routable, method: string | undefined): boolean {
181
+ if (tgt.kind === 'accounts') return accountRouteAllows(tgt.route, method);
182
+ return ALLOWED[tgt.kind].includes(method ?? '');
183
+ }
184
+
185
+ async function dispatch(
186
+ req: IncomingMessage,
187
+ res: ServerResponse,
188
+ deps: AgentApiDeps,
189
+ tgt: Routable,
190
+ ): Promise<void> {
191
+ const session = await apiSession(req);
192
+ if (!session) {
193
+ sendJson(req, res, 401, { error: 'unauthorized' });
194
+ return;
195
+ }
196
+ if (tgt.kind === 'accounts')
197
+ await handleAccountRoute(req, res, deps, session, tgt.id, tgt.route);
198
+ else await routeAgent(req, res, deps, session, tgt);
199
+ }
200
+
201
+ export function handleAgentApiRequest(
202
+ req: IncomingMessage,
203
+ res: ServerResponse,
204
+ deps: AgentApiDeps,
205
+ ): boolean {
206
+ const tgt = target((req.url ?? '').split('?')[0] ?? '');
207
+ if (tgt === null) return false;
208
+ if (req.method === 'OPTIONS') {
209
+ res.writeHead(204, cors(req)).end();
210
+ return true;
211
+ }
212
+ if (tgt.kind === 'unknown') {
213
+ sendJson(req, res, 404, { error: 'no such agent' });
214
+ return true;
215
+ }
216
+ if (!methodAllowed(tgt, req.method)) {
217
+ sendJson(req, res, 405, { error: 'method not allowed' });
218
+ return true;
219
+ }
220
+ dispatch(req, res, deps, tgt).catch((err: unknown) => {
221
+ if (res.headersSent) return;
222
+ apiFailure(req, res, err, 'agent-api');
223
+ });
224
+ return true;
225
+ }
@@ -0,0 +1,122 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { errMsg, log } from '@metro-labs/core/log';
3
+ import { apiFailure, apiSession, cors, readJsonBody, sendJson } from '@metro-labs/http/api-http';
4
+ import { ApiError } from '@metro-labs/http/api-error';
5
+ import { isRecord } from '@metro-labs/core/is-record';
6
+ import { parseId } from '@metro-labs/core/ids';
7
+ import type { LoadedAccount, LoadedAgent, LoadedConnector } from '../stations/materialize.js';
8
+ import { STATIONS } from '@metro-labs/core/station-names';
9
+
10
+ const AGENTS = '/api/agents';
11
+ const RESTORE_PATH = `${AGENTS}/restore`;
12
+ const BUNDLE_MAX = 2 * 1024 * 1024;
13
+ const STATION_NAMES = new Set<string>(STATIONS);
14
+
15
+ export interface AgentBundle {
16
+ version: 1;
17
+ agent: { id: string; name: string; key: string; stations: LoadedAccount[] };
18
+ connectors: LoadedConnector[];
19
+ }
20
+
21
+ export const loadedAgentOf = (bundle: AgentBundle): LoadedAgent => ({
22
+ id: bundle.agent.id,
23
+ name: bundle.agent.name,
24
+ key: bundle.agent.key,
25
+ accounts: bundle.agent.stations,
26
+ connectors: [],
27
+ });
28
+
29
+ export interface RestoredAgent {
30
+ id: string;
31
+ name: string;
32
+ stations: number;
33
+ connectors: number;
34
+ }
35
+
36
+ export type ImportMode = 'append' | 'overwrite';
37
+
38
+ export interface BundleApiDeps {
39
+ bundle: (subject: string, agentId: string) => Promise<AgentBundle>;
40
+ restore: (subject: string, bundle: AgentBundle, mode: ImportMode) => Promise<RestoredAgent>;
41
+ }
42
+
43
+ export function parseMode(raw: unknown): ImportMode {
44
+ const mode = isRecord(raw) && typeof raw.mode === 'string' ? raw.mode : 'overwrite';
45
+ if (mode !== 'append' && mode !== 'overwrite') throw bad("mode is 'append' or 'overwrite'");
46
+ return mode;
47
+ }
48
+
49
+ const bad = (what: string): ApiError => new ApiError(`bundle: ${what}`, 400);
50
+
51
+ function stationOf(raw: unknown): LoadedAccount {
52
+ if (!isRecord(raw) || typeof raw.station !== 'string' || !STATION_NAMES.has(raw.station))
53
+ throw bad('a station is not a known station');
54
+ if (typeof raw.id !== 'string' || !isRecord(raw.config)) throw bad('a station has no id or config');
55
+ const allowlist = Array.isArray(raw.allowlist)
56
+ ? raw.allowlist.filter((s): s is string => typeof s === 'string')
57
+ : null;
58
+ return { station: raw.station as LoadedAccount['station'], id: raw.id, allowlist, enabled: raw.enabled !== false, config: raw.config };
59
+ }
60
+
61
+ function connectorOf(raw: unknown): LoadedConnector {
62
+ if (!isRecord(raw) || typeof raw.id !== 'string' || typeof raw.name !== 'string' || typeof raw.url !== 'string')
63
+ throw bad('a connector is missing its id, name or url');
64
+ if (!isRecord(raw.config)) throw bad('a connector has no config');
65
+ return { id: raw.id, name: raw.name, url: raw.url, transport: 'http', config: raw.config };
66
+ }
67
+
68
+ function agentOf(raw: unknown): AgentBundle['agent'] {
69
+ if (!isRecord(raw)) throw bad('not a v1 agent bundle');
70
+ const { id, name, key, stations } = raw;
71
+ if (typeof id !== 'string' || parseId(id) === null) throw bad('agent id is not an id');
72
+ if (typeof key !== 'string' || key === '') throw bad('agent has no key');
73
+ if (!Array.isArray(stations)) throw bad('agent has no station list');
74
+ return { id, name: typeof name === 'string' ? name : '', key, stations: stations.map(stationOf) };
75
+ }
76
+
77
+ export function parseBundle(raw: unknown): AgentBundle {
78
+ if (!isRecord(raw) || raw.version !== 1) throw bad('not a v1 agent bundle');
79
+ const connectors = Array.isArray(raw.connectors) ? raw.connectors : [];
80
+ return { version: 1, agent: agentOf(raw.agent), connectors: connectors.map(connectorOf) };
81
+ }
82
+
83
+ async function answer(req: IncomingMessage, deps: BundleApiDeps, subject: string, path: string): Promise<unknown> {
84
+ if (path === RESTORE_PATH) {
85
+ const body: unknown = await readJsonBody(req, BUNDLE_MAX);
86
+ return deps.restore(subject, parseBundle(body), parseMode(body));
87
+ }
88
+ const id = parseId(path.slice(AGENTS.length + 1).split('/')[0] ?? '');
89
+ if (id === null) throw new ApiError('no such agent', 404);
90
+ return deps.bundle(subject, id);
91
+ }
92
+
93
+ export function handleBundleRequest(req: IncomingMessage, res: ServerResponse, deps: BundleApiDeps): boolean {
94
+ const path = (req.url ?? '').split('?')[0] ?? '';
95
+ const isRestore = path === RESTORE_PATH;
96
+ const isBundle = /^\/api\/agents\/[^/]+\/bundle$/.test(path);
97
+ if (!isRestore && !isBundle) return false;
98
+ if (req.method === 'OPTIONS') {
99
+ res.writeHead(204, cors(req)).end();
100
+ return true;
101
+ }
102
+ if (req.method !== (isRestore ? 'POST' : 'GET')) {
103
+ sendJson(req, res, 405, { error: 'method not allowed' });
104
+ return true;
105
+ }
106
+ apiSession(req)
107
+ .then((session) => {
108
+ if (!session) throw new ApiError('unauthorized', 401);
109
+ return answer(req, deps, session.subject, path);
110
+ })
111
+ .then((body) => {
112
+ sendJson(req, res, isRestore ? 201 : 200, body);
113
+ })
114
+ .catch((err: unknown) => {
115
+ if (err instanceof ApiError) apiFailure(req, res, err, 'bundle-api');
116
+ else {
117
+ log.warn({ err: errMsg(err) }, 'bundle-api: request failed');
118
+ if (!res.headersSent) sendJson(req, res, 500, { error: 'bundle api failed' });
119
+ }
120
+ });
121
+ return true;
122
+ }
@@ -0,0 +1,364 @@
1
+ import { readFileSync, rmdirSync, rmSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { ApiError } from '@metro-labs/http/api-error';
4
+ import { isOrganizationId } from '@metro-labs/http/workos-token';
5
+ import { ensureSecureDir, writeSecure } from '@metro-labs/core/secure-fs';
6
+ import {
7
+ newApiKey,
8
+ AgentAdminError,
9
+ normalizeAgentName,
10
+ type AgentSummary,
11
+ type CreatedAgent,
12
+ type DeletedAgent,
13
+ type OwnedAgent,
14
+ } from './admin.js';
15
+ import type { AccountRef } from './account-attach.js';
16
+ import {
17
+ agentFilePath,
18
+ AgentFileError,
19
+ agentsDir,
20
+ listAgentFiles,
21
+ parseAgentFile,
22
+ readAgentFile,
23
+ type AgentFile,
24
+ } from './files.js';
25
+ import { newId } from '@metro-labs/core/ids';
26
+ import { registerKey, unregisterAgentKey } from './keys.js';
27
+ import { MOVABLE_STATIONS, type LoadedAgent } from '../stations/materialize.js';
28
+ import type { StationName } from '@metro-labs/core/station-names';
29
+ import { normalizeAddress } from '@metro-labs/core/address';
30
+
31
+ export const LOCAL_PROJECT_ID = 'localdaemon';
32
+ const OWNER_FILE = '.owner';
33
+
34
+ interface Stored {
35
+ path: string;
36
+ file: AgentFile;
37
+ }
38
+
39
+ const missing = (): AgentAdminError => new AgentAdminError('no such agent', 404);
40
+
41
+ export function parseOwner(raw: string): string | null {
42
+ const text = raw.trim();
43
+ return isOrganizationId(text) ? text : normalizeAddress(text);
44
+ }
45
+
46
+ export function localOwner(dir = agentsDir()): string | null {
47
+ try {
48
+ return parseOwner(readFileSync(join(dir, OWNER_FILE), 'utf8'));
49
+ } catch {
50
+ return null;
51
+ }
52
+ }
53
+
54
+ export function setLocalOwner(raw: string, dir = agentsDir()): string {
55
+ const owner = parseOwner(raw);
56
+ if (owner === null) throw new ApiError(`'${raw}' is neither an organization id nor an Ethereum address`, 400);
57
+ ensureSecureDir(dir);
58
+ writeSecure(join(dir, OWNER_FILE), `${owner}\n`);
59
+ return owner;
60
+ }
61
+
62
+
63
+ function isOwner(subject: string, dir: string): boolean {
64
+ const owner = localOwner(dir);
65
+ return owner !== null && owner === parseOwner(subject);
66
+ }
67
+
68
+ export function storedAgents(dir: string): Stored[] {
69
+ return listAgentFiles(dir).map((path) => ({ path, file: readAgentFile(path) }));
70
+ }
71
+
72
+ function save(stored: Stored): void {
73
+ writeSecure(stored.path, `${JSON.stringify(stored.file, null, 2)}\n`);
74
+ }
75
+
76
+ function ownedOrThrow(subject: string, id: string, dir: string): Stored {
77
+ if (!isOwner(subject, dir)) throw missing();
78
+ const found = storedAgents(dir).find((s) => s.file.id === id);
79
+ if (found === undefined) throw missing();
80
+ return found;
81
+ }
82
+
83
+ export async function localOwnedAgentOrThrow(
84
+ subject: string,
85
+ id: string,
86
+ dir = agentsDir(),
87
+ ): Promise<{ agent: OwnedAgent }> {
88
+ const { file } = ownedOrThrow(subject, id, dir);
89
+ return Promise.resolve({ agent: { id: file.id, name: file.name } });
90
+ }
91
+
92
+ export async function localListAgents(
93
+ subject: string,
94
+ project: string,
95
+ dir = agentsDir(),
96
+ ): Promise<AgentSummary[]> {
97
+ if (project !== LOCAL_PROJECT_ID || !isOwner(subject, dir))
98
+ throw new AgentAdminError('no such project', 404);
99
+ return Promise.resolve(
100
+ storedAgents(dir)
101
+ .map(({ file }) => ({ id: file.id, name: file.name, owned: true, key: file.key }))
102
+ .sort((a, b) => a.id.localeCompare(b.id)),
103
+ );
104
+ }
105
+
106
+ function freshId(taken: Set<string>): string {
107
+ for (let attempt = 0; attempt < 5; attempt += 1) {
108
+ const id = newId();
109
+ if (!taken.has(id)) return id;
110
+ }
111
+ throw new AgentAdminError('could not allocate a free id', 500);
112
+ }
113
+
114
+ export async function localCreateAgent(
115
+ subject: string,
116
+ project: string,
117
+ rawName?: string,
118
+ dir = agentsDir(),
119
+ ): Promise<CreatedAgent> {
120
+ if (project !== LOCAL_PROJECT_ID || !isOwner(subject, dir))
121
+ throw new AgentAdminError('no such project', 404);
122
+ const name = rawName === undefined ? null : normalizeAgentName(rawName);
123
+ const existing = storedAgents(dir);
124
+ if (existing.length > 0) throw new AgentAdminError('this box already has its agent', 409);
125
+ const id = freshId(new Set());
126
+ const key = newApiKey();
127
+ ensureSecureDir(dir);
128
+ save({
129
+ path: agentFilePath(dir),
130
+ file: { version: 1, id, name, key, owner: localOwner(dir), stations: [], connectors: [] },
131
+ });
132
+ registerKey(key, id);
133
+ return Promise.resolve({ id, name, key });
134
+ }
135
+
136
+ export type Ensured = 'created' | 'present' | 'no-owner';
137
+
138
+ export async function ensureLocalAgent(dir = agentsDir()): Promise<Ensured> {
139
+ if (storedAgents(dir).length > 0) return 'present';
140
+ const owner = localOwner(dir);
141
+ if (owner === null) return 'no-owner';
142
+ await localCreateAgent(owner, LOCAL_PROJECT_ID, undefined, dir);
143
+ return 'created';
144
+ }
145
+
146
+ export async function localDeleteAgent(
147
+ subject: string,
148
+ id: string,
149
+ dir = agentsDir(),
150
+ ): Promise<DeletedAgent> {
151
+ const stored = ownedOrThrow(subject, id, dir);
152
+ const attached = stored.file.stations.length;
153
+ if (attached > 0)
154
+ throw new AgentAdminError(
155
+ `the agent still has ${String(attached)} station account(s) attached — detach them first`,
156
+ 409,
157
+ );
158
+ rmSync(stored.path);
159
+ removeIfEmpty(join(stored.path, '..'));
160
+ unregisterAgentKey(id);
161
+ return Promise.resolve({ id, name: stored.file.name });
162
+ }
163
+
164
+ function assertUnclaimed(existing: Stored[], agent: LoadedAgent): void {
165
+ for (const { file } of existing) {
166
+ if (file.id === agent.id)
167
+ throw new AgentAdminError(`agent ${agent.id} is already on this machine`, 409);
168
+ if (agent.key !== null && file.key === agent.key)
169
+ throw new AgentAdminError('that agent key is already on this machine', 409);
170
+ }
171
+ }
172
+
173
+ export function assertLocalOwner(subject: string, dir = agentsDir()): void {
174
+ if (!isOwner(subject, dir)) throw new AgentAdminError('no such project', 404);
175
+ }
176
+
177
+ function assertImportable(agent: LoadedAgent): asserts agent is LoadedAgent & { key: string } {
178
+ const immovable = agent.accounts.find((a) => !MOVABLE_STATIONS.has(a.station));
179
+ if (immovable !== undefined)
180
+ throw new AgentAdminError(
181
+ `a ${immovable.station} endpoint needs a public url and cannot live on a local daemon`,
182
+ 400,
183
+ );
184
+ if (agent.key === null)
185
+ throw new AgentAdminError('that agent has no key; reset it on metro.box first', 400);
186
+ }
187
+
188
+ interface ImportTarget {
189
+ path: string;
190
+ previous: AgentFile | undefined;
191
+ }
192
+
193
+ function importTarget(dir: string, agent: LoadedAgent & { key: string }): ImportTarget {
194
+ const existing = storedAgents(dir);
195
+ const same = existing.find((s) => s.file.id === agent.id);
196
+ if (same === undefined && existing.length > 0)
197
+ throw new AgentAdminError('this box already has its agent; import into it rather than bringing another', 409);
198
+ assertUnclaimed(existing.filter((s) => s !== same), agent);
199
+ return { path: same?.path ?? agentFilePath(dir), previous: same?.file };
200
+ }
201
+
202
+ export type ImportMode = 'append' | 'overwrite';
203
+
204
+ export async function localImportAgent(
205
+ subject: string,
206
+ agent: LoadedAgent,
207
+ dir = agentsDir(),
208
+ mode: ImportMode = 'overwrite',
209
+ ): Promise<{ id: string; name: string; key: string; stations: number }> {
210
+ assertLocalOwner(subject, dir);
211
+ assertImportable(agent);
212
+ const { path, previous } = importTarget(dir, agent);
213
+ const file = fileFor(agent, localOwner(dir), path, previous, mode);
214
+ ensureSecureDir(join(path, '..'));
215
+ save({ path, file });
216
+ if (previous !== undefined && previous.key !== agent.key) unregisterAgentKey(agent.id);
217
+ registerKey(agent.key, agent.id);
218
+ return Promise.resolve({
219
+ id: agent.id,
220
+ name: agent.name,
221
+ key: agent.key,
222
+ stations: file.stations.length,
223
+ });
224
+ }
225
+
226
+ function mergedStations(
227
+ agent: LoadedAgent,
228
+ previous: AgentFile | undefined,
229
+ mode: ImportMode,
230
+ ): AgentFile['stations'] {
231
+ const before = previous?.stations ?? [];
232
+ const here = (station: string, id: string): boolean => before.some((s) => s.station === station && s.id === id);
233
+ const fromMetro = agent.accounts
234
+ .map((a) => ({ ...a, allowlist: a.allowlist ?? ['*'] }))
235
+ .filter((m) => mode === 'overwrite' || !here(m.station, m.id));
236
+ const kept = before.filter((s) => !fromMetro.some((m) => m.station === s.station && m.id === s.id));
237
+ return [...fromMetro, ...kept];
238
+ }
239
+
240
+ function fileFor(
241
+ agent: LoadedAgent,
242
+ owner: string | null,
243
+ path: string,
244
+ previous: AgentFile | undefined,
245
+ mode: ImportMode,
246
+ ): AgentFile {
247
+ try {
248
+ return parseAgentFile(
249
+ JSON.stringify({
250
+ version: 1,
251
+ id: agent.id,
252
+ name: previous?.name ?? null,
253
+ key: agent.key,
254
+ owner,
255
+ stations: mergedStations(agent, previous, mode),
256
+ connectors: previous?.connectors ?? [],
257
+ }),
258
+ path,
259
+ );
260
+ } catch (err) {
261
+ if (err instanceof AgentFileError)
262
+ throw new AgentAdminError(`metro's copy of this agent cannot be written here: ${err.message}`, 400);
263
+ throw err;
264
+ }
265
+ }
266
+
267
+ export function readLocalAgentFile(agentId: string, dir = agentsDir()): AgentFile {
268
+ const found = storedAgents(dir).find((s) => s.file.id === agentId);
269
+ if (found === undefined) throw missing();
270
+ return found.file;
271
+ }
272
+
273
+ function removeIfEmpty(folder: string): void {
274
+ try {
275
+ rmdirSync(folder);
276
+ } catch {
277
+ return;
278
+ }
279
+ }
280
+
281
+ function assertTokenFree(all: Stored[], station: StationName, token: string): void {
282
+ const taken = all.some((s) =>
283
+ s.file.stations.some(
284
+ (a) => a.station === station && a.config.token === token,
285
+ ),
286
+ );
287
+ if (taken)
288
+ throw new AgentAdminError(
289
+ 'that bot token is already attached to an agent on this machine',
290
+ 409,
291
+ );
292
+ }
293
+
294
+ export async function localAttachAccount(
295
+ subject: string,
296
+ agentId: string,
297
+ station: StationName,
298
+ config: Record<string, unknown>,
299
+ dir = agentsDir(),
300
+ ): Promise<AccountRef> {
301
+ const stored = ownedOrThrow(subject, agentId, dir);
302
+ if (!MOVABLE_STATIONS.has(station))
303
+ throw new AgentAdminError(
304
+ `a ${station} endpoint needs a public url and cannot live on a local daemon`,
305
+ 400,
306
+ );
307
+ const token = config.token;
308
+ if (typeof token === 'string') assertTokenFree(storedAgents(dir), station, token);
309
+ const taken = new Set(stored.file.stations.map((a) => a.id));
310
+ const accountId = freshId(taken);
311
+ stored.file.stations.push({ station, id: accountId, allowlist: ['*'], enabled: true, config });
312
+ save(stored);
313
+ return Promise.resolve({ agentId, station, accountId });
314
+ }
315
+
316
+ export async function localSetAllowlist(
317
+ subject: string,
318
+ agentId: string,
319
+ station: StationName,
320
+ accountId: string,
321
+ allowlist: string[],
322
+ dir = agentsDir(),
323
+ ): Promise<string[]> {
324
+ const stored = ownedOrThrow(subject, agentId, dir);
325
+ const account = stored.file.stations.find((a) => a.station === station && a.id === accountId);
326
+ if (account === undefined) throw new AgentAdminError('no such account on this agent', 404);
327
+ account.allowlist = allowlist;
328
+ save(stored);
329
+ return Promise.resolve(allowlist);
330
+ }
331
+
332
+ export async function localSetAccountEnabled(
333
+ subject: string,
334
+ agentId: string,
335
+ station: StationName,
336
+ accountId: string,
337
+ enabled: boolean,
338
+ dir = agentsDir(),
339
+ ): Promise<boolean> {
340
+ const stored = ownedOrThrow(subject, agentId, dir);
341
+ const account = stored.file.stations.find((a) => a.station === station && a.id === accountId);
342
+ if (account === undefined) throw new AgentAdminError('no such account on this agent', 404);
343
+ account.enabled = enabled;
344
+ save(stored);
345
+ return Promise.resolve(enabled);
346
+ }
347
+
348
+ export async function localDetachAccount(
349
+ subject: string,
350
+ agentId: string,
351
+ station: StationName,
352
+ accountId: string,
353
+ dir = agentsDir(),
354
+ ): Promise<AccountRef> {
355
+ const stored = ownedOrThrow(subject, agentId, dir);
356
+ const before = stored.file.stations.length;
357
+ stored.file.stations = stored.file.stations.filter(
358
+ (a) => !(a.station === station && a.id === accountId),
359
+ );
360
+ if (stored.file.stations.length === before)
361
+ throw new AgentAdminError('no such account on this agent', 404);
362
+ save(stored);
363
+ return Promise.resolve({ agentId, station, accountId });
364
+ }