@stage-labs/metro 0.1.0-beta.2 → 0.1.0-beta.200
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.
- package/dist/api.js +11 -0
- package/dist/claude-settings.js +40 -0
- package/dist/claude.js +163 -0
- package/dist/cli.js +77 -567
- package/dist/control.js +74 -0
- package/dist/hold.js +211 -0
- package/dist/local.js +43 -0
- package/dist/mcp-config.js +25 -0
- package/dist/node-name.js +71 -0
- package/dist/onboarding.js +44 -0
- package/dist/provider-flags.js +9 -0
- package/dist/route.js +58 -0
- package/dist/runtime-install.js +97 -0
- package/dist/runtime.js +74 -0
- package/dist/serve.js +158 -0
- package/dist/service.js +243 -0
- package/dist/tail.js +102 -144
- package/dist/update.js +45 -0
- package/dist/version.js +97 -0
- package/package.json +14 -23
- package/runtime/marketplace/.claude-plugin/marketplace.json +12 -0
- package/runtime/marketplace/plugin/.claude-plugin/plugin.json +17 -0
- package/runtime/marketplace/plugin/.mcp.json +1 -0
- package/runtime/marketplace/plugin/bin/guard.mjs +71 -0
- package/runtime/marketplace/plugin/bin/metro-plugin.mjs +30 -0
- package/runtime/marketplace/plugin/bin/policy.mjs +97 -0
- package/runtime/marketplace/plugin/bin/session-start.mjs +31 -0
- package/runtime/marketplace/plugin/hooks/hooks.json +25 -0
- package/runtime/marketplace/plugin/orchestrator.md +24 -0
- package/runtime/node_modules/@metro-labs/core/package.json +47 -0
- package/runtime/node_modules/@metro-labs/core/src/endpoints.ts +7 -0
- package/runtime/node_modules/@metro-labs/core/src/events.ts +119 -0
- package/runtime/node_modules/@metro-labs/core/src/identity.ts +7 -0
- package/runtime/node_modules/@metro-labs/core/src/ids.ts +24 -0
- package/runtime/node_modules/@metro-labs/core/src/is-record.ts +2 -0
- package/runtime/node_modules/@metro-labs/core/src/lines.ts +91 -0
- package/runtime/node_modules/@metro-labs/core/src/log.ts +42 -0
- package/runtime/node_modules/@metro-labs/core/src/protocol.ts +227 -0
- package/runtime/node_modules/@metro-labs/core/src/secure-fs.ts +74 -0
- package/runtime/node_modules/@metro-labs/core/src/station-names.ts +13 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/account-files.ts +41 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/account-store.ts +60 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/attachments.ts +196 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +88 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/profile.ts +58 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/sender-profile.ts +64 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/station-runtime.ts +61 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/train-boot.ts +42 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/train-events.ts +89 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/types.ts +123 -0
- package/runtime/node_modules/@metro-labs/core/src/str.ts +5 -0
- package/runtime/node_modules/@metro-labs/core/src/tickets.ts +37 -0
- package/runtime/node_modules/@metro-labs/core/src/train-error.ts +53 -0
- package/runtime/node_modules/@metro-labs/core/src/version.ts +2 -0
- package/runtime/node_modules/@metro-labs/daemon/package.json +48 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/agent-fs.ts +134 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/files-api.ts +12 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/files.ts +119 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/home-fs.ts +113 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/job-env.ts +51 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/provision.ts +91 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedule-detail.ts +100 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules-api.ts +27 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules.ts +252 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/unit-files.ts +61 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/user.ts +91 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/view.ts +87 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/account-attach.ts +19 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/account-lookups.ts +55 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/account-routes.ts +76 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +357 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +30 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/allowlist.ts +59 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +131 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +132 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +305 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +168 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/keys.ts +35 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/map.ts +128 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/policy-route.ts +27 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/scope.ts +64 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/sender-cards.ts +86 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/senders.ts +38 -0
- package/runtime/node_modules/@metro-labs/daemon/src/approvals/api.ts +55 -0
- package/runtime/node_modules/@metro-labs/daemon/src/approvals/needs.ts +2 -0
- package/runtime/node_modules/@metro-labs/daemon/src/approvals/pending.ts +149 -0
- package/runtime/node_modules/@metro-labs/daemon/src/approvals/preview.ts +66 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +221 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +39 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/crash-guard.ts +49 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +21 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/paths.ts +106 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +26 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/dedupe.ts +12 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +370 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/media-note.ts +90 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +78 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/react-note.ts +13 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/relay.ts +129 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/reply-id.ts +1 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/webhook-note.ts +51 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +355 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +359 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +208 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/memory.ts +82 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +60 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +149 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +75 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +283 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +109 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +271 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +120 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +113 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +306 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +176 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/gates.ts +72 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/health.ts +18 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-client.ts +159 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-discovery.ts +135 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-routes.ts +158 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth.ts +166 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +131 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/reach.ts +27 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-policy.ts +90 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-target.ts +29 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +348 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +358 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +81 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +37 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +106 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +99 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-owner.ts +32 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-reaper.ts +174 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +123 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +278 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/upload-store.ts +181 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +233 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +208 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +173 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +194 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +137 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/effort.ts +77 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +123 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +237 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/frames.ts +118 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +274 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-auth.ts +160 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-client.ts +46 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-setup.ts +150 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-stream.ts +139 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-translate.ts +223 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini.ts +208 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +199 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +299 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-signin.ts +193 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-store.ts +70 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +90 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/pkce.ts +36 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +66 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +18 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/subscription.ts +120 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/text.ts +15 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +244 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +156 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/attachment-schema.ts +91 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +226 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/ctx.ts +62 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +90 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +129 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +137 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/instructions.ts +19 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/member-tools.ts +72 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-prompt.ts +87 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +79 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-gate.ts +83 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-snapshot.ts +79 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/profile-lookup.ts +60 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/profile-tool.ts +96 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/raw-get-stream.ts +207 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/read-tool.ts +87 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +46 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-slot.ts +48 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +202 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-catalog.ts +88 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-def.ts +9 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-dispatch.ts +225 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-schemas.ts +333 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/upload-tool.ts +61 -0
- package/runtime/node_modules/@metro-labs/daemon/src/metro-user/api.ts +43 -0
- package/runtime/node_modules/@metro-labs/daemon/src/metro-user/helper-script.ts +126 -0
- package/runtime/node_modules/@metro-labs/daemon/src/metro-user/move.ts +165 -0
- package/runtime/node_modules/@metro-labs/daemon/src/metro-user/privilege.ts +48 -0
- package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +132 -0
- package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +316 -0
- package/runtime/node_modules/@metro-labs/daemon/src/policy/policy.ts +118 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/bearer.ts +35 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/body.ts +24 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +365 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +208 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +71 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/threema-callback.ts +103 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +39 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +58 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/owner.ts +26 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +90 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-driver.ts +42 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-inline.ts +128 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-interactive.ts +134 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-outlook.ts +63 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +225 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-session.ts +288 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-xmtp.ts +111 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +248 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +265 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/registry.ts +51 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor-io.ts +97 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +320 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/train-call.ts +22 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/webhook-endpoints.ts +51 -0
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +84 -0
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +124 -0
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +15 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/api.ts +45 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/config.ts +51 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/firewall.ts +48 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/index.ts +96 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/install.ts +56 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/paths.ts +26 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/proxy.ts +148 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/store.ts +130 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/trust.ts +61 -0
- package/runtime/node_modules/@metro-labs/discord-bot/package.json +26 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +117 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +240 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +174 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +122 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/profile.ts +68 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +22 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +1 -0
- package/runtime/node_modules/@metro-labs/http/package.json +29 -0
- package/runtime/node_modules/@metro-labs/http/src/api-error.ts +8 -0
- package/runtime/node_modules/@metro-labs/http/src/api-http.ts +138 -0
- package/runtime/node_modules/@metro-labs/http/src/cors.ts +29 -0
- package/runtime/node_modules/@metro-labs/http/src/mode-api.ts +32 -0
- package/runtime/node_modules/@metro-labs/http/src/return-to.ts +21 -0
- package/runtime/node_modules/@metro-labs/http/src/workos-token.ts +149 -0
- package/runtime/node_modules/@metro-labs/outlook/package.json +25 -0
- package/runtime/node_modules/@metro-labs/outlook/src/accounts.ts +107 -0
- package/runtime/node_modules/@metro-labs/outlook/src/actions.ts +20 -0
- package/runtime/node_modules/@metro-labs/outlook/src/attachments.ts +97 -0
- package/runtime/node_modules/@metro-labs/outlook/src/auth.ts +150 -0
- package/runtime/node_modules/@metro-labs/outlook/src/browser.ts +53 -0
- package/runtime/node_modules/@metro-labs/outlook/src/config.ts +36 -0
- package/runtime/node_modules/@metro-labs/outlook/src/format.ts +141 -0
- package/runtime/node_modules/@metro-labs/outlook/src/graph.ts +60 -0
- package/runtime/node_modules/@metro-labs/outlook/src/html.ts +72 -0
- package/runtime/node_modules/@metro-labs/outlook/src/inbound.ts +104 -0
- package/runtime/node_modules/@metro-labs/outlook/src/index.ts +12 -0
- package/runtime/node_modules/@metro-labs/outlook/src/login.ts +150 -0
- package/runtime/node_modules/@metro-labs/outlook/src/me.ts +41 -0
- package/runtime/node_modules/@metro-labs/outlook/src/outbound.ts +116 -0
- package/runtime/node_modules/@metro-labs/outlook/src/query.ts +119 -0
- package/runtime/node_modules/@metro-labs/outlook/src/state.ts +43 -0
- package/runtime/node_modules/@metro-labs/outlook/src/station.ts +15 -0
- package/runtime/node_modules/@metro-labs/outlook/src/trust.ts +70 -0
- package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
- package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
- package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +256 -0
- package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
- package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
- package/runtime/node_modules/@metro-labs/telegram/src/format.ts +128 -0
- package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
- package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
- package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +57 -0
- package/runtime/node_modules/@metro-labs/telegram/src/index.ts +7 -0
- package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
- package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
- package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
- package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
- package/runtime/node_modules/@metro-labs/telegram/src/profile.ts +70 -0
- package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
- package/runtime/node_modules/@metro-labs/telegram/src/station.ts +27 -0
- package/runtime/node_modules/@metro-labs/telegram/src/types.ts +6 -0
- package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +119 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +246 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +173 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +103 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +95 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/profile.ts +65 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +20 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +56 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +1 -0
- package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
- package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +106 -0
- package/runtime/node_modules/@metro-labs/threema/src/actions.ts +134 -0
- package/runtime/node_modules/@metro-labs/threema/src/api.ts +152 -0
- package/runtime/node_modules/@metro-labs/threema/src/crypto.ts +100 -0
- package/runtime/node_modules/@metro-labs/threema/src/files.ts +58 -0
- package/runtime/node_modules/@metro-labs/threema/src/format.ts +169 -0
- package/runtime/node_modules/@metro-labs/threema/src/groups.ts +96 -0
- package/runtime/node_modules/@metro-labs/threema/src/ids.ts +21 -0
- package/runtime/node_modules/@metro-labs/threema/src/inbound.ts +89 -0
- package/runtime/node_modules/@metro-labs/threema/src/index.ts +24 -0
- package/runtime/node_modules/@metro-labs/threema/src/messages.ts +264 -0
- package/runtime/node_modules/@metro-labs/threema/src/outbound.ts +160 -0
- package/runtime/node_modules/@metro-labs/threema/src/station.ts +13 -0
- package/runtime/node_modules/@metro-labs/threema/src/verify.ts +117 -0
- package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
- package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
- package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +213 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +388 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +83 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +40 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +7 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/names.ts +28 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/resolve.ts +52 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +24 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +94 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +5 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +1 -0
- package/runtime/node_modules/@metro-labs/xmtp/package.json +34 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +114 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +101 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +118 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +244 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +316 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +69 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +47 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +122 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +139 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/names.ts +61 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/profile.ts +153 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/sender.ts +112 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/smart.ts +72 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +17 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +79 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +198 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +88 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +98 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +51 -0
- package/runtime/runtime.json +3 -0
- package/runtime/server.ts +1 -0
- package/runtime/stations.json +37 -0
- package/runtime/trains/.keep +0 -0
- package/LICENSE +0 -21
- package/README.md +0 -78
- package/dist/channels/discord.js +0 -148
- package/dist/channels/telegram.js +0 -137
- package/dist/lib/address.js +0 -21
- package/dist/lib/codex-rc.js +0 -274
- package/dist/lib/dotenv.js +0 -31
- package/dist/log.js +0 -12
- package/dist/paths.js +0 -45
- package/skills/metro/SKILL.md +0 -122
|
@@ -0,0 +1,358 @@
|
|
|
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 { fixedTarget, unrefreshedTarget, type RelayTarget } from './relay-target.js';
|
|
34
|
+
import { agentsDir } from '../agents/files.js';
|
|
35
|
+
import { newId } from '@metro-labs/core/ids';
|
|
36
|
+
import type { LoadedConnector } from '../stations/materialize.js';
|
|
37
|
+
|
|
38
|
+
export interface LocalConnectorRow {
|
|
39
|
+
id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
url: string;
|
|
42
|
+
config: ConnectorConfig;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface Connector {
|
|
46
|
+
id: string;
|
|
47
|
+
name: string;
|
|
48
|
+
url: string;
|
|
49
|
+
auth: ConnectorAuth['kind'];
|
|
50
|
+
header: string | null;
|
|
51
|
+
signIn: ConnectorSignIn;
|
|
52
|
+
verified: VerifiedRecord;
|
|
53
|
+
client: OAuthClient | null;
|
|
54
|
+
policy: ToolPolicy;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export interface PendingConnectorInput {
|
|
58
|
+
name: unknown;
|
|
59
|
+
url: unknown;
|
|
60
|
+
clientId: unknown;
|
|
61
|
+
clientSecret: unknown;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface ConnectorInput extends PendingConnectorInput {
|
|
65
|
+
header: unknown;
|
|
66
|
+
value: unknown;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type ConnectorCheck =
|
|
70
|
+
| { id: string; name: string; ok: true; verified: VerifiedRecord }
|
|
71
|
+
| { id: string; name: string; ok: false; reason: string };
|
|
72
|
+
|
|
73
|
+
export interface DeletedConnector {
|
|
74
|
+
id: string;
|
|
75
|
+
name: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const UNVERIFIED = { at: '', server: '' };
|
|
79
|
+
|
|
80
|
+
function connectorFromRow(row: LocalConnectorRow): Connector {
|
|
81
|
+
const auth = row.config.auth;
|
|
82
|
+
return {
|
|
83
|
+
id: row.id,
|
|
84
|
+
name: row.name,
|
|
85
|
+
url: row.url,
|
|
86
|
+
auth: auth.kind,
|
|
87
|
+
header: auth.kind === 'header' ? auth.name : null,
|
|
88
|
+
signIn: signInState(row.config),
|
|
89
|
+
verified: row.config.verified,
|
|
90
|
+
client: row.config.client,
|
|
91
|
+
policy: row.config.policy ?? {},
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const FILE = 'connectors.json';
|
|
96
|
+
const filePath = (dir: string): string => join(dir, FILE);
|
|
97
|
+
|
|
98
|
+
const isRow = (v: unknown): v is { id: string; name: string; url: string; config?: unknown } =>
|
|
99
|
+
typeof v === 'object' &&
|
|
100
|
+
v !== null &&
|
|
101
|
+
typeof (v as { id?: unknown }).id === 'string' &&
|
|
102
|
+
typeof (v as { name?: unknown }).name === 'string' &&
|
|
103
|
+
typeof (v as { url?: unknown }).url === 'string';
|
|
104
|
+
|
|
105
|
+
const asList = (value: unknown): unknown[] =>
|
|
106
|
+
Array.isArray(value) ? value.map((item: unknown) => item) : [];
|
|
107
|
+
|
|
108
|
+
export function readLocalConnectors(dir = agentsDir()): LocalConnectorRow[] {
|
|
109
|
+
const raw = readJson<{ connectors?: unknown }>(filePath(dir), {}, {
|
|
110
|
+
warn: 'connectors.json: malformed, ignoring',
|
|
111
|
+
});
|
|
112
|
+
return asList(raw.connectors).filter(isRow).map((r) => ({
|
|
113
|
+
id: r.id,
|
|
114
|
+
name: r.name,
|
|
115
|
+
url: r.url,
|
|
116
|
+
config: readConfig(r.config),
|
|
117
|
+
}));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function writeRows(dir: string, rows: LocalConnectorRow[]): void {
|
|
121
|
+
writeSecure(filePath(dir), `${JSON.stringify({ version: 1, connectors: rows }, null, 2)}\n`);
|
|
122
|
+
if (dir !== agentsDir()) return;
|
|
123
|
+
syncPluginServers(rows);
|
|
124
|
+
registerConnectors(rows);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const missing = (): ConnectorError => new ConnectorError('no such connector', 404);
|
|
128
|
+
|
|
129
|
+
function rowOrThrow(id: string, dir: string): LocalConnectorRow {
|
|
130
|
+
const row = readLocalConnectors(dir).find((r) => r.id === id);
|
|
131
|
+
if (row === undefined) throw missing();
|
|
132
|
+
return row;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function replace(dir: string, row: LocalConnectorRow): Connector {
|
|
136
|
+
writeRows(dir, readLocalConnectors(dir).map((r) => (r.id === row.id ? row : r)));
|
|
137
|
+
return connectorFromRow(row);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function oauthCapable(url: URL, auth: ConnectorAuth): Promise<boolean> {
|
|
141
|
+
if (auth.kind === 'oauth') return true;
|
|
142
|
+
if (auth.kind === 'header') return false;
|
|
143
|
+
return advertisesOAuth(url);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function localImportConnectors(
|
|
147
|
+
rows: LoadedConnector[],
|
|
148
|
+
dir = agentsDir(),
|
|
149
|
+
mode: 'append' | 'overwrite' = 'overwrite',
|
|
150
|
+
): number {
|
|
151
|
+
const current = readLocalConnectors(dir);
|
|
152
|
+
const fresh =
|
|
153
|
+
mode === 'overwrite'
|
|
154
|
+
? rows
|
|
155
|
+
: rows.filter((row) => !current.some((r) => r.id === row.id || r.name === row.name));
|
|
156
|
+
const imported: LocalConnectorRow[] = fresh.map((row) => ({
|
|
157
|
+
id: row.id,
|
|
158
|
+
name: row.name,
|
|
159
|
+
url: row.url,
|
|
160
|
+
config: readConfig(row.config),
|
|
161
|
+
}));
|
|
162
|
+
const ids = new Set(imported.map((r) => r.id));
|
|
163
|
+
const names = new Set(imported.map((r) => r.name));
|
|
164
|
+
writeRows(dir, [...current.filter((r) => !ids.has(r.id) && !names.has(r.name)), ...imported]);
|
|
165
|
+
return imported.length;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export async function localListConnectors(dir = agentsDir()): Promise<Connector[]> {
|
|
169
|
+
return Promise.resolve(readLocalConnectors(dir).map(connectorFromRow));
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export async function localGetConnector(id: string, dir = agentsDir()): Promise<Connector> {
|
|
173
|
+
return Promise.resolve(connectorFromRow(rowOrThrow(id, dir)));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function assertNameFree(name: string, exceptId: string | null, dir: string): void {
|
|
177
|
+
if (readLocalConnectors(dir).some((r) => r.id !== exceptId && r.name === name))
|
|
178
|
+
throw new ConnectorError(`a connector named '${name}' already exists on this daemon`, 409);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function insert(dir: string, name: string, url: URL, config: ConnectorConfig): Connector {
|
|
182
|
+
assertNameFree(name, null, dir);
|
|
183
|
+
const row: LocalConnectorRow = { id: newId(), name, url: connectorUrlText(url), config };
|
|
184
|
+
writeRows(dir, [...readLocalConnectors(dir), row]);
|
|
185
|
+
return connectorFromRow(row);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export async function localCreateConnector(input: ConnectorInput, dir = agentsDir()): Promise<Connector> {
|
|
189
|
+
const name = connectorName(input.name);
|
|
190
|
+
const url = parseConnectorUrl(input.url);
|
|
191
|
+
const auth = connectorAuth(input.header, input.value);
|
|
192
|
+
const client = connectorClient(input.clientId, input.clientSecret);
|
|
193
|
+
const verified = stamp(await verifyRemoteMcp(url, auth));
|
|
194
|
+
return insert(dir, name, url, {
|
|
195
|
+
auth,
|
|
196
|
+
createdAt: new Date().toISOString(),
|
|
197
|
+
verified,
|
|
198
|
+
oauth: await oauthCapable(url, auth),
|
|
199
|
+
client,
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export async function localCreatePendingConnector(input: PendingConnectorInput, dir = agentsDir()): Promise<Connector> {
|
|
204
|
+
const name = connectorName(input.name);
|
|
205
|
+
const url = parseConnectorUrl(input.url);
|
|
206
|
+
const client = connectorClient(input.clientId, input.clientSecret);
|
|
207
|
+
return Promise.resolve(
|
|
208
|
+
insert(dir, name, url, { auth: { kind: 'none' }, createdAt: new Date().toISOString(), verified: UNVERIFIED, oauth: true, client }),
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export async function localReconnectConnector(
|
|
213
|
+
id: string,
|
|
214
|
+
auth: OAuthAuth,
|
|
215
|
+
dir = agentsDir(),
|
|
216
|
+
): Promise<Connector> {
|
|
217
|
+
const row = rowOrThrow(id, dir);
|
|
218
|
+
const verified = stamp(await verifyRemoteMcp(parseConnectorUrl(row.url), auth));
|
|
219
|
+
return replace(dir, { ...row, config: { ...row.config, auth, verified, oauth: true } });
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const signedOut = (row: LocalConnectorRow): LocalConnectorRow => ({ ...row, config: { ...row.config, auth: { kind: 'none' }, oauth: true } });
|
|
223
|
+
|
|
224
|
+
export async function localDisconnectConnector(id: string, dir = agentsDir()): Promise<Connector> {
|
|
225
|
+
const row = rowOrThrow(id, dir);
|
|
226
|
+
if (row.config.auth.kind !== 'oauth') throw new ConnectorError('that connector is not signed in', 400);
|
|
227
|
+
return Promise.resolve(replace(dir, signedOut(row)));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function localMarkSignedOut(id: string, dir = agentsDir()): void {
|
|
231
|
+
const row = readLocalConnectors(dir).find((r) => r.id === id);
|
|
232
|
+
if (row?.config.auth.kind !== 'oauth') return;
|
|
233
|
+
log.info({ id, name: row.name }, 'local relay: the vendor refused the token after a refresh; the connector is signed out');
|
|
234
|
+
replace(dir, signedOut(row));
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function freshAuth(row: LocalConnectorRow, dir: string): Promise<ConnectorAuth> {
|
|
238
|
+
const auth = row.config.auth;
|
|
239
|
+
if (auth.kind !== 'oauth' || !oauthExpired(auth)) return auth;
|
|
240
|
+
return refreshOnce(row, auth, dir);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
function updateRow(dir: string, id: string, config: Partial<LocalConnectorRow['config']>): void {
|
|
244
|
+
const now = readLocalConnectors(dir).find((r) => r.id === id);
|
|
245
|
+
if (now !== undefined) replace(dir, { ...now, config: { ...now.config, ...config } });
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
export async function localVerifyConnector(id: string, dir = agentsDir()): Promise<ConnectorCheck> {
|
|
249
|
+
const row = rowOrThrow(id, dir);
|
|
250
|
+
try {
|
|
251
|
+
const url = parseConnectorUrl(row.url);
|
|
252
|
+
const auth = await freshAuth(row, dir);
|
|
253
|
+
const verified = stamp(await verifyRemoteMcp(url, auth));
|
|
254
|
+
const oauth = row.config.oauth || (await oauthCapable(url, auth));
|
|
255
|
+
updateRow(dir, row.id, { auth, verified, oauth });
|
|
256
|
+
return { id: row.id, name: row.name, ok: true, verified };
|
|
257
|
+
} catch (err) {
|
|
258
|
+
if (!(err instanceof ConnectorVerifyError)) throw err;
|
|
259
|
+
return { id: row.id, name: row.name, ok: false, reason: err.message };
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const groupsOf = (tools: RemoteTool[]): Record<string, ToolGroup> =>
|
|
264
|
+
Object.fromEntries(tools.map((tool) => [tool.name, tool.readOnly ? 'read' : 'write'] as const));
|
|
265
|
+
|
|
266
|
+
function keepGroups(dir: string, id: string, tools: RemoteTool[]): void {
|
|
267
|
+
const now = readLocalConnectors(dir).find((r) => r.id === id);
|
|
268
|
+
const groups = groupsOf(tools);
|
|
269
|
+
if (now === undefined || JSON.stringify(now.config.toolGroups ?? {}) === JSON.stringify(groups)) return;
|
|
270
|
+
const config = { ...now.config };
|
|
271
|
+
delete config.toolGroups;
|
|
272
|
+
replace(dir, { ...now, config: Object.keys(groups).length > 0 ? { ...config, toolGroups: groups } : config });
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export async function localConnectorTools(id: string, dir = agentsDir()): Promise<RemoteTool[]> {
|
|
276
|
+
const row = rowOrThrow(id, dir);
|
|
277
|
+
const url = parseConnectorUrl(row.url);
|
|
278
|
+
const tools = await listRemoteTools(url, await freshAuth(row, dir));
|
|
279
|
+
keepGroups(dir, id, tools);
|
|
280
|
+
return tools;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async function refreshGroups(id: string, dir: string): Promise<void> {
|
|
284
|
+
try {
|
|
285
|
+
await localConnectorTools(id, dir);
|
|
286
|
+
} catch (err) {
|
|
287
|
+
log.warn({ id, err: errMsg(err) }, 'connector policy: could not list the tools; the last known read and write groups stay');
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
export async function localSetConnectorPolicy(id: string, policy: ToolPolicy, dir = agentsDir()): Promise<Connector> {
|
|
292
|
+
const row = rowOrThrow(id, dir);
|
|
293
|
+
const config = { ...row.config };
|
|
294
|
+
delete config.policy;
|
|
295
|
+
const saved = replace(dir, { ...row, config: Object.keys(policy).length > 0 ? { ...config, policy } : config });
|
|
296
|
+
await refreshGroups(id, dir);
|
|
297
|
+
return saved;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
export async function loadConnectorPolicies(dir = agentsDir()): Promise<void> {
|
|
301
|
+
const rows = readLocalConnectors(dir);
|
|
302
|
+
registerConnectors(rows);
|
|
303
|
+
for (const row of rows) if (row.config.policy !== undefined) await refreshGroups(row.id, dir);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export async function localRenameConnector(id: string, raw: string, dir = agentsDir()): Promise<Connector> {
|
|
307
|
+
const name = connectorName(raw);
|
|
308
|
+
const row = rowOrThrow(id, dir);
|
|
309
|
+
assertNameFree(name, id, dir);
|
|
310
|
+
return Promise.resolve(replace(dir, { ...row, name }));
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
export async function localDeleteConnector(id: string, dir = agentsDir()): Promise<DeletedConnector> {
|
|
314
|
+
const row = rowOrThrow(id, dir);
|
|
315
|
+
writeRows(dir, readLocalConnectors(dir).filter((r) => r.id !== id));
|
|
316
|
+
return Promise.resolve({ id: row.id, name: row.name });
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const inflight = new Map<string, Promise<OAuthAuth>>();
|
|
320
|
+
|
|
321
|
+
function refreshOnce(row: LocalConnectorRow, auth: OAuthAuth, dir: string): Promise<OAuthAuth> {
|
|
322
|
+
const running = inflight.get(row.id);
|
|
323
|
+
if (running !== undefined) return running;
|
|
324
|
+
const job = refreshOAuth(auth, parseConnectorUrl(row.url).toString())
|
|
325
|
+
.then((fresh) => {
|
|
326
|
+
updateRow(dir, row.id, { auth: fresh });
|
|
327
|
+
return fresh;
|
|
328
|
+
})
|
|
329
|
+
.finally(() => {
|
|
330
|
+
inflight.delete(row.id);
|
|
331
|
+
});
|
|
332
|
+
inflight.set(row.id, job);
|
|
333
|
+
return job;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async function oauthTarget(row: LocalConnectorRow, auth: OAuthAuth, force: boolean, dir: string): Promise<RelayTarget> {
|
|
337
|
+
if (!force && !oauthExpired(auth)) return { kind: 'ok', url: row.url, headers: authHeaders(auth) };
|
|
338
|
+
try {
|
|
339
|
+
const fresh = await refreshOnce(row, auth, dir);
|
|
340
|
+
return { kind: 'ok', url: row.url, headers: authHeaders(fresh) };
|
|
341
|
+
} catch (err) {
|
|
342
|
+
log.warn({ id: row.id, err: errMsg(err) }, 'local relay: token refresh failed');
|
|
343
|
+
return unrefreshedTarget(row.url, auth, force);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export async function localRelayTarget(
|
|
348
|
+
connectorId: string,
|
|
349
|
+
force: boolean,
|
|
350
|
+
dir = agentsDir(),
|
|
351
|
+
): Promise<RelayTarget> {
|
|
352
|
+
const row = readLocalConnectors(dir).find((r) => r.id === connectorId);
|
|
353
|
+
if (row === undefined) return { kind: 'missing' };
|
|
354
|
+
parseConnectorUrl(row.url);
|
|
355
|
+
const auth = row.config.auth;
|
|
356
|
+
if (auth.kind === 'oauth') return oauthTarget(row, auth, force, dir);
|
|
357
|
+
return fixedTarget(row.url, auth, force);
|
|
358
|
+
}
|
|
@@ -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,37 @@
|
|
|
1
|
+
import { ApiError } from '@metro-labs/http/api-error';
|
|
2
|
+
|
|
3
|
+
export class ConnectorVerifyError extends ApiError {}
|
|
4
|
+
|
|
5
|
+
export class ConnectorUnauthorized extends ConnectorVerifyError {}
|
|
6
|
+
|
|
7
|
+
export function refused(message: string): ConnectorVerifyError {
|
|
8
|
+
return new ConnectorVerifyError(message, 400);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function refuseShape(url: URL): void {
|
|
12
|
+
if (url.protocol !== 'https:' && url.protocol !== 'http:')
|
|
13
|
+
throw refused('a connector url must start with https:// or http://');
|
|
14
|
+
if (url.username !== '' || url.password !== '')
|
|
15
|
+
throw refused('a connector url must not carry a user:password');
|
|
16
|
+
if (url.hash !== '')
|
|
17
|
+
throw refused('a connector url must not carry a #fragment');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function parseConnectorUrl(raw: unknown): URL {
|
|
21
|
+
const text = typeof raw === 'string' ? raw.trim() : '';
|
|
22
|
+
if (text === '') throw refused('a connector url is required');
|
|
23
|
+
let url: URL;
|
|
24
|
+
try {
|
|
25
|
+
url = new URL(text);
|
|
26
|
+
} catch {
|
|
27
|
+
throw refused('that is not a valid url');
|
|
28
|
+
}
|
|
29
|
+
refuseShape(url);
|
|
30
|
+
return url;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function connectorUrlText(url: URL): string {
|
|
34
|
+
const text = url.toString();
|
|
35
|
+
if (url.pathname !== '/' || url.search !== '') return text;
|
|
36
|
+
return text.endsWith('/') ? text.slice(0, -1) : text;
|
|
37
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { isRecord } from '@metro-labs/core/is-record';
|
|
2
|
+
import { whyUnreachable } from './reach.js';
|
|
3
|
+
import { ConnectorUnauthorized, refused } from './url.js';
|
|
4
|
+
|
|
5
|
+
export {
|
|
6
|
+
ConnectorUnauthorized,
|
|
7
|
+
ConnectorVerifyError,
|
|
8
|
+
connectorUrlText,
|
|
9
|
+
parseConnectorUrl,
|
|
10
|
+
refused,
|
|
11
|
+
} from './url.js';
|
|
12
|
+
|
|
13
|
+
export interface OAuthTokens {
|
|
14
|
+
accessToken: string;
|
|
15
|
+
refreshToken?: string;
|
|
16
|
+
expiresAt?: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface OAuthAuth extends OAuthTokens {
|
|
20
|
+
kind: 'oauth';
|
|
21
|
+
issuer: string;
|
|
22
|
+
tokenEndpoint: string;
|
|
23
|
+
clientId: string;
|
|
24
|
+
clientSecret?: string;
|
|
25
|
+
scope?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type ConnectorAuth =
|
|
29
|
+
| { kind: 'none' }
|
|
30
|
+
| { kind: 'header'; name: string; value: string }
|
|
31
|
+
| OAuthAuth;
|
|
32
|
+
|
|
33
|
+
export interface VerifiedServer {
|
|
34
|
+
server: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface VerifiedRecord extends VerifiedServer {
|
|
38
|
+
at: string;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const PROBE_TIMEOUT_MS = 10_000;
|
|
42
|
+
|
|
43
|
+
export const INITIALIZE = {
|
|
44
|
+
jsonrpc: '2.0',
|
|
45
|
+
id: 1,
|
|
46
|
+
method: 'initialize',
|
|
47
|
+
params: { protocolVersion: '2025-11-25', capabilities: {}, clientInfo: { name: 'metro', version: '0.1.0' } },
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export function authHeaders(auth: ConnectorAuth): Record<string, string> {
|
|
51
|
+
if (auth.kind === 'header') return { [auth.name]: auth.value };
|
|
52
|
+
if (auth.kind === 'oauth') return { Authorization: `Bearer ${auth.accessToken}` };
|
|
53
|
+
return {};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function payloadOf(text: string, contentType: string): string {
|
|
57
|
+
if (!contentType.includes('text/event-stream')) return text;
|
|
58
|
+
for (const line of text.replace(/\r\n/g, '\n').split('\n')) if (line.startsWith('data:')) return line.slice(5).trim();
|
|
59
|
+
return '';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export async function mcpPost(
|
|
63
|
+
url: URL,
|
|
64
|
+
auth: ConnectorAuth,
|
|
65
|
+
body: unknown,
|
|
66
|
+
opts: { timeoutMs: number; headers?: Record<string, string> },
|
|
67
|
+
): Promise<Response> {
|
|
68
|
+
try {
|
|
69
|
+
return await fetch(url, {
|
|
70
|
+
method: 'POST',
|
|
71
|
+
redirect: 'manual',
|
|
72
|
+
signal: AbortSignal.timeout(opts.timeoutMs),
|
|
73
|
+
headers: { 'content-type': 'application/json', accept: 'application/json, text/event-stream', ...authHeaders(auth), ...opts.headers },
|
|
74
|
+
body: JSON.stringify(body),
|
|
75
|
+
});
|
|
76
|
+
} catch (err) {
|
|
77
|
+
throw refused(`Metro could not reach ${url.hostname}: ${whyUnreachable(err)}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function serverNameOf(url: URL, text: string, contentType: string): string {
|
|
82
|
+
let parsed: unknown;
|
|
83
|
+
try {
|
|
84
|
+
parsed = JSON.parse(payloadOf(text, contentType));
|
|
85
|
+
} catch {
|
|
86
|
+
throw refused(`${url.hostname} answered, but it does not speak MCP.`);
|
|
87
|
+
}
|
|
88
|
+
const result = isRecord(parsed) && isRecord(parsed.result) ? parsed.result : null;
|
|
89
|
+
if (result === null || typeof result.protocolVersion !== 'string') throw refused(`${url.hostname} answered, but it does not speak MCP.`);
|
|
90
|
+
const info = isRecord(result.serverInfo) ? result.serverInfo : {};
|
|
91
|
+
const title = typeof info.title === 'string' ? info.title : '';
|
|
92
|
+
if (title !== '') return title;
|
|
93
|
+
return typeof info.name === 'string' ? info.name : url.hostname;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function verifyRemoteMcp(url: URL, auth: ConnectorAuth): Promise<VerifiedServer> {
|
|
97
|
+
const res = await mcpPost(url, auth, INITIALIZE, { timeoutMs: PROBE_TIMEOUT_MS });
|
|
98
|
+
const text = await res.text().catch(() => '');
|
|
99
|
+
if (res.status >= 300 && res.status < 400) throw refused('that url redirects — use the url it redirects to');
|
|
100
|
+
if (res.status === 401 || res.status === 403)
|
|
101
|
+
throw new ConnectorUnauthorized(auth.kind === 'none' ? `${url.hostname} requires authorization.` : `${url.hostname} rejected that credential.`, 400);
|
|
102
|
+
if (res.status === 404 || res.status === 405)
|
|
103
|
+
throw refused(`${url.hostname} did not accept an MCP initialize over HTTP POST. If it is a legacy SSE server, Metro cannot verify it yet.`);
|
|
104
|
+
if (!res.ok) throw refused(`${url.hostname} answered ${String(res.status)} instead of an MCP initialize.`);
|
|
105
|
+
return { server: serverNameOf(url, text, res.headers.get('content-type') ?? '') };
|
|
106
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { randomBytes, timingSafeEqual } from 'node:crypto';
|
|
2
|
+
import { ID_RE } from '@metro-labs/core/ids';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { resolveCachedAttachment } from '@metro-labs/core/stations/attachments';
|
|
5
|
+
import { writeSecure } from '@metro-labs/core/secure-fs';
|
|
6
|
+
|
|
7
|
+
const GRANT_SUFFIX = '.grant';
|
|
8
|
+
|
|
9
|
+
export interface AttachmentGrant {
|
|
10
|
+
token: string;
|
|
11
|
+
agentId: string;
|
|
12
|
+
mintedAt: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function newAttachmentToken(prefix = 'at'): string {
|
|
16
|
+
return `${prefix}_${randomBytes(32).toString('base64url')}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function parseGrant(raw: string): AttachmentGrant | undefined {
|
|
20
|
+
let parsed: unknown;
|
|
21
|
+
try {
|
|
22
|
+
parsed = JSON.parse(raw);
|
|
23
|
+
} catch {
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
if (typeof parsed !== 'object' || parsed === null) return undefined;
|
|
27
|
+
const { token, agentId, mintedAt } = parsed as Record<string, unknown>;
|
|
28
|
+
if (typeof token !== 'string' || token === '') return undefined;
|
|
29
|
+
if (typeof agentId !== 'string' || !ID_RE.test(agentId)) return undefined;
|
|
30
|
+
return {
|
|
31
|
+
token,
|
|
32
|
+
agentId,
|
|
33
|
+
mintedAt: typeof mintedAt === 'number' ? mintedAt : 0,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function readGrant(path: string): AttachmentGrant | undefined {
|
|
38
|
+
try {
|
|
39
|
+
return parseGrant(readFileSync(`${path}${GRANT_SUFFIX}`, 'utf8'));
|
|
40
|
+
} catch {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export function issueGrant(
|
|
46
|
+
path: string,
|
|
47
|
+
agentId: string,
|
|
48
|
+
prefix?: string,
|
|
49
|
+
): string {
|
|
50
|
+
const existing = readGrant(path);
|
|
51
|
+
if (existing?.agentId === agentId) return existing.token;
|
|
52
|
+
const grant: AttachmentGrant = {
|
|
53
|
+
token: newAttachmentToken(prefix),
|
|
54
|
+
agentId,
|
|
55
|
+
mintedAt: Date.now(),
|
|
56
|
+
};
|
|
57
|
+
writeSecure(`${path}${GRANT_SUFFIX}`, `${JSON.stringify(grant)}\n`);
|
|
58
|
+
return grant.token;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function sameToken(presented: string, stored: string): boolean {
|
|
62
|
+
const a = Buffer.from(presented, 'utf8');
|
|
63
|
+
const b = Buffer.from(stored, 'utf8');
|
|
64
|
+
return a.length === b.length && timingSafeEqual(a, b);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function grantAllowsPath(
|
|
68
|
+
path: string,
|
|
69
|
+
owner: string,
|
|
70
|
+
presented: string,
|
|
71
|
+
): boolean {
|
|
72
|
+
if (presented === '') return false;
|
|
73
|
+
const grant = readGrant(path);
|
|
74
|
+
if (grant === undefined) return false;
|
|
75
|
+
if (grant.agentId !== owner) return false;
|
|
76
|
+
return sameToken(presented, grant.token);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function readAttachmentGrant(name: string): AttachmentGrant | undefined {
|
|
80
|
+
const path = resolveCachedAttachment(name);
|
|
81
|
+
return path === null ? undefined : readGrant(path);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function issueAttachmentGrant(
|
|
85
|
+
name: string,
|
|
86
|
+
agentId: string,
|
|
87
|
+
): string | undefined {
|
|
88
|
+
const path = resolveCachedAttachment(name);
|
|
89
|
+
return path === null ? undefined : issueGrant(path, agentId);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export function grantAllows(
|
|
93
|
+
name: string,
|
|
94
|
+
owner: string,
|
|
95
|
+
presented: string,
|
|
96
|
+
): boolean {
|
|
97
|
+
const path = resolveCachedAttachment(name);
|
|
98
|
+
return path === null ? false : grantAllowsPath(path, owner, presented);
|
|
99
|
+
}
|