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

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 (457) hide show
  1. package/dist/api.js +9 -101
  2. package/dist/background.js +123 -0
  3. package/dist/claude-settings.js +40 -0
  4. package/dist/claude.js +197 -0
  5. package/dist/cli.js +52 -110
  6. package/dist/control.js +19 -103
  7. package/dist/hold.js +210 -0
  8. package/dist/local.js +43 -0
  9. package/dist/mcp-config.js +25 -0
  10. package/dist/memory.js +222 -0
  11. package/dist/node-name.js +71 -0
  12. package/dist/onboarding.js +44 -0
  13. package/dist/provider-flags.js +9 -0
  14. package/dist/route.js +58 -0
  15. package/dist/runtime-install.js +96 -0
  16. package/dist/runtime.js +29 -52
  17. package/dist/serve.js +157 -0
  18. package/dist/service.js +304 -0
  19. package/dist/tail.js +119 -0
  20. package/dist/update.js +13 -1
  21. package/package.json +2 -23
  22. package/runtime/marketplace/.claude-plugin/marketplace.json +12 -0
  23. package/runtime/marketplace/plugin/.claude-plugin/plugin.json +17 -0
  24. package/runtime/marketplace/plugin/.mcp.json +1 -0
  25. package/runtime/marketplace/plugin/MEMORY.md +78 -0
  26. package/runtime/marketplace/plugin/METRO.md +26 -0
  27. package/runtime/marketplace/plugin/STAGE.md +42 -0
  28. package/runtime/marketplace/plugin/bin/guard.mjs +71 -0
  29. package/runtime/marketplace/plugin/bin/metro-plugin.mjs +30 -0
  30. package/runtime/marketplace/plugin/bin/policy.mjs +97 -0
  31. package/runtime/marketplace/plugin/bin/session-start.mjs +31 -0
  32. package/runtime/marketplace/plugin/hooks/hooks.json +25 -0
  33. package/runtime/node_modules/@metro-labs/core/package.json +51 -0
  34. package/runtime/node_modules/@metro-labs/core/src/endpoints.ts +7 -0
  35. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/events.ts +11 -7
  36. package/runtime/node_modules/@metro-labs/{mcp/src/db → core/src}/ids.ts +5 -0
  37. package/runtime/node_modules/@metro-labs/core/src/is-record.ts +2 -0
  38. package/runtime/node_modules/@metro-labs/{mcp/src/stations → core/src}/lines.ts +22 -26
  39. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/protocol.ts +7 -4
  40. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/secure-fs.ts +17 -0
  41. package/runtime/node_modules/@metro-labs/core/src/station-names.ts +14 -0
  42. package/runtime/node_modules/@metro-labs/core/src/stations/account-files.ts +41 -0
  43. package/runtime/node_modules/@metro-labs/core/src/stations/account-store.ts +83 -0
  44. package/runtime/node_modules/@metro-labs/{mcp → core}/src/stations/attachments.ts +0 -1
  45. package/runtime/node_modules/@metro-labs/core/src/stations/mail-text.ts +72 -0
  46. package/runtime/node_modules/@metro-labs/core/src/stations/mail-trust.ts +29 -0
  47. package/runtime/node_modules/@metro-labs/core/src/stations/mail.ts +212 -0
  48. package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +16 -0
  49. package/runtime/node_modules/@metro-labs/core/src/stations/oauth.ts +126 -0
  50. package/runtime/node_modules/@metro-labs/core/src/stations/profile.ts +58 -0
  51. package/runtime/node_modules/@metro-labs/core/src/stations/sender-profile.ts +64 -0
  52. package/runtime/node_modules/@metro-labs/{mcp → core}/src/stations/station-runtime.ts +6 -11
  53. package/runtime/node_modules/@metro-labs/core/src/stations/train-boot.ts +42 -0
  54. package/runtime/node_modules/@metro-labs/core/src/stations/train-events.ts +84 -0
  55. package/runtime/node_modules/@metro-labs/{mcp → core}/src/stations/types.ts +18 -1
  56. package/runtime/node_modules/@metro-labs/core/src/tickets.ts +37 -0
  57. package/runtime/node_modules/@metro-labs/core/src/version.ts +2 -0
  58. package/runtime/node_modules/@metro-labs/daemon/package.json +49 -0
  59. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/agent-fs.ts +134 -0
  60. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/crontab.ts +44 -0
  61. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/files-api.ts +12 -0
  62. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/files.ts +89 -0
  63. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/home-fs.ts +113 -0
  64. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/job-env.ts +39 -0
  65. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/provision.ts +80 -0
  66. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedule-detail.ts +99 -0
  67. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules-api.ts +27 -0
  68. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules.ts +144 -0
  69. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/unit-files.ts +43 -0
  70. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/user.ts +91 -0
  71. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/view.ts +87 -0
  72. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-attach.ts +19 -0
  73. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-lookups.ts +55 -0
  74. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-routes.ts +76 -0
  75. package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +357 -0
  76. package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +30 -0
  77. package/runtime/node_modules/@metro-labs/daemon/src/agents/allowlist.ts +59 -0
  78. package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +131 -0
  79. package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +132 -0
  80. package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +305 -0
  81. package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +142 -0
  82. package/runtime/node_modules/@metro-labs/{mcp/src/db/key-map.ts → daemon/src/agents/keys.ts} +1 -11
  83. package/runtime/node_modules/@metro-labs/daemon/src/agents/map.ts +133 -0
  84. package/runtime/node_modules/@metro-labs/daemon/src/agents/policy-route.ts +27 -0
  85. package/runtime/node_modules/@metro-labs/{mcp/src/db/agent-scope.ts → daemon/src/agents/scope.ts} +2 -2
  86. package/runtime/node_modules/@metro-labs/daemon/src/agents/sender-cards.ts +94 -0
  87. package/runtime/node_modules/@metro-labs/daemon/src/agents/senders.ts +38 -0
  88. package/runtime/node_modules/@metro-labs/daemon/src/approvals/api.ts +55 -0
  89. package/runtime/node_modules/@metro-labs/daemon/src/approvals/pending.ts +150 -0
  90. package/runtime/node_modules/@metro-labs/daemon/src/approvals/preview.ts +66 -0
  91. package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +223 -0
  92. package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +36 -0
  93. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/boot}/crash-guard.ts +1 -1
  94. package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +21 -0
  95. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/boot}/paths.ts +6 -34
  96. package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +26 -0
  97. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/inbound.ts +75 -45
  98. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/media-note.ts +11 -7
  99. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/pending.ts +4 -1
  100. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/relay.ts +2 -2
  101. package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +380 -0
  102. package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +359 -0
  103. package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +208 -0
  104. package/runtime/node_modules/@metro-labs/daemon/src/claude/memory-routine.ts +114 -0
  105. package/runtime/node_modules/@metro-labs/daemon/src/claude/memory.ts +32 -0
  106. package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +60 -0
  107. package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +149 -0
  108. package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +75 -0
  109. package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +295 -0
  110. package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +109 -0
  111. package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +346 -0
  112. package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +120 -0
  113. package/runtime/node_modules/@metro-labs/daemon/src/claude/usage-probe.ts +127 -0
  114. package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +113 -0
  115. package/runtime/node_modules/@metro-labs/{mcp/src/daemon/connector-api.ts → daemon/src/connectors/api.ts} +79 -113
  116. package/runtime/node_modules/@metro-labs/{mcp/src/db/connector-config.ts → daemon/src/connectors/config.ts} +67 -41
  117. package/runtime/node_modules/@metro-labs/daemon/src/connectors/gates.ts +72 -0
  118. package/runtime/node_modules/@metro-labs/daemon/src/connectors/health.ts +18 -0
  119. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/connectors}/oauth-client.ts +14 -17
  120. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/connectors}/oauth-discovery.ts +33 -20
  121. package/runtime/node_modules/@metro-labs/{mcp/src/daemon/connector-oauth-routes.ts → daemon/src/connectors/oauth-routes.ts} +30 -32
  122. package/runtime/node_modules/@metro-labs/{mcp/src/daemon/connector-oauth.ts → daemon/src/connectors/oauth.ts} +61 -22
  123. package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +131 -0
  124. package/runtime/node_modules/@metro-labs/daemon/src/connectors/reach.ts +27 -0
  125. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-policy.ts +90 -0
  126. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +347 -0
  127. package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +385 -0
  128. package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +81 -0
  129. package/runtime/node_modules/@metro-labs/daemon/src/connectors/transfer-api.ts +116 -0
  130. package/runtime/node_modules/@metro-labs/daemon/src/connectors/transfer-crypto.ts +44 -0
  131. package/runtime/node_modules/@metro-labs/daemon/src/connectors/transfer-store.ts +48 -0
  132. package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +37 -0
  133. package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +106 -0
  134. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/attach-grant.ts +4 -4
  135. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/attach-owner.ts +3 -3
  136. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-reaper.ts +174 -0
  137. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/attach-serve.ts +12 -13
  138. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/upload-api.ts +10 -27
  139. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/upload-store.ts +4 -4
  140. package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +233 -0
  141. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +214 -0
  142. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
  143. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +173 -0
  144. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +209 -0
  145. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-version.ts +44 -0
  146. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +155 -0
  147. package/runtime/node_modules/@metro-labs/daemon/src/gateway/effort.ts +93 -0
  148. package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +123 -0
  149. package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +237 -0
  150. package/runtime/node_modules/@metro-labs/daemon/src/gateway/frames.ts +118 -0
  151. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +307 -0
  152. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-auth.ts +160 -0
  153. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-client.ts +46 -0
  154. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-setup.ts +150 -0
  155. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-stream.ts +139 -0
  156. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-translate.ts +235 -0
  157. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini.ts +208 -0
  158. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +203 -0
  159. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +285 -0
  160. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-lists.ts +57 -0
  161. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-signin.ts +161 -0
  162. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-store.ts +72 -0
  163. package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +104 -0
  164. package/runtime/node_modules/@metro-labs/daemon/src/gateway/pkce.ts +36 -0
  165. package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +118 -0
  166. package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +18 -0
  167. package/runtime/node_modules/@metro-labs/daemon/src/gateway/subscription.ts +120 -0
  168. package/runtime/node_modules/@metro-labs/daemon/src/gateway/text.ts +35 -0
  169. package/runtime/node_modules/@metro-labs/daemon/src/gateway/tool-search.ts +126 -0
  170. package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage-refresh.ts +102 -0
  171. package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +348 -0
  172. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/accounts.ts +28 -17
  173. package/runtime/node_modules/@metro-labs/daemon/src/mcp/attachment-schema.ts +91 -0
  174. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/call-tools.ts +49 -20
  175. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/ctx.ts +4 -4
  176. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/event-store.ts +15 -16
  177. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/group-tools.ts +2 -2
  178. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/index.ts +34 -80
  179. package/runtime/node_modules/@metro-labs/daemon/src/mcp/instructions.ts +19 -0
  180. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/member-tools.ts +2 -2
  181. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-prompt.ts +87 -0
  182. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +88 -0
  183. package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-gate.ts +82 -0
  184. package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-snapshot.ts +79 -0
  185. package/runtime/node_modules/@metro-labs/daemon/src/mcp/profile-lookup.ts +60 -0
  186. package/runtime/node_modules/@metro-labs/daemon/src/mcp/profile-tool.ts +96 -0
  187. package/runtime/node_modules/@metro-labs/daemon/src/mcp/read-tool.ts +87 -0
  188. package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +46 -0
  189. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-slot.ts +71 -0
  190. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/session.ts +47 -44
  191. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-catalog.ts +93 -0
  192. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-def.ts +9 -0
  193. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/tool-dispatch.ts +53 -40
  194. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/tool-schemas.ts +64 -113
  195. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/upload-tool.ts +6 -11
  196. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/helper-script.ts +126 -0
  197. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/install.ts +37 -0
  198. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/privilege.ts +48 -0
  199. package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +132 -0
  200. package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +316 -0
  201. package/runtime/node_modules/@metro-labs/daemon/src/policy/policy.ts +118 -0
  202. package/runtime/node_modules/@metro-labs/daemon/src/routes/bearer.ts +35 -0
  203. package/runtime/node_modules/@metro-labs/daemon/src/routes/body.ts +24 -0
  204. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/routes}/http.ts +63 -51
  205. package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +209 -0
  206. package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +69 -0
  207. package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
  208. package/runtime/node_modules/@metro-labs/daemon/src/routes/threema-callback.ts +103 -0
  209. package/runtime/node_modules/@metro-labs/daemon/src/server/cloudwatch.ts +178 -0
  210. package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +39 -0
  211. package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +58 -0
  212. package/runtime/node_modules/@metro-labs/daemon/src/server/owner.ts +26 -0
  213. package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +90 -0
  214. package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
  215. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-browser.ts +102 -0
  216. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-driver.ts +42 -0
  217. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-inline.ts +29 -1
  218. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-interactive.ts +5 -31
  219. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-resolve.ts +38 -13
  220. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/attach-session.ts +32 -22
  221. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-xmtp.ts +8 -9
  222. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach.ts +69 -3
  223. package/runtime/node_modules/@metro-labs/{mcp/src/db → daemon/src/stations}/materialize.ts +60 -179
  224. package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/registry.ts +20 -2
  225. package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
  226. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/supervisor-io.ts +3 -3
  227. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/supervisor.ts +6 -9
  228. package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
  229. package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/train-call.ts +1 -1
  230. package/runtime/node_modules/@metro-labs/daemon/src/stations/webhook-endpoints.ts +51 -0
  231. package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +84 -0
  232. package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +124 -0
  233. package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +15 -0
  234. package/runtime/node_modules/@metro-labs/daemon/src/vault/api.ts +45 -0
  235. package/runtime/node_modules/@metro-labs/daemon/src/vault/config.ts +51 -0
  236. package/runtime/node_modules/@metro-labs/daemon/src/vault/firewall.ts +9 -0
  237. package/runtime/node_modules/@metro-labs/daemon/src/vault/index.ts +96 -0
  238. package/runtime/node_modules/@metro-labs/daemon/src/vault/install.ts +56 -0
  239. package/runtime/node_modules/@metro-labs/daemon/src/vault/paths.ts +26 -0
  240. package/runtime/node_modules/@metro-labs/daemon/src/vault/proxy.ts +147 -0
  241. package/runtime/node_modules/@metro-labs/daemon/src/vault/store.ts +129 -0
  242. package/runtime/node_modules/@metro-labs/daemon/src/vault/trust.ts +44 -0
  243. package/runtime/node_modules/@metro-labs/discord-bot/package.json +2 -4
  244. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +3 -23
  245. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +19 -187
  246. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +2 -2
  247. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +26 -67
  248. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +1 -1
  249. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +2 -2
  250. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +8 -14
  251. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +2 -2
  252. package/runtime/node_modules/@metro-labs/discord-bot/src/profile.ts +68 -0
  253. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +12 -13
  254. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +5 -1
  255. package/runtime/node_modules/@metro-labs/gmail/package.json +25 -0
  256. package/runtime/node_modules/@metro-labs/gmail/src/accounts.ts +81 -0
  257. package/runtime/node_modules/@metro-labs/gmail/src/actions.ts +14 -0
  258. package/runtime/node_modules/@metro-labs/gmail/src/api.ts +31 -0
  259. package/runtime/node_modules/@metro-labs/gmail/src/attachments.ts +35 -0
  260. package/runtime/node_modules/@metro-labs/gmail/src/auth.ts +105 -0
  261. package/runtime/node_modules/@metro-labs/gmail/src/config.ts +13 -0
  262. package/runtime/node_modules/@metro-labs/gmail/src/inbound.ts +96 -0
  263. package/runtime/node_modules/@metro-labs/gmail/src/index.ts +12 -0
  264. package/runtime/node_modules/@metro-labs/gmail/src/login.ts +37 -0
  265. package/runtime/node_modules/@metro-labs/gmail/src/message.ts +125 -0
  266. package/runtime/node_modules/@metro-labs/gmail/src/mime.ts +70 -0
  267. package/runtime/node_modules/@metro-labs/gmail/src/outbound.ts +124 -0
  268. package/runtime/node_modules/@metro-labs/gmail/src/query.ts +99 -0
  269. package/runtime/node_modules/@metro-labs/gmail/src/state.ts +24 -0
  270. package/runtime/node_modules/@metro-labs/gmail/src/station.ts +15 -0
  271. package/runtime/node_modules/@metro-labs/gmail/src/trust.ts +42 -0
  272. package/runtime/node_modules/@metro-labs/http/package.json +30 -0
  273. package/runtime/node_modules/@metro-labs/http/src/api-http.ts +135 -0
  274. package/runtime/node_modules/@metro-labs/{mcp/src/daemon/session-api.ts → http/src/mode-api.ts} +13 -9
  275. package/runtime/node_modules/@metro-labs/http/src/return-to.ts +21 -0
  276. package/runtime/node_modules/@metro-labs/http/src/sigv4.ts +90 -0
  277. package/runtime/node_modules/@metro-labs/http/src/workos-token.ts +149 -0
  278. package/runtime/node_modules/@metro-labs/outlook/package.json +25 -0
  279. package/runtime/node_modules/@metro-labs/outlook/src/accounts.ts +78 -0
  280. package/runtime/node_modules/@metro-labs/outlook/src/actions.ts +14 -0
  281. package/runtime/node_modules/@metro-labs/outlook/src/attachments.ts +81 -0
  282. package/runtime/node_modules/@metro-labs/outlook/src/auth.ts +120 -0
  283. package/runtime/node_modules/@metro-labs/outlook/src/browser.ts +42 -0
  284. package/runtime/node_modules/@metro-labs/outlook/src/config.ts +23 -0
  285. package/runtime/node_modules/@metro-labs/outlook/src/format.ts +59 -0
  286. package/runtime/node_modules/@metro-labs/outlook/src/graph.ts +55 -0
  287. package/runtime/node_modules/@metro-labs/outlook/src/inbound.ts +96 -0
  288. package/runtime/node_modules/@metro-labs/outlook/src/index.ts +12 -0
  289. package/runtime/node_modules/@metro-labs/outlook/src/login.ts +129 -0
  290. package/runtime/node_modules/@metro-labs/outlook/src/me.ts +31 -0
  291. package/runtime/node_modules/@metro-labs/outlook/src/outbound.ts +103 -0
  292. package/runtime/node_modules/@metro-labs/outlook/src/query.ts +84 -0
  293. package/runtime/node_modules/@metro-labs/outlook/src/state.ts +28 -0
  294. package/runtime/node_modules/@metro-labs/outlook/src/station.ts +15 -0
  295. package/runtime/node_modules/@metro-labs/outlook/src/trust.ts +41 -0
  296. package/runtime/node_modules/@metro-labs/telegram/package.json +1 -3
  297. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +5 -24
  298. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +8 -6
  299. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +1 -1
  300. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +2 -2
  301. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +19 -34
  302. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +3 -3
  303. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +13 -34
  304. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +4 -43
  305. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +2 -3
  306. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +1 -1
  307. package/runtime/node_modules/@metro-labs/telegram/src/profile.ts +70 -0
  308. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +2 -2
  309. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +4 -1
  310. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +0 -1
  311. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +1 -1
  312. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +7 -41
  313. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +22 -77
  314. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +2 -2
  315. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +46 -48
  316. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +15 -15
  317. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +6 -82
  318. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +1 -1
  319. package/runtime/node_modules/@metro-labs/telegram-bot/src/profile.ts +85 -0
  320. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +5 -1
  321. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +20 -12
  322. package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
  323. package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +96 -0
  324. package/runtime/node_modules/@metro-labs/threema/src/actions.ts +124 -0
  325. package/runtime/node_modules/@metro-labs/threema/src/api.ts +152 -0
  326. package/runtime/node_modules/@metro-labs/threema/src/crypto.ts +100 -0
  327. package/runtime/node_modules/@metro-labs/threema/src/files.ts +47 -0
  328. package/runtime/node_modules/@metro-labs/threema/src/format.ts +164 -0
  329. package/runtime/node_modules/@metro-labs/threema/src/groups.ts +96 -0
  330. package/runtime/node_modules/@metro-labs/threema/src/ids.ts +21 -0
  331. package/runtime/node_modules/@metro-labs/threema/src/inbound.ts +87 -0
  332. package/runtime/node_modules/@metro-labs/threema/src/index.ts +24 -0
  333. package/runtime/node_modules/@metro-labs/threema/src/messages.ts +264 -0
  334. package/runtime/node_modules/@metro-labs/threema/src/outbound.ts +160 -0
  335. package/runtime/node_modules/@metro-labs/threema/src/station.ts +13 -0
  336. package/runtime/node_modules/@metro-labs/threema/src/verify.ts +117 -0
  337. package/runtime/node_modules/@metro-labs/webhook/package.json +1 -1
  338. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +5 -8
  339. package/runtime/node_modules/@metro-labs/whatsapp/package.json +2 -5
  340. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +6 -28
  341. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +1 -1
  342. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +54 -11
  343. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +2 -2
  344. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +1 -1
  345. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +86 -4
  346. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +7 -58
  347. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +11 -28
  348. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +4 -37
  349. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +1 -1
  350. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +1 -1
  351. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +1 -1
  352. package/runtime/node_modules/@metro-labs/whatsapp/src/names.ts +75 -0
  353. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +44 -0
  354. package/runtime/node_modules/@metro-labs/whatsapp/src/resolve.ts +52 -0
  355. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +16 -1
  356. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +5 -9
  357. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +0 -1
  358. package/runtime/node_modules/@metro-labs/xmtp/package.json +3 -2
  359. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +10 -20
  360. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +2 -2
  361. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +15 -204
  362. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +40 -53
  363. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +72 -220
  364. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +3 -3
  365. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +27 -0
  366. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +11 -7
  367. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +7 -24
  368. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +8 -18
  369. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +1 -1
  370. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +2 -2
  371. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +30 -16
  372. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +12 -20
  373. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +57 -33
  374. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +1 -1
  375. package/runtime/node_modules/@metro-labs/xmtp/src/names.ts +61 -0
  376. package/runtime/node_modules/@metro-labs/xmtp/src/profile.ts +153 -0
  377. package/runtime/node_modules/@metro-labs/xmtp/src/resolve.ts +44 -0
  378. package/runtime/node_modules/@metro-labs/xmtp/src/sender.ts +112 -0
  379. package/runtime/node_modules/@metro-labs/xmtp/src/smart.ts +72 -0
  380. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +7 -2
  381. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +22 -58
  382. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +55 -48
  383. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +4 -6
  384. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +19 -5
  385. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +14 -4
  386. package/runtime/runtime.json +1 -1
  387. package/runtime/server.ts +1 -0
  388. package/runtime/stations.json +38 -0
  389. package/dist/prompt.js +0 -61
  390. package/dist/store.js +0 -70
  391. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +0 -195
  392. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +0 -292
  393. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +0 -197
  394. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +0 -5
  395. package/runtime/node_modules/@metro-labs/mcp/package.json +0 -66
  396. package/runtime/node_modules/@metro-labs/mcp/src/channels/dedupe.ts +0 -24
  397. package/runtime/node_modules/@metro-labs/mcp/src/daemon/account-api.ts +0 -311
  398. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +0 -341
  399. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-http.ts +0 -129
  400. package/runtime/node_modules/@metro-labs/mcp/src/daemon/boot.ts +0 -320
  401. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair-api.ts +0 -111
  402. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair.ts +0 -20
  403. package/runtime/node_modules/@metro-labs/mcp/src/daemon/collection-api.ts +0 -202
  404. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-json.ts +0 -31
  405. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-tools.ts +0 -100
  406. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-url.ts +0 -57
  407. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-verify.ts +0 -333
  408. package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-auth.ts +0 -164
  409. package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-oauth.ts +0 -208
  410. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +0 -79
  411. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-pending.ts +0 -65
  412. package/runtime/node_modules/@metro-labs/mcp/src/daemon/pair-codes.ts +0 -58
  413. package/runtime/node_modules/@metro-labs/mcp/src/daemon/project-api.ts +0 -214
  414. package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-api.ts +0 -125
  415. package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-pair.ts +0 -18
  416. package/runtime/node_modules/@metro-labs/mcp/src/daemon/runtime-source.ts +0 -105
  417. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-apis.ts +0 -41
  418. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session.ts +0 -218
  419. package/runtime/node_modules/@metro-labs/mcp/src/daemon/tunnel.ts +0 -169
  420. package/runtime/node_modules/@metro-labs/mcp/src/db/account-attach.ts +0 -91
  421. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-admin.ts +0 -215
  422. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-map.ts +0 -71
  423. package/runtime/node_modules/@metro-labs/mcp/src/db/client.ts +0 -37
  424. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-collections.ts +0 -218
  425. package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +0 -329
  426. package/runtime/node_modules/@metro-labs/mcp/src/db/projects.ts +0 -300
  427. package/runtime/node_modules/@metro-labs/mcp/src/db/runtime-admin.ts +0 -31
  428. package/runtime/node_modules/@metro-labs/mcp/src/db/runtimes.ts +0 -105
  429. package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +0 -131
  430. package/runtime/node_modules/@metro-labs/mcp/src/db/token-carry.ts +0 -45
  431. package/runtime/node_modules/@metro-labs/mcp/src/db/users.ts +0 -57
  432. package/runtime/node_modules/@metro-labs/mcp/src/db/whatsapp-login.ts +0 -18
  433. package/runtime/node_modules/@metro-labs/mcp/src/mcp/channel-owner.ts +0 -29
  434. package/runtime/node_modules/@metro-labs/mcp/src/mcp/frame-scope.ts +0 -21
  435. package/runtime/node_modules/@metro-labs/mcp/src/mcp/permission-relay.ts +0 -66
  436. package/runtime/node_modules/@metro-labs/mcp/src/mcp/request-identity.ts +0 -73
  437. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-registry.ts +0 -172
  438. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-route.ts +0 -43
  439. package/runtime/node_modules/@metro-labs/mcp/src/monitor/api.ts +0 -240
  440. package/runtime/node_modules/@metro-labs/mcp/src/server.ts +0 -2
  441. package/runtime/node_modules/@metro-labs/mcp/src/stations/account-store.ts +0 -80
  442. package/runtime/node_modules/@metro-labs/mcp/src/stations/messaging-normalize.ts +0 -88
  443. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +0 -22
  444. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +0 -1
  445. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +0 -3
  446. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +0 -22
  447. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +0 -5
  448. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +0 -87
  449. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +0 -55
  450. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +0 -349
  451. /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/log.ts +0 -0
  452. /package/runtime/node_modules/@metro-labs/{mcp/src/mcp → core/src}/str.ts +0 -0
  453. /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/train-error.ts +0 -0
  454. /package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/webhook-note.ts +0 -0
  455. /package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/raw-get-stream.ts +0 -0
  456. /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → http/src}/api-error.ts +0 -0
  457. /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → http/src}/cors.ts +0 -0
@@ -0,0 +1,385 @@
1
+ import { join } from 'node:path';
2
+ import { syncPluginServers } from './plugin-sync.js';
3
+ import { registerConnectors } from './gates.js';
4
+ import type { ToolPolicy } from '../policy/policy.js';
5
+ import type { ToolGroup } from '@metro-labs/core/stations/types';
6
+ import { listRemoteTools, type RemoteTool } from './tools.js';
7
+ import { readJson, writeSecure } from '@metro-labs/core/secure-fs';
8
+ import { errMsg, log } from '@metro-labs/core/log';
9
+ import {
10
+ authHeaders,
11
+ connectorUrlText,
12
+ ConnectorVerifyError,
13
+ parseConnectorUrl,
14
+ verifyRemoteMcp,
15
+ type ConnectorAuth,
16
+ type OAuthAuth,
17
+ type VerifiedRecord,
18
+ } from './verify.js';
19
+ import { oauthExpired, refreshOAuth } from './oauth.js';
20
+ import { advertisesOAuth } from './oauth-discovery.js';
21
+ import {
22
+ ConnectorError,
23
+ connectorAuth,
24
+ connectorClient,
25
+ connectorName,
26
+ readConfig,
27
+ signInState,
28
+ stamp,
29
+ type ConnectorConfig,
30
+ type ConnectorSignIn,
31
+ } from './config.js';
32
+ import type { OAuthClient } from './oauth-client.js';
33
+ import { agentsDir } from '../agents/files.js';
34
+ import { newId } from '@metro-labs/core/ids';
35
+ import type { LoadedConnector } from '../stations/materialize.js';
36
+
37
+ export interface LocalConnectorRow {
38
+ id: string;
39
+ name: string;
40
+ url: string;
41
+ config: ConnectorConfig;
42
+ }
43
+
44
+ export interface Connector {
45
+ id: string;
46
+ name: string;
47
+ url: string;
48
+ auth: ConnectorAuth['kind'];
49
+ header: string | null;
50
+ signIn: ConnectorSignIn;
51
+ verified: VerifiedRecord;
52
+ client: OAuthClient | null;
53
+ policy: ToolPolicy;
54
+ }
55
+
56
+ export interface PendingConnectorInput {
57
+ name: unknown;
58
+ url: unknown;
59
+ clientId: unknown;
60
+ clientSecret: unknown;
61
+ }
62
+
63
+ export interface ConnectorInput extends PendingConnectorInput {
64
+ header: unknown;
65
+ value: unknown;
66
+ }
67
+
68
+ export type ConnectorCheck =
69
+ | { id: string; name: string; ok: true; verified: VerifiedRecord }
70
+ | { id: string; name: string; ok: false; reason: string };
71
+
72
+ export interface DeletedConnector {
73
+ id: string;
74
+ name: string;
75
+ }
76
+
77
+ const UNVERIFIED = { at: '', server: '' };
78
+
79
+ function connectorFromRow(row: LocalConnectorRow): Connector {
80
+ const auth = row.config.auth;
81
+ return {
82
+ id: row.id,
83
+ name: row.name,
84
+ url: row.url,
85
+ auth: auth.kind,
86
+ header: auth.kind === 'header' ? auth.name : null,
87
+ signIn: signInState(row.config),
88
+ verified: row.config.verified,
89
+ client: row.config.client,
90
+ policy: row.config.policy ?? {},
91
+ };
92
+ }
93
+
94
+ const FILE = 'connectors.json';
95
+ const filePath = (dir: string): string => join(dir, FILE);
96
+
97
+ const isRow = (v: unknown): v is { id: string; name: string; url: string; config?: unknown } =>
98
+ typeof v === 'object' &&
99
+ v !== null &&
100
+ typeof (v as { id?: unknown }).id === 'string' &&
101
+ typeof (v as { name?: unknown }).name === 'string' &&
102
+ typeof (v as { url?: unknown }).url === 'string';
103
+
104
+ const asList = (value: unknown): unknown[] =>
105
+ Array.isArray(value) ? value.map((item: unknown) => item) : [];
106
+
107
+ export function readLocalConnectors(dir = agentsDir()): LocalConnectorRow[] {
108
+ const raw = readJson<{ connectors?: unknown }>(filePath(dir), {}, {
109
+ warn: 'connectors.json: malformed, ignoring',
110
+ });
111
+ return asList(raw.connectors).filter(isRow).map((r) => ({
112
+ id: r.id,
113
+ name: r.name,
114
+ url: r.url,
115
+ config: readConfig(r.config),
116
+ }));
117
+ }
118
+
119
+ function writeRows(dir: string, rows: LocalConnectorRow[]): void {
120
+ writeSecure(filePath(dir), `${JSON.stringify({ version: 1, connectors: rows }, null, 2)}\n`);
121
+ if (dir !== agentsDir()) return;
122
+ syncPluginServers(rows);
123
+ registerConnectors(rows);
124
+ }
125
+
126
+ const missing = (): ConnectorError => new ConnectorError('no such connector', 404);
127
+
128
+ function rowOrThrow(id: string, dir: string): LocalConnectorRow {
129
+ const row = readLocalConnectors(dir).find((r) => r.id === id);
130
+ if (row === undefined) throw missing();
131
+ return row;
132
+ }
133
+
134
+ function replace(dir: string, row: LocalConnectorRow): Connector {
135
+ writeRows(dir, readLocalConnectors(dir).map((r) => (r.id === row.id ? row : r)));
136
+ return connectorFromRow(row);
137
+ }
138
+
139
+ async function oauthCapable(url: URL, auth: ConnectorAuth): Promise<boolean> {
140
+ if (auth.kind === 'oauth') return true;
141
+ if (auth.kind === 'header') return false;
142
+ return advertisesOAuth(url);
143
+ }
144
+
145
+ export function localImportConnectors(
146
+ rows: LoadedConnector[],
147
+ dir = agentsDir(),
148
+ mode: 'append' | 'overwrite' = 'overwrite',
149
+ ): number {
150
+ const current = readLocalConnectors(dir);
151
+ const fresh =
152
+ mode === 'overwrite'
153
+ ? rows
154
+ : rows.filter((row) => !current.some((r) => r.id === row.id || r.name === row.name));
155
+ const imported: LocalConnectorRow[] = fresh.map((row) => ({
156
+ id: row.id,
157
+ name: row.name,
158
+ url: row.url,
159
+ config: readConfig(row.config),
160
+ }));
161
+ const ids = new Set(imported.map((r) => r.id));
162
+ const names = new Set(imported.map((r) => r.name));
163
+ writeRows(dir, [...current.filter((r) => !ids.has(r.id) && !names.has(r.name)), ...imported]);
164
+ return imported.length;
165
+ }
166
+
167
+ export async function localListConnectors(dir = agentsDir()): Promise<Connector[]> {
168
+ return Promise.resolve(readLocalConnectors(dir).map(connectorFromRow));
169
+ }
170
+
171
+ export async function localGetConnector(id: string, dir = agentsDir()): Promise<Connector> {
172
+ return Promise.resolve(connectorFromRow(rowOrThrow(id, dir)));
173
+ }
174
+
175
+ function assertNameFree(name: string, exceptId: string | null, dir: string): void {
176
+ if (readLocalConnectors(dir).some((r) => r.id !== exceptId && r.name === name))
177
+ throw new ConnectorError(`a connector named '${name}' already exists on this daemon`, 409);
178
+ }
179
+
180
+ function insert(dir: string, name: string, url: URL, config: ConnectorConfig): Connector {
181
+ assertNameFree(name, null, dir);
182
+ const row: LocalConnectorRow = { id: newId(), name, url: connectorUrlText(url), config };
183
+ writeRows(dir, [...readLocalConnectors(dir), row]);
184
+ return connectorFromRow(row);
185
+ }
186
+
187
+ export async function localCreateConnector(input: ConnectorInput, dir = agentsDir()): Promise<Connector> {
188
+ const name = connectorName(input.name);
189
+ const url = parseConnectorUrl(input.url);
190
+ const auth = connectorAuth(input.header, input.value);
191
+ const client = connectorClient(input.clientId, input.clientSecret);
192
+ const verified = stamp(await verifyRemoteMcp(url, auth));
193
+ return insert(dir, name, url, {
194
+ auth,
195
+ createdAt: new Date().toISOString(),
196
+ verified,
197
+ oauth: await oauthCapable(url, auth),
198
+ client,
199
+ });
200
+ }
201
+
202
+ export async function localCreatePendingConnector(input: PendingConnectorInput, dir = agentsDir()): Promise<Connector> {
203
+ const name = connectorName(input.name);
204
+ const url = parseConnectorUrl(input.url);
205
+ const client = connectorClient(input.clientId, input.clientSecret);
206
+ return Promise.resolve(
207
+ insert(dir, name, url, { auth: { kind: 'none' }, createdAt: new Date().toISOString(), verified: UNVERIFIED, oauth: true, client }),
208
+ );
209
+ }
210
+
211
+ export async function localReconnectConnector(
212
+ id: string,
213
+ auth: OAuthAuth,
214
+ dir = agentsDir(),
215
+ ): Promise<Connector> {
216
+ const row = rowOrThrow(id, dir);
217
+ const verified = stamp(await verifyRemoteMcp(parseConnectorUrl(row.url), auth));
218
+ return replace(dir, { ...row, config: { ...row.config, auth, verified, oauth: true } });
219
+ }
220
+
221
+ const signedOut = (row: LocalConnectorRow): LocalConnectorRow => ({ ...row, config: { ...row.config, auth: { kind: 'none' }, oauth: true } });
222
+
223
+ export async function localDisconnectConnector(id: string, dir = agentsDir()): Promise<Connector> {
224
+ const row = rowOrThrow(id, dir);
225
+ if (row.config.auth.kind !== 'oauth') throw new ConnectorError('that connector is not signed in', 400);
226
+ return Promise.resolve(replace(dir, signedOut(row)));
227
+ }
228
+
229
+ export function localMarkSignedOut(id: string, dir = agentsDir()): void {
230
+ const row = readLocalConnectors(dir).find((r) => r.id === id);
231
+ if (row?.config.auth.kind !== 'oauth') return;
232
+ log.info({ id, name: row.name }, 'local relay: the vendor refused the token after a refresh; the connector is signed out');
233
+ replace(dir, signedOut(row));
234
+ }
235
+
236
+ async function freshAuth(row: LocalConnectorRow, dir: string): Promise<ConnectorAuth> {
237
+ const auth = row.config.auth;
238
+ if (auth.kind !== 'oauth' || !oauthExpired(auth)) return auth;
239
+ return refreshOnce(row, auth, dir);
240
+ }
241
+
242
+ function updateRow(dir: string, id: string, config: Partial<LocalConnectorRow['config']>): void {
243
+ const now = readLocalConnectors(dir).find((r) => r.id === id);
244
+ if (now !== undefined) replace(dir, { ...now, config: { ...now.config, ...config } });
245
+ }
246
+
247
+ export async function localVerifyConnector(id: string, dir = agentsDir()): Promise<ConnectorCheck> {
248
+ const row = rowOrThrow(id, dir);
249
+ try {
250
+ const url = parseConnectorUrl(row.url);
251
+ const auth = await freshAuth(row, dir);
252
+ const verified = stamp(await verifyRemoteMcp(url, auth));
253
+ const oauth = row.config.oauth || (await oauthCapable(url, auth));
254
+ updateRow(dir, row.id, { auth, verified, oauth });
255
+ return { id: row.id, name: row.name, ok: true, verified };
256
+ } catch (err) {
257
+ if (!(err instanceof ConnectorVerifyError)) throw err;
258
+ return { id: row.id, name: row.name, ok: false, reason: err.message };
259
+ }
260
+ }
261
+
262
+ const groupsOf = (tools: RemoteTool[]): Record<string, ToolGroup> =>
263
+ Object.fromEntries(tools.map((tool) => [tool.name, tool.readOnly ? 'read' : 'write'] as const));
264
+
265
+ function keepGroups(dir: string, id: string, tools: RemoteTool[]): void {
266
+ const now = readLocalConnectors(dir).find((r) => r.id === id);
267
+ const groups = groupsOf(tools);
268
+ if (now === undefined || JSON.stringify(now.config.toolGroups ?? {}) === JSON.stringify(groups)) return;
269
+ const config = { ...now.config };
270
+ delete config.toolGroups;
271
+ replace(dir, { ...now, config: Object.keys(groups).length > 0 ? { ...config, toolGroups: groups } : config });
272
+ }
273
+
274
+ export async function localConnectorTools(id: string, dir = agentsDir()): Promise<RemoteTool[]> {
275
+ const row = rowOrThrow(id, dir);
276
+ const url = parseConnectorUrl(row.url);
277
+ const tools = await listRemoteTools(url, await freshAuth(row, dir));
278
+ keepGroups(dir, id, tools);
279
+ return tools;
280
+ }
281
+
282
+ async function refreshGroups(id: string, dir: string): Promise<void> {
283
+ try {
284
+ await localConnectorTools(id, dir);
285
+ } catch (err) {
286
+ log.warn({ id, err: errMsg(err) }, 'connector policy: could not list the tools; the last known read and write groups stay');
287
+ }
288
+ }
289
+
290
+ export async function localSetConnectorPolicy(id: string, policy: ToolPolicy, dir = agentsDir()): Promise<Connector> {
291
+ const row = rowOrThrow(id, dir);
292
+ const config = { ...row.config };
293
+ delete config.policy;
294
+ const saved = replace(dir, { ...row, config: Object.keys(policy).length > 0 ? { ...config, policy } : config });
295
+ await refreshGroups(id, dir);
296
+ return saved;
297
+ }
298
+
299
+ export async function loadConnectorPolicies(dir = agentsDir()): Promise<void> {
300
+ const rows = readLocalConnectors(dir);
301
+ registerConnectors(rows);
302
+ for (const row of rows) if (row.config.policy !== undefined) await refreshGroups(row.id, dir);
303
+ }
304
+
305
+ export async function localRenameConnector(id: string, raw: string, dir = agentsDir()): Promise<Connector> {
306
+ const name = connectorName(raw);
307
+ const row = rowOrThrow(id, dir);
308
+ assertNameFree(name, id, dir);
309
+ return Promise.resolve(replace(dir, { ...row, name }));
310
+ }
311
+
312
+ export async function localDeleteConnector(id: string, dir = agentsDir()): Promise<DeletedConnector> {
313
+ const row = rowOrThrow(id, dir);
314
+ writeRows(dir, readLocalConnectors(dir).filter((r) => r.id !== id));
315
+ return Promise.resolve({ id: row.id, name: row.name });
316
+ }
317
+
318
+ const inflight = new Map<string, Promise<OAuthAuth>>();
319
+
320
+ function refreshOnce(row: LocalConnectorRow, auth: OAuthAuth, dir: string): Promise<OAuthAuth> {
321
+ const running = inflight.get(row.id);
322
+ if (running !== undefined) return running;
323
+ const job = refreshOAuth(auth, parseConnectorUrl(row.url).toString())
324
+ .then((fresh) => {
325
+ updateRow(dir, row.id, { auth: fresh });
326
+ return fresh;
327
+ })
328
+ .finally(() => {
329
+ inflight.delete(row.id);
330
+ });
331
+ inflight.set(row.id, job);
332
+ return job;
333
+ }
334
+
335
+ export type RelayTarget =
336
+ | { kind: 'ok'; url: string; headers: Record<string, string> }
337
+ | { kind: 'missing' }
338
+ | { kind: 'signin' };
339
+
340
+ function staleUsable(auth: OAuthAuth, now = Date.now()): boolean {
341
+ return auth.expiresAt === undefined || auth.expiresAt > now;
342
+ }
343
+
344
+ function unrefreshedTarget(
345
+ url: string,
346
+ auth: OAuthAuth,
347
+ force: boolean,
348
+ now = Date.now(),
349
+ ): RelayTarget {
350
+ if (force) return { kind: 'signin' };
351
+ return { kind: 'ok', url, headers: staleUsable(auth, now) ? authHeaders(auth) : {} };
352
+ }
353
+
354
+ function fixedTarget(
355
+ url: string,
356
+ auth: Exclude<ConnectorAuth, OAuthAuth>,
357
+ force: boolean,
358
+ ): RelayTarget {
359
+ if (force) return { kind: 'signin' };
360
+ return { kind: 'ok', url, headers: authHeaders(auth) };
361
+ }
362
+
363
+ async function oauthTarget(row: LocalConnectorRow, auth: OAuthAuth, force: boolean, dir: string): Promise<RelayTarget> {
364
+ if (!force && !oauthExpired(auth)) return { kind: 'ok', url: row.url, headers: authHeaders(auth) };
365
+ try {
366
+ const fresh = await refreshOnce(row, auth, dir);
367
+ return { kind: 'ok', url: row.url, headers: authHeaders(fresh) };
368
+ } catch (err) {
369
+ log.warn({ id: row.id, err: errMsg(err) }, 'local relay: token refresh failed');
370
+ return unrefreshedTarget(row.url, auth, force);
371
+ }
372
+ }
373
+
374
+ export async function localRelayTarget(
375
+ connectorId: string,
376
+ force: boolean,
377
+ dir = agentsDir(),
378
+ ): Promise<RelayTarget> {
379
+ const row = readLocalConnectors(dir).find((r) => r.id === connectorId);
380
+ if (row === undefined) return { kind: 'missing' };
381
+ parseConnectorUrl(row.url);
382
+ const auth = row.config.auth;
383
+ if (auth.kind === 'oauth') return oauthTarget(row, auth, force, dir);
384
+ return fixedTarget(row.url, auth, force);
385
+ }
@@ -0,0 +1,81 @@
1
+ import { isRecord } from '@metro-labs/core/is-record';
2
+ import { errMsg, log } from '@metro-labs/core/log';
3
+ import { authHeaders, INITIALIZE, mcpPost, payloadOf, refused, type ConnectorAuth } from './verify.js';
4
+
5
+ export interface RemoteTool {
6
+ name: string;
7
+ title: string;
8
+ description: string;
9
+ readOnly: boolean;
10
+ }
11
+
12
+ const TIMEOUT_MS = 15_000;
13
+ const MAX_PAGES = 10;
14
+
15
+ function resultOf(text: string, contentType: string): Record<string, unknown> | null {
16
+ let parsed: unknown = null;
17
+ try {
18
+ parsed = JSON.parse(payloadOf(text, contentType));
19
+ } catch {
20
+ return null;
21
+ }
22
+ return isRecord(parsed) && isRecord(parsed.result) ? parsed.result : null;
23
+ }
24
+
25
+ async function rpc(url: URL, auth: ConnectorAuth, session: Map<string, string>, body: unknown): Promise<Record<string, unknown> | null> {
26
+ const res = await mcpPost(url, auth, body, { timeoutMs: TIMEOUT_MS, headers: Object.fromEntries(session) });
27
+ const id = res.headers.get('mcp-session-id');
28
+ if (id !== null && id !== '') session.set('mcp-session-id', id);
29
+ const text = await res.text().catch(() => '');
30
+ if (res.status === 401 || res.status === 403) throw refused(`${url.hostname} rejected the credential; connect it again`);
31
+ if (!res.ok) throw refused(`${url.hostname} answered ${String(res.status)}`);
32
+ return resultOf(text, res.headers.get('content-type') ?? '');
33
+ }
34
+
35
+ function toTool(raw: unknown): RemoteTool | null {
36
+ if (!isRecord(raw) || typeof raw.name !== 'string') return null;
37
+ const annotations = isRecord(raw.annotations) ? raw.annotations : {};
38
+ const title = typeof raw.title === 'string' && raw.title !== '' ? raw.title : raw.name;
39
+ return { name: raw.name, title, description: typeof raw.description === 'string' ? raw.description : '', readOnly: annotations.readOnlyHint === true };
40
+ }
41
+
42
+ async function endSession(url: URL, auth: ConnectorAuth, session: Map<string, string>): Promise<void> {
43
+ if (!session.has('mcp-session-id')) return;
44
+ try {
45
+ const res = await fetch(url, { method: 'DELETE', redirect: 'manual', signal: AbortSignal.timeout(TIMEOUT_MS), headers: { ...authHeaders(auth), ...Object.fromEntries(session) } });
46
+ await res.body?.cancel();
47
+ } catch (err) {
48
+ log.debug({ host: url.hostname, err: errMsg(err) }, 'connector-tools: the remote declined to end the session');
49
+ }
50
+ }
51
+
52
+ const toolsOf = (result: Record<string, unknown>): RemoteTool[] =>
53
+ Array.isArray(result.tools) ? result.tools.flatMap((raw: unknown) => toTool(raw) ?? []) : [];
54
+
55
+ function nextCursor(result: Record<string, unknown>, current: string): string | null {
56
+ const next = result.nextCursor;
57
+ return typeof next !== 'string' || next === '' || next === current ? null : next;
58
+ }
59
+
60
+ async function walkTools(url: URL, auth: ConnectorAuth, session: Map<string, string>): Promise<RemoteTool[]> {
61
+ const tools: RemoteTool[] = [];
62
+ let cursor: string | null = '';
63
+ for (let page = 0; page < MAX_PAGES && cursor !== null; page += 1) {
64
+ const result = await rpc(url, auth, session, { jsonrpc: '2.0', id: 2 + page, method: 'tools/list', params: cursor === '' ? {} : { cursor } });
65
+ if (result === null) break;
66
+ tools.push(...toolsOf(result));
67
+ cursor = nextCursor(result, cursor);
68
+ }
69
+ return tools;
70
+ }
71
+
72
+ export async function listRemoteTools(url: URL, auth: ConnectorAuth): Promise<RemoteTool[]> {
73
+ const session = new Map<string, string>();
74
+ const init = await rpc(url, auth, session, INITIALIZE);
75
+ if (init === null || typeof init.protocolVersion !== 'string') throw refused(`${url.hostname} answered, but it does not speak MCP.`);
76
+ session.set('mcp-protocol-version', init.protocolVersion);
77
+ await rpc(url, auth, session, { jsonrpc: '2.0', method: 'notifications/initialized' });
78
+ const tools = await walkTools(url, auth, session);
79
+ await endSession(url, auth, session);
80
+ return tools;
81
+ }
@@ -0,0 +1,116 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { randomUUID, type KeyObject } from 'node:crypto';
3
+ import { isRecord } from '@metro-labs/core/is-record';
4
+ import { parseId } from '@metro-labs/core/ids';
5
+ import { log } from '@metro-labs/core/log';
6
+ import { ApiError } from '@metro-labs/http/api-error';
7
+ import { apiSession, cors, readJsonBody, requireAdmin, sendJson } from '@metro-labs/http/api-http';
8
+ import { openTransfer, sealTransfer, transferKeys } from './transfer-crypto.js';
9
+ import type { LocalConnectorRow } from './store.js';
10
+ import type { ConnectorCopyResult } from './transfer-store.js';
11
+
12
+ const PREFIX = '/api/connectors/transfer';
13
+ const MAX_BODY = 4 * 1024 * 1024 + 4096;
14
+ const TTL = 5 * 60_000;
15
+
16
+ export interface ConnectorTransferDeps {
17
+ read: () => LocalConnectorRow[];
18
+ copy: (value: unknown) => ConnectorCopyResult[];
19
+ now?: () => number;
20
+ }
21
+
22
+ interface Ticket {
23
+ privateKey: KeyObject;
24
+ organization: string;
25
+ expiresAt: number;
26
+ }
27
+
28
+ function selectedIds(raw: unknown): string[] | null {
29
+ if (raw === null) return null;
30
+ if (!Array.isArray(raw) || raw.length === 0 || raw.length > 500) throw new ApiError('choose connectors to copy', 400);
31
+ return [...new Set(raw.map((id: unknown) => {
32
+ if (typeof id !== 'string' || parseId(id) === null) throw new ApiError('choose connectors to copy', 400);
33
+ return id;
34
+ }))];
35
+ }
36
+
37
+ export class ConnectorTransfers {
38
+ private readonly tickets = new Map<string, Ticket>();
39
+
40
+ constructor(private readonly deps: ConnectorTransferDeps) {}
41
+
42
+ private prepare(organization: string): unknown {
43
+ const now = (this.deps.now ?? Date.now)();
44
+ for (const [id, ticket] of this.tickets) if (ticket.expiresAt <= now) this.tickets.delete(id);
45
+ if (this.tickets.size >= 64) throw new ApiError('too many pending connector copies, try again later', 429);
46
+ const { publicKey, privateKey } = transferKeys();
47
+ const ticket = randomUUID();
48
+ this.tickets.set(ticket, { privateKey, organization, expiresAt: now + TTL });
49
+ return { ticket, publicKey };
50
+ }
51
+
52
+ private export(body: Record<string, unknown>): unknown {
53
+ if (typeof body.publicKey !== 'string') throw new ApiError('invalid transfer key', 400);
54
+ const ids = selectedIds(body.ids);
55
+ const all = this.deps.read();
56
+ const rows = ids === null ? all : all.filter((row) => ids.includes(row.id));
57
+ const expected = ids?.length ?? rows.length;
58
+ if (rows.length !== expected) throw new ApiError('a selected connector no longer exists', 409);
59
+ if (rows.length > 500) throw new ApiError('copy at most 500 connectors at once', 413);
60
+ try {
61
+ return sealTransfer({ version: 1, connectors: rows }, body.publicKey);
62
+ } catch (err) {
63
+ if (err instanceof ApiError) throw err;
64
+ throw new ApiError('invalid transfer key', 400);
65
+ }
66
+ }
67
+
68
+ private receive(body: Record<string, unknown>, organization: string): unknown {
69
+ const id = typeof body.ticket === 'string' ? body.ticket : '';
70
+ const ticket = this.tickets.get(id);
71
+ if (ticket?.organization !== organization || ticket.expiresAt <= (this.deps.now ?? Date.now)()) {
72
+ this.tickets.delete(id);
73
+ throw new ApiError('this connector copy has expired, start again', 410);
74
+ }
75
+ let value: unknown;
76
+ try {
77
+ value = openTransfer(body.envelope, ticket.privateKey);
78
+ } catch {
79
+ throw new ApiError('invalid encrypted connector copy', 400);
80
+ }
81
+ const results = this.deps.copy(value);
82
+ this.tickets.delete(id);
83
+ return { results };
84
+ }
85
+
86
+ private async answer(req: IncomingMessage, path: string): Promise<unknown> {
87
+ const session = await apiSession(req);
88
+ if (session === null) throw new ApiError('unauthorized', 401);
89
+ requireAdmin(session);
90
+ const body = await readJsonBody(req, MAX_BODY);
91
+ if (!isRecord(body) || body.confirmed !== true) throw new ApiError('confirm copying saved logins and settings', 400);
92
+ if (path === `${PREFIX}/prepare`) return this.prepare(session.subject);
93
+ if (path === `${PREFIX}/export`) return this.export(body);
94
+ return this.receive(body, session.subject);
95
+ }
96
+
97
+ handle(req: IncomingMessage, res: ServerResponse): boolean {
98
+ const path = (req.url ?? '').split('?')[0] ?? '';
99
+ if (![`${PREFIX}/prepare`, `${PREFIX}/export`, `${PREFIX}/receive`].includes(path)) return false;
100
+ if (req.method === 'OPTIONS') {
101
+ res.writeHead(204, cors(req)).end();
102
+ return true;
103
+ }
104
+ if (req.method !== 'POST') {
105
+ sendJson(req, res, 405, { error: 'method not allowed' });
106
+ return true;
107
+ }
108
+ this.answer(req, path).then((body) => {
109
+ sendJson(req, res, 200, body);
110
+ }).catch((err: unknown) => {
111
+ if (!(err instanceof ApiError)) log.warn('connector-transfer: request failed');
112
+ sendJson(req, res, err instanceof ApiError ? err.status : 500, { error: err instanceof ApiError ? err.message : 'connector copy failed' });
113
+ });
114
+ return true;
115
+ }
116
+ }
@@ -0,0 +1,44 @@
1
+ import { createCipheriv, createDecipheriv, createPublicKey, diffieHellman, generateKeyPairSync, hkdfSync, randomBytes, type KeyObject } from 'node:crypto';
2
+ import { isRecord } from '@metro-labs/core/is-record';
3
+ import { ApiError } from '@metro-labs/http/api-error';
4
+
5
+ export interface TransferEnvelope {
6
+ key: string;
7
+ iv: string;
8
+ tag: string;
9
+ data: string;
10
+ }
11
+
12
+ export const transferKeys = (): { publicKey: string; privateKey: KeyObject } => {
13
+ const pair = generateKeyPairSync('x25519');
14
+ return { publicKey: pair.publicKey.export({ type: 'spki', format: 'pem' }).toString(), privateKey: pair.privateKey };
15
+ };
16
+
17
+ function sharedKey(privateKey: KeyObject, pem: string): ArrayBuffer {
18
+ if (pem.length > 1024) throw new ApiError('invalid transfer key', 400);
19
+ const publicKey = createPublicKey(pem);
20
+ if (publicKey.asymmetricKeyType !== 'x25519') throw new ApiError('invalid transfer key', 400);
21
+ return hkdfSync('sha256', diffieHellman({ privateKey, publicKey }), '', 'metro-connector-copy-v1', 32);
22
+ }
23
+
24
+ export function sealTransfer(value: unknown, publicKey: string): TransferEnvelope {
25
+ const pair = transferKeys();
26
+ const iv = randomBytes(12);
27
+ const cipher = createCipheriv('aes-256-gcm', Buffer.from(sharedKey(pair.privateKey, publicKey)), iv);
28
+ const plain = JSON.stringify(value);
29
+ if (Buffer.byteLength(plain) > 3 * 1024 * 1024) throw new ApiError('too many connector settings for one copy', 413);
30
+ const data = Buffer.concat([cipher.update(plain, 'utf8'), cipher.final()]);
31
+ return { key: pair.publicKey, iv: iv.toString('base64'), tag: cipher.getAuthTag().toString('base64'), data: data.toString('base64') };
32
+ }
33
+
34
+ export function openTransfer(raw: unknown, privateKey: KeyObject): unknown {
35
+ if (!isRecord(raw) || typeof raw.key !== 'string' || typeof raw.iv !== 'string' || typeof raw.tag !== 'string' || typeof raw.data !== 'string')
36
+ throw new ApiError('invalid encrypted transfer', 400);
37
+ const iv = Buffer.from(raw.iv, 'base64');
38
+ const tag = Buffer.from(raw.tag, 'base64');
39
+ if (iv.length !== 12 || tag.length !== 16) throw new ApiError('invalid encrypted transfer', 400);
40
+ const decipher = createDecipheriv('aes-256-gcm', Buffer.from(sharedKey(privateKey, raw.key)), iv);
41
+ decipher.setAuthTag(tag);
42
+ const plain = Buffer.concat([decipher.update(Buffer.from(raw.data, 'base64')), decipher.final()]);
43
+ return JSON.parse(plain.toString('utf8')) as unknown;
44
+ }
@@ -0,0 +1,48 @@
1
+ import { isDeepStrictEqual } from 'node:util';
2
+ import { isRecord } from '@metro-labs/core/is-record';
3
+ import { newId, parseId } from '@metro-labs/core/ids';
4
+ import { ApiError } from '@metro-labs/http/api-error';
5
+ import { connectorAuth, connectorName, readConfig } from './config.js';
6
+ import { parseConnectorUrl } from './url.js';
7
+ import { normalizePolicy } from '../policy/policy.js';
8
+ import { localImportConnectors, readLocalConnectors, type LocalConnectorRow } from './store.js';
9
+
10
+ export interface ConnectorCopyResult {
11
+ sourceId: string | null;
12
+ id?: string;
13
+ status: 'copied' | 'skipped' | 'invalid';
14
+ }
15
+
16
+ function copyRow(raw: unknown): LocalConnectorRow {
17
+ if (!isRecord(raw) || typeof raw.id !== 'string' || parseId(raw.id) === null || !isRecord(raw.config)) throw new ApiError('invalid connector', 400);
18
+ const name = connectorName(raw.name);
19
+ const url = parseConnectorUrl(raw.url).toString();
20
+ if (raw.config.policy !== undefined) normalizePolicy(raw.config.policy);
21
+ const config = readConfig(raw.config);
22
+ if (!isDeepStrictEqual(config, raw.config)) throw new ApiError('invalid connector settings', 400);
23
+ if (config.auth.kind === 'header') connectorAuth(config.auth.name, config.auth.value);
24
+ return { id: raw.id, name, url, config };
25
+ }
26
+
27
+ export function copyConnectorRows(value: unknown, dir?: string): ConnectorCopyResult[] {
28
+ if (!isRecord(value) || value.version !== 1 || !Array.isArray(value.connectors) || value.connectors.length > 500)
29
+ throw new ApiError('invalid connector transfer', 400);
30
+ const names = new Set(readLocalConnectors(dir).map((row) => row.name));
31
+ const imported: LocalConnectorRow[] = [];
32
+ const results: ConnectorCopyResult[] = value.connectors.map((raw: unknown) => {
33
+ const sourceId = isRecord(raw) && typeof raw.id === 'string' ? parseId(raw.id) : null;
34
+ let row: LocalConnectorRow;
35
+ try {
36
+ row = copyRow(raw);
37
+ } catch {
38
+ return { sourceId, status: 'invalid' };
39
+ }
40
+ if (names.has(row.name)) return { sourceId, status: 'skipped' };
41
+ names.add(row.name);
42
+ const id = newId();
43
+ imported.push({ ...row, id });
44
+ return { sourceId, id, status: 'copied' };
45
+ });
46
+ if (imported.length > 0) localImportConnectors(imported.map((row) => ({ ...row, config: { ...row.config } })), dir, 'append');
47
+ return results;
48
+ }