@stage-labs/metro 0.1.0-beta.23 → 0.1.0-beta.230

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 (453) hide show
  1. package/dist/api.js +9 -101
  2. package/dist/background.js +123 -0
  3. package/dist/claude-settings.js +40 -0
  4. package/dist/claude.js +173 -0
  5. package/dist/cli.js +47 -110
  6. package/dist/control.js +19 -103
  7. package/dist/hold.js +210 -0
  8. package/dist/local.js +43 -0
  9. package/dist/mcp-config.js +25 -0
  10. package/dist/node-name.js +71 -0
  11. package/dist/onboarding.js +44 -0
  12. package/dist/provider-flags.js +9 -0
  13. package/dist/route.js +58 -0
  14. package/dist/runtime-install.js +96 -0
  15. package/dist/runtime.js +29 -52
  16. package/dist/serve.js +157 -0
  17. package/dist/service.js +304 -0
  18. package/dist/tail.js +119 -0
  19. package/dist/update.js +13 -1
  20. package/package.json +2 -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/METRO.md +26 -0
  25. package/runtime/marketplace/plugin/STAGE.md +42 -0
  26. package/runtime/marketplace/plugin/bin/guard.mjs +71 -0
  27. package/runtime/marketplace/plugin/bin/metro-plugin.mjs +30 -0
  28. package/runtime/marketplace/plugin/bin/policy.mjs +97 -0
  29. package/runtime/marketplace/plugin/bin/session-start.mjs +31 -0
  30. package/runtime/marketplace/plugin/hooks/hooks.json +25 -0
  31. package/runtime/node_modules/@metro-labs/core/package.json +51 -0
  32. package/runtime/node_modules/@metro-labs/core/src/endpoints.ts +7 -0
  33. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/events.ts +11 -7
  34. package/runtime/node_modules/@metro-labs/{mcp/src/db → core/src}/ids.ts +5 -0
  35. package/runtime/node_modules/@metro-labs/core/src/is-record.ts +2 -0
  36. package/runtime/node_modules/@metro-labs/{mcp/src/stations → core/src}/lines.ts +22 -26
  37. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/protocol.ts +7 -4
  38. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/secure-fs.ts +17 -0
  39. package/runtime/node_modules/@metro-labs/core/src/station-names.ts +14 -0
  40. package/runtime/node_modules/@metro-labs/core/src/stations/account-files.ts +41 -0
  41. package/runtime/node_modules/@metro-labs/core/src/stations/account-store.ts +83 -0
  42. package/runtime/node_modules/@metro-labs/{mcp → core}/src/stations/attachments.ts +0 -1
  43. package/runtime/node_modules/@metro-labs/core/src/stations/mail-text.ts +72 -0
  44. package/runtime/node_modules/@metro-labs/core/src/stations/mail-trust.ts +29 -0
  45. package/runtime/node_modules/@metro-labs/core/src/stations/mail.ts +212 -0
  46. package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +16 -0
  47. package/runtime/node_modules/@metro-labs/core/src/stations/oauth.ts +126 -0
  48. package/runtime/node_modules/@metro-labs/core/src/stations/profile.ts +58 -0
  49. package/runtime/node_modules/@metro-labs/core/src/stations/sender-profile.ts +64 -0
  50. package/runtime/node_modules/@metro-labs/{mcp → core}/src/stations/station-runtime.ts +6 -11
  51. package/runtime/node_modules/@metro-labs/core/src/stations/train-boot.ts +42 -0
  52. package/runtime/node_modules/@metro-labs/core/src/stations/train-events.ts +84 -0
  53. package/runtime/node_modules/@metro-labs/{mcp → core}/src/stations/types.ts +18 -1
  54. package/runtime/node_modules/@metro-labs/core/src/tickets.ts +37 -0
  55. package/runtime/node_modules/@metro-labs/core/src/version.ts +2 -0
  56. package/runtime/node_modules/@metro-labs/daemon/package.json +49 -0
  57. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/agent-fs.ts +134 -0
  58. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/files-api.ts +12 -0
  59. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/files.ts +89 -0
  60. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/home-fs.ts +113 -0
  61. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/job-env.ts +51 -0
  62. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/provision.ts +80 -0
  63. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedule-detail.ts +99 -0
  64. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules-api.ts +27 -0
  65. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules.ts +144 -0
  66. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/unit-files.ts +37 -0
  67. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/user.ts +89 -0
  68. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/view.ts +87 -0
  69. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-attach.ts +19 -0
  70. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-lookups.ts +55 -0
  71. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-routes.ts +76 -0
  72. package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +357 -0
  73. package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +30 -0
  74. package/runtime/node_modules/@metro-labs/daemon/src/agents/allowlist.ts +59 -0
  75. package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +131 -0
  76. package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +132 -0
  77. package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +305 -0
  78. package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +142 -0
  79. package/runtime/node_modules/@metro-labs/{mcp/src/db/key-map.ts → daemon/src/agents/keys.ts} +1 -11
  80. package/runtime/node_modules/@metro-labs/daemon/src/agents/map.ts +133 -0
  81. package/runtime/node_modules/@metro-labs/daemon/src/agents/policy-route.ts +27 -0
  82. package/runtime/node_modules/@metro-labs/{mcp/src/db/agent-scope.ts → daemon/src/agents/scope.ts} +2 -2
  83. package/runtime/node_modules/@metro-labs/daemon/src/agents/sender-cards.ts +94 -0
  84. package/runtime/node_modules/@metro-labs/daemon/src/agents/senders.ts +38 -0
  85. package/runtime/node_modules/@metro-labs/daemon/src/approvals/api.ts +55 -0
  86. package/runtime/node_modules/@metro-labs/daemon/src/approvals/pending.ts +150 -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 +36 -0
  90. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/boot}/crash-guard.ts +1 -1
  91. package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +21 -0
  92. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/boot}/paths.ts +6 -34
  93. package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +26 -0
  94. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/inbound.ts +75 -45
  95. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/media-note.ts +11 -7
  96. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/pending.ts +4 -1
  97. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/relay.ts +2 -2
  98. package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +368 -0
  99. package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +359 -0
  100. package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +208 -0
  101. package/runtime/node_modules/@metro-labs/daemon/src/claude/memory.ts +32 -0
  102. package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +60 -0
  103. package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +149 -0
  104. package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +75 -0
  105. package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +295 -0
  106. package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +109 -0
  107. package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +329 -0
  108. package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +120 -0
  109. package/runtime/node_modules/@metro-labs/daemon/src/claude/usage-probe.ts +127 -0
  110. package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +113 -0
  111. package/runtime/node_modules/@metro-labs/{mcp/src/daemon/connector-api.ts → daemon/src/connectors/api.ts} +79 -113
  112. package/runtime/node_modules/@metro-labs/{mcp/src/db/connector-config.ts → daemon/src/connectors/config.ts} +67 -41
  113. package/runtime/node_modules/@metro-labs/daemon/src/connectors/gates.ts +72 -0
  114. package/runtime/node_modules/@metro-labs/daemon/src/connectors/health.ts +18 -0
  115. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/connectors}/oauth-client.ts +14 -17
  116. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/connectors}/oauth-discovery.ts +33 -20
  117. package/runtime/node_modules/@metro-labs/{mcp/src/daemon/connector-oauth-routes.ts → daemon/src/connectors/oauth-routes.ts} +30 -32
  118. package/runtime/node_modules/@metro-labs/{mcp/src/daemon/connector-oauth.ts → daemon/src/connectors/oauth.ts} +61 -22
  119. package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +131 -0
  120. package/runtime/node_modules/@metro-labs/daemon/src/connectors/reach.ts +27 -0
  121. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-policy.ts +90 -0
  122. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +347 -0
  123. package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +385 -0
  124. package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +81 -0
  125. package/runtime/node_modules/@metro-labs/daemon/src/connectors/transfer-api.ts +116 -0
  126. package/runtime/node_modules/@metro-labs/daemon/src/connectors/transfer-crypto.ts +44 -0
  127. package/runtime/node_modules/@metro-labs/daemon/src/connectors/transfer-store.ts +48 -0
  128. package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +37 -0
  129. package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +106 -0
  130. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/attach-grant.ts +4 -4
  131. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/attach-owner.ts +3 -3
  132. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-reaper.ts +174 -0
  133. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/attach-serve.ts +12 -13
  134. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/upload-api.ts +10 -27
  135. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/upload-store.ts +4 -4
  136. package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +233 -0
  137. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +214 -0
  138. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
  139. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +173 -0
  140. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +209 -0
  141. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-version.ts +44 -0
  142. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +155 -0
  143. package/runtime/node_modules/@metro-labs/daemon/src/gateway/effort.ts +93 -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 +307 -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 +235 -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 +203 -0
  155. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +285 -0
  156. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-lists.ts +57 -0
  157. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-signin.ts +161 -0
  158. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-store.ts +72 -0
  159. package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +104 -0
  160. package/runtime/node_modules/@metro-labs/daemon/src/gateway/pkce.ts +36 -0
  161. package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +118 -0
  162. package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +18 -0
  163. package/runtime/node_modules/@metro-labs/daemon/src/gateway/subscription.ts +120 -0
  164. package/runtime/node_modules/@metro-labs/daemon/src/gateway/text.ts +35 -0
  165. package/runtime/node_modules/@metro-labs/daemon/src/gateway/tool-search.ts +126 -0
  166. package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage-refresh.ts +102 -0
  167. package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +348 -0
  168. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/accounts.ts +28 -17
  169. package/runtime/node_modules/@metro-labs/daemon/src/mcp/attachment-schema.ts +91 -0
  170. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/call-tools.ts +49 -20
  171. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/ctx.ts +4 -4
  172. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/event-store.ts +15 -16
  173. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/group-tools.ts +2 -2
  174. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/index.ts +34 -80
  175. package/runtime/node_modules/@metro-labs/daemon/src/mcp/instructions.ts +19 -0
  176. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/member-tools.ts +2 -2
  177. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-prompt.ts +87 -0
  178. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +88 -0
  179. package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-gate.ts +82 -0
  180. package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-snapshot.ts +79 -0
  181. package/runtime/node_modules/@metro-labs/daemon/src/mcp/profile-lookup.ts +60 -0
  182. package/runtime/node_modules/@metro-labs/daemon/src/mcp/profile-tool.ts +96 -0
  183. package/runtime/node_modules/@metro-labs/daemon/src/mcp/read-tool.ts +87 -0
  184. package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +46 -0
  185. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-slot.ts +71 -0
  186. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/session.ts +47 -44
  187. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-catalog.ts +93 -0
  188. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-def.ts +9 -0
  189. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/tool-dispatch.ts +53 -40
  190. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/tool-schemas.ts +64 -113
  191. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/upload-tool.ts +6 -11
  192. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/helper-script.ts +126 -0
  193. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/install.ts +37 -0
  194. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/privilege.ts +48 -0
  195. package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +132 -0
  196. package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +316 -0
  197. package/runtime/node_modules/@metro-labs/daemon/src/policy/policy.ts +118 -0
  198. package/runtime/node_modules/@metro-labs/daemon/src/routes/bearer.ts +35 -0
  199. package/runtime/node_modules/@metro-labs/daemon/src/routes/body.ts +24 -0
  200. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/routes}/http.ts +63 -51
  201. package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +209 -0
  202. package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +69 -0
  203. package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
  204. package/runtime/node_modules/@metro-labs/daemon/src/routes/threema-callback.ts +103 -0
  205. package/runtime/node_modules/@metro-labs/daemon/src/server/cloudwatch.ts +178 -0
  206. package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +39 -0
  207. package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +58 -0
  208. package/runtime/node_modules/@metro-labs/daemon/src/server/owner.ts +26 -0
  209. package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +90 -0
  210. package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
  211. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-browser.ts +102 -0
  212. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-driver.ts +42 -0
  213. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-inline.ts +29 -1
  214. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-interactive.ts +5 -31
  215. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-resolve.ts +38 -13
  216. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/attach-session.ts +32 -22
  217. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-xmtp.ts +8 -9
  218. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach.ts +69 -3
  219. package/runtime/node_modules/@metro-labs/{mcp/src/db → daemon/src/stations}/materialize.ts +60 -179
  220. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/registry.ts +20 -2
  221. package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
  222. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/supervisor-io.ts +3 -3
  223. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/supervisor.ts +6 -9
  224. package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
  225. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/train-call.ts +1 -1
  226. package/runtime/node_modules/@metro-labs/daemon/src/stations/webhook-endpoints.ts +51 -0
  227. package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +84 -0
  228. package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +124 -0
  229. package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +15 -0
  230. package/runtime/node_modules/@metro-labs/daemon/src/vault/api.ts +45 -0
  231. package/runtime/node_modules/@metro-labs/daemon/src/vault/config.ts +51 -0
  232. package/runtime/node_modules/@metro-labs/daemon/src/vault/firewall.ts +9 -0
  233. package/runtime/node_modules/@metro-labs/daemon/src/vault/index.ts +96 -0
  234. package/runtime/node_modules/@metro-labs/daemon/src/vault/install.ts +56 -0
  235. package/runtime/node_modules/@metro-labs/daemon/src/vault/paths.ts +26 -0
  236. package/runtime/node_modules/@metro-labs/daemon/src/vault/proxy.ts +147 -0
  237. package/runtime/node_modules/@metro-labs/daemon/src/vault/store.ts +129 -0
  238. package/runtime/node_modules/@metro-labs/daemon/src/vault/trust.ts +44 -0
  239. package/runtime/node_modules/@metro-labs/discord-bot/package.json +2 -4
  240. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +3 -23
  241. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +19 -187
  242. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +2 -2
  243. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +26 -67
  244. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +1 -1
  245. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +2 -2
  246. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +8 -14
  247. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +2 -2
  248. package/runtime/node_modules/@metro-labs/discord-bot/src/profile.ts +68 -0
  249. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +12 -13
  250. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +5 -1
  251. package/runtime/node_modules/@metro-labs/gmail/package.json +25 -0
  252. package/runtime/node_modules/@metro-labs/gmail/src/accounts.ts +81 -0
  253. package/runtime/node_modules/@metro-labs/gmail/src/actions.ts +14 -0
  254. package/runtime/node_modules/@metro-labs/gmail/src/api.ts +31 -0
  255. package/runtime/node_modules/@metro-labs/gmail/src/attachments.ts +35 -0
  256. package/runtime/node_modules/@metro-labs/gmail/src/auth.ts +105 -0
  257. package/runtime/node_modules/@metro-labs/gmail/src/config.ts +13 -0
  258. package/runtime/node_modules/@metro-labs/gmail/src/inbound.ts +96 -0
  259. package/runtime/node_modules/@metro-labs/gmail/src/index.ts +12 -0
  260. package/runtime/node_modules/@metro-labs/gmail/src/login.ts +37 -0
  261. package/runtime/node_modules/@metro-labs/gmail/src/message.ts +125 -0
  262. package/runtime/node_modules/@metro-labs/gmail/src/mime.ts +70 -0
  263. package/runtime/node_modules/@metro-labs/gmail/src/outbound.ts +124 -0
  264. package/runtime/node_modules/@metro-labs/gmail/src/query.ts +99 -0
  265. package/runtime/node_modules/@metro-labs/gmail/src/state.ts +24 -0
  266. package/runtime/node_modules/@metro-labs/gmail/src/station.ts +15 -0
  267. package/runtime/node_modules/@metro-labs/gmail/src/trust.ts +42 -0
  268. package/runtime/node_modules/@metro-labs/http/package.json +30 -0
  269. package/runtime/node_modules/@metro-labs/http/src/api-http.ts +135 -0
  270. package/runtime/node_modules/@metro-labs/{mcp/src/daemon/session-api.ts → http/src/mode-api.ts} +13 -9
  271. package/runtime/node_modules/@metro-labs/http/src/return-to.ts +21 -0
  272. package/runtime/node_modules/@metro-labs/http/src/sigv4.ts +90 -0
  273. package/runtime/node_modules/@metro-labs/http/src/workos-token.ts +149 -0
  274. package/runtime/node_modules/@metro-labs/outlook/package.json +25 -0
  275. package/runtime/node_modules/@metro-labs/outlook/src/accounts.ts +78 -0
  276. package/runtime/node_modules/@metro-labs/outlook/src/actions.ts +14 -0
  277. package/runtime/node_modules/@metro-labs/outlook/src/attachments.ts +81 -0
  278. package/runtime/node_modules/@metro-labs/outlook/src/auth.ts +120 -0
  279. package/runtime/node_modules/@metro-labs/outlook/src/browser.ts +42 -0
  280. package/runtime/node_modules/@metro-labs/outlook/src/config.ts +23 -0
  281. package/runtime/node_modules/@metro-labs/outlook/src/format.ts +59 -0
  282. package/runtime/node_modules/@metro-labs/outlook/src/graph.ts +55 -0
  283. package/runtime/node_modules/@metro-labs/outlook/src/inbound.ts +96 -0
  284. package/runtime/node_modules/@metro-labs/outlook/src/index.ts +12 -0
  285. package/runtime/node_modules/@metro-labs/outlook/src/login.ts +129 -0
  286. package/runtime/node_modules/@metro-labs/outlook/src/me.ts +31 -0
  287. package/runtime/node_modules/@metro-labs/outlook/src/outbound.ts +103 -0
  288. package/runtime/node_modules/@metro-labs/outlook/src/query.ts +84 -0
  289. package/runtime/node_modules/@metro-labs/outlook/src/state.ts +28 -0
  290. package/runtime/node_modules/@metro-labs/outlook/src/station.ts +15 -0
  291. package/runtime/node_modules/@metro-labs/outlook/src/trust.ts +41 -0
  292. package/runtime/node_modules/@metro-labs/telegram/package.json +1 -3
  293. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +5 -24
  294. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +8 -6
  295. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +1 -1
  296. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +2 -2
  297. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +19 -34
  298. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +3 -3
  299. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +13 -34
  300. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +4 -43
  301. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +2 -3
  302. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +1 -1
  303. package/runtime/node_modules/@metro-labs/telegram/src/profile.ts +70 -0
  304. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +2 -2
  305. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +4 -1
  306. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +0 -1
  307. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +1 -1
  308. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +7 -41
  309. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +22 -77
  310. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +2 -2
  311. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +46 -48
  312. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +15 -15
  313. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +6 -82
  314. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +1 -1
  315. package/runtime/node_modules/@metro-labs/telegram-bot/src/profile.ts +85 -0
  316. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +5 -1
  317. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +20 -12
  318. package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
  319. package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +96 -0
  320. package/runtime/node_modules/@metro-labs/threema/src/actions.ts +124 -0
  321. package/runtime/node_modules/@metro-labs/threema/src/api.ts +152 -0
  322. package/runtime/node_modules/@metro-labs/threema/src/crypto.ts +100 -0
  323. package/runtime/node_modules/@metro-labs/threema/src/files.ts +47 -0
  324. package/runtime/node_modules/@metro-labs/threema/src/format.ts +164 -0
  325. package/runtime/node_modules/@metro-labs/threema/src/groups.ts +96 -0
  326. package/runtime/node_modules/@metro-labs/threema/src/ids.ts +21 -0
  327. package/runtime/node_modules/@metro-labs/threema/src/inbound.ts +87 -0
  328. package/runtime/node_modules/@metro-labs/threema/src/index.ts +24 -0
  329. package/runtime/node_modules/@metro-labs/threema/src/messages.ts +264 -0
  330. package/runtime/node_modules/@metro-labs/threema/src/outbound.ts +160 -0
  331. package/runtime/node_modules/@metro-labs/threema/src/station.ts +13 -0
  332. package/runtime/node_modules/@metro-labs/threema/src/verify.ts +117 -0
  333. package/runtime/node_modules/@metro-labs/webhook/package.json +1 -1
  334. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +5 -8
  335. package/runtime/node_modules/@metro-labs/whatsapp/package.json +2 -5
  336. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +6 -28
  337. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +1 -1
  338. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +54 -11
  339. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +2 -2
  340. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +1 -1
  341. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +86 -4
  342. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +7 -58
  343. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +11 -28
  344. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +4 -37
  345. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +1 -1
  346. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +1 -1
  347. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +1 -1
  348. package/runtime/node_modules/@metro-labs/whatsapp/src/names.ts +75 -0
  349. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +44 -0
  350. package/runtime/node_modules/@metro-labs/whatsapp/src/resolve.ts +52 -0
  351. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +16 -1
  352. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +5 -9
  353. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +0 -1
  354. package/runtime/node_modules/@metro-labs/xmtp/package.json +3 -2
  355. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +10 -20
  356. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +2 -2
  357. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +15 -204
  358. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +40 -53
  359. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +72 -220
  360. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +3 -3
  361. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +27 -0
  362. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +11 -7
  363. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +7 -24
  364. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +8 -18
  365. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +1 -1
  366. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +2 -2
  367. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +30 -16
  368. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +12 -20
  369. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +57 -33
  370. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +1 -1
  371. package/runtime/node_modules/@metro-labs/xmtp/src/names.ts +61 -0
  372. package/runtime/node_modules/@metro-labs/xmtp/src/profile.ts +153 -0
  373. package/runtime/node_modules/@metro-labs/xmtp/src/resolve.ts +44 -0
  374. package/runtime/node_modules/@metro-labs/xmtp/src/sender.ts +112 -0
  375. package/runtime/node_modules/@metro-labs/xmtp/src/smart.ts +72 -0
  376. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +7 -2
  377. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +22 -58
  378. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +55 -48
  379. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +4 -6
  380. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +19 -5
  381. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +14 -4
  382. package/runtime/runtime.json +1 -1
  383. package/runtime/server.ts +1 -0
  384. package/runtime/stations.json +38 -0
  385. package/dist/prompt.js +0 -61
  386. package/dist/store.js +0 -70
  387. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +0 -195
  388. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +0 -292
  389. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +0 -197
  390. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +0 -5
  391. package/runtime/node_modules/@metro-labs/mcp/package.json +0 -66
  392. package/runtime/node_modules/@metro-labs/mcp/src/channels/dedupe.ts +0 -24
  393. package/runtime/node_modules/@metro-labs/mcp/src/daemon/account-api.ts +0 -311
  394. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +0 -341
  395. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-http.ts +0 -129
  396. package/runtime/node_modules/@metro-labs/mcp/src/daemon/boot.ts +0 -320
  397. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair-api.ts +0 -111
  398. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair.ts +0 -20
  399. package/runtime/node_modules/@metro-labs/mcp/src/daemon/collection-api.ts +0 -202
  400. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-json.ts +0 -31
  401. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-tools.ts +0 -100
  402. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-url.ts +0 -57
  403. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-verify.ts +0 -333
  404. package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-auth.ts +0 -164
  405. package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-oauth.ts +0 -208
  406. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +0 -79
  407. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-pending.ts +0 -65
  408. package/runtime/node_modules/@metro-labs/mcp/src/daemon/pair-codes.ts +0 -58
  409. package/runtime/node_modules/@metro-labs/mcp/src/daemon/project-api.ts +0 -214
  410. package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-api.ts +0 -125
  411. package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-pair.ts +0 -18
  412. package/runtime/node_modules/@metro-labs/mcp/src/daemon/runtime-source.ts +0 -105
  413. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-apis.ts +0 -41
  414. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session.ts +0 -218
  415. package/runtime/node_modules/@metro-labs/mcp/src/daemon/tunnel.ts +0 -169
  416. package/runtime/node_modules/@metro-labs/mcp/src/db/account-attach.ts +0 -91
  417. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-admin.ts +0 -215
  418. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-map.ts +0 -71
  419. package/runtime/node_modules/@metro-labs/mcp/src/db/client.ts +0 -37
  420. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-collections.ts +0 -218
  421. package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +0 -329
  422. package/runtime/node_modules/@metro-labs/mcp/src/db/projects.ts +0 -300
  423. package/runtime/node_modules/@metro-labs/mcp/src/db/runtime-admin.ts +0 -31
  424. package/runtime/node_modules/@metro-labs/mcp/src/db/runtimes.ts +0 -105
  425. package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +0 -131
  426. package/runtime/node_modules/@metro-labs/mcp/src/db/token-carry.ts +0 -45
  427. package/runtime/node_modules/@metro-labs/mcp/src/db/users.ts +0 -57
  428. package/runtime/node_modules/@metro-labs/mcp/src/db/whatsapp-login.ts +0 -18
  429. package/runtime/node_modules/@metro-labs/mcp/src/mcp/channel-owner.ts +0 -29
  430. package/runtime/node_modules/@metro-labs/mcp/src/mcp/frame-scope.ts +0 -21
  431. package/runtime/node_modules/@metro-labs/mcp/src/mcp/permission-relay.ts +0 -66
  432. package/runtime/node_modules/@metro-labs/mcp/src/mcp/request-identity.ts +0 -73
  433. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-registry.ts +0 -172
  434. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-route.ts +0 -43
  435. package/runtime/node_modules/@metro-labs/mcp/src/monitor/api.ts +0 -240
  436. package/runtime/node_modules/@metro-labs/mcp/src/server.ts +0 -2
  437. package/runtime/node_modules/@metro-labs/mcp/src/stations/account-store.ts +0 -80
  438. package/runtime/node_modules/@metro-labs/mcp/src/stations/messaging-normalize.ts +0 -88
  439. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +0 -22
  440. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +0 -1
  441. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +0 -3
  442. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +0 -22
  443. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +0 -5
  444. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +0 -87
  445. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +0 -55
  446. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +0 -349
  447. /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/log.ts +0 -0
  448. /package/runtime/node_modules/@metro-labs/{mcp/src/mcp → core/src}/str.ts +0 -0
  449. /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/train-error.ts +0 -0
  450. /package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/webhook-note.ts +0 -0
  451. /package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/raw-get-stream.ts +0 -0
  452. /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → http/src}/api-error.ts +0 -0
  453. /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → http/src}/cors.ts +0 -0
@@ -0,0 +1,96 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { copyFileSync, cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ import { localStations } from './local.js';
6
+ import { STORE_ENTRY, daemonEntry, findBun, runtimeDir } from './runtime.js';
7
+ const MANIFEST_FILE = 'stations.json';
8
+ const MARKETPLACE = 'marketplace';
9
+ const METRO_SOURCES = join('node_modules', '@metro-labs');
10
+ const INSTALL_TIMEOUT_MS = 15 * 60_000;
11
+ export function runtimeStore() {
12
+ const env = process.env.METRO_RUNTIME_STORE?.trim() ?? '';
13
+ return env === '' ? join(homedir(), '.metro', 'runtime') : env;
14
+ }
15
+ const isRecord = (v) => typeof v === 'object' && v !== null;
16
+ const ranges = (raw) => isRecord(raw)
17
+ ? Object.fromEntries(Object.entries(raw).filter((e) => typeof e[1] === 'string'))
18
+ : {};
19
+ export function readManifest(path) {
20
+ const raw = JSON.parse(readFileSync(path, 'utf8'));
21
+ if (!isRecord(raw))
22
+ throw new Error(`${path} is not a runtime manifest`);
23
+ const stations = isRecord(raw.stations) ? raw.stations : {};
24
+ return {
25
+ core: ranges(raw.core),
26
+ stations: Object.fromEntries(Object.entries(stations).map(([name, deps]) => [name, ranges(deps)])),
27
+ };
28
+ }
29
+ export function dependenciesFor(manifest, stations) {
30
+ const out = { ...manifest.core };
31
+ for (const station of new Set(stations))
32
+ Object.assign(out, manifest.stations[station] ?? {});
33
+ return Object.fromEntries(Object.entries(out).sort(([a], [b]) => a.localeCompare(b)));
34
+ }
35
+ const packageText = (deps) => `${JSON.stringify({ name: 'metro-runtime', private: true, dependencies: deps }, null, 2)}\n`;
36
+ function readOrNull(path) {
37
+ try {
38
+ return readFileSync(path, 'utf8');
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ }
44
+ function copyMarketplace(sources, store) {
45
+ const staged = join(sources, MARKETPLACE);
46
+ if (!existsSync(staged))
47
+ return false;
48
+ cpSync(staged, join(store, MARKETPLACE), { recursive: true });
49
+ return true;
50
+ }
51
+ function syncSources(sources, store) {
52
+ const shim = join(sources, STORE_ENTRY);
53
+ if (existsSync(shim))
54
+ copyFileSync(shim, join(store, STORE_ENTRY));
55
+ const stamp = readOrNull(join(sources, 'runtime.json'));
56
+ if (stamp !== null && readOrNull(join(store, 'runtime.json')) === stamp)
57
+ return existsSync(join(store, MARKETPLACE)) ? false : copyMarketplace(sources, store);
58
+ rmSync(join(store, METRO_SOURCES), { recursive: true, force: true });
59
+ cpSync(join(sources, METRO_SOURCES), join(store, METRO_SOURCES), { recursive: true });
60
+ rmSync(join(store, MARKETPLACE), { recursive: true, force: true });
61
+ copyMarketplace(sources, store);
62
+ if (stamp !== null)
63
+ writeFileSync(join(store, 'runtime.json'), stamp);
64
+ return true;
65
+ }
66
+ export function installDependencies(store, deps, bun, log) {
67
+ const wanted = packageText(deps);
68
+ const marker = join(store, 'node_modules', '.metro-installed');
69
+ if (existsSync(marker) && readOrNull(join(store, 'package.json')) === wanted)
70
+ return false;
71
+ writeFileSync(join(store, 'package.json'), wanted);
72
+ log(`Installing the channel SDKs this machine needs into ${store}`);
73
+ const run = spawnSync(bun, ['install', '--no-summary', '--no-progress'], {
74
+ cwd: store,
75
+ stdio: ['ignore', 'inherit', 'inherit'],
76
+ timeout: INSTALL_TIMEOUT_MS,
77
+ });
78
+ if (run.error !== undefined || run.status !== 0)
79
+ throw new Error(`bun install failed in ${store}: ${run.error?.message ?? `exit ${String(run.status)}`}`);
80
+ writeFileSync(marker, `${new Date().toISOString()}\n`);
81
+ return true;
82
+ }
83
+ export function prepareRuntime(opts = {}) {
84
+ const sources = opts.sources ?? runtimeDir();
85
+ const manifestPath = join(sources, MANIFEST_FILE);
86
+ const manifest = readManifest(manifestPath);
87
+ const store = opts.store ?? runtimeStore();
88
+ const log = opts.log ??
89
+ ((line) => {
90
+ process.stderr.write(`${line}\n`);
91
+ });
92
+ mkdirSync(join(store, 'trains'), { recursive: true });
93
+ syncSources(sources, store);
94
+ installDependencies(store, dependenciesFor(manifest, localStations(opts.agents)), opts.bun ?? findBun(), log);
95
+ return { dir: store, entry: daemonEntry(store), trains: join(store, 'trains'), manifest: manifestPath };
96
+ }
package/dist/runtime.js CHANGED
@@ -1,12 +1,10 @@
1
1
  import { spawn, spawnSync } from 'node:child_process';
2
- import { mkdirSync, readFileSync, writeFileSync, chmodSync } from 'node:fs';
2
+ import { existsSync } from 'node:fs';
3
3
  import { dirname, join } from 'node:path';
4
4
  import { fileURLToPath } from 'node:url';
5
- import { existsSync } from 'node:fs';
6
- import { homedir } from 'node:os';
7
- import { metroUrl } from './store.js';
8
5
  const AGENT_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{10}$/;
9
- const SERVER_ENTRY = join('node_modules', '@metro-labs', 'mcp', 'src', 'server.ts');
6
+ export const STORE_ENTRY = 'server.ts';
7
+ export const daemonEntry = (dir) => join(dir, STORE_ENTRY);
10
8
  export class MissingRuntime extends Error {
11
9
  }
12
10
  export function runtimeDir() {
@@ -14,11 +12,11 @@ export function runtimeDir() {
14
12
  const dir = explicit !== undefined && explicit !== ''
15
13
  ? explicit
16
14
  : join(dirname(dirname(fileURLToPath(import.meta.url))), 'runtime');
17
- if (!existsSync(join(dir, SERVER_ENTRY)))
15
+ if (!existsSync(daemonEntry(dir)))
18
16
  throw new MissingRuntime(`no metro daemon at ${dir}. Reinstall with: npm i -g @stage-labs/metro@beta`);
19
17
  return dir;
20
18
  }
21
- export class MissingBun extends Error {
19
+ class MissingBun extends Error {
22
20
  }
23
21
  export function assertAgentId(id) {
24
22
  if (id === undefined || !AGENT_RE.test(id))
@@ -28,54 +26,15 @@ export function assertAgentId(id) {
28
26
  export function findBun() {
29
27
  const found = spawnSync('bun', ['--version'], { stdio: 'ignore' });
30
28
  if (found.error !== undefined || found.status !== 0)
31
- throw new MissingBun('metro start needs Bun, which is not on PATH.\n' +
29
+ throw new MissingBun('metro serve needs Bun, which is not on PATH.\n' +
32
30
  'Install it with: curl -fsSL https://bun.sh/install | bash');
33
31
  return 'bun';
34
32
  }
35
- function tokenPath(agentId) {
36
- const xdg = process.env.XDG_CONFIG_HOME?.trim();
37
- const base = xdg === undefined || xdg === '' ? join(homedir(), '.config') : xdg;
38
- return join(base, 'metro', `runtime-${agentId}.json`);
39
- }
40
- export function readRunToken(agentId) {
41
- const fromEnv = process.env.METRO_RUN_TOKEN?.trim();
42
- if (fromEnv !== undefined && fromEnv !== '')
43
- return fromEnv;
44
- try {
45
- const parsed = JSON.parse(readFileSync(tokenPath(agentId), 'utf8'));
46
- if (typeof parsed.token !== 'string' || parsed.token === '')
47
- return null;
48
- return parsed.url === metroUrl() ? parsed.token : null;
49
- }
50
- catch {
51
- return null;
52
- }
53
- }
54
- export function writeRunToken(agentId, token) {
55
- const path = tokenPath(agentId);
56
- mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
57
- writeFileSync(path, JSON.stringify({ token, url: metroUrl() }, null, 2));
58
- chmodSync(path, 0o600);
59
- }
60
33
  export const localPort = () => Number(process.env.METRO_WEBHOOK_PORT) || 8420;
61
34
  export const localUrl = () => `http://127.0.0.1:${String(localPort())}`;
62
- export function daemonPlan(opts) {
63
- return {
64
- command: findBun(),
65
- args: [SERVER_ENTRY],
66
- cwd: opts.dir,
67
- env: {
68
- ...process.env,
69
- METRO_RUN_TOKEN: opts.token,
70
- METRO_AGENT: opts.agentId,
71
- METRO_URL: metroUrl(),
72
- METRO_HTTP_HOST: process.env.METRO_HTTP_HOST ?? '127.0.0.1',
73
- METRO_TRAINS_DIR: join(opts.dir, 'trains'),
74
- },
75
- };
76
- }
77
- export function runDaemon(agentId, plan) {
78
- process.stderr.write(`Starting metro for ${agentId}\n`);
35
+ export const RESTART_CODE = 75;
36
+ export const HOLD_CODE = 76;
37
+ function spawnOnce(plan) {
79
38
  const child = spawn(plan.command, plan.args, {
80
39
  cwd: plan.cwd,
81
40
  env: plan.env,
@@ -84,11 +43,29 @@ export function runDaemon(agentId, plan) {
84
43
  const relay = (sig) => () => {
85
44
  child.kill(sig);
86
45
  };
87
- process.on('SIGINT', relay('SIGINT'));
88
- process.on('SIGTERM', relay('SIGTERM'));
46
+ const onInt = relay('SIGINT');
47
+ const onTerm = relay('SIGTERM');
48
+ process.on('SIGINT', onInt);
49
+ process.on('SIGTERM', onTerm);
89
50
  return new Promise((resolve) => {
90
51
  child.on('exit', (code) => {
52
+ process.off('SIGINT', onInt);
53
+ process.off('SIGTERM', onTerm);
91
54
  resolve(code ?? 0);
92
55
  });
93
56
  });
94
57
  }
58
+ export async function spawnPlan(plan, hold) {
59
+ for (;;) {
60
+ const code = await spawnOnce(plan());
61
+ if (code === RESTART_CODE) {
62
+ process.stderr.write('metro was updated; restarting the daemon on the new version\n');
63
+ continue;
64
+ }
65
+ if (code !== HOLD_CODE || hold === undefined)
66
+ return code;
67
+ if ((await hold()) === 'exit')
68
+ return 0;
69
+ process.stderr.write('starting the daemon again\n');
70
+ }
71
+ }
package/dist/serve.js ADDED
@@ -0,0 +1,157 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { agentsDir } from './local.js';
5
+ import { currentVersion } from './version.js';
6
+ import { serveLockedBy, serveStateDir } from './control.js';
7
+ import { findBun, localPort, spawnPlan } from './runtime.js';
8
+ import { prepareRuntime } from './runtime-install.js';
9
+ import { ensureNodeName } from './node-name.js';
10
+ import { holdUntilStart } from './hold.js';
11
+ const SCRUBBED = new Set(['DATABASE_URL']);
12
+ const PORT_FLAG = /^--port=(.*)$/;
13
+ const OWNER_FLAG = /^--owner=(.*)$/;
14
+ const ADDRESS = /^0x[0-9a-fA-F]{40}$/;
15
+ const USAGE = 'usage: metro serve [--port <n>] [--owner <organization id>]';
16
+ const TUNNEL_FLAG = /^--(no-)?tunnel(=.*)?$/;
17
+ const TAILSCALE_APP = '/Applications/Tailscale.app/Contents/MacOS/Tailscale';
18
+ function portOf(raw) {
19
+ const n = Number(raw);
20
+ if (raw === undefined || raw === '' || !Number.isInteger(n) || n < 1 || n > 65535)
21
+ throw new Error(`'${raw ?? ''}' is not a port — ${USAGE}`);
22
+ return n;
23
+ }
24
+ const ORGANIZATION = /^org_[A-Za-z0-9]{10,64}$/;
25
+ function ownerOf(raw) {
26
+ if (raw !== undefined && ORGANIZATION.test(raw))
27
+ return { owner: raw };
28
+ if (raw !== undefined && ADDRESS.test(raw))
29
+ return { owner: null, ignoredOwner: raw };
30
+ throw new Error(`'${raw ?? ''}' is not an organization id (org_…) — ${USAGE}`);
31
+ }
32
+ export function parseServeArgs(argv) {
33
+ let port = localPort();
34
+ let owner = { owner: null };
35
+ for (let i = 0; i < argv.length; i += 1) {
36
+ const arg = argv[i] ?? '';
37
+ if (TUNNEL_FLAG.test(arg))
38
+ throw new Error('--tunnel is gone: metro serve always publishes through Tailscale Funnel — ' + USAGE);
39
+ const inlineOwner = OWNER_FLAG.exec(arg);
40
+ if (inlineOwner) {
41
+ owner = ownerOf(inlineOwner[1]);
42
+ continue;
43
+ }
44
+ if (arg === '--owner') {
45
+ owner = ownerOf(argv[i + 1]);
46
+ i += 1;
47
+ continue;
48
+ }
49
+ const inline = PORT_FLAG.exec(arg);
50
+ if (inline) {
51
+ port = portOf(inline[1]);
52
+ continue;
53
+ }
54
+ if (arg === '--port' || arg === '-p') {
55
+ port = portOf(argv[i + 1]);
56
+ i += 1;
57
+ continue;
58
+ }
59
+ throw new Error(`unknown argument '${arg}' — ${USAGE}`);
60
+ }
61
+ return { port, ...owner };
62
+ }
63
+ export function requireOwner(args, dir = agentsDir()) {
64
+ if (args.ignoredOwner !== undefined) {
65
+ process.stderr.write(`--owner ${args.ignoredOwner} is a wallet address, which metro ignores: the organization id in ${join(dir, '.owner')} decides who signs in\n`);
66
+ return;
67
+ }
68
+ if (args.owner !== null || existsSync(join(dir, '.owner')))
69
+ return;
70
+ throw new Error('no owner is set for this machine, so nobody could sign in.\n' +
71
+ 'Pass the organization that owns it once; it is remembered in ' +
72
+ join(dir, '.owner') +
73
+ ':\n metro serve --owner <org_…>');
74
+ }
75
+ function readOwner(dir = agentsDir()) {
76
+ try {
77
+ const raw = readFileSync(join(dir, '.owner'), 'utf8').trim();
78
+ return ORGANIZATION.test(raw) ? raw : null;
79
+ }
80
+ catch {
81
+ return null;
82
+ }
83
+ }
84
+ function holdInfo(port, owner, tailscaleBin) {
85
+ return {
86
+ port,
87
+ host: process.env.METRO_HTTP_HOST ?? '127.0.0.1',
88
+ owner: owner ?? readOwner(),
89
+ version: currentVersion(),
90
+ funnel: tailscaleBin,
91
+ lockFile: join(process.env.METRO_STATE_DIR ?? serveStateDir(), '.tail-lock'),
92
+ };
93
+ }
94
+ function tailscaleStatus(bin) {
95
+ const run = spawnSync(bin, ['status', '--json'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
96
+ if (run.error !== undefined || run.status !== 0)
97
+ return { ok: false, state: 'not running' };
98
+ try {
99
+ const parsed = JSON.parse(run.stdout);
100
+ if (parsed.BackendState !== 'Running')
101
+ return { ok: false, state: String(parsed.BackendState) };
102
+ if (parsed.Self?.Online === false)
103
+ return { ok: false, state: 'Running, but not connected to the tailnet: the machine may have been removed from it' };
104
+ return { ok: true };
105
+ }
106
+ catch {
107
+ return { ok: false, state: 'unreadable' };
108
+ }
109
+ }
110
+ export function findTailscale(candidates = ['tailscale', TAILSCALE_APP]) {
111
+ const bin = candidates.find((c) => spawnSync(c, ['version'], { stdio: 'ignore' }).status === 0);
112
+ if (bin === undefined)
113
+ throw new Error('metro serve needs Tailscale on this machine: the daemon is published through Tailscale Funnel.\n' +
114
+ 'macOS: brew install --cask tailscale (or the App Store app)\n' +
115
+ 'Linux: curl -fsSL https://tailscale.com/install.sh | sh\n' +
116
+ 'Then sign the machine in: tailscale up');
117
+ const status = tailscaleStatus(bin);
118
+ if (!status.ok)
119
+ throw new Error(`Tailscale is installed but this machine is not connected (${status.state}). Sign it in first: tailscale up\n` +
120
+ 'Funnel also has to be enabled once on your tailnet: https://tailscale.com/kb/1223/funnel');
121
+ return bin;
122
+ }
123
+ export function servePlan(opts) {
124
+ const env = Object.fromEntries(Object.entries(process.env).filter(([key]) => !SCRUBBED.has(key)));
125
+ return {
126
+ command: findBun(),
127
+ args: [opts.runtime.entry],
128
+ cwd: opts.runtime.dir,
129
+ env: {
130
+ ...env,
131
+ METRO_VERSION: currentVersion(),
132
+ METRO_CLI_BIN: process.argv[1] ?? '',
133
+ METRO_WEBHOOK_PORT: String(opts.port),
134
+ METRO_HTTP_HOST: process.env.METRO_HTTP_HOST ?? '127.0.0.1',
135
+ METRO_TRAINS_DIR: opts.runtime.trains,
136
+ METRO_RUNTIME_STORE: opts.runtime.dir,
137
+ METRO_RUNTIME_MANIFEST: opts.runtime.manifest,
138
+ METRO_STATE_DIR: process.env.METRO_STATE_DIR ?? serveStateDir(),
139
+ METRO_TUNNEL: 'tailscale',
140
+ METRO_TAILSCALE_BIN: opts.tailscaleBin,
141
+ ...(opts.owner === null ? {} : { METRO_OWNER: opts.owner }),
142
+ },
143
+ };
144
+ }
145
+ export function serve(argv) {
146
+ const args = parseServeArgs(argv);
147
+ const { port, owner } = args;
148
+ const running = serveLockedBy();
149
+ if (running !== null)
150
+ throw new Error(`a metro serve daemon is already running on this machine (pid ${String(running)}). ` +
151
+ 'Stop it first: metro stop');
152
+ requireOwner(args);
153
+ const tailscaleBin = findTailscale();
154
+ const node = ensureNodeName(tailscaleBin);
155
+ process.stderr.write(`Starting a metro daemon of your own on http://127.0.0.1:${String(port)}, published as ${node} on your tailnet\n`);
156
+ return spawnPlan(() => servePlan({ runtime: prepareRuntime(), port, owner, tailscaleBin }), () => holdUntilStart(holdInfo(port, owner, tailscaleBin)));
157
+ }
@@ -0,0 +1,304 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync, mkdirSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { homedir, platform, userInfo } from 'node:os';
4
+ import { dirname, join, resolve } from 'node:path';
5
+ import { serveLockedBy } from './control.js';
6
+ import { findBun, runtimeDir } from './runtime.js';
7
+ import { findTailscale, parseServeArgs, requireOwner } from './serve.js';
8
+ const USAGE = 'usage: metro service install [--port <n>] [--owner <organization id>] [--user metro] | uninstall | status';
9
+ const SERVICE = 'metro';
10
+ const LABEL = 'box.metro.serve';
11
+ const CARRIED = /^(METRO_.*|XDG_CACHE_HOME|PATH|HOME)$/;
12
+ const OWN_PATHS = new Set(['METRO_AGENTS_DIR', 'METRO_RUNTIME_STORE']);
13
+ const METRO_USER = 'metro';
14
+ const USER_FLAG = /^--user=(.*)$/;
15
+ const PLAIN_WORD = /^[A-Za-z0-9_/.:=+@,-]+$/;
16
+ const carriedEnv = (env) => Object.entries(env)
17
+ .filter((entry) => CARRIED.test(entry[0]) && typeof entry[1] === 'string' && entry[1] !== '')
18
+ .sort(([a], [b]) => a.localeCompare(b));
19
+ const cacheDir = (host) => {
20
+ const xdg = host.env.XDG_CACHE_HOME?.trim();
21
+ return xdg === undefined || xdg === '' ? join(host.home, '.cache') : xdg;
22
+ };
23
+ function unitWord(arg) {
24
+ const escaped = arg.replaceAll('%', '%%');
25
+ if (PLAIN_WORD.test(arg))
26
+ return escaped;
27
+ return `"${escaped.replaceAll('\\', '\\\\').replaceAll('"', '\\"')}"`;
28
+ }
29
+ function unitText(service, target) {
30
+ return [
31
+ '[Unit]',
32
+ 'Description=Metro daemon (metro serve)',
33
+ 'After=network-online.target tailscaled.service',
34
+ 'Wants=network-online.target',
35
+ '',
36
+ '[Service]',
37
+ ...service,
38
+ '',
39
+ '[Install]',
40
+ `WantedBy=${target}`,
41
+ '',
42
+ ].join('\n');
43
+ }
44
+ const RESTART = ['Restart=always', 'RestartSec=2', 'OOMPolicy=continue'];
45
+ function systemdUnit(host, exec, target) {
46
+ const env = carriedEnv(host.env).map(([key, value]) => `Environment=${unitWord(`${key}=${value}`)}`);
47
+ return unitText([`ExecStart=${exec.map(unitWord).join(' ')}`, ...RESTART, `WorkingDirectory=${unitWord(host.home)}`, ...env], target);
48
+ }
49
+ const npmPrefix = (home) => join(home, '.npm-global');
50
+ function runAsUnit(host, runAs, serveArgs) {
51
+ const prefix = npmPrefix(runAs.home);
52
+ const exec = [host.node, join(prefix, 'lib', 'node_modules', '@stage-labs', 'metro', 'dist', 'cli.js'), 'serve', ...serveArgs];
53
+ const env = carriedEnv(host.env)
54
+ .filter(([key]) => key.startsWith('METRO_') && !OWN_PATHS.has(key))
55
+ .map(([key, value]) => `Environment=${unitWord(`${key}=${value}`)}`);
56
+ return unitText([
57
+ `User=${runAs.name}`,
58
+ `Group=${runAs.name}`,
59
+ `ExecStart=${exec.map(unitWord).join(' ')}`,
60
+ ...RESTART,
61
+ 'WorkingDirectory=/',
62
+ `Environment=HOME=${unitWord(runAs.home)}`,
63
+ `Environment=${unitWord(`PATH=${prefix}/bin:/usr/local/bin:/usr/bin:/bin`)}`,
64
+ `Environment=${unitWord(`NPM_CONFIG_PREFIX=${prefix}`)}`,
65
+ ...env,
66
+ ], 'multi-user.target');
67
+ }
68
+ const xml = (text) => text
69
+ .replaceAll('&', '&amp;')
70
+ .replaceAll('<', '&lt;')
71
+ .replaceAll('>', '&gt;')
72
+ .replaceAll('"', '&quot;')
73
+ .replaceAll("'", '&apos;');
74
+ function launchdPlist(host, exec, logFile) {
75
+ const env = carriedEnv(host.env).map(([key, value]) => ` <key>${xml(key)}</key><string>${xml(value)}</string>`);
76
+ return [
77
+ '<?xml version="1.0" encoding="UTF-8"?>',
78
+ '<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">',
79
+ '<plist version="1.0">',
80
+ '<dict>',
81
+ ` <key>Label</key><string>${LABEL}</string>`,
82
+ ' <key>ProgramArguments</key>',
83
+ ' <array>',
84
+ ...exec.map((arg) => ` <string>${xml(arg)}</string>`),
85
+ ' </array>',
86
+ ' <key>EnvironmentVariables</key>',
87
+ ' <dict>',
88
+ ...env,
89
+ ' </dict>',
90
+ ` <key>WorkingDirectory</key><string>${xml(host.home)}</string>`,
91
+ ' <key>RunAtLoad</key><true/>',
92
+ ' <key>KeepAlive</key><true/>',
93
+ ` <key>StandardOutPath</key><string>${xml(logFile)}</string>`,
94
+ ` <key>StandardErrorPath</key><string>${xml(logFile)}</string>`,
95
+ '</dict>',
96
+ '</plist>',
97
+ '',
98
+ ].join('\n');
99
+ }
100
+ const ROOT_HINT = `Metro running as root never starts Claude Code; install it as its own user: metro service install --user ${METRO_USER}`;
101
+ function systemdPlan(host, serveArgs, runAs) {
102
+ const exec = [host.node, host.cli, 'serve', ...serveArgs];
103
+ const system = host.uid === 0;
104
+ const ctl = system ? ['systemctl'] : ['systemctl', '--user'];
105
+ const file = system
106
+ ? `/etc/systemd/system/${SERVICE}.service`
107
+ : join(host.home, '.config', 'systemd', 'user', `${SERVICE}.service`);
108
+ return {
109
+ kind: 'systemd',
110
+ file,
111
+ content: runAs === undefined ? systemdUnit(host, exec, system ? 'multi-user.target' : 'default.target') : runAsUnit(host, runAs, serveArgs),
112
+ dirs: [],
113
+ install: [...(runAs === undefined ? [] : [{ args: ['bun', runAs.helper] }]), { args: [...ctl, 'daemon-reload'] }, { args: [...ctl, 'enable', '--now', SERVICE] }],
114
+ uninstall: [{ args: [...ctl, 'disable', '--now', SERVICE], mayFail: true }],
115
+ reload: [{ args: [...ctl, 'daemon-reload'], mayFail: true }],
116
+ status: { args: [...ctl, 'is-active', SERVICE], mayFail: true },
117
+ hints: [
118
+ `Logs: journalctl ${system ? '' : '--user '}-u ${SERVICE} -f`,
119
+ ...(system && runAs === undefined ? [ROOT_HINT] : []),
120
+ ...(system
121
+ ? []
122
+ : [`A user service stops with your login session; keep it up without one: loginctl enable-linger ${host.user}`]),
123
+ ],
124
+ };
125
+ }
126
+ function launchdPlan(host, exec) {
127
+ const file = join(host.home, 'Library', 'LaunchAgents', `${LABEL}.plist`);
128
+ const domain = `gui/${String(host.uid)}`;
129
+ const logFile = join(cacheDir(host), 'metro', 'serve', 'service.log');
130
+ return {
131
+ kind: 'launchd',
132
+ file,
133
+ content: launchdPlist(host, exec, logFile),
134
+ dirs: [dirname(logFile)],
135
+ install: [
136
+ { args: ['launchctl', 'bootout', `${domain}/${LABEL}`], mayFail: true },
137
+ { args: ['launchctl', 'bootstrap', domain, file] },
138
+ ],
139
+ uninstall: [{ args: ['launchctl', 'bootout', `${domain}/${LABEL}`], mayFail: true }],
140
+ reload: [],
141
+ status: { args: ['launchctl', 'print', `${domain}/${LABEL}`], mayFail: true },
142
+ hints: [`Logs: tail -f ${logFile}`],
143
+ };
144
+ }
145
+ export function servicePlan(host, serveArgs, runAs) {
146
+ const exec = [host.node, host.cli, 'serve', ...serveArgs];
147
+ if (runAs !== undefined && (host.platform !== 'linux' || host.uid !== 0))
148
+ throw new Error('--user installs a system service for another user: run it as root on Linux');
149
+ if (host.platform === 'linux')
150
+ return systemdPlan(host, serveArgs, runAs);
151
+ if (host.platform === 'darwin')
152
+ return launchdPlan(host, exec);
153
+ throw new Error(`metro service supports Linux (systemd) and macOS (launchd), not ${host.platform}`);
154
+ }
155
+ function splitUser(argv) {
156
+ const rest = [];
157
+ let user = null;
158
+ for (let i = 0; i < argv.length; i += 1) {
159
+ const arg = argv[i] ?? '';
160
+ const inline = USER_FLAG.exec(arg);
161
+ if (inline === null && arg !== '--user') {
162
+ rest.push(arg);
163
+ continue;
164
+ }
165
+ user = inline?.[1] ?? argv[i + 1] ?? '';
166
+ if (inline === null)
167
+ i += 1;
168
+ }
169
+ return { user, rest };
170
+ }
171
+ function runAsOf(name, deps) {
172
+ if (name === null)
173
+ return undefined;
174
+ if (name !== METRO_USER)
175
+ throw new Error(`the root helper and its sudo rules are written for the user ${METRO_USER}; pass --user ${METRO_USER}, not '${name}'`);
176
+ const account = deps.account(name);
177
+ if (account === null)
178
+ throw new Error(`there is no user ${name} on this machine; create it first (useradd --system --create-home --home-dir /var/lib/${name} ${name})`);
179
+ return { ...account, helper: deps.helperEntry() };
180
+ }
181
+ function install(argv, deps) {
182
+ const { user, rest: serveArgs } = splitUser(argv);
183
+ const args = parseServeArgs(serveArgs);
184
+ const runAs = runAsOf(user, deps);
185
+ const plan = servicePlan(deps.host, serveArgs, runAs);
186
+ if (deps.exists(plan.file)) {
187
+ deps.out(`metro is already installed as a ${plan.kind} service (${plan.file}) and restarts on its own; metro service status says whether it is running. To change its arguments: metro service uninstall, then install again`);
188
+ return 0;
189
+ }
190
+ deps.preflight(args, runAs === undefined ? undefined : join(runAs.home, '.metro', 'agents'));
191
+ const pid = deps.running();
192
+ if (pid !== null)
193
+ throw new Error(`a metro serve is running on this machine (pid ${String(pid)}). Stop it first (metro stop), then install: the service takes over from there`);
194
+ for (const dir of plan.dirs)
195
+ deps.mkdir(dir);
196
+ deps.write(plan.file, plan.content);
197
+ for (const command of plan.install)
198
+ deps.run(command);
199
+ deps.out(`Installed ${plan.file}: metro serve now starts at boot and after a crash, and the Server page on metro.box can stop, start and restart it.`);
200
+ for (const hint of plan.hints)
201
+ deps.out(hint);
202
+ return 0;
203
+ }
204
+ function uninstall(deps) {
205
+ const plan = servicePlan(deps.host, []);
206
+ if (!deps.exists(plan.file)) {
207
+ deps.out('metro is not installed as a service on this machine');
208
+ return 1;
209
+ }
210
+ for (const command of plan.uninstall)
211
+ deps.run(command);
212
+ deps.remove(plan.file);
213
+ for (const command of plan.reload)
214
+ deps.run(command);
215
+ deps.out(`Removed ${plan.file}; a running daemon was stopped, and metro serve no longer starts on its own`);
216
+ return 0;
217
+ }
218
+ function status(deps) {
219
+ const plan = servicePlan(deps.host, []);
220
+ if (!deps.exists(plan.file)) {
221
+ deps.out(`not installed (metro service install writes ${plan.file})`);
222
+ return 1;
223
+ }
224
+ const active = deps.run(plan.status).status === 0;
225
+ deps.out(active
226
+ ? `running as a ${plan.kind} service (${plan.file})`
227
+ : `installed as a ${plan.kind} service (${plan.file}), not running`);
228
+ return active ? 0 : 1;
229
+ }
230
+ function realHost() {
231
+ const who = userInfo();
232
+ return {
233
+ platform: platform(),
234
+ uid: who.uid,
235
+ user: who.username,
236
+ home: homedir(),
237
+ node: process.execPath,
238
+ cli: resolve(process.argv[1] ?? ''),
239
+ env: process.env,
240
+ };
241
+ }
242
+ export function serviceStopHint(host = realHost(), exists = existsSync) {
243
+ if (host.platform !== 'linux' && host.platform !== 'darwin')
244
+ return null;
245
+ const plan = servicePlan(host, []);
246
+ if (!exists(plan.file))
247
+ return null;
248
+ const keep = plan.kind === 'systemd'
249
+ ? `systemctl ${host.uid === 0 ? '' : '--user '}stop ${SERVICE}`
250
+ : `launchctl bootout gui/${String(host.uid)}/${LABEL}`;
251
+ return `metro runs as a ${plan.kind} service here (${plan.file}), so it starts again on its own in a moment. To keep it stopped: ${keep}`;
252
+ }
253
+ function lookupAccount(name) {
254
+ const run = spawnSync('getent', ['passwd', name], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
255
+ const home = run.status === 0 ? (run.stdout.trim().split(':')[5] ?? '') : '';
256
+ return home.startsWith('/') ? { name, home } : null;
257
+ }
258
+ function realDeps() {
259
+ return {
260
+ host: realHost(),
261
+ run: (command) => {
262
+ const [bin = '', ...args] = command.args;
263
+ const result = spawnSync(bin, args, { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
264
+ const code = result.status ?? 1;
265
+ if (code !== 0 && command.mayFail !== true) {
266
+ const said = [result.stderr, result.stdout, result.error?.message].find((t) => typeof t === 'string' && t.trim() !== '') ?? '';
267
+ throw new Error(`${command.args.join(' ')} failed (exit ${String(code)}): ${said.trim()}`);
268
+ }
269
+ return { status: code, output: `${result.stdout}${result.stderr}` };
270
+ },
271
+ running: serveLockedBy,
272
+ account: lookupAccount,
273
+ helperEntry: () => join(runtimeDir(), 'node_modules', '@metro-labs', 'daemon', 'src', 'metro-user', 'install.ts'),
274
+ preflight: (args, agents) => {
275
+ requireOwner(args, agents);
276
+ findBun();
277
+ findTailscale();
278
+ },
279
+ mkdir: (dir) => {
280
+ mkdirSync(dir, { recursive: true });
281
+ },
282
+ write: (file, content) => {
283
+ mkdirSync(dirname(file), { recursive: true });
284
+ writeFileSync(file, content, { mode: 0o644 });
285
+ },
286
+ remove: (file) => {
287
+ rmSync(file, { force: true });
288
+ },
289
+ exists: existsSync,
290
+ out: (line) => {
291
+ process.stderr.write(`${line}\n`);
292
+ },
293
+ };
294
+ }
295
+ export function service(argv, deps = realDeps()) {
296
+ const [verb, ...rest] = argv;
297
+ if (verb === 'install')
298
+ return Promise.resolve(install(rest, deps));
299
+ if (verb === 'uninstall' && rest.length === 0)
300
+ return Promise.resolve(uninstall(deps));
301
+ if (verb === 'status' && rest.length === 0)
302
+ return Promise.resolve(status(deps));
303
+ throw new Error(USAGE);
304
+ }