@stage-labs/metro 0.1.0-beta.7 → 0.1.0-beta.70

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 (266) hide show
  1. package/dist/api.js +14 -0
  2. package/dist/bedrock-eventstream.js +120 -0
  3. package/dist/bedrock-proxy.js +260 -0
  4. package/dist/bedrock-upstream.js +72 -0
  5. package/dist/bedrock.js +106 -0
  6. package/dist/claude.js +15 -0
  7. package/dist/cli.js +101 -0
  8. package/dist/control.js +74 -0
  9. package/dist/hold.js +197 -0
  10. package/dist/local.js +93 -0
  11. package/dist/node-name.js +51 -0
  12. package/dist/plugin.js +80 -0
  13. package/dist/runtime-install.js +84 -0
  14. package/dist/runtime.js +70 -0
  15. package/dist/serve.js +147 -0
  16. package/dist/service.js +224 -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 -25
  21. package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
  22. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -0
  23. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
  24. package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
  25. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
  26. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +220 -0
  27. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
  28. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
  29. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
  30. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
  31. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
  32. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
  33. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
  34. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
  35. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
  36. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
  37. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
  38. package/runtime/node_modules/@metro-labs/mcp/package.json +64 -0
  39. package/runtime/node_modules/@metro-labs/mcp/src/channels/addressed.ts +25 -0
  40. package/runtime/node_modules/@metro-labs/mcp/src/channels/dedupe.ts +24 -0
  41. package/runtime/node_modules/@metro-labs/mcp/src/channels/inbound.ts +377 -0
  42. package/runtime/node_modules/@metro-labs/mcp/src/channels/media-note.ts +85 -0
  43. package/runtime/node_modules/@metro-labs/mcp/src/channels/pending.ts +75 -0
  44. package/runtime/node_modules/@metro-labs/mcp/src/channels/relay.ts +129 -0
  45. package/runtime/node_modules/@metro-labs/mcp/src/channels/webhook-note.ts +51 -0
  46. package/runtime/node_modules/@metro-labs/mcp/src/daemon/account-api.ts +311 -0
  47. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +302 -0
  48. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-error.ts +8 -0
  49. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-http.ts +95 -0
  50. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-grant.ts +99 -0
  51. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-owner.ts +32 -0
  52. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-serve.ts +128 -0
  53. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-session.ts +280 -0
  54. package/runtime/node_modules/@metro-labs/mcp/src/daemon/boot.ts +204 -0
  55. package/runtime/node_modules/@metro-labs/mcp/src/daemon/bundle-api.ts +111 -0
  56. package/runtime/node_modules/@metro-labs/mcp/src/daemon/claude-api.ts +97 -0
  57. package/runtime/node_modules/@metro-labs/mcp/src/daemon/claude-files.ts +329 -0
  58. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connect-hint.ts +39 -0
  59. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-api.ts +307 -0
  60. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-json.ts +21 -0
  61. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth-routes.ts +157 -0
  62. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth.ts +127 -0
  63. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-tools.ts +98 -0
  64. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-url.ts +70 -0
  65. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-verify.ts +330 -0
  66. package/runtime/node_modules/@metro-labs/mcp/src/daemon/control-api.ts +64 -0
  67. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cors.ts +29 -0
  68. package/runtime/node_modules/@metro-labs/mcp/src/daemon/crash-guard.ts +49 -0
  69. package/runtime/node_modules/@metro-labs/mcp/src/daemon/events.ts +120 -0
  70. package/runtime/node_modules/@metro-labs/mcp/src/daemon/http.ts +384 -0
  71. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity-key.ts +33 -0
  72. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity-registry.ts +13 -0
  73. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity-routes.ts +54 -0
  74. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +7 -0
  75. package/runtime/node_modules/@metro-labs/mcp/src/daemon/is-record.ts +2 -0
  76. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-cli-api.ts +63 -0
  77. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-mode.ts +184 -0
  78. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-owner.ts +14 -0
  79. package/runtime/node_modules/@metro-labs/mcp/src/daemon/log.ts +42 -0
  80. package/runtime/node_modules/@metro-labs/mcp/src/daemon/machine-api.ts +61 -0
  81. package/runtime/node_modules/@metro-labs/mcp/src/daemon/mode-api.ts +32 -0
  82. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-client.ts +159 -0
  83. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-discovery.ts +117 -0
  84. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-pending.ts +65 -0
  85. package/runtime/node_modules/@metro-labs/mcp/src/daemon/paths.ts +109 -0
  86. package/runtime/node_modules/@metro-labs/mcp/src/daemon/protocol.ts +226 -0
  87. package/runtime/node_modules/@metro-labs/mcp/src/daemon/relay.ts +295 -0
  88. package/runtime/node_modules/@metro-labs/mcp/src/daemon/return-to.ts +21 -0
  89. package/runtime/node_modules/@metro-labs/mcp/src/daemon/runtime-deps.ts +82 -0
  90. package/runtime/node_modules/@metro-labs/mcp/src/daemon/secure-fs.ts +57 -0
  91. package/runtime/node_modules/@metro-labs/mcp/src/daemon/server-types.ts +21 -0
  92. package/runtime/node_modules/@metro-labs/mcp/src/daemon/servers-api.ts +80 -0
  93. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-api.ts +29 -0
  94. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-apis.ts +61 -0
  95. package/runtime/node_modules/@metro-labs/mcp/src/daemon/signed-identity.ts +36 -0
  96. package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor-io.ts +97 -0
  97. package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor.ts +320 -0
  98. package/runtime/node_modules/@metro-labs/mcp/src/daemon/terminal-api.ts +71 -0
  99. package/runtime/node_modules/@metro-labs/mcp/src/daemon/terminal-tickets.ts +40 -0
  100. package/runtime/node_modules/@metro-labs/mcp/src/daemon/terminal-ws.ts +114 -0
  101. package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-call.ts +22 -0
  102. package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-error.ts +53 -0
  103. package/runtime/node_modules/@metro-labs/mcp/src/daemon/tunnel.ts +352 -0
  104. package/runtime/node_modules/@metro-labs/mcp/src/daemon/update-api.ts +116 -0
  105. package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-api.ts +294 -0
  106. package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-store.ts +181 -0
  107. package/runtime/node_modules/@metro-labs/mcp/src/daemon/vault-api.ts +78 -0
  108. package/runtime/node_modules/@metro-labs/mcp/src/daemon/vault-types.ts +12 -0
  109. package/runtime/node_modules/@metro-labs/mcp/src/daemon/version.ts +2 -0
  110. package/runtime/node_modules/@metro-labs/mcp/src/db/account-attach.ts +19 -0
  111. package/runtime/node_modules/@metro-labs/mcp/src/db/address.ts +6 -0
  112. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-admin.ts +43 -0
  113. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-map.ts +84 -0
  114. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-scope.ts +64 -0
  115. package/runtime/node_modules/@metro-labs/mcp/src/db/client.ts +32 -0
  116. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-config.ts +144 -0
  117. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-relay.ts +39 -0
  118. package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +69 -0
  119. package/runtime/node_modules/@metro-labs/mcp/src/db/file-admin.ts +329 -0
  120. package/runtime/node_modules/@metro-labs/mcp/src/db/file-source.ts +155 -0
  121. package/runtime/node_modules/@metro-labs/mcp/src/db/ids.ts +21 -0
  122. package/runtime/node_modules/@metro-labs/mcp/src/db/key-map.ts +45 -0
  123. package/runtime/node_modules/@metro-labs/mcp/src/db/local-connectors.ts +277 -0
  124. package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +253 -0
  125. package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +33 -0
  126. package/runtime/node_modules/@metro-labs/mcp/src/db/servers.ts +83 -0
  127. package/runtime/node_modules/@metro-labs/mcp/src/db/stations.ts +12 -0
  128. package/runtime/node_modules/@metro-labs/mcp/src/db/vault.ts +109 -0
  129. package/runtime/node_modules/@metro-labs/mcp/src/mcp/accounts.ts +141 -0
  130. package/runtime/node_modules/@metro-labs/mcp/src/mcp/call-tools.ts +216 -0
  131. package/runtime/node_modules/@metro-labs/mcp/src/mcp/channel-owner.ts +29 -0
  132. package/runtime/node_modules/@metro-labs/mcp/src/mcp/ctx.ts +62 -0
  133. package/runtime/node_modules/@metro-labs/mcp/src/mcp/event-store.ts +91 -0
  134. package/runtime/node_modules/@metro-labs/mcp/src/mcp/frame-scope.ts +21 -0
  135. package/runtime/node_modules/@metro-labs/mcp/src/mcp/group-tools.ts +129 -0
  136. package/runtime/node_modules/@metro-labs/mcp/src/mcp/index.ts +191 -0
  137. package/runtime/node_modules/@metro-labs/mcp/src/mcp/member-tools.ts +72 -0
  138. package/runtime/node_modules/@metro-labs/mcp/src/mcp/permission-relay.ts +66 -0
  139. package/runtime/node_modules/@metro-labs/mcp/src/mcp/raw-get-stream.ts +207 -0
  140. package/runtime/node_modules/@metro-labs/mcp/src/mcp/request-identity.ts +47 -0
  141. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-registry.ts +172 -0
  142. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-route.ts +43 -0
  143. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session.ts +215 -0
  144. package/runtime/node_modules/@metro-labs/mcp/src/mcp/str.ts +5 -0
  145. package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-dispatch.ts +215 -0
  146. package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-schemas.ts +400 -0
  147. package/runtime/node_modules/@metro-labs/mcp/src/mcp/upload-tool.ts +66 -0
  148. package/runtime/node_modules/@metro-labs/mcp/src/monitor/api.ts +237 -0
  149. package/runtime/node_modules/@metro-labs/mcp/src/server.ts +2 -0
  150. package/runtime/node_modules/@metro-labs/mcp/src/stations/account-store.ts +60 -0
  151. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-inline.ts +100 -0
  152. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-interactive.ts +161 -0
  153. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-resolve.ts +200 -0
  154. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-xmtp.ts +111 -0
  155. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach.ts +184 -0
  156. package/runtime/node_modules/@metro-labs/mcp/src/stations/attachments.ts +196 -0
  157. package/runtime/node_modules/@metro-labs/mcp/src/stations/lines.ts +82 -0
  158. package/runtime/node_modules/@metro-labs/mcp/src/stations/messaging-normalize.ts +88 -0
  159. package/runtime/node_modules/@metro-labs/mcp/src/stations/registry.ts +35 -0
  160. package/runtime/node_modules/@metro-labs/mcp/src/stations/station-runtime.ts +64 -0
  161. package/runtime/node_modules/@metro-labs/mcp/src/stations/types.ts +107 -0
  162. package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
  163. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
  164. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +254 -0
  165. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
  166. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
  167. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +156 -0
  168. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
  169. package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
  170. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
  171. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
  172. package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
  173. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
  174. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
  175. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
  176. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
  177. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
  178. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
  179. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
  180. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
  181. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
  182. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
  183. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
  184. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +201 -0
  185. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +109 -0
  186. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
  187. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
  188. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
  189. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +58 -0
  190. package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
  191. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
  192. package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
  193. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
  194. package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
  195. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
  196. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
  197. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +178 -0
  198. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
  199. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
  200. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +327 -0
  201. package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
  202. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +138 -0
  203. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
  204. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
  205. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
  206. package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
  207. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
  208. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
  209. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
  210. package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
  211. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
  212. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +17 -0
  213. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
  214. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
  215. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
  216. package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
  217. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +112 -0
  218. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
  219. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
  220. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
  221. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
  222. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
  223. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
  224. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
  225. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
  226. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
  227. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +335 -0
  228. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +80 -0
  229. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
  230. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
  231. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
  232. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
  233. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
  234. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
  235. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
  236. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
  237. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
  238. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
  239. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
  240. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
  241. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +53 -0
  242. package/runtime/runtime.json +3 -0
  243. package/runtime/stations.json +36 -0
  244. package/runtime/trains/.keep +0 -0
  245. package/LICENSE +0 -21
  246. package/README.md +0 -193
  247. package/dist/cache.js +0 -75
  248. package/dist/cli/actions.js +0 -139
  249. package/dist/cli/config.js +0 -185
  250. package/dist/cli/index.js +0 -159
  251. package/dist/cli/skill.js +0 -62
  252. package/dist/cli/util.js +0 -72
  253. package/dist/codex-rc.js +0 -236
  254. package/dist/dispatcher.js +0 -95
  255. package/dist/history.js +0 -84
  256. package/dist/ipc.js +0 -72
  257. package/dist/log.js +0 -15
  258. package/dist/paths.js +0 -68
  259. package/dist/stations/discord.js +0 -200
  260. package/dist/stations/index.js +0 -73
  261. package/dist/stations/telegram-md.js +0 -34
  262. package/dist/stations/telegram-upload.js +0 -113
  263. package/dist/stations/telegram.js +0 -213
  264. package/docs/agents.md +0 -185
  265. package/docs/uri-scheme.md +0 -71
  266. package/skills/metro/SKILL.md +0 -241
@@ -0,0 +1,74 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ import { readFileSync, rmSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ export function serveStateDir() {
6
+ const xdg = process.env.XDG_CACHE_HOME?.trim();
7
+ const base = xdg === undefined || xdg === '' ? join(homedir(), '.cache') : xdg;
8
+ return join(base, 'metro', 'serve');
9
+ }
10
+ const serveLockPath = () => join(serveStateDir(), '.tail-lock');
11
+ function isAlive(pid) {
12
+ try {
13
+ process.kill(pid, 0);
14
+ return true;
15
+ }
16
+ catch {
17
+ return false;
18
+ }
19
+ }
20
+ async function stopPid(pid, waitMs) {
21
+ process.kill(pid, 'SIGTERM');
22
+ const until = Date.now() + waitMs;
23
+ while (Date.now() < until) {
24
+ if (!isAlive(pid))
25
+ return;
26
+ await new Promise((r) => setTimeout(r, 100));
27
+ }
28
+ process.kill(pid, 'SIGKILL');
29
+ }
30
+ const lockPath = () => process.env.METRO_STATE_DIR?.trim()
31
+ ? join(process.env.METRO_STATE_DIR.trim(), '.tail-lock')
32
+ : join(homedir(), '.cache', 'metro', '.tail-lock');
33
+ function commandOf(pid) {
34
+ try {
35
+ return execFileSync('ps', ['-o', 'command=', '-p', String(pid)], {
36
+ encoding: 'utf8',
37
+ stdio: ['ignore', 'pipe', 'ignore'],
38
+ });
39
+ }
40
+ catch {
41
+ return '';
42
+ }
43
+ }
44
+ const looksLikeMetro = (command) => command.includes('server.ts') || command.includes('metro');
45
+ function holderOf(lockFile) {
46
+ let pid;
47
+ try {
48
+ pid = Number(readFileSync(lockFile, 'utf8').trim());
49
+ }
50
+ catch {
51
+ return null;
52
+ }
53
+ if (!Number.isInteger(pid) || pid <= 0 || !isAlive(pid))
54
+ return null;
55
+ return looksLikeMetro(commandOf(pid)) ? pid : null;
56
+ }
57
+ export const lockedBy = () => holderOf(lockPath());
58
+ export const serveLockedBy = () => holderOf(serveLockPath());
59
+ export async function stopAll(waitMs = 10_000) {
60
+ const stopped = [];
61
+ const holder = lockedBy();
62
+ if (holder !== null) {
63
+ await stopPid(holder, waitMs);
64
+ rmSync(lockPath(), { force: true });
65
+ stopped.push({ pid: holder, via: 'the machine lock' });
66
+ }
67
+ const served = serveLockedBy();
68
+ if (served !== null && served !== holder) {
69
+ await stopPid(served, waitMs);
70
+ rmSync(serveLockPath(), { force: true });
71
+ stopped.push({ pid: served, via: 'metro serve' });
72
+ }
73
+ return stopped;
74
+ }
package/dist/hold.js ADDED
@@ -0,0 +1,197 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { readFileSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { createServer } from 'node:http';
4
+ const FUNNEL_RETRY_MS = 2_000;
5
+ const FUNNEL_EXIT_WAIT_MS = 5_000;
6
+ const LISTEN_ATTEMPTS = 10;
7
+ const LISTEN_RETRY_MS = 500;
8
+ const STOPPED = 503;
9
+ export const STOPPED_MESSAGE = 'metro is stopped on this machine. Start it from the Server page on metro.box, or run metro serve.';
10
+ const wait = (ms) => new Promise((r) => setTimeout(r, ms));
11
+ function corsHeaders(req) {
12
+ return {
13
+ 'access-control-allow-origin': req.headers.origin ?? '*',
14
+ 'access-control-allow-methods': 'GET, POST, PUT, DELETE, OPTIONS',
15
+ 'access-control-allow-headers': 'Authorization, Content-Type',
16
+ 'access-control-allow-private-network': 'true',
17
+ 'access-control-max-age': '86400',
18
+ vary: 'Origin',
19
+ };
20
+ }
21
+ function sendJson(req, res, status, body) {
22
+ res.writeHead(status, {
23
+ 'content-type': 'application/json',
24
+ 'cache-control': 'no-store',
25
+ ...corsHeaders(req),
26
+ });
27
+ res.end(JSON.stringify(body));
28
+ }
29
+ export function holdRequest(req, res, info, onStart) {
30
+ const path = (req.url ?? '').split('?')[0] ?? '';
31
+ if (req.method === 'OPTIONS') {
32
+ res.writeHead(204, corsHeaders(req)).end();
33
+ return;
34
+ }
35
+ if (path === '/api/mode' && req.method === 'GET') {
36
+ sendJson(req, res, 200, {
37
+ mode: 'local',
38
+ owner: info.owner,
39
+ project: 'localdaemon',
40
+ version: info.version,
41
+ stopped: true,
42
+ });
43
+ return;
44
+ }
45
+ if (path === '/api/start') {
46
+ if (req.method !== 'POST') {
47
+ sendJson(req, res, 405, { error: 'method not allowed' });
48
+ return;
49
+ }
50
+ sendJson(req, res, 200, { starting: true });
51
+ onStart();
52
+ return;
53
+ }
54
+ if (path === '/health' || path === '/healthz') {
55
+ sendJson(req, res, STOPPED, { status: 'stopped', version: info.version });
56
+ return;
57
+ }
58
+ sendJson(req, res, STOPPED, { error: STOPPED_MESSAGE, stopped: true });
59
+ }
60
+ export const holdServer = (info, onStart) => createServer((req, res) => {
61
+ holdRequest(req, res, info, onStart);
62
+ });
63
+ const lastLine = (chunk) => String(chunk).trim().split('\n').at(-1) ?? '';
64
+ class HeldFunnel {
65
+ bin;
66
+ port;
67
+ log;
68
+ child = null;
69
+ closed = false;
70
+ timer = null;
71
+ exits = 0;
72
+ constructor(bin, port, log) {
73
+ this.bin = bin;
74
+ this.port = port;
75
+ this.log = log;
76
+ }
77
+ start() {
78
+ if (this.closed)
79
+ return;
80
+ const child = spawn(this.bin, ['funnel', String(this.port)], { stdio: ['ignore', 'pipe', 'pipe'] });
81
+ this.child = child;
82
+ let last = '';
83
+ const read = (chunk) => {
84
+ const line = lastLine(chunk);
85
+ if (line !== '')
86
+ last = line;
87
+ };
88
+ child.stdout?.on('data', read);
89
+ child.stderr?.on('data', read);
90
+ child.on('error', (err) => {
91
+ this.child = null;
92
+ this.closed = true;
93
+ this.log(`funnel could not start (${err.message}); the loopback address is still held`);
94
+ });
95
+ child.on('exit', () => {
96
+ this.child = null;
97
+ if (this.closed)
98
+ return;
99
+ this.exits += 1;
100
+ if (this.exits > 1)
101
+ this.log(`funnel exited (${last}); retrying in ${String(FUNNEL_RETRY_MS / 1000)}s`);
102
+ this.timer = setTimeout(() => {
103
+ this.start();
104
+ }, FUNNEL_RETRY_MS);
105
+ });
106
+ }
107
+ async stop() {
108
+ this.closed = true;
109
+ if (this.timer !== null)
110
+ clearTimeout(this.timer);
111
+ const child = this.child;
112
+ this.child = null;
113
+ if (child === null)
114
+ return;
115
+ if (child.exitCode !== null || child.signalCode !== null)
116
+ return;
117
+ await new Promise((resolve) => {
118
+ const kill = setTimeout(() => {
119
+ child.kill('SIGKILL');
120
+ }, FUNNEL_EXIT_WAIT_MS);
121
+ child.once('exit', () => {
122
+ clearTimeout(kill);
123
+ resolve();
124
+ });
125
+ child.kill('SIGINT');
126
+ });
127
+ }
128
+ }
129
+ async function listen(server, info) {
130
+ for (let attempt = 1;; attempt += 1) {
131
+ try {
132
+ await new Promise((resolve, reject) => {
133
+ server.once('error', reject);
134
+ server.listen(info.port, info.host, () => {
135
+ server.off('error', reject);
136
+ resolve();
137
+ });
138
+ });
139
+ return;
140
+ }
141
+ catch (err) {
142
+ if (attempt >= LISTEN_ATTEMPTS)
143
+ throw err;
144
+ await wait(LISTEN_RETRY_MS);
145
+ }
146
+ }
147
+ }
148
+ const close = (server) => new Promise((resolve) => {
149
+ server.close(() => {
150
+ resolve();
151
+ });
152
+ server.closeAllConnections();
153
+ });
154
+ function releaseLock(lockFile) {
155
+ try {
156
+ if (readFileSync(lockFile, 'utf8').trim() === String(process.pid))
157
+ rmSync(lockFile, { force: true });
158
+ }
159
+ catch {
160
+ return;
161
+ }
162
+ }
163
+ export const holdBanner = (info) => `metro is stopped. Holding http://${info.host}:${String(info.port)}${info.funnel === null ? '' : ' and the Funnel address'} ` +
164
+ 'until Start on the Server page; Ctrl-C or metro stop ends metro serve';
165
+ export async function holdUntilStart(info, deps = {}) {
166
+ const signals = deps.signals ?? process;
167
+ const log = deps.log ??
168
+ ((line) => {
169
+ process.stderr.write(`${line}\n`);
170
+ });
171
+ let finish = () => undefined;
172
+ const ended = new Promise((resolve) => {
173
+ finish = resolve;
174
+ });
175
+ const server = holdServer(info, () => {
176
+ finish('start');
177
+ });
178
+ const onSignal = () => {
179
+ finish('exit');
180
+ };
181
+ if (info.lockFile !== null)
182
+ writeFileSync(info.lockFile, String(process.pid));
183
+ await listen(server, info);
184
+ const funnel = info.funnel === null ? null : new HeldFunnel(info.funnel, info.port, log);
185
+ funnel?.start();
186
+ signals.on('SIGINT', onSignal);
187
+ signals.on('SIGTERM', onSignal);
188
+ log(holdBanner(info));
189
+ const end = await ended;
190
+ signals.off('SIGINT', onSignal);
191
+ signals.off('SIGTERM', onSignal);
192
+ await funnel?.stop();
193
+ await close(server);
194
+ if (info.lockFile !== null)
195
+ releaseLock(info.lockFile);
196
+ return end;
197
+ }
package/dist/local.js ADDED
@@ -0,0 +1,93 @@
1
+ import { existsSync, readdirSync, readFileSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { join } from 'node:path';
4
+ import { localUrl } from './runtime.js';
5
+ const PROBE_MS = 3_000;
6
+ const READ_MS = 20_000;
7
+ export function agentsDir() {
8
+ const explicit = process.env.METRO_AGENTS_DIR?.trim();
9
+ return explicit !== undefined && explicit !== '' ? explicit : join(homedir(), '.metro', 'agents');
10
+ }
11
+ export function localAgents(dir = agentsDir()) {
12
+ if (!existsSync(dir))
13
+ return [];
14
+ const out = [];
15
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
16
+ if (!entry.isDirectory())
17
+ continue;
18
+ const path = join(dir, entry.name, 'agent.json');
19
+ if (!existsSync(path))
20
+ continue;
21
+ try {
22
+ const file = JSON.parse(readFileSync(path, 'utf8'));
23
+ if (typeof file.id === 'string' && typeof file.name === 'string' && typeof file.key === 'string')
24
+ out.push({ id: file.id, name: file.name, key: file.key });
25
+ }
26
+ catch {
27
+ continue;
28
+ }
29
+ }
30
+ return out.sort((a, b) => a.name.localeCompare(b.name));
31
+ }
32
+ export function localStations(dir = agentsDir()) {
33
+ if (!existsSync(dir))
34
+ return [];
35
+ const out = new Set();
36
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
37
+ if (!entry.isDirectory())
38
+ continue;
39
+ const path = join(dir, entry.name, 'agent.json');
40
+ if (!existsSync(path))
41
+ continue;
42
+ try {
43
+ const file = JSON.parse(readFileSync(path, 'utf8'));
44
+ if (!Array.isArray(file.stations))
45
+ continue;
46
+ for (const s of file.stations) {
47
+ const station = s.station;
48
+ if (typeof station === 'string')
49
+ out.add(station);
50
+ }
51
+ }
52
+ catch {
53
+ continue;
54
+ }
55
+ }
56
+ return [...out].sort();
57
+ }
58
+ export function pickLocalAgent(agents, wanted) {
59
+ if (wanted !== undefined && wanted !== '') {
60
+ const found = agents.find((a) => a.name === wanted || a.id === wanted);
61
+ if (found === undefined)
62
+ throw new Error(`no local agent named '${wanted}'`);
63
+ return found;
64
+ }
65
+ const only = agents[0];
66
+ if (only !== undefined && agents.length === 1)
67
+ return only;
68
+ throw new Error(agents.length === 0
69
+ ? 'no agent on this machine yet'
70
+ : `several agents on this machine — name one: ${agents.map((a) => a.name).join(', ')}`);
71
+ }
72
+ export async function localDaemonUp(base = localUrl()) {
73
+ try {
74
+ const res = await fetch(`${base}/api/mode`, { signal: AbortSignal.timeout(PROBE_MS) });
75
+ const body = (await res.json());
76
+ return res.ok && body.mode === 'local';
77
+ }
78
+ catch {
79
+ return false;
80
+ }
81
+ }
82
+ export async function localMcpServers(agent, base = localUrl()) {
83
+ const res = await fetch(`${base}/api/cli/mcp`, {
84
+ headers: { authorization: `Bearer ${agent.key}` },
85
+ signal: AbortSignal.timeout(READ_MS),
86
+ });
87
+ if (!res.ok)
88
+ throw new Error(`the local daemon answered ${String(res.status)} for the connectors`);
89
+ const body = (await res.json());
90
+ if (typeof body.json !== 'string')
91
+ throw new Error('the local daemon returned an unexpected response');
92
+ return body.json;
93
+ }
@@ -0,0 +1,51 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { randomBytes } from 'node:crypto';
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
4
+ import { join } from 'node:path';
5
+ import { agentsDir } from './local.js';
6
+ const NODE_FILE = '.node';
7
+ const ALPHABET = 'abcdefghjkmnpqrstuvwxyz23456789';
8
+ const NAME_RE = /^metro-[a-z0-9]{6}$/;
9
+ export function newNodeName() {
10
+ const bytes = randomBytes(6);
11
+ return `metro-${[...bytes].map((b) => ALPHABET[b % ALPHABET.length] ?? 'x').join('')}`;
12
+ }
13
+ export function nodeName(dir = agentsDir()) {
14
+ const path = join(dir, NODE_FILE);
15
+ if (existsSync(path)) {
16
+ const held = readFileSync(path, 'utf8').trim();
17
+ if (NAME_RE.test(held))
18
+ return held;
19
+ }
20
+ const name = newNodeName();
21
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
22
+ writeFileSync(path, `${name}\n`, { mode: 0o600 });
23
+ return name;
24
+ }
25
+ export function currentNodeLabel(bin) {
26
+ const run = spawnSync(bin, ['status', '--json'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });
27
+ if (run.error !== undefined || run.status !== 0)
28
+ return null;
29
+ try {
30
+ const parsed = JSON.parse(run.stdout);
31
+ const dns = typeof parsed.Self?.DNSName === 'string' ? parsed.Self.DNSName : '';
32
+ const label = dns.split('.')[0] ?? '';
33
+ return label === '' ? null : label.toLowerCase();
34
+ }
35
+ catch {
36
+ return null;
37
+ }
38
+ }
39
+ export function ensureNodeName(bin, dir = agentsDir()) {
40
+ const wanted = nodeName(dir);
41
+ if (currentNodeLabel(bin) === wanted)
42
+ return wanted;
43
+ const run = spawnSync(bin, ['set', '--hostname', wanted], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] });
44
+ if (run.error !== undefined || run.status !== 0) {
45
+ const said = run.stderr.trim();
46
+ const reason = said === '' ? (run.error?.message ?? `exit ${String(run.status)}`) : said;
47
+ throw new Error(`could not name this machine ${wanted} on your tailnet (${reason}).\n` +
48
+ `Run it once yourself, then start again: sudo tailscale set --hostname ${wanted}`);
49
+ }
50
+ return wanted;
51
+ }
package/dist/plugin.js ADDED
@@ -0,0 +1,80 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { existsSync, readFileSync } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ const MARKETPLACE_REPO = 'bonustrack/metro';
6
+ const PLUGIN_SPEC = 'metro@metro';
7
+ export function installPathFrom(raw) {
8
+ let parsed;
9
+ try {
10
+ parsed = JSON.parse(raw);
11
+ }
12
+ catch {
13
+ return null;
14
+ }
15
+ if (typeof parsed !== 'object' || parsed === null)
16
+ return null;
17
+ const plugins = parsed.plugins;
18
+ if (typeof plugins !== 'object' || plugins === null)
19
+ return null;
20
+ const entries = plugins[PLUGIN_SPEC];
21
+ if (!Array.isArray(entries))
22
+ return null;
23
+ const first = entries[0];
24
+ return typeof first?.installPath === 'string' ? first.installPath : null;
25
+ }
26
+ export function pluginInstallPath() {
27
+ const registry = join(homedir(), '.claude', 'plugins', 'installed_plugins.json');
28
+ let raw;
29
+ try {
30
+ raw = readFileSync(registry, 'utf8');
31
+ }
32
+ catch {
33
+ return null;
34
+ }
35
+ const path = installPathFrom(raw);
36
+ if (path === null)
37
+ return null;
38
+ return existsSync(join(path, 'bin', 'metro-plugin.mjs')) ? path : null;
39
+ }
40
+ function runClaude(args) {
41
+ const res = spawnSync('claude', args, { encoding: 'utf8' });
42
+ if (res.error !== undefined)
43
+ throw new Error('the `claude` command is not on PATH — install Claude Code first');
44
+ const output = `${res.stdout ?? ''}${res.stderr ?? ''}`;
45
+ return { ok: res.status === 0, output };
46
+ }
47
+ const tolerable = (output) => /already|exists|latest version/i.test(output);
48
+ export function syncPluginServers() {
49
+ const path = pluginInstallPath();
50
+ if (path === null)
51
+ return false;
52
+ const script = join(path, 'bin', 'metro-plugin.mjs');
53
+ const refreshed = spawnSync(process.execPath, [script, 'refresh'], {
54
+ stdio: ['ignore', 'inherit', 'inherit'],
55
+ });
56
+ if (refreshed.status !== 0)
57
+ return false;
58
+ runClaude(['plugin', 'update', PLUGIN_SPEC]);
59
+ return true;
60
+ }
61
+ export async function installPlugin() {
62
+ const added = runClaude(['plugin', 'marketplace', 'add', MARKETPLACE_REPO]);
63
+ if (!added.ok && !tolerable(added.output)) {
64
+ process.stderr.write(added.output);
65
+ return 1;
66
+ }
67
+ const installed = runClaude(['plugin', 'install', PLUGIN_SPEC]);
68
+ if (!installed.ok && !tolerable(installed.output)) {
69
+ process.stderr.write(installed.output);
70
+ return 1;
71
+ }
72
+ process.stderr.write('Claude Code plugin installed.\n');
73
+ if (syncPluginServers()) {
74
+ process.stderr.write('Connector servers loaded — new Claude sessions have them; ' +
75
+ 'run /reload-plugins in any session already open.\n');
76
+ return 0;
77
+ }
78
+ process.stderr.write('Start the daemon on this machine (metro serve), then run /metro:refresh inside Claude Code.\n');
79
+ return Promise.resolve(0);
80
+ }
@@ -0,0 +1,84 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ import { 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 { SERVER_ENTRY, findBun, runtimeDir } from './runtime.js';
7
+ export const MANIFEST_FILE = 'stations.json';
8
+ const METRO_SOURCES = join('node_modules', '@metro-labs');
9
+ const INSTALL_TIMEOUT_MS = 15 * 60_000;
10
+ export function runtimeStore() {
11
+ const env = process.env.METRO_RUNTIME_STORE?.trim() ?? '';
12
+ return env === '' ? join(homedir(), '.metro', 'runtime') : env;
13
+ }
14
+ const isRecord = (v) => typeof v === 'object' && v !== null;
15
+ const ranges = (raw) => isRecord(raw)
16
+ ? Object.fromEntries(Object.entries(raw).filter((e) => typeof e[1] === 'string'))
17
+ : {};
18
+ export function readManifest(path) {
19
+ const raw = JSON.parse(readFileSync(path, 'utf8'));
20
+ if (!isRecord(raw))
21
+ throw new Error(`${path} is not a runtime manifest`);
22
+ const stations = isRecord(raw.stations) ? raw.stations : {};
23
+ return {
24
+ core: ranges(raw.core),
25
+ stations: Object.fromEntries(Object.entries(stations).map(([name, deps]) => [name, ranges(deps)])),
26
+ };
27
+ }
28
+ export function dependenciesFor(manifest, stations) {
29
+ const out = { ...manifest.core };
30
+ for (const station of new Set(stations))
31
+ Object.assign(out, manifest.stations[station] ?? {});
32
+ return Object.fromEntries(Object.entries(out).sort(([a], [b]) => a.localeCompare(b)));
33
+ }
34
+ const packageText = (deps) => `${JSON.stringify({ name: 'metro-runtime', private: true, dependencies: deps }, null, 2)}\n`;
35
+ function readOrNull(path) {
36
+ try {
37
+ return readFileSync(path, 'utf8');
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ }
43
+ function syncSources(sources, store) {
44
+ const stamp = readOrNull(join(sources, 'runtime.json'));
45
+ if (stamp !== null && readOrNull(join(store, 'runtime.json')) === stamp)
46
+ return false;
47
+ rmSync(join(store, METRO_SOURCES), { recursive: true, force: true });
48
+ cpSync(join(sources, METRO_SOURCES), join(store, METRO_SOURCES), { recursive: true });
49
+ if (stamp !== null)
50
+ writeFileSync(join(store, 'runtime.json'), stamp);
51
+ return true;
52
+ }
53
+ export function installDependencies(store, deps, bun, log) {
54
+ const wanted = packageText(deps);
55
+ const marker = join(store, 'node_modules', '.metro-installed');
56
+ if (existsSync(marker) && readOrNull(join(store, 'package.json')) === wanted)
57
+ return false;
58
+ writeFileSync(join(store, 'package.json'), wanted);
59
+ log(`Installing the channel SDKs this machine needs into ${store}`);
60
+ const run = spawnSync(bun, ['install', '--no-summary', '--no-progress'], {
61
+ cwd: store,
62
+ stdio: ['ignore', 'inherit', 'inherit'],
63
+ timeout: INSTALL_TIMEOUT_MS,
64
+ });
65
+ if (run.error !== undefined || run.status !== 0)
66
+ throw new Error(`bun install failed in ${store}: ${run.error?.message ?? `exit ${String(run.status)}`}`);
67
+ writeFileSync(marker, `${new Date().toISOString()}\n`);
68
+ return true;
69
+ }
70
+ export function prepareRuntime(opts = {}) {
71
+ const sources = opts.sources ?? runtimeDir();
72
+ const manifestPath = join(sources, MANIFEST_FILE);
73
+ if (!existsSync(manifestPath))
74
+ return { dir: sources, entry: join(sources, SERVER_ENTRY), trains: join(sources, 'trains'), manifest: null };
75
+ const store = opts.store ?? runtimeStore();
76
+ const log = opts.log ??
77
+ ((line) => {
78
+ process.stderr.write(`${line}\n`);
79
+ });
80
+ mkdirSync(join(store, 'trains'), { recursive: true });
81
+ syncSources(sources, store);
82
+ installDependencies(store, dependenciesFor(readManifest(manifestPath), localStations(opts.agents)), opts.bun ?? findBun(), log);
83
+ return { dir: store, entry: join(store, SERVER_ENTRY), trains: join(store, 'trains'), manifest: manifestPath };
84
+ }
@@ -0,0 +1,70 @@
1
+ import { spawn, spawnSync } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { dirname, join } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ const AGENT_RE = /^[A-Za-z0-9][A-Za-z0-9_-]{10}$/;
6
+ export const SERVER_ENTRY = join('node_modules', '@metro-labs', 'mcp', 'src', 'server.ts');
7
+ export class MissingRuntime extends Error {
8
+ }
9
+ export function runtimeDir() {
10
+ const explicit = process.env.METRO_RUNTIME_DIR?.trim();
11
+ const dir = explicit !== undefined && explicit !== ''
12
+ ? explicit
13
+ : join(dirname(dirname(fileURLToPath(import.meta.url))), 'runtime');
14
+ if (!existsSync(join(dir, SERVER_ENTRY)))
15
+ throw new MissingRuntime(`no metro daemon at ${dir}. Reinstall with: npm i -g @stage-labs/metro@beta`);
16
+ return dir;
17
+ }
18
+ class MissingBun extends Error {
19
+ }
20
+ export function assertAgentId(id) {
21
+ if (id === undefined || !AGENT_RE.test(id))
22
+ throw new Error(`'${id ?? ''}' is not an agent id — copy it from the agent's page in the web UI`);
23
+ return id;
24
+ }
25
+ export function findBun() {
26
+ const found = spawnSync('bun', ['--version'], { stdio: 'ignore' });
27
+ if (found.error !== undefined || found.status !== 0)
28
+ throw new MissingBun('metro serve needs Bun, which is not on PATH.\n' +
29
+ 'Install it with: curl -fsSL https://bun.sh/install | bash');
30
+ return 'bun';
31
+ }
32
+ export const localPort = () => Number(process.env.METRO_WEBHOOK_PORT) || 8420;
33
+ export const localUrl = () => `http://127.0.0.1:${String(localPort())}`;
34
+ export const RESTART_CODE = 75;
35
+ export const HOLD_CODE = 76;
36
+ function spawnOnce(plan) {
37
+ const child = spawn(plan.command, plan.args, {
38
+ cwd: plan.cwd,
39
+ env: plan.env,
40
+ stdio: ['ignore', 'inherit', 'inherit'],
41
+ });
42
+ const relay = (sig) => () => {
43
+ child.kill(sig);
44
+ };
45
+ const onInt = relay('SIGINT');
46
+ const onTerm = relay('SIGTERM');
47
+ process.on('SIGINT', onInt);
48
+ process.on('SIGTERM', onTerm);
49
+ return new Promise((resolve) => {
50
+ child.on('exit', (code) => {
51
+ process.off('SIGINT', onInt);
52
+ process.off('SIGTERM', onTerm);
53
+ resolve(code ?? 0);
54
+ });
55
+ });
56
+ }
57
+ export async function spawnPlan(plan, hold) {
58
+ for (;;) {
59
+ const code = await spawnOnce(plan());
60
+ if (code === RESTART_CODE) {
61
+ process.stderr.write('metro was updated; restarting the daemon on the new version\n');
62
+ continue;
63
+ }
64
+ if (code !== HOLD_CODE || hold === undefined)
65
+ return code;
66
+ if ((await hold()) === 'exit')
67
+ return 0;
68
+ process.stderr.write('starting the daemon again\n');
69
+ }
70
+ }