@stage-labs/metro 0.1.0-beta.8 → 0.1.0-beta.80

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 (282) hide show
  1. package/dist/api.js +14 -0
  2. package/dist/claude-settings.js +40 -0
  3. package/dist/claude.js +107 -0
  4. package/dist/cli.js +101 -0
  5. package/dist/control.js +74 -0
  6. package/dist/hold.js +211 -0
  7. package/dist/local.js +93 -0
  8. package/dist/node-name.js +71 -0
  9. package/dist/plugin.js +80 -0
  10. package/dist/provider-flags.js +9 -0
  11. package/dist/runtime-install.js +87 -0
  12. package/dist/runtime.js +74 -0
  13. package/dist/serve.js +146 -0
  14. package/dist/service.js +242 -0
  15. package/dist/tail.js +119 -0
  16. package/dist/update.js +45 -0
  17. package/dist/version.js +97 -0
  18. package/package.json +14 -25
  19. package/runtime/node_modules/@metro-labs/core/package.json +43 -0
  20. package/runtime/node_modules/@metro-labs/core/src/address.ts +6 -0
  21. package/runtime/node_modules/@metro-labs/core/src/endpoints.ts +7 -0
  22. package/runtime/node_modules/@metro-labs/core/src/events.ts +120 -0
  23. package/runtime/node_modules/@metro-labs/core/src/identity.ts +7 -0
  24. package/runtime/node_modules/@metro-labs/core/src/ids.ts +21 -0
  25. package/runtime/node_modules/@metro-labs/core/src/is-record.ts +2 -0
  26. package/runtime/node_modules/@metro-labs/core/src/lines.ts +82 -0
  27. package/runtime/node_modules/@metro-labs/core/src/log.ts +42 -0
  28. package/runtime/node_modules/@metro-labs/core/src/protocol.ts +226 -0
  29. package/runtime/node_modules/@metro-labs/core/src/secure-fs.ts +57 -0
  30. package/runtime/node_modules/@metro-labs/core/src/station-names.ts +12 -0
  31. package/runtime/node_modules/@metro-labs/core/src/stations/account-store.ts +60 -0
  32. package/runtime/node_modules/@metro-labs/core/src/stations/attachments.ts +196 -0
  33. package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +88 -0
  34. package/runtime/node_modules/@metro-labs/core/src/stations/station-runtime.ts +64 -0
  35. package/runtime/node_modules/@metro-labs/core/src/stations/types.ts +107 -0
  36. package/runtime/node_modules/@metro-labs/core/src/str.ts +5 -0
  37. package/runtime/node_modules/@metro-labs/core/src/train-error.ts +53 -0
  38. package/runtime/node_modules/@metro-labs/core/src/version.ts +2 -0
  39. package/runtime/node_modules/@metro-labs/daemon/package.json +47 -0
  40. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-attach.ts +19 -0
  41. package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +311 -0
  42. package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +43 -0
  43. package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +302 -0
  44. package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +111 -0
  45. package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +329 -0
  46. package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +155 -0
  47. package/runtime/node_modules/@metro-labs/daemon/src/agents/keys.ts +45 -0
  48. package/runtime/node_modules/@metro-labs/daemon/src/agents/map.ts +84 -0
  49. package/runtime/node_modules/@metro-labs/daemon/src/agents/scope.ts +64 -0
  50. package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +176 -0
  51. package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +39 -0
  52. package/runtime/node_modules/@metro-labs/daemon/src/boot/crash-guard.ts +49 -0
  53. package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +14 -0
  54. package/runtime/node_modules/@metro-labs/daemon/src/boot/paths.ts +106 -0
  55. package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +25 -0
  56. package/runtime/node_modules/@metro-labs/daemon/src/channels/dedupe.ts +24 -0
  57. package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +377 -0
  58. package/runtime/node_modules/@metro-labs/daemon/src/channels/media-note.ts +85 -0
  59. package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +75 -0
  60. package/runtime/node_modules/@metro-labs/daemon/src/channels/relay.ts +129 -0
  61. package/runtime/node_modules/@metro-labs/daemon/src/channels/webhook-note.ts +51 -0
  62. package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +148 -0
  63. package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +329 -0
  64. package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +167 -0
  65. package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +117 -0
  66. package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +307 -0
  67. package/runtime/node_modules/@metro-labs/daemon/src/connectors/cli-api.ts +63 -0
  68. package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +144 -0
  69. package/runtime/node_modules/@metro-labs/daemon/src/connectors/json.ts +21 -0
  70. package/runtime/node_modules/@metro-labs/daemon/src/connectors/model.ts +69 -0
  71. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-client.ts +159 -0
  72. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-discovery.ts +117 -0
  73. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-pending.ts +65 -0
  74. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-routes.ts +157 -0
  75. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth.ts +127 -0
  76. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-target.ts +39 -0
  77. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +295 -0
  78. package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +277 -0
  79. package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +98 -0
  80. package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +70 -0
  81. package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +330 -0
  82. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +99 -0
  83. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-owner.ts +32 -0
  84. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +121 -0
  85. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +294 -0
  86. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-store.ts +181 -0
  87. package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +252 -0
  88. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +231 -0
  89. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
  90. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +288 -0
  91. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +203 -0
  92. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +220 -0
  93. package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +123 -0
  94. package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +182 -0
  95. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +197 -0
  96. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +195 -0
  97. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +198 -0
  98. package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +40 -0
  99. package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +17 -0
  100. package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +137 -0
  101. package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +216 -0
  102. package/runtime/node_modules/@metro-labs/daemon/src/mcp/channel-owner.ts +29 -0
  103. package/runtime/node_modules/@metro-labs/daemon/src/mcp/ctx.ts +62 -0
  104. package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +91 -0
  105. package/runtime/node_modules/@metro-labs/daemon/src/mcp/frame-scope.ts +21 -0
  106. package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +129 -0
  107. package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +191 -0
  108. package/runtime/node_modules/@metro-labs/daemon/src/mcp/member-tools.ts +72 -0
  109. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +66 -0
  110. package/runtime/node_modules/@metro-labs/daemon/src/mcp/raw-get-stream.ts +207 -0
  111. package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +47 -0
  112. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-registry.ts +172 -0
  113. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-route.ts +43 -0
  114. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +215 -0
  115. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-dispatch.ts +215 -0
  116. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-schemas.ts +400 -0
  117. package/runtime/node_modules/@metro-labs/daemon/src/mcp/upload-tool.ts +66 -0
  118. package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +237 -0
  119. package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +389 -0
  120. package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +390 -0
  121. package/runtime/node_modules/@metro-labs/daemon/src/routes/identity.ts +54 -0
  122. package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +187 -0
  123. package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +60 -0
  124. package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
  125. package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +64 -0
  126. package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +61 -0
  127. package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +116 -0
  128. package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
  129. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-inline.ts +100 -0
  130. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-interactive.ts +161 -0
  131. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +202 -0
  132. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-session.ts +280 -0
  133. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-xmtp.ts +111 -0
  134. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +184 -0
  135. package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +248 -0
  136. package/runtime/node_modules/@metro-labs/daemon/src/stations/registry.ts +35 -0
  137. package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
  138. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor-io.ts +97 -0
  139. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +320 -0
  140. package/runtime/node_modules/@metro-labs/daemon/src/stations/train-call.ts +22 -0
  141. package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +71 -0
  142. package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +123 -0
  143. package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +40 -0
  144. package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
  145. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -0
  146. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
  147. package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
  148. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
  149. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +220 -0
  150. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
  151. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
  152. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
  153. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
  154. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
  155. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
  156. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
  157. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
  158. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
  159. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
  160. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
  161. package/runtime/node_modules/@metro-labs/http/package.json +33 -0
  162. package/runtime/node_modules/@metro-labs/http/src/api-error.ts +8 -0
  163. package/runtime/node_modules/@metro-labs/http/src/api-http.ts +95 -0
  164. package/runtime/node_modules/@metro-labs/http/src/cors.ts +29 -0
  165. package/runtime/node_modules/@metro-labs/http/src/identity-key.ts +33 -0
  166. package/runtime/node_modules/@metro-labs/http/src/identity-registry.ts +13 -0
  167. package/runtime/node_modules/@metro-labs/http/src/mode-api.ts +32 -0
  168. package/runtime/node_modules/@metro-labs/http/src/return-to.ts +21 -0
  169. package/runtime/node_modules/@metro-labs/http/src/signed-identity.ts +36 -0
  170. package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
  171. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
  172. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +254 -0
  173. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
  174. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
  175. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +156 -0
  176. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
  177. package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
  178. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
  179. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
  180. package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
  181. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
  182. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
  183. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
  184. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
  185. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
  186. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
  187. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
  188. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
  189. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
  190. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
  191. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
  192. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +201 -0
  193. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +109 -0
  194. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
  195. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
  196. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
  197. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +58 -0
  198. package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
  199. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
  200. package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
  201. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
  202. package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
  203. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
  204. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
  205. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +178 -0
  206. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
  207. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
  208. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +327 -0
  209. package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
  210. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +138 -0
  211. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
  212. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
  213. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
  214. package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
  215. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
  216. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
  217. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
  218. package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
  219. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
  220. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +17 -0
  221. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
  222. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
  223. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
  224. package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
  225. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +112 -0
  226. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
  227. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
  228. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
  229. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
  230. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
  231. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
  232. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
  233. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
  234. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
  235. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +335 -0
  236. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +80 -0
  237. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
  238. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
  239. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
  240. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
  241. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
  242. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
  243. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
  244. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
  245. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
  246. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
  247. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
  248. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
  249. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +53 -0
  250. package/runtime/runtime.json +3 -0
  251. package/runtime/server.ts +1 -0
  252. package/runtime/stations.json +36 -0
  253. package/runtime/trains/.keep +0 -0
  254. package/LICENSE +0 -21
  255. package/README.md +0 -249
  256. package/dist/cache.js +0 -75
  257. package/dist/cli/actions.js +0 -139
  258. package/dist/cli/config.js +0 -185
  259. package/dist/cli/index.js +0 -177
  260. package/dist/cli/skill.js +0 -62
  261. package/dist/cli/util.js +0 -72
  262. package/dist/cli/webhook.js +0 -83
  263. package/dist/codex-rc.js +0 -245
  264. package/dist/dispatcher.js +0 -122
  265. package/dist/history.js +0 -99
  266. package/dist/ipc.js +0 -72
  267. package/dist/log.js +0 -15
  268. package/dist/paths.js +0 -68
  269. package/dist/registry.js +0 -48
  270. package/dist/stations/claude.js +0 -45
  271. package/dist/stations/codex.js +0 -68
  272. package/dist/stations/discord.js +0 -200
  273. package/dist/stations/index.js +0 -133
  274. package/dist/stations/telegram-md.js +0 -34
  275. package/dist/stations/telegram-upload.js +0 -113
  276. package/dist/stations/telegram.js +0 -213
  277. package/dist/stations/webhook.js +0 -100
  278. package/dist/tunnel.js +0 -49
  279. package/dist/webhooks.js +0 -40
  280. package/docs/agents.md +0 -216
  281. package/docs/uri-scheme.md +0 -125
  282. package/skills/metro/SKILL.md +0 -249
@@ -0,0 +1,117 @@
1
+ import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { ApiError } from '@metro-labs/http/api-error';
4
+ import { claudeDir, listClaudeProjects } from './files.js';
5
+ import { isRecord } from '@metro-labs/core/is-record';
6
+ import { errMsg } from '@metro-labs/core/log';
7
+
8
+ export const SETTINGS_MAX = 256 * 1024;
9
+ const USER_ID = 'user';
10
+ const LOCAL_SUFFIX = '.local';
11
+ const DEFAULT_MODE = 0o644;
12
+
13
+ export type SettingsScope = 'user' | 'project' | 'local';
14
+
15
+ export interface ClaudeSettingsFile {
16
+ id: string;
17
+ scope: SettingsScope;
18
+ label: string;
19
+ path: string;
20
+ exists: boolean;
21
+ editable: boolean;
22
+ text: string;
23
+ modifiedAt: string | null;
24
+ }
25
+
26
+ const missing = (id: string, scope: SettingsScope, label: string, path: string): ClaudeSettingsFile => ({
27
+ id,
28
+ scope,
29
+ label,
30
+ path,
31
+ exists: false,
32
+ editable: true,
33
+ text: '',
34
+ modifiedAt: null,
35
+ });
36
+
37
+ function entryOf(id: string, scope: SettingsScope, label: string, path: string): ClaudeSettingsFile {
38
+ if (!existsSync(path)) return missing(id, scope, label, path);
39
+ const stat = statSync(path);
40
+ const editable = stat.size <= SETTINGS_MAX;
41
+ return {
42
+ id,
43
+ scope,
44
+ label,
45
+ path,
46
+ exists: true,
47
+ editable,
48
+ text: editable ? readFileSync(path, 'utf8') : '',
49
+ modifiedAt: stat.mtime.toISOString(),
50
+ };
51
+ }
52
+
53
+ const PROJECT_FILES: [string, SettingsScope, string][] = [
54
+ ['', 'project', 'settings.json'],
55
+ [LOCAL_SUFFIX, 'local', 'settings.local.json'],
56
+ ];
57
+
58
+ function projectEntries(dir: string): ClaudeSettingsFile[] {
59
+ const out: ClaudeSettingsFile[] = [];
60
+ for (const project of listClaudeProjects(dir)) {
61
+ const cwd = project.cwd;
62
+ if (cwd === null) continue;
63
+ for (const [suffix, scope, name] of PROJECT_FILES) {
64
+ const path = join(cwd, '.claude', name);
65
+ if (existsSync(path)) out.push(entryOf(`${project.id}${suffix}`, scope, cwd, path));
66
+ }
67
+ }
68
+ return out;
69
+ }
70
+
71
+ export function listClaudeSettings(dir = claudeDir()): ClaudeSettingsFile[] {
72
+ return [entryOf(USER_ID, 'user', 'This machine', join(dir, 'settings.json')), ...projectEntries(dir)];
73
+ }
74
+
75
+ function assertSettingsJson(text: string): void {
76
+ if (text.length > SETTINGS_MAX) throw new ApiError('that is more text than a settings file may hold', 413);
77
+ let parsed: unknown;
78
+ try {
79
+ parsed = JSON.parse(text);
80
+ } catch (err) {
81
+ throw new ApiError(`that is not valid JSON: ${errMsg(err)}`, 400);
82
+ }
83
+ if (!isRecord(parsed)) throw new ApiError('Claude Code settings must be a JSON object', 400);
84
+ }
85
+
86
+ function modeOf(path: string): number {
87
+ try {
88
+ return statSync(path).mode & 0o777;
89
+ } catch {
90
+ return DEFAULT_MODE;
91
+ }
92
+ }
93
+
94
+ function writeAtomic(path: string, text: string): void {
95
+ const mode = existsSync(path) ? modeOf(path) : DEFAULT_MODE;
96
+ mkdirSync(dirname(path), { recursive: true });
97
+ const tmp = `${path}.metro-${String(process.pid)}`;
98
+ writeFileSync(tmp, text, { mode });
99
+ chmodSync(tmp, mode);
100
+ renameSync(tmp, path);
101
+ }
102
+
103
+ export function writeClaudeSettings(
104
+ id: string,
105
+ text: string,
106
+ seenAt: string | null | undefined,
107
+ dir = claudeDir(),
108
+ ): ClaudeSettingsFile {
109
+ const target = listClaudeSettings(dir).find((file) => file.id === id);
110
+ if (target === undefined) throw new ApiError('no such settings file', 404);
111
+ if (!target.editable) throw new ApiError('that settings file is too large to edit here', 409);
112
+ assertSettingsJson(text);
113
+ if (seenAt !== undefined && seenAt !== target.modifiedAt)
114
+ throw new ApiError('that file changed on disk since you opened it; reload it before saving', 409);
115
+ writeAtomic(target.path, text);
116
+ return entryOf(target.id, target.scope, target.label, target.path);
117
+ }
@@ -0,0 +1,307 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { errMsg, log } from '@metro-labs/core/log';
3
+ import {
4
+ apiFailure,
5
+ apiSession,
6
+ projectParam,
7
+ bodyField,
8
+ cors,
9
+ readJsonBody,
10
+ sendJson,
11
+ type ApiSession,
12
+ } from '@metro-labs/http/api-http';
13
+ import { parseId } from '@metro-labs/core/ids';
14
+ import {
15
+ handleCallback,
16
+ handleConnect,
17
+ hostOf,
18
+ startOAuth,
19
+ type OAuthRouteDeps,
20
+ } from './oauth-routes.js';
21
+ import { ConnectorUnauthorized } from './verify.js';
22
+ import type {
23
+ Connector,
24
+ ConnectorCheck,
25
+ ConnectorInput,
26
+ DeletedConnector,
27
+ } from './model.js';
28
+
29
+ const PREFIX = '/api/connectors';
30
+
31
+ const asText = (value: unknown): string =>
32
+ typeof value === 'string' ? value : '';
33
+
34
+ export interface ConnectorApiDeps extends OAuthRouteDeps {
35
+ listConnectors: (subject: string, project: string) => Promise<Connector[]>;
36
+ createConnector: (
37
+ subject: string,
38
+ project: string,
39
+ input: ConnectorInput,
40
+ ) => Promise<Connector>;
41
+ verifyConnector: (subject: string, id: string) => Promise<ConnectorCheck>;
42
+ disconnectConnector: (subject: string, id: string) => Promise<Connector>;
43
+ renameConnector: (
44
+ subject: string,
45
+ id: string,
46
+ name: string,
47
+ ) => Promise<Connector>;
48
+ deleteConnector: (subject: string, id: string) => Promise<DeletedConnector>;
49
+ }
50
+
51
+ type Routable =
52
+ | { kind: 'collection' }
53
+ | { kind: 'callback' }
54
+ | { kind: 'connector'; id: string }
55
+ | { kind: 'verify'; id: string }
56
+ | { kind: 'connect'; id: string }
57
+ | { kind: 'disconnect'; id: string }
58
+ | { kind: 'rename'; id: string };
59
+
60
+ type Target = Routable | { kind: 'unknown' } | null;
61
+
62
+ function subTarget(id: string, rest: string[]): Target {
63
+ if (rest.length === 0) return { kind: 'connector', id };
64
+ if (rest.length > 1) return { kind: 'unknown' };
65
+ const head = rest[0];
66
+ if (head === 'verify') return { kind: 'verify', id };
67
+ if (head === 'connect') return { kind: 'connect', id };
68
+ if (head === 'disconnect') return { kind: 'disconnect', id };
69
+ if (head === 'rename') return { kind: 'rename', id };
70
+ return { kind: 'unknown' };
71
+ }
72
+
73
+ function target(path: string): Target {
74
+ if (path === PREFIX || path === `${PREFIX}/`) return { kind: 'collection' };
75
+ if (!path.startsWith(`${PREFIX}/`)) return null;
76
+ const segments = path.slice(PREFIX.length + 1).split('/').filter(Boolean);
77
+ const head = segments[0];
78
+ if (head === undefined) return { kind: 'collection' };
79
+ if (head === 'callback' && segments.length === 1) return { kind: 'callback' };
80
+ const id = parseId(head);
81
+ return id === null ? { kind: 'unknown' } : subTarget(id, segments.slice(1));
82
+ }
83
+
84
+ function connectorPayload(
85
+ row: Connector,
86
+ detail = false,
87
+ ): Record<string, unknown> {
88
+ const { catalog, ...summary } = row.verified;
89
+ return {
90
+ id: row.id,
91
+ name: row.name,
92
+ url: row.url,
93
+ transport: row.transport,
94
+ auth: row.auth,
95
+ header: row.header,
96
+ signIn: row.signIn,
97
+ verified: detail ? { ...summary, catalog } : summary,
98
+ };
99
+ }
100
+
101
+ async function handleList(
102
+ req: IncomingMessage,
103
+ res: ServerResponse,
104
+ deps: ConnectorApiDeps,
105
+ session: ApiSession,
106
+ ): Promise<void> {
107
+ const project = projectParam(req);
108
+ if (project === null) {
109
+ sendJson(req, res, 400, { error: 'a project is required' });
110
+ return;
111
+ }
112
+ const rows = await deps.listConnectors(session.subject, project);
113
+ sendJson(req, res, 200, {
114
+ connectors: rows.map((row) => connectorPayload(row)),
115
+ });
116
+ }
117
+
118
+ async function handleCreate(
119
+ req: IncomingMessage,
120
+ res: ServerResponse,
121
+ deps: ConnectorApiDeps,
122
+ session: ApiSession,
123
+ ): Promise<void> {
124
+ const body = await readJsonBody(req);
125
+ const project = projectParam(req);
126
+ if (project === null) {
127
+ sendJson(req, res, 400, { error: 'a project is required' });
128
+ return;
129
+ }
130
+ const offered = asText(bodyField(body, 'value')).trim() !== '';
131
+ try {
132
+ const created = await deps.createConnector(session.subject, project, {
133
+ name: bodyField(body, 'name'),
134
+ url: bodyField(body, 'url'),
135
+ header: bodyField(body, 'header'),
136
+ value: bodyField(body, 'value'),
137
+ });
138
+ log.info(
139
+ { id: created.id, name: created.name, host: hostOf(created.url) },
140
+ 'connector-api: created connector',
141
+ );
142
+ sendJson(req, res, 201, connectorPayload(created));
143
+ } catch (err) {
144
+ if (offered || !(err instanceof ConnectorUnauthorized)) throw err;
145
+ await startOAuth(req, res, deps, session, project, body, (row) =>
146
+ connectorPayload(row),
147
+ );
148
+ }
149
+ }
150
+
151
+ async function handleVerify(
152
+ req: IncomingMessage,
153
+ res: ServerResponse,
154
+ deps: ConnectorApiDeps,
155
+ session: ApiSession,
156
+ id: string,
157
+ ): Promise<void> {
158
+ const check = await deps.verifyConnector(session.subject, id);
159
+ log.info(
160
+ { id: check.id, name: check.name, ok: check.ok },
161
+ 'connector-api: re-verified connector',
162
+ );
163
+ sendJson(req, res, 200, check);
164
+ }
165
+
166
+ async function handleDisconnect(
167
+ req: IncomingMessage,
168
+ res: ServerResponse,
169
+ deps: ConnectorApiDeps,
170
+ session: ApiSession,
171
+ id: string,
172
+ ): Promise<void> {
173
+ const row = await deps.disconnectConnector(session.subject, id);
174
+ log.info(
175
+ { id: row.id, name: row.name },
176
+ 'connector-api: signed the connector out',
177
+ );
178
+ sendJson(req, res, 200, connectorPayload(row));
179
+ }
180
+
181
+ async function handleDelete(
182
+ req: IncomingMessage,
183
+ res: ServerResponse,
184
+ deps: ConnectorApiDeps,
185
+ session: ApiSession,
186
+ id: string,
187
+ ): Promise<void> {
188
+ const gone = await deps.deleteConnector(session.subject, id);
189
+ log.info(
190
+ { id: gone.id, name: gone.name },
191
+ 'connector-api: deleted connector',
192
+ );
193
+ sendJson(req, res, 200, { id: gone.id, name: gone.name, deleted: true });
194
+ }
195
+
196
+ async function handleConnector(
197
+ req: IncomingMessage,
198
+ res: ServerResponse,
199
+ deps: ConnectorApiDeps,
200
+ session: ApiSession,
201
+ id: string,
202
+ ): Promise<void> {
203
+ if (req.method !== 'GET') {
204
+ await handleDelete(req, res, deps, session, id);
205
+ return;
206
+ }
207
+ const row = await deps.getConnector(session.subject, id);
208
+ sendJson(req, res, 200, connectorPayload(row, true));
209
+ }
210
+
211
+ async function handleRename(
212
+ req: IncomingMessage,
213
+ res: ServerResponse,
214
+ deps: ConnectorApiDeps,
215
+ session: ApiSession,
216
+ id: string,
217
+ ): Promise<void> {
218
+ const name = bodyField(await readJsonBody(req), 'name');
219
+ if (typeof name !== 'string') {
220
+ sendJson(req, res, 400, { error: 'name is required' });
221
+ return;
222
+ }
223
+ const row = await deps.renameConnector(session.subject, id, name);
224
+ log.info({ id: row.id, name: row.name }, 'connector-api: renamed connector');
225
+ sendJson(req, res, 200, connectorPayload(row));
226
+ }
227
+
228
+ async function route(
229
+ req: IncomingMessage,
230
+ res: ServerResponse,
231
+ deps: ConnectorApiDeps,
232
+ session: ApiSession,
233
+ tgt: Routable,
234
+ ): Promise<void> {
235
+ try {
236
+ if (tgt.kind === 'callback') return;
237
+ if (tgt.kind === 'verify')
238
+ await handleVerify(req, res, deps, session, tgt.id);
239
+ else if (tgt.kind === 'connect')
240
+ await handleConnect(req, res, deps, session, tgt.id);
241
+ else if (tgt.kind === 'disconnect')
242
+ await handleDisconnect(req, res, deps, session, tgt.id);
243
+ else if (tgt.kind === 'rename')
244
+ await handleRename(req, res, deps, session, tgt.id);
245
+ else if (tgt.kind === 'connector')
246
+ await handleConnector(req, res, deps, session, tgt.id);
247
+ else if (req.method === 'GET') await handleList(req, res, deps, session);
248
+ else await handleCreate(req, res, deps, session);
249
+ } catch (err) {
250
+ apiFailure(req, res, err, 'connector-api');
251
+ }
252
+ }
253
+
254
+ const ALLOWED: Record<Routable['kind'], string[]> = {
255
+ collection: ['GET', 'POST'],
256
+ callback: ['GET'],
257
+ connector: ['GET', 'DELETE'],
258
+ verify: ['POST'],
259
+ connect: ['POST'],
260
+ disconnect: ['POST'],
261
+ rename: ['POST'],
262
+ };
263
+
264
+ async function dispatch(
265
+ req: IncomingMessage,
266
+ res: ServerResponse,
267
+ deps: ConnectorApiDeps,
268
+ tgt: Routable,
269
+ ): Promise<void> {
270
+ if (tgt.kind === 'callback') {
271
+ handleCallback(req, res, deps);
272
+ return;
273
+ }
274
+ const session = await apiSession(req);
275
+ if (!session) {
276
+ sendJson(req, res, 401, { error: 'unauthorized' });
277
+ return;
278
+ }
279
+ await route(req, res, deps, session, tgt);
280
+ }
281
+
282
+ export function handleConnectorApiRequest(
283
+ req: IncomingMessage,
284
+ res: ServerResponse,
285
+ deps: ConnectorApiDeps,
286
+ ): boolean {
287
+ const tgt = target((req.url ?? '').split('?')[0] ?? '');
288
+ if (tgt === null) return false;
289
+ if (req.method === 'OPTIONS') {
290
+ res.writeHead(204, cors(req)).end();
291
+ return true;
292
+ }
293
+ if (tgt.kind === 'unknown') {
294
+ sendJson(req, res, 404, { error: 'no such connector' });
295
+ return true;
296
+ }
297
+ if (!ALLOWED[tgt.kind].includes(req.method ?? '')) {
298
+ sendJson(req, res, 405, { error: 'method not allowed' });
299
+ return true;
300
+ }
301
+ dispatch(req, res, deps, tgt).catch((err: unknown) => {
302
+ log.warn({ err: errMsg(err) }, 'connector-api: unhandled error');
303
+ if (!res.headersSent)
304
+ sendJson(req, res, 500, { error: 'connector api failed' });
305
+ });
306
+ return true;
307
+ }
@@ -0,0 +1,63 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { errMsg, log } from '@metro-labs/core/log';
3
+ import { cors, sendJson, type AgentIdentity } from '@metro-labs/http/api-http';
4
+ import { publicBaseOrDefault } from '../files/attach-serve.js';
5
+ import { relayServersJson, type RelayServerEntry } from './json.js';
6
+ import { agentIdForKey } from '../agents/keys.js';
7
+
8
+ const PATH = '/api/cli/mcp';
9
+
10
+ export interface LocalCliDeps {
11
+ agentName: (agentId: string) => string | null;
12
+ connectorEntries: (agentId: string) => Promise<RelayServerEntry[]>;
13
+ }
14
+
15
+ function keyOf(req: IncomingMessage): string {
16
+ const header = req.headers.authorization ?? '';
17
+ if (header.toLowerCase().startsWith('bearer ')) return header.slice(7).trim();
18
+ const query = new URLSearchParams((req.url ?? '').split('?')[1] ?? '');
19
+ return query.get('token')?.trim() ?? '';
20
+ }
21
+
22
+ export function keyIdentity(req: IncomingMessage): AgentIdentity | null {
23
+ const key = keyOf(req);
24
+ const agentId = key === '' ? undefined : agentIdForKey(key);
25
+ return agentId === undefined ? null : { subject: 'agent-key', agentId };
26
+ }
27
+
28
+ export function handleLocalCliRequest(
29
+ req: IncomingMessage,
30
+ res: ServerResponse,
31
+ deps: LocalCliDeps,
32
+ ): boolean {
33
+ const path = (req.url ?? '').split('?')[0] ?? '';
34
+ if (path !== PATH && !path.startsWith('/api/cli/')) return false;
35
+ if (req.method === 'OPTIONS') {
36
+ res.writeHead(204, cors(req)).end();
37
+ return true;
38
+ }
39
+ if (path !== PATH) {
40
+ sendJson(req, res, 404, { error: 'not on a local daemon' });
41
+ return true;
42
+ }
43
+ if (req.method !== 'GET') {
44
+ sendJson(req, res, 405, { error: 'method not allowed' });
45
+ return true;
46
+ }
47
+ const who = keyIdentity(req);
48
+ if (who === null) {
49
+ sendJson(req, res, 401, { error: 'unauthorized' });
50
+ return true;
51
+ }
52
+ deps
53
+ .connectorEntries(who.agentId)
54
+ .then((entries) => {
55
+ const agent = deps.agentName(who.agentId) ?? '';
56
+ sendJson(req, res, 200, { json: relayServersJson(entries, publicBaseOrDefault(), keyOf(req)), agent });
57
+ })
58
+ .catch((err: unknown) => {
59
+ log.warn({ err: errMsg(err) }, 'local cli: request failed');
60
+ if (!res.headersSent) sendJson(req, res, 500, { error: 'cli failed' });
61
+ });
62
+ return true;
63
+ }
@@ -0,0 +1,144 @@
1
+ import { ApiError } from '@metro-labs/http/api-error';
2
+ import type {
3
+ ConnectorAuth,
4
+ VerifiedRecord,
5
+ VerifiedServer,
6
+ } from './verify.js';
7
+ import { readStoredTools } from './tools.js';
8
+ import { isRecord } from '@metro-labs/core/is-record';
9
+
10
+ const CONNECTOR_NAME_MAX = 64;
11
+ const HEADER_NAME_RE = /^[A-Za-z0-9!#$%&'*+.^_`|~-]{1,64}$/;
12
+ const HEADER_VALUE_RE = /^[\x20-\x7e]{1,4096}$/;
13
+ const DEFAULT_HEADER = 'Authorization';
14
+
15
+ export class ConnectorError extends ApiError {}
16
+
17
+ export type ConnectorSignIn = 'connected' | 'disconnected' | null;
18
+
19
+ export interface ConnectorConfig {
20
+ auth: ConnectorAuth;
21
+ createdAt: string;
22
+ verified: VerifiedRecord;
23
+ oauth: boolean;
24
+ }
25
+
26
+ function text(value: unknown): string {
27
+ return typeof value === 'string' ? value : '';
28
+ }
29
+
30
+ function hasControlChar(value: string): boolean {
31
+ for (const ch of value) {
32
+ const code = ch.codePointAt(0) ?? 0;
33
+ if (code < 0x20 || code === 0x7f) return true;
34
+ }
35
+ return false;
36
+ }
37
+
38
+ export function connectorName(raw: unknown): string {
39
+ const name = typeof raw === 'string' ? raw.trim() : '';
40
+ if (name === '') throw new ConnectorError('a connector needs a name', 400);
41
+ if (name.length > CONNECTOR_NAME_MAX)
42
+ throw new ConnectorError(
43
+ `a connector name must be ${String(CONNECTOR_NAME_MAX)} characters or fewer`,
44
+ 400,
45
+ );
46
+ if (hasControlChar(name))
47
+ throw new ConnectorError(
48
+ 'a connector name must not contain control characters',
49
+ 400,
50
+ );
51
+ return name;
52
+ }
53
+
54
+ export function connectorAuth(
55
+ rawHeader: unknown,
56
+ rawValue: unknown,
57
+ ): ConnectorAuth {
58
+ const header = text(rawHeader).trim();
59
+ const value = text(rawValue).trim();
60
+ if (header === '' && value === '') return { kind: 'none' };
61
+ if (value === '')
62
+ throw new ConnectorError(
63
+ 'that header has no value — give both a header name and its value, or neither',
64
+ 400,
65
+ );
66
+ const name = header === '' ? DEFAULT_HEADER : header;
67
+ if (!HEADER_NAME_RE.test(name))
68
+ throw new ConnectorError('that is not a valid HTTP header name', 400);
69
+ if (!HEADER_VALUE_RE.test(value))
70
+ throw new ConnectorError('that header value is not sendable', 400);
71
+ return { kind: 'header', name, value };
72
+ }
73
+
74
+ function readOAuth(raw: Record<string, unknown>): ConnectorAuth {
75
+ const accessToken = text(raw.accessToken);
76
+ const clientId = text(raw.clientId);
77
+ const tokenEndpoint = text(raw.tokenEndpoint);
78
+ if (accessToken === '' || clientId === '' || tokenEndpoint === '')
79
+ return { kind: 'none' };
80
+ const refreshToken = text(raw.refreshToken);
81
+ const clientSecret = text(raw.clientSecret);
82
+ return {
83
+ kind: 'oauth',
84
+ accessToken,
85
+ clientId,
86
+ tokenEndpoint,
87
+ issuer: text(raw.issuer),
88
+ ...(refreshToken === '' ? {} : { refreshToken }),
89
+ ...(clientSecret === '' ? {} : { clientSecret }),
90
+ ...(typeof raw.expiresAt === 'number' ? { expiresAt: raw.expiresAt } : {}),
91
+ };
92
+ }
93
+
94
+ function readAuth(raw: unknown): ConnectorAuth {
95
+ if (!isRecord(raw)) return { kind: 'none' };
96
+ if (raw.kind === 'oauth') return readOAuth(raw);
97
+ if (raw.kind !== 'header') return { kind: 'none' };
98
+ const name = text(raw.name);
99
+ const value = text(raw.value);
100
+ if (name === '' || value === '') return { kind: 'none' };
101
+ return { kind: 'header', name, value };
102
+ }
103
+
104
+ function readVerified(raw: unknown): VerifiedRecord {
105
+ const record = isRecord(raw) ? raw : {};
106
+ const catalog = readStoredTools(record.catalog);
107
+ return {
108
+ at: text(record.at),
109
+ server: text(record.server),
110
+ version: text(record.version),
111
+ protocol: text(record.protocol),
112
+ icon: text(record.icon),
113
+ tools: typeof record.tools === 'number' ? record.tools : 0,
114
+ catalog,
115
+ };
116
+ }
117
+
118
+ export function readConfig(raw: unknown): ConnectorConfig {
119
+ const record = isRecord(raw) ? raw : {};
120
+ const auth = readAuth(record.auth);
121
+ return {
122
+ auth,
123
+ createdAt: text(record.createdAt),
124
+ verified: readVerified(record.verified),
125
+ oauth: record.oauth === true || auth.kind === 'oauth',
126
+ };
127
+ }
128
+
129
+ export function signInState(config: ConnectorConfig): ConnectorSignIn {
130
+ if (config.auth.kind === 'oauth') return 'connected';
131
+ return config.oauth ? 'disconnected' : null;
132
+ }
133
+
134
+ export function stamp(server: VerifiedServer): VerifiedRecord {
135
+ return {
136
+ at: new Date().toISOString(),
137
+ server: server.server,
138
+ version: server.version,
139
+ protocol: server.protocol,
140
+ icon: server.icon,
141
+ tools: server.tools,
142
+ catalog: server.catalog,
143
+ };
144
+ }
@@ -0,0 +1,21 @@
1
+ export interface RelayServerEntry {
2
+ id: string;
3
+ name: string;
4
+ }
5
+
6
+ export const NAME_PREFIX = 'metro.box ';
7
+
8
+ export function relayServersJson(
9
+ entries: RelayServerEntry[],
10
+ base: string,
11
+ cliToken: string,
12
+ ): string {
13
+ const mcpServers: Record<string, unknown> = {};
14
+ for (const entry of entries)
15
+ mcpServers[`${NAME_PREFIX}${entry.name}`] = {
16
+ type: 'http',
17
+ url: `${base}/relay/${entry.id}`,
18
+ headers: { Authorization: `Bearer ${cliToken}` },
19
+ };
20
+ return JSON.stringify({ mcpServers }, null, 2);
21
+ }