@stage-labs/metro 0.1.0-beta.4 → 0.1.0-beta.41

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 (249) hide show
  1. package/dist/api.js +131 -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 +181 -207
  8. package/dist/control.js +173 -0
  9. package/dist/plugin.js +82 -0
  10. package/dist/prompt.js +61 -0
  11. package/dist/runtime.js +117 -0
  12. package/dist/serve.js +77 -0
  13. package/dist/store.js +93 -0
  14. package/dist/tail.js +120 -0
  15. package/dist/update.js +33 -0
  16. package/dist/version.js +97 -0
  17. package/package.json +34 -21
  18. package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
  19. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +119 -0
  20. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
  21. package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
  22. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
  23. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +210 -0
  24. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
  25. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
  26. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
  27. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
  28. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
  29. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
  30. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
  31. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
  32. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
  33. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
  34. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
  35. package/runtime/node_modules/@metro-labs/mcp/package.json +67 -0
  36. package/runtime/node_modules/@metro-labs/mcp/src/channels/dedupe.ts +24 -0
  37. package/runtime/node_modules/@metro-labs/mcp/src/channels/inbound.ts +367 -0
  38. package/runtime/node_modules/@metro-labs/mcp/src/channels/media-note.ts +85 -0
  39. package/runtime/node_modules/@metro-labs/mcp/src/channels/pending.ts +75 -0
  40. package/runtime/node_modules/@metro-labs/mcp/src/channels/relay.ts +129 -0
  41. package/runtime/node_modules/@metro-labs/mcp/src/channels/webhook-note.ts +51 -0
  42. package/runtime/node_modules/@metro-labs/mcp/src/daemon/account-api.ts +311 -0
  43. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +334 -0
  44. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-connector-api.ts +150 -0
  45. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-import.ts +76 -0
  46. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-pair.ts +20 -0
  47. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-error.ts +8 -0
  48. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-http.ts +153 -0
  49. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-grant.ts +99 -0
  50. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-owner.ts +32 -0
  51. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-serve.ts +133 -0
  52. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-session.ts +278 -0
  53. package/runtime/node_modules/@metro-labs/mcp/src/daemon/boot.ts +383 -0
  54. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair-api.ts +143 -0
  55. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connect-hint.ts +33 -0
  56. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-api.ts +315 -0
  57. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-json.ts +21 -0
  58. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth-routes.ts +157 -0
  59. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth.ts +127 -0
  60. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-tools.ts +100 -0
  61. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-url.ts +57 -0
  62. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-verify.ts +333 -0
  63. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cors.ts +29 -0
  64. package/runtime/node_modules/@metro-labs/mcp/src/daemon/crash-guard.ts +49 -0
  65. package/runtime/node_modules/@metro-labs/mcp/src/daemon/events.ts +117 -0
  66. package/runtime/node_modules/@metro-labs/mcp/src/daemon/hosted-connectors.ts +118 -0
  67. package/runtime/node_modules/@metro-labs/mcp/src/daemon/http.ts +369 -0
  68. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +86 -0
  69. package/runtime/node_modules/@metro-labs/mcp/src/daemon/import-api.ts +67 -0
  70. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-connectors-api.ts +56 -0
  71. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-mode.ts +187 -0
  72. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-secret.ts +21 -0
  73. package/runtime/node_modules/@metro-labs/mcp/src/daemon/log.ts +42 -0
  74. package/runtime/node_modules/@metro-labs/mcp/src/daemon/mode-api.ts +31 -0
  75. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-client.ts +162 -0
  76. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-discovery.ts +120 -0
  77. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-pending.ts +65 -0
  78. package/runtime/node_modules/@metro-labs/mcp/src/daemon/pair-codes.ts +58 -0
  79. package/runtime/node_modules/@metro-labs/mcp/src/daemon/paths.ts +145 -0
  80. package/runtime/node_modules/@metro-labs/mcp/src/daemon/project-api.ts +214 -0
  81. package/runtime/node_modules/@metro-labs/mcp/src/daemon/protocol.ts +224 -0
  82. package/runtime/node_modules/@metro-labs/mcp/src/daemon/relay.ts +303 -0
  83. package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-api.ts +139 -0
  84. package/runtime/node_modules/@metro-labs/mcp/src/daemon/runtime-source.ts +105 -0
  85. package/runtime/node_modules/@metro-labs/mcp/src/daemon/secure-fs.ts +57 -0
  86. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-api.ts +27 -0
  87. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-apis.ts +63 -0
  88. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-config.ts +27 -0
  89. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session.ts +218 -0
  90. package/runtime/node_modules/@metro-labs/mcp/src/daemon/siwe-auth.ts +82 -0
  91. package/runtime/node_modules/@metro-labs/mcp/src/daemon/siwe-nonces.ts +43 -0
  92. package/runtime/node_modules/@metro-labs/mcp/src/daemon/siwe-routes.ts +89 -0
  93. package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor-io.ts +97 -0
  94. package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor.ts +320 -0
  95. package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-call.ts +22 -0
  96. package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-error.ts +53 -0
  97. package/runtime/node_modules/@metro-labs/mcp/src/daemon/tunnel.ts +257 -0
  98. package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-api.ts +295 -0
  99. package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-store.ts +181 -0
  100. package/runtime/node_modules/@metro-labs/mcp/src/db/account-attach.ts +91 -0
  101. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-admin.ts +214 -0
  102. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-connectors.ts +143 -0
  103. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-map.ts +84 -0
  104. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-scope.ts +64 -0
  105. package/runtime/node_modules/@metro-labs/mcp/src/db/client.ts +37 -0
  106. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-config.ts +147 -0
  107. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-relay.ts +126 -0
  108. package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +326 -0
  109. package/runtime/node_modules/@metro-labs/mcp/src/db/file-admin.ts +319 -0
  110. package/runtime/node_modules/@metro-labs/mcp/src/db/file-source.ts +152 -0
  111. package/runtime/node_modules/@metro-labs/mcp/src/db/ids.ts +21 -0
  112. package/runtime/node_modules/@metro-labs/mcp/src/db/key-map.ts +45 -0
  113. package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +373 -0
  114. package/runtime/node_modules/@metro-labs/mcp/src/db/projects.ts +309 -0
  115. package/runtime/node_modules/@metro-labs/mcp/src/db/runtime-admin.ts +24 -0
  116. package/runtime/node_modules/@metro-labs/mcp/src/db/runtimes.ts +105 -0
  117. package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +118 -0
  118. package/runtime/node_modules/@metro-labs/mcp/src/db/token-carry.ts +45 -0
  119. package/runtime/node_modules/@metro-labs/mcp/src/db/users.ts +72 -0
  120. package/runtime/node_modules/@metro-labs/mcp/src/db/whatsapp-login.ts +18 -0
  121. package/runtime/node_modules/@metro-labs/mcp/src/mcp/accounts.ts +141 -0
  122. package/runtime/node_modules/@metro-labs/mcp/src/mcp/call-tools.ts +203 -0
  123. package/runtime/node_modules/@metro-labs/mcp/src/mcp/channel-owner.ts +29 -0
  124. package/runtime/node_modules/@metro-labs/mcp/src/mcp/ctx.ts +62 -0
  125. package/runtime/node_modules/@metro-labs/mcp/src/mcp/event-store.ts +91 -0
  126. package/runtime/node_modules/@metro-labs/mcp/src/mcp/frame-scope.ts +21 -0
  127. package/runtime/node_modules/@metro-labs/mcp/src/mcp/group-tools.ts +129 -0
  128. package/runtime/node_modules/@metro-labs/mcp/src/mcp/index.ts +192 -0
  129. package/runtime/node_modules/@metro-labs/mcp/src/mcp/member-tools.ts +72 -0
  130. package/runtime/node_modules/@metro-labs/mcp/src/mcp/permission-relay.ts +66 -0
  131. package/runtime/node_modules/@metro-labs/mcp/src/mcp/raw-get-stream.ts +207 -0
  132. package/runtime/node_modules/@metro-labs/mcp/src/mcp/request-identity.ts +73 -0
  133. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-registry.ts +172 -0
  134. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-route.ts +43 -0
  135. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session.ts +211 -0
  136. package/runtime/node_modules/@metro-labs/mcp/src/mcp/str.ts +5 -0
  137. package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-dispatch.ts +212 -0
  138. package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-schemas.ts +397 -0
  139. package/runtime/node_modules/@metro-labs/mcp/src/mcp/upload-tool.ts +66 -0
  140. package/runtime/node_modules/@metro-labs/mcp/src/monitor/api.ts +240 -0
  141. package/runtime/node_modules/@metro-labs/mcp/src/server.ts +2 -0
  142. package/runtime/node_modules/@metro-labs/mcp/src/stations/account-store.ts +80 -0
  143. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-inline.ts +100 -0
  144. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-interactive.ts +161 -0
  145. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-resolve.ts +200 -0
  146. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-xmtp.ts +111 -0
  147. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach.ts +182 -0
  148. package/runtime/node_modules/@metro-labs/mcp/src/stations/attachments.ts +196 -0
  149. package/runtime/node_modules/@metro-labs/mcp/src/stations/lines.ts +82 -0
  150. package/runtime/node_modules/@metro-labs/mcp/src/stations/messaging-normalize.ts +88 -0
  151. package/runtime/node_modules/@metro-labs/mcp/src/stations/registry.ts +35 -0
  152. package/runtime/node_modules/@metro-labs/mcp/src/stations/station-runtime.ts +64 -0
  153. package/runtime/node_modules/@metro-labs/mcp/src/stations/types.ts +107 -0
  154. package/runtime/node_modules/@metro-labs/telegram/package.json +28 -0
  155. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +81 -0
  156. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +254 -0
  157. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
  158. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
  159. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +141 -0
  160. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
  161. package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
  162. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
  163. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
  164. package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
  165. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
  166. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
  167. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
  168. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
  169. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
  170. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
  171. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
  172. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
  173. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
  174. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
  175. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
  176. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +172 -0
  177. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +103 -0
  178. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
  179. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
  180. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
  181. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +48 -0
  182. package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
  183. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
  184. package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
  185. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +40 -0
  186. package/runtime/node_modules/@metro-labs/whatsapp/package.json +29 -0
  187. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +67 -0
  188. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
  189. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +178 -0
  190. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
  191. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
  192. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +318 -0
  193. package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
  194. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +132 -0
  195. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
  196. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
  197. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
  198. package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
  199. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
  200. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
  201. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
  202. package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
  203. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +141 -0
  204. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +17 -0
  205. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
  206. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
  207. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
  208. package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
  209. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +113 -0
  210. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
  211. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
  212. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
  213. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
  214. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
  215. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
  216. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
  217. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
  218. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
  219. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +333 -0
  220. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +78 -0
  221. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
  222. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
  223. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
  224. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
  225. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
  226. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
  227. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
  228. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
  229. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
  230. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
  231. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
  232. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
  233. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +39 -0
  234. package/runtime/runtime.json +3 -0
  235. package/runtime/trains/.keep +0 -0
  236. package/LICENSE +0 -21
  237. package/README.md +0 -117
  238. package/dist/agents/claude.js +0 -207
  239. package/dist/agents/codex.js +0 -207
  240. package/dist/agents/types.js +0 -2
  241. package/dist/channels/discord.js +0 -104
  242. package/dist/channels/telegram.js +0 -189
  243. package/dist/helpers/scope-cache.js +0 -65
  244. package/dist/helpers/streaming.js +0 -209
  245. package/dist/helpers/telegram-format.js +0 -39
  246. package/dist/helpers/turn.js +0 -40
  247. package/dist/log.js +0 -10
  248. package/dist/orchestrator.js +0 -208
  249. package/dist/paths.js +0 -68
package/dist/claude.js ADDED
@@ -0,0 +1,15 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ const CHANNEL_FLAGS = ['--dangerously-load-development-channels', 'server:metro'];
3
+ export const claudeArgs = (extra) => [
4
+ ...CHANNEL_FLAGS,
5
+ ...extra,
6
+ ];
7
+ export function launchClaude(extra) {
8
+ const leaveToChild = () => undefined;
9
+ process.on('SIGINT', leaveToChild);
10
+ process.on('SIGTERM', leaveToChild);
11
+ const res = spawnSync('claude', claudeArgs(extra), { stdio: 'inherit' });
12
+ if (res.error !== undefined)
13
+ throw new Error('the `claude` command is not on PATH — install Claude Code first');
14
+ return Promise.resolve(res.status ?? 1);
15
+ }
package/dist/cli.js CHANGED
@@ -1,221 +1,195 @@
1
1
  #!/usr/bin/env node
2
- import { spawn } from 'node:child_process';
3
- import { existsSync, readFileSync } from 'node:fs';
4
- import { join } from 'node:path';
5
- import pkg from '../package.json' with { type: 'json' };
6
- import * as discord from './channels/discord.js';
7
- import * as telegram from './channels/telegram.js';
8
- import { errMsg } from './log.js';
9
- import { CONFIG_ENV_FILE, configuredPlatforms, loadMetroEnv, readDotenv, STATE_DIR, writeDotenv } from './paths.js';
10
- const USAGE = `metro — Telegram + Discord bridge for your Claude Code / Codex agent
2
+ import { hostname } from 'node:os';
3
+ import { claimCode, claimRuntime, mcpServers, NotSignedIn, whoisAuthorized, } from './api.js';
4
+ import { askSecret } from './prompt.js';
5
+ import { clearToken, credentialsPath, metroUrl, metroWebUrl, writeToken, } from './store.js';
6
+ import { detach, lockedBy, probe, runningPid, stopAll, tail } from './control.js';
7
+ import { tailEvents } from './tail.js';
8
+ import { assertAgentId, daemonPlan, localUrl, readRunToken, runDaemon, runtimeDir, runTokenState, writeRunToken, } from './runtime.js';
9
+ import { launchClaude } from './claude.js';
10
+ import { bedrock } from './bedrock.js';
11
+ import { installPlugin, syncPluginServers } from './plugin.js';
12
+ import { update } from './update.js';
13
+ import { serve } from './serve.js';
14
+ import { currentVersion } from './version.js';
15
+ const USAGE = `metro — the command line for your MCP connectors
11
16
 
12
- Usage:
13
- metro Run the orchestrator daemon.
14
- metro setup [telegram|discord <token>] Save token, or show status with no args.
15
- metro setup clear [telegram|discord|all] Remove tokens.
16
- metro doctor Health check.
17
- metro update Upgrade in place.
18
- metro --version | --help
17
+ metro start <agent-id> [--detach]
18
+ run that agent's stations on this machine
19
+ metro serve [--port <n>] [--tunnel]
20
+ run a daemon of your own: its agents and stations live in ~/.metro/agents
21
+ here, and metro.box manages it through the link it prints; --tunnel adds
22
+ a public https address through a Cloudflare quick tunnel (needs
23
+ cloudflared, no account) so the link works from anywhere
24
+ metro stop [agent-id]
25
+ stop every metro daemon on this machine, however it was started
26
+ metro status <agent-id>
27
+ is it running, and is it healthy
28
+ metro logs <agent-id> [-f]
29
+ show the detached daemon's log
30
+ metro tail <agent-id>
31
+ follow this machine's inbound events, one JSON line each
32
+ metro login [code]
33
+ authorize this machine for one agent with a code from its page
34
+ metro logout forget this machine's sign-in
35
+ metro whoami print the account and agent this machine may read
36
+ metro mcp print the mcpServers block of the authorized agent's connectors
37
+ metro plugin set up the Claude Code plugin (connector servers + /metro:login)
38
+ metro claude [args...]
39
+ open Claude Code with the metro channel; every argument is passed through
40
+ metro bedrock [args...]
41
+ the same, with inference on Amazon Bedrock through a local proxy so the
42
+ channel still works (needs AWS_BEARER_TOKEN_BEDROCK and AWS_REGION)
43
+ metro update update to the newest published version
44
+ metro version print this CLI's version
19
45
 
20
- Exit codes: 0 success · 1 usage · 2 config · 3 upstream
46
+ Start Claude Code with all of them, without writing them to disk:
47
+
48
+ claude --mcp-config <(metro mcp)
49
+
50
+ METRO_URL the metro to talk to (default https://mcp.metro.box)
51
+ METRO_UI_URL where the web UI lives (default https://metro.box)
52
+ METRO_TOKEN use this agent sign-in instead of the stored one; with neither,
53
+ a machine running metro start uses its run token
54
+ METRO_RUN_TOKEN use this runtime authorization instead of the stored one
55
+ METRO_BEDROCK_MODEL
56
+ send every request to this Bedrock model id (default: derive from the
57
+ model Claude Code asks for, e.g. eu.anthropic.claude-sonnet-4-6)
58
+ METRO_RUNTIME_DIR run the daemon from this directory instead of the bundled one
21
59
  `;
22
- const exitErr = (msg, code) => Object.assign(new Error(msg), { code });
23
- const isJson = (f) => f.json === true;
24
- const emit = (f, human, structured) => {
25
- process.stdout.write(isJson(f) ? JSON.stringify(structured) + '\n' : human + '\n');
26
- };
27
- const maskToken = (t) => !t ? '' : t.length <= 8 ? '••••' : `${t.slice(0, 6)}…${t.slice(-2)}`;
28
- const TOKEN_KEYS = { telegram: 'TELEGRAM_BOT_TOKEN', discord: 'DISCORD_BOT_TOKEN' };
29
- function parseArgs(argv) {
30
- const positional = [];
31
- const flags = {};
32
- for (let i = 0; i < argv.length; i++) {
33
- const a = argv[i];
34
- if (!a.startsWith('--')) {
35
- positional.push(a);
36
- continue;
37
- }
38
- const eq = a.indexOf('=');
39
- if (eq !== -1) {
40
- flags[a.slice(2, eq)] = a.slice(eq + 1);
41
- continue;
42
- }
43
- const key = a.slice(2);
44
- const next = argv[i + 1];
45
- if (next !== undefined && !next.startsWith('--')) {
46
- flags[key] = next;
47
- i++;
48
- }
49
- else
50
- flags[key] = true;
51
- }
52
- return { positional, flags };
60
+ function hostLabel() {
61
+ const name = hostname().trim();
62
+ return name === '' ? 'unnamed machine' : name;
53
63
  }
54
- async function cmdSetup(positional, flags) {
55
- const [sub, value] = positional;
56
- if (!sub)
57
- return cmdSetupStatus(flags);
58
- if (sub === 'telegram' || sub === 'discord') {
59
- if (!value)
60
- throw new Error(`metro setup ${sub} <token> — token is required`);
61
- const trimmed = value.trim();
62
- let identity;
63
- if (!flags['no-validate']) {
64
- process.env[TOKEN_KEYS[sub]] = trimmed;
65
- try {
66
- identity = sub === 'telegram' ? `@${(await telegram.getMe()).username}` : (await discord.getMe()).username;
67
- }
68
- catch (err) {
69
- delete process.env[TOKEN_KEYS[sub]];
70
- throw exitErr(`token rejected by ${sub}: ${errMsg(err)} (use --no-validate to save anyway)`, 3);
71
- }
72
- }
73
- const env = readDotenv(CONFIG_ENV_FILE);
74
- env[TOKEN_KEYS[sub]] = trimmed;
75
- writeDotenv(CONFIG_ENV_FILE, env);
76
- const human = `saved ${TOKEN_KEYS[sub]}${identity ? ` (verified as ${identity})` : ''} to ${CONFIG_ENV_FILE} (chmod 0600)`;
77
- emit(flags, human, { ok: true, saved: TOKEN_KEYS[sub], verified_as: identity ?? null });
78
- return;
79
- }
80
- if (sub === 'clear') {
81
- const target = value ?? 'all';
82
- const env = readDotenv(CONFIG_ENV_FILE);
83
- if (target === 'all') {
84
- delete env.TELEGRAM_BOT_TOKEN;
85
- delete env.DISCORD_BOT_TOKEN;
86
- }
87
- else if (target === 'telegram' || target === 'discord')
88
- delete env[TOKEN_KEYS[target]];
89
- else
90
- throw new Error(`metro setup clear <telegram|discord|all> — got '${target}'`);
91
- writeDotenv(CONFIG_ENV_FILE, env);
92
- emit(flags, `cleared ${target === 'all' ? 'all metro tokens' : TOKEN_KEYS[target]}`, { ok: true, cleared: target });
93
- return;
94
- }
95
- throw new Error(`unknown setup subcommand '${sub}' (try: telegram, discord, clear)`);
64
+ async function authorizeRuntime(agentId) {
65
+ process.stderr.write(`Authorize this machine at ${metroWebUrl()}/#/authorize/${agentId}\n`);
66
+ const code = (await askSecret('Paste the code (input is hidden): ')).trim();
67
+ if (code === '')
68
+ throw new Error('no code given');
69
+ const claimed = await claimRuntime(code, hostLabel());
70
+ writeRunToken(claimed.agent, claimed.token);
71
+ process.stderr.write(`Authorized as '${claimed.label}'.\n`);
72
+ return claimed.token;
96
73
  }
97
- async function cmdSetupStatus(flags) {
98
- loadMetroEnv();
99
- const tg = process.env.TELEGRAM_BOT_TOKEN ?? '';
100
- const dc = process.env.DISCORD_BOT_TOKEN ?? '';
101
- if (isJson(flags)) {
102
- process.stdout.write(JSON.stringify({
103
- version: pkg.version,
104
- config_env_file: CONFIG_ENV_FILE,
105
- tokens: { telegram: { set: !!tg, masked: maskToken(tg) }, discord: { set: !!dc, masked: maskToken(dc) } },
106
- }) + '\n');
107
- return;
108
- }
109
- const cfgState = existsSync(CONFIG_ENV_FILE) ? '' : ' (not yet written)';
110
- process.stdout.write(`metro ${pkg.version}\n\nconfig: ${CONFIG_ENV_FILE}${cfgState}\n\n` +
111
- ` TELEGRAM_BOT_TOKEN ${tg ? `set (${maskToken(tg)})` : 'not set'}\n` +
112
- ` DISCORD_BOT_TOKEN ${dc ? `set (${maskToken(dc)})` : 'not set'}\n\n`);
113
- process.stdout.write(!tg && !dc
114
- ? 'Get started:\n 1. metro setup telegram <token> # https://t.me/BotFather\n metro setup discord <token> # https://discord.com/developers/applications\n 2. metro doctor\n 3. metro\n'
115
- : 'Run `metro` to start the orchestrator, or `metro doctor` to verify.\n');
74
+ async function usableRunToken(agentId) {
75
+ const stored = readRunToken(agentId);
76
+ if (stored === null)
77
+ return authorizeRuntime(agentId);
78
+ const state = await runTokenState(stored);
79
+ if (state !== 'stale')
80
+ return stored;
81
+ process.stderr.write(`metro no longer recognises this machine as the holder of ${agentId}: ` +
82
+ 'it was taken back, or another machine claimed it since. Authorize it again.\n');
83
+ return authorizeRuntime(agentId);
84
+ }
85
+ async function start(argv) {
86
+ const agentId = assertAgentId(argv[0]);
87
+ if (runningPid(agentId) !== null || lockedBy() !== null)
88
+ throw new Error('a metro daemon is already running on this machine. ' +
89
+ 'Stop it first: metro stop');
90
+ const dir = runtimeDir();
91
+ const token = await usableRunToken(agentId);
92
+ const detached = argv.includes('--detach');
93
+ const plan = daemonPlan({ agentId, token, dir });
94
+ if (!detached)
95
+ return runDaemon(agentId, plan);
96
+ const pid = detach({ agentId, ...plan });
97
+ process.stderr.write(`metro is running for ${agentId} (pid ${String(pid)})\n` +
98
+ ` logs: metro logs ${agentId} -f\n` +
99
+ ` stop: metro stop ${agentId}\n`);
100
+ return 0;
116
101
  }
117
- async function cmdDoctor(flags) {
118
- loadMetroEnv();
119
- const cfg = configuredPlatforms();
120
- const checks = [{
121
- name: 'tokens',
122
- ok: cfg.telegram || cfg.discord,
123
- detail: cfg.telegram || cfg.discord ? `loaded from ${existsSync(CONFIG_ENV_FILE) ? CONFIG_ENV_FILE : 'process env'}` : 'no platform configured — run `metro setup telegram|discord <token>`',
124
- }];
125
- for (const [p, getMe] of [['telegram', telegram.getMe], ['discord', discord.getMe]]) {
126
- if (!cfg[p]) {
127
- checks.push({ name: p, ok: null, detail: 'not configured' });
128
- continue;
129
- }
130
- try {
131
- const me = await getMe();
132
- checks.push({ name: p, ok: true, detail: `getMe → ${p === 'telegram' ? '@' : ''}${me.username}` });
133
- }
134
- catch (err) {
135
- checks.push({ name: p, ok: false, detail: errMsg(err) });
136
- }
102
+ async function stopDaemon(argv) {
103
+ const agentId = argv[0] === undefined ? undefined : assertAgentId(argv[0]);
104
+ const stopped = await stopAll(agentId);
105
+ if (stopped.length === 0) {
106
+ process.stderr.write('no metro daemon is running on this machine\n');
107
+ return 1;
137
108
  }
138
- const lockFile = join(STATE_DIR, '.tail-lock');
139
- if (!existsSync(lockFile))
140
- checks.push({ name: 'orchestrator', ok: null, detail: 'not running' });
141
- else
142
- try {
143
- const pid = Number(readFileSync(lockFile, 'utf8').trim());
144
- if (!Number.isInteger(pid) || pid <= 0)
145
- throw new Error('invalid pid');
146
- process.kill(pid, 0);
147
- checks.push({ name: 'orchestrator', ok: true, detail: `running (pid ${pid})` });
148
- }
149
- catch {
150
- checks.push({ name: 'orchestrator', ok: null, detail: 'stale lockfile (will auto-reclaim on next start)' });
151
- }
152
- if (isJson(flags))
153
- process.stdout.write(JSON.stringify({ checks }) + '\n');
154
- else {
155
- process.stdout.write('metro doctor\n\n');
156
- for (const c of checks)
157
- process.stdout.write(` ${c.ok === true ? '✓' : c.ok === false ? '✗' : '–'} ${c.name.padEnd(15)} ${c.detail}\n`);
158
- process.stdout.write('\n');
109
+ for (const d of stopped)
110
+ process.stderr.write(`Stopped metro (pid ${String(d.pid)}, via ${d.via})\n`);
111
+ return 0;
112
+ }
113
+ async function status(argv) {
114
+ const agentId = assertAgentId(argv[0]);
115
+ const pid = runningPid(agentId);
116
+ if (pid === null) {
117
+ process.stdout.write(`${agentId}: not running\n`);
118
+ return 1;
159
119
  }
160
- if (checks.some(c => c.ok === false))
161
- throw exitErr('one or more checks failed', 3);
120
+ const health = await probe(localUrl());
121
+ process.stdout.write(`${agentId}: running (pid ${String(pid)}) · ` +
122
+ (health === null
123
+ ? `not answering on ${localUrl()}\n`
124
+ : `healthy, up ${String(health.uptime)}s\n`));
125
+ return health === null ? 1 : 0;
126
+ }
127
+ function logs(argv) {
128
+ const agentId = assertAgentId(argv[0]);
129
+ return tail(agentId, argv.includes('-f') || argv.includes('--follow'));
130
+ }
131
+ async function pastedCode(given) {
132
+ if (given !== undefined && given.trim() !== '')
133
+ return given.trim();
134
+ process.stderr.write(`Get a pairing code from the agent's page, or pick the agent at ${metroWebUrl()}/#/authorize\n`);
135
+ return askSecret('Paste the code here (input is hidden): ');
136
+ }
137
+ async function login(given) {
138
+ const { token, email, agent } = await claimCode(await pastedCode(given));
139
+ writeToken(token);
140
+ process.stderr.write(`Authorized '${agent}' for ${email}. Stored in ${credentialsPath()}\n`);
141
+ if (syncPluginServers())
142
+ process.stderr.write('Claude Code plugin refreshed — new sessions have the connectors; ' +
143
+ 'run /reload-plugins in any session already open.\n');
162
144
  }
163
- async function cmdUpdate(flags) {
164
- const tag = pkg.version.includes('-') ? 'beta' : 'latest';
165
- const res = await fetch('https://registry.npmjs.org/@stage-labs/metro', { signal: AbortSignal.timeout(15_000) });
166
- if (!res.ok)
167
- throw new Error(`npm registry: ${res.status}`);
168
- const latest = (await res.json())['dist-tags']?.[tag];
169
- if (!latest)
170
- throw new Error(`no '${tag}' dist-tag for @stage-labs/metro`);
171
- if (latest === pkg.version)
172
- return emit(flags, `already on ${pkg.version} (latest ${tag})`, { ok: true, current: pkg.version, latest, upgraded: false });
173
- const argv1 = process.argv[1] ?? '';
174
- const spec = `@stage-labs/metro@${tag}`;
175
- const argv = argv1.includes('/.bun/') || argv1.includes('\\bun\\') ? ['bun', 'add', '-g', spec]
176
- : argv1.includes('/pnpm/') || argv1.includes('\\pnpm\\') ? ['pnpm', 'add', '-g', spec]
177
- : ['npm', 'install', '-g', spec];
178
- if (isJson(flags))
179
- process.stdout.write(JSON.stringify({ ok: true, current: pkg.version, latest, command: argv.join(' ') }) + '\n');
180
- else
181
- process.stdout.write(`metro ${pkg.version} → ${latest}\n$ ${argv.join(' ')}\n`);
182
- await new Promise((resolve, reject) => {
183
- const child = spawn(argv[0], argv.slice(1), { stdio: isJson(flags) ? 'ignore' : 'inherit' });
184
- child.on('exit', code => code === 0 ? resolve() : reject(new Error(`${argv[0]} exited ${code}`)));
185
- child.on('error', reject);
186
- });
145
+ async function whoami() {
146
+ const { email, agent } = await whoisAuthorized();
147
+ process.stdout.write(`${email} · agent '${agent}' on ${metroUrl()}\n`);
187
148
  }
149
+ const HELP = new Set([undefined, 'help', '--help', '-h']);
188
150
  const COMMANDS = {
189
- setup: cmdSetup,
190
- doctor: (_, f) => cmdDoctor(f),
191
- update: (_, f) => cmdUpdate(f),
151
+ login: async () => {
152
+ await login(process.argv[3]);
153
+ return 0;
154
+ },
155
+ logout: async () => {
156
+ clearToken();
157
+ process.stderr.write('Signed out.\n');
158
+ return Promise.resolve(0);
159
+ },
160
+ whoami: async () => {
161
+ await whoami();
162
+ return 0;
163
+ },
164
+ start: () => start(process.argv.slice(3)),
165
+ serve: () => serve(process.argv.slice(3)),
166
+ stop: () => stopDaemon(process.argv.slice(3)),
167
+ status: () => status(process.argv.slice(3)),
168
+ logs: () => logs(process.argv.slice(3)),
169
+ tail: () => tailEvents(process.argv.slice(3)),
170
+ mcp: async () => {
171
+ process.stdout.write(`${await mcpServers()}\n`);
172
+ return 0;
173
+ },
174
+ plugin: installPlugin,
175
+ claude: () => launchClaude(process.argv.slice(3)),
176
+ bedrock: () => bedrock(process.argv.slice(3)),
177
+ update,
178
+ version: async () => {
179
+ process.stdout.write(`${currentVersion()}\n`);
180
+ return Promise.resolve(0);
181
+ },
192
182
  };
193
- async function main() {
194
- const cmd = process.argv[2];
195
- if (cmd === '--version' || cmd === '-v')
196
- return void process.stdout.write(`${pkg.version}\n`);
197
- if (cmd === '--help' || cmd === '-h')
198
- return void process.stdout.write(USAGE);
199
- if (!cmd) {
200
- await import('./orchestrator.js');
201
- return;
202
- }
203
- const handler = COMMANDS[cmd];
204
- if (!handler) {
205
- process.stderr.write(`unknown command '${cmd}'\n\n${USAGE}`);
206
- process.exit(1);
207
- }
208
- const { positional, flags } = parseArgs(process.argv.slice(3));
209
- try {
210
- await handler(positional, flags);
211
- }
212
- catch (err) {
213
- const code = err.code;
214
- if (isJson(flags))
215
- process.stdout.write(JSON.stringify({ ok: false, error: errMsg(err), code: code ?? 1 }) + '\n');
216
- else
217
- process.stderr.write(`error: ${errMsg(err)}\n`);
218
- process.exit(typeof code === 'number' ? code : 1);
219
- }
183
+ async function run(command) {
184
+ const handler = command === undefined ? undefined : COMMANDS[command];
185
+ if (handler !== undefined)
186
+ return handler();
187
+ process.stderr.write(USAGE);
188
+ return Promise.resolve(HELP.has(command) ? 0 : 1);
220
189
  }
221
- await main();
190
+ const code = await run(process.argv[2]).catch((err) => {
191
+ const message = err instanceof Error ? err.message : String(err);
192
+ process.stderr.write(`metro: ${message}\n`);
193
+ return err instanceof NotSignedIn ? 2 : 1;
194
+ });
195
+ process.exit(code);
@@ -0,0 +1,173 @@
1
+ import { execFileSync, spawn } from 'node:child_process';
2
+ import { existsSync, mkdirSync, openSync, readdirSync, readFileSync, rmSync, writeFileSync, } from 'node:fs';
3
+ import { homedir } from 'node:os';
4
+ import { join } from 'node:path';
5
+ const STATE = () => join(homedir(), '.metro');
6
+ export function serveStateDir() {
7
+ const xdg = process.env.XDG_CACHE_HOME?.trim();
8
+ const base = xdg === undefined || xdg === '' ? join(homedir(), '.cache') : xdg;
9
+ return join(base, 'metro', 'serve');
10
+ }
11
+ const serveLockPath = () => join(serveStateDir(), '.tail-lock');
12
+ const pidPath = (agentId) => join(STATE(), `run-${agentId}.pid`);
13
+ export const logPath = (agentId) => join(STATE(), 'logs', `${agentId}.log`);
14
+ function ensureStateDirs() {
15
+ mkdirSync(STATE(), { recursive: true, mode: 0o700 });
16
+ mkdirSync(join(STATE(), 'logs'), { recursive: true, mode: 0o700 });
17
+ }
18
+ function readPid(agentId) {
19
+ try {
20
+ const pid = Number(readFileSync(pidPath(agentId), 'utf8').trim());
21
+ return Number.isInteger(pid) && pid > 0 ? pid : null;
22
+ }
23
+ catch {
24
+ return null;
25
+ }
26
+ }
27
+ function isAlive(pid) {
28
+ try {
29
+ process.kill(pid, 0);
30
+ return true;
31
+ }
32
+ catch {
33
+ return false;
34
+ }
35
+ }
36
+ export function runningPid(agentId) {
37
+ const pid = readPid(agentId);
38
+ if (pid === null)
39
+ return null;
40
+ if (isAlive(pid))
41
+ return pid;
42
+ rmSync(pidPath(agentId), { force: true });
43
+ return null;
44
+ }
45
+ function writePid(agentId, pid) {
46
+ writeFileSync(pidPath(agentId), String(pid));
47
+ }
48
+ function clearPid(agentId) {
49
+ rmSync(pidPath(agentId), { force: true });
50
+ }
51
+ export function detach(opts) {
52
+ ensureStateDirs();
53
+ const out = openSync(logPath(opts.agentId), 'a');
54
+ const child = spawn(opts.command, opts.args, {
55
+ cwd: opts.cwd,
56
+ env: opts.env,
57
+ detached: true,
58
+ stdio: ['ignore', out, out],
59
+ });
60
+ child.unref();
61
+ const pid = child.pid ?? 0;
62
+ if (pid === 0)
63
+ throw new Error('could not start the metro daemon');
64
+ writePid(opts.agentId, pid);
65
+ return pid;
66
+ }
67
+ async function stopPid(pid, waitMs) {
68
+ process.kill(pid, 'SIGTERM');
69
+ const until = Date.now() + waitMs;
70
+ while (Date.now() < until) {
71
+ if (!isAlive(pid))
72
+ return;
73
+ await new Promise((r) => setTimeout(r, 100));
74
+ }
75
+ process.kill(pid, 'SIGKILL');
76
+ }
77
+ const lockPath = () => process.env.METRO_STATE_DIR?.trim()
78
+ ? join(process.env.METRO_STATE_DIR.trim(), '.tail-lock')
79
+ : join(homedir(), '.cache', 'metro', '.tail-lock');
80
+ function commandOf(pid) {
81
+ try {
82
+ return execFileSync('ps', ['-o', 'command=', '-p', String(pid)], {
83
+ encoding: 'utf8',
84
+ stdio: ['ignore', 'pipe', 'ignore'],
85
+ });
86
+ }
87
+ catch {
88
+ return '';
89
+ }
90
+ }
91
+ const looksLikeMetro = (command) => command.includes('server.ts') || command.includes('metro');
92
+ function holderOf(lockFile) {
93
+ let pid;
94
+ try {
95
+ pid = Number(readFileSync(lockFile, 'utf8').trim());
96
+ }
97
+ catch {
98
+ return null;
99
+ }
100
+ if (!Number.isInteger(pid) || pid <= 0 || !isAlive(pid))
101
+ return null;
102
+ return looksLikeMetro(commandOf(pid)) ? pid : null;
103
+ }
104
+ export const lockedBy = () => holderOf(lockPath());
105
+ export const serveLockedBy = () => holderOf(serveLockPath());
106
+ function trackedAgents(agentId) {
107
+ const tracked = new Set();
108
+ if (agentId !== undefined)
109
+ tracked.add(agentId);
110
+ try {
111
+ for (const name of readdirSync(STATE())) {
112
+ const found = /^run-(.+)\.pid$/.exec(name);
113
+ if (found?.[1] !== undefined)
114
+ tracked.add(found[1]);
115
+ }
116
+ }
117
+ catch {
118
+ return tracked;
119
+ }
120
+ return tracked;
121
+ }
122
+ export async function stopAll(agentId, waitMs = 10_000) {
123
+ const stopped = [];
124
+ const seen = new Set();
125
+ for (const id of trackedAgents(agentId)) {
126
+ const pid = runningPid(id);
127
+ if (pid === null || seen.has(pid))
128
+ continue;
129
+ seen.add(pid);
130
+ await stopPid(pid, waitMs);
131
+ clearPid(id);
132
+ stopped.push({ pid, via: id });
133
+ }
134
+ const holder = lockedBy();
135
+ if (holder !== null && !seen.has(holder)) {
136
+ seen.add(holder);
137
+ await stopPid(holder, waitMs);
138
+ rmSync(lockPath(), { force: true });
139
+ stopped.push({ pid: holder, via: 'the machine lock' });
140
+ }
141
+ const served = serveLockedBy();
142
+ if (served !== null && !seen.has(served)) {
143
+ await stopPid(served, waitMs);
144
+ rmSync(serveLockPath(), { force: true });
145
+ stopped.push({ pid: served, via: 'metro serve' });
146
+ }
147
+ return stopped;
148
+ }
149
+ export function tail(agentId, follow) {
150
+ const path = logPath(agentId);
151
+ if (!existsSync(path))
152
+ throw new Error(`no log for ${agentId} yet — it is only written when started with --detach`);
153
+ const args = follow ? ['-n', '200', '-f', path] : ['-n', '200', path];
154
+ const child = spawn('tail', args, { stdio: ['ignore', 'inherit', 'inherit'] });
155
+ return new Promise((resolve) => {
156
+ child.on('exit', (code) => {
157
+ resolve(code ?? 0);
158
+ });
159
+ });
160
+ }
161
+ export async function probe(url) {
162
+ try {
163
+ const res = await fetch(`${url}/health`, {
164
+ signal: AbortSignal.timeout(3_000),
165
+ });
166
+ if (!res.ok)
167
+ return null;
168
+ return (await res.json());
169
+ }
170
+ catch {
171
+ return null;
172
+ }
173
+ }