@stage-labs/metro 0.1.0-beta.21 → 0.1.0-beta.211
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 +9 -101
- package/dist/claude-settings.js +40 -0
- package/dist/claude.js +152 -0
- package/dist/cli.js +52 -109
- package/dist/control.js +47 -75
- package/dist/hold.js +210 -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 +96 -0
- package/dist/runtime.js +29 -52
- package/dist/serve.js +157 -0
- package/dist/service.js +304 -0
- package/dist/tail.js +119 -0
- package/dist/update.js +13 -1
- package/package.json +2 -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 +26 -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/{mcp/src/daemon → core/src}/events.ts +11 -7
- package/runtime/node_modules/@metro-labs/{mcp/src/db → core/src}/ids.ts +5 -0
- package/runtime/node_modules/@metro-labs/core/src/is-record.ts +2 -0
- package/runtime/node_modules/@metro-labs/{mcp/src/stations → core/src}/lines.ts +24 -28
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/protocol.ts +7 -4
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/secure-fs.ts +17 -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 +83 -0
- package/runtime/node_modules/@metro-labs/{mcp → core}/src/stations/attachments.ts +0 -1
- package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +16 -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/{mcp → core}/src/stations/station-runtime.ts +6 -11
- 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 +84 -0
- package/runtime/node_modules/@metro-labs/{mcp → core}/src/stations/types.ts +18 -1
- package/runtime/node_modules/@metro-labs/core/src/tickets.ts +37 -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 +89 -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 +80 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedule-detail.ts +99 -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 +144 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/unit-files.ts +37 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/user.ts +89 -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 +142 -0
- package/runtime/node_modules/@metro-labs/{mcp/src/db/key-map.ts → daemon/src/agents/keys.ts} +1 -11
- package/runtime/node_modules/@metro-labs/{mcp/src/db/agent-map.ts → daemon/src/agents/map.ts} +58 -1
- package/runtime/node_modules/@metro-labs/daemon/src/agents/policy-route.ts +27 -0
- package/runtime/node_modules/@metro-labs/{mcp/src/db/agent-scope.ts → daemon/src/agents/scope.ts} +2 -2
- package/runtime/node_modules/@metro-labs/daemon/src/agents/sender-cards.ts +94 -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/pending.ts +150 -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 +218 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +36 -0
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/boot}/crash-guard.ts +1 -1
- package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +21 -0
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/boot}/paths.ts +6 -34
- package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +26 -0
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/inbound.ts +70 -45
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/media-note.ts +11 -7
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/pending.ts +4 -1
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/relay.ts +2 -2
- 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 +32 -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 +313 -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 +273 -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/{mcp/src/daemon/connector-api.ts → daemon/src/connectors/api.ts} +79 -113
- package/runtime/node_modules/@metro-labs/{mcp/src/db/connector-config.ts → daemon/src/connectors/config.ts} +67 -41
- 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/{mcp/src/daemon → daemon/src/connectors}/oauth-client.ts +14 -17
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/connectors}/oauth-discovery.ts +33 -20
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon/connector-oauth-routes.ts → daemon/src/connectors/oauth-routes.ts} +30 -32
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon/connector-oauth.ts → daemon/src/connectors/oauth.ts} +61 -22
- 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.ts +347 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +385 -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/{mcp/src/daemon → daemon/src/files}/attach-grant.ts +4 -4
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/attach-owner.ts +3 -3
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-reaper.ts +174 -0
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/attach-serve.ts +12 -13
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/upload-api.ts +10 -27
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/files}/upload-store.ts +4 -4
- 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 +195 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-version.ts +44 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +128 -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 +224 -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 +278 -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 +14 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +244 -0
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/accounts.ts +28 -17
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/attachment-schema.ts +91 -0
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/call-tools.ts +49 -20
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/ctx.ts +4 -4
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/event-store.ts +15 -16
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/group-tools.ts +2 -2
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/index.ts +24 -79
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/instructions.ts +19 -0
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/member-tools.ts +2 -2
- 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 +82 -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/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/{mcp → daemon}/src/mcp/session.ts +33 -42
- 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/{mcp → daemon}/src/mcp/tool-dispatch.ts +53 -40
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/tool-schemas.ts +68 -119
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/upload-tool.ts +6 -11
- 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/install.ts +37 -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/{mcp/src/daemon → daemon/src/routes}/http.ts +59 -51
- package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +205 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +66 -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/{mcp → daemon}/src/stations/attach-inline.ts +29 -1
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-interactive.ts +9 -36
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-outlook.ts +63 -0
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-resolve.ts +38 -13
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/attach-session.ts +32 -22
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach-xmtp.ts +8 -9
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/attach.ts +75 -9
- package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +265 -0
- package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/stations/registry.ts +22 -6
- package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/supervisor-io.ts +3 -3
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/supervisor.ts +6 -9
- package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon → daemon/src/stations}/train-call.ts +1 -1
- 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 +9 -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 +147 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/store.ts +129 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/trust.ts +44 -0
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/package.json +3 -5
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/accounts.ts +8 -28
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/actions.ts +19 -187
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/attachments.ts +3 -3
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/format.ts +31 -72
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/group-actions.ts +2 -2
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/group.ts +2 -2
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/index.ts +15 -21
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/members.ts +2 -2
- package/runtime/node_modules/@metro-labs/discord-bot/src/profile.ts +68 -0
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/send-files.ts +12 -13
- package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +23 -0
- package/runtime/node_modules/@metro-labs/http/package.json +29 -0
- package/runtime/node_modules/@metro-labs/http/src/api-http.ts +135 -0
- package/runtime/node_modules/@metro-labs/{mcp/src/daemon/session-api.ts → http/src/mode-api.ts} +13 -9
- 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/{telegram-user → outlook}/package.json +2 -5
- package/runtime/node_modules/@metro-labs/outlook/src/accounts.ts +93 -0
- package/runtime/node_modules/@metro-labs/outlook/src/actions.ts +14 -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 +140 -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 +3 -2
- package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +38 -96
- package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +214 -253
- package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +67 -108
- package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/client.ts +6 -6
- package/runtime/node_modules/@metro-labs/telegram/src/format.ts +101 -147
- package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/group.ts +4 -4
- package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/history.ts +1 -1
- package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +57 -0
- package/runtime/node_modules/@metro-labs/telegram/src/index.ts +6 -102
- package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/login.ts +20 -20
- package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +64 -151
- package/runtime/node_modules/@metro-labs/telegram/src/members.ts +61 -33
- package/runtime/node_modules/@metro-labs/telegram/src/profile.ts +70 -0
- package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/reactions.ts +3 -3
- package/runtime/node_modules/@metro-labs/telegram/src/station.ts +11 -1
- package/runtime/node_modules/@metro-labs/telegram/src/types.ts +5 -47
- package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +86 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +240 -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 +170 -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 +90 -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 +85 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +21 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +56 -0
- package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
- package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +95 -0
- package/runtime/node_modules/@metro-labs/threema/src/actions.ts +124 -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 +47 -0
- package/runtime/node_modules/@metro-labs/threema/src/format.ts +164 -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 +1 -1
- package/runtime/node_modules/@metro-labs/webhook/src/station.ts +5 -8
- package/runtime/node_modules/@metro-labs/whatsapp/package.json +2 -5
- package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +6 -28
- package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +1 -1
- package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +54 -11
- package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +2 -2
- package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +1 -1
- package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +86 -4
- package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +7 -58
- package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +11 -28
- package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +4 -37
- package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +1 -1
- package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +1 -1
- package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +1 -1
- package/runtime/node_modules/@metro-labs/whatsapp/src/names.ts +75 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +44 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/resolve.ts +52 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +16 -1
- package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +5 -9
- package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +0 -1
- package/runtime/node_modules/@metro-labs/xmtp/package.json +3 -2
- package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +10 -20
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +2 -2
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +15 -204
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +4 -43
- package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +41 -219
- package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +3 -3
- package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +3 -3
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +7 -24
- package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +8 -18
- package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +1 -1
- package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +2 -2
- package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +30 -16
- package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +12 -20
- package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +1 -19
- package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +1 -1
- 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 +5 -1
- package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +4 -51
- package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +30 -44
- package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +4 -6
- package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +19 -5
- package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +14 -4
- package/runtime/runtime.json +1 -1
- package/runtime/server.ts +1 -0
- package/runtime/stations.json +37 -0
- package/dist/prompt.js +0 -10
- package/dist/store.js +0 -70
- package/runtime/node_modules/@metro-labs/discord/src/station.ts +0 -19
- package/runtime/node_modules/@metro-labs/discord/src/voice-speak.ts +0 -195
- package/runtime/node_modules/@metro-labs/discord/src/voice-transcribe.ts +0 -292
- package/runtime/node_modules/@metro-labs/discord/src/voice.ts +0 -197
- package/runtime/node_modules/@metro-labs/discord/src/wire.ts +0 -5
- package/runtime/node_modules/@metro-labs/mcp/package.json +0 -66
- package/runtime/node_modules/@metro-labs/mcp/src/channels/dedupe.ts +0 -24
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/account-api.ts +0 -311
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +0 -335
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-http.ts +0 -129
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/boot.ts +0 -322
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair-api.ts +0 -111
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair.ts +0 -20
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/collection-api.ts +0 -202
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-json.ts +0 -31
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-tools.ts +0 -100
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-url.ts +0 -57
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-verify.ts +0 -333
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-auth.ts +0 -164
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-oauth.ts +0 -208
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +0 -79
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-pending.ts +0 -65
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/pair-codes.ts +0 -58
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/project-api.ts +0 -214
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-api.ts +0 -125
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-pair.ts +0 -18
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/runtime-source.ts +0 -105
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-apis.ts +0 -41
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/session.ts +0 -218
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/tunnel.ts +0 -169
- package/runtime/node_modules/@metro-labs/mcp/src/db/account-attach.ts +0 -91
- package/runtime/node_modules/@metro-labs/mcp/src/db/agent-admin.ts +0 -215
- package/runtime/node_modules/@metro-labs/mcp/src/db/client.ts +0 -37
- package/runtime/node_modules/@metro-labs/mcp/src/db/connector-collections.ts +0 -218
- package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +0 -329
- package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +0 -364
- package/runtime/node_modules/@metro-labs/mcp/src/db/projects.ts +0 -300
- package/runtime/node_modules/@metro-labs/mcp/src/db/runtime-admin.ts +0 -31
- package/runtime/node_modules/@metro-labs/mcp/src/db/runtimes.ts +0 -105
- package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +0 -131
- package/runtime/node_modules/@metro-labs/mcp/src/db/token-carry.ts +0 -45
- package/runtime/node_modules/@metro-labs/mcp/src/db/users.ts +0 -57
- package/runtime/node_modules/@metro-labs/mcp/src/db/whatsapp-login.ts +0 -18
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/channel-owner.ts +0 -29
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/frame-scope.ts +0 -21
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/permission-relay.ts +0 -66
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/request-identity.ts +0 -73
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-registry.ts +0 -172
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-route.ts +0 -43
- package/runtime/node_modules/@metro-labs/mcp/src/monitor/api.ts +0 -240
- package/runtime/node_modules/@metro-labs/mcp/src/server.ts +0 -2
- package/runtime/node_modules/@metro-labs/mcp/src/stations/account-store.ts +0 -80
- package/runtime/node_modules/@metro-labs/mcp/src/stations/messaging-normalize.ts +0 -88
- package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +0 -3
- package/runtime/node_modules/@metro-labs/telegram-user/src/accounts.ts +0 -81
- package/runtime/node_modules/@metro-labs/telegram-user/src/actions.ts +0 -254
- package/runtime/node_modules/@metro-labs/telegram-user/src/attachments.ts +0 -87
- package/runtime/node_modules/@metro-labs/telegram-user/src/format.ts +0 -141
- package/runtime/node_modules/@metro-labs/telegram-user/src/inbound.ts +0 -78
- package/runtime/node_modules/@metro-labs/telegram-user/src/index.ts +0 -46
- package/runtime/node_modules/@metro-labs/telegram-user/src/media-actions.ts +0 -80
- package/runtime/node_modules/@metro-labs/telegram-user/src/members.ts +0 -75
- package/runtime/node_modules/@metro-labs/telegram-user/src/normalize.ts +0 -22
- package/runtime/node_modules/@metro-labs/telegram-user/src/station.ts +0 -24
- package/runtime/node_modules/@metro-labs/telegram-user/src/types.ts +0 -7
- package/runtime/node_modules/@metro-labs/telegram-user/src/wire.ts +0 -1
- package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +0 -22
- package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +0 -5
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +0 -87
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +0 -55
- package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +0 -349
- /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/log.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{mcp/src/mcp → core/src}/str.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → core/src}/train-error.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/channels/webhook-note.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{mcp → daemon}/src/mcp/raw-get-stream.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/api-base.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/verify.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → http/src}/api-error.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{mcp/src/daemon → http/src}/cors.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{telegram → telegram-bot}/src/verify.ts +0 -0
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { parsePolicy } from '../policy/policy.js';
|
|
2
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
3
|
+
import { errMsg, log } from '@metro-labs/core/log';
|
|
4
|
+
import { apiFailure, apiSession, cors, readJsonBody, requireAdmin, sendJson } from '@metro-labs/http/api-http';
|
|
5
|
+
import { ApiError } from '@metro-labs/http/api-error';
|
|
6
|
+
import { isRecord } from '@metro-labs/core/is-record';
|
|
7
|
+
import { parseId } from '@metro-labs/core/ids';
|
|
8
|
+
import type { LoadedAccount, LoadedAgent, LoadedConnector } from '../stations/materialize.js';
|
|
9
|
+
import { STATIONS } from '@metro-labs/core/station-names';
|
|
10
|
+
import { approversOf } from './files.js';
|
|
11
|
+
|
|
12
|
+
const AGENTS = '/api/agents';
|
|
13
|
+
const RESTORE_PATH = `${AGENTS}/restore`;
|
|
14
|
+
const BUNDLE_MAX = 2 * 1024 * 1024;
|
|
15
|
+
const STATION_NAMES = new Set<string>(STATIONS);
|
|
16
|
+
|
|
17
|
+
export interface AgentBundle {
|
|
18
|
+
version: 1;
|
|
19
|
+
agent: { id: string; name: string; stations: LoadedAccount[] };
|
|
20
|
+
connectors: LoadedConnector[];
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export const loadedAgentOf = (bundle: AgentBundle): LoadedAgent => ({
|
|
24
|
+
id: bundle.agent.id,
|
|
25
|
+
name: bundle.agent.name,
|
|
26
|
+
key: null,
|
|
27
|
+
accounts: bundle.agent.stations,
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
interface RestoredAgent {
|
|
31
|
+
id: string;
|
|
32
|
+
name: string;
|
|
33
|
+
stations: number;
|
|
34
|
+
connectors: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
type ImportMode = 'append' | 'overwrite';
|
|
38
|
+
|
|
39
|
+
export interface BundleApiDeps {
|
|
40
|
+
bundle: (agentId: string) => Promise<AgentBundle>;
|
|
41
|
+
restore: (bundle: AgentBundle, mode: ImportMode) => Promise<RestoredAgent>;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parseMode(raw: unknown): ImportMode {
|
|
45
|
+
const mode = isRecord(raw) && typeof raw.mode === 'string' ? raw.mode : 'overwrite';
|
|
46
|
+
if (mode !== 'append' && mode !== 'overwrite') throw bad("mode is 'append' or 'overwrite'");
|
|
47
|
+
return mode;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const bad = (what: string): ApiError => new ApiError(`bundle: ${what}`, 400);
|
|
51
|
+
|
|
52
|
+
function stationOf(raw: unknown): LoadedAccount {
|
|
53
|
+
if (!isRecord(raw) || typeof raw.station !== 'string' || !STATION_NAMES.has(raw.station))
|
|
54
|
+
throw bad('a station is not a known station');
|
|
55
|
+
if (typeof raw.id !== 'string' || !isRecord(raw.config)) throw bad('a station has no id or config');
|
|
56
|
+
const allowlist = Array.isArray(raw.allowlist)
|
|
57
|
+
? raw.allowlist.filter((s): s is string => typeof s === 'string')
|
|
58
|
+
: null;
|
|
59
|
+
const policy = parsePolicy(raw.policy, `bundle ${raw.station}/${raw.id}`);
|
|
60
|
+
return {
|
|
61
|
+
station: raw.station as LoadedAccount['station'],
|
|
62
|
+
id: raw.id,
|
|
63
|
+
allowlist,
|
|
64
|
+
...approversOf(raw.approvers),
|
|
65
|
+
enabled: raw.enabled !== false,
|
|
66
|
+
...(policy === undefined ? {} : { policy }),
|
|
67
|
+
config: raw.config,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function connectorOf(raw: unknown): LoadedConnector {
|
|
72
|
+
if (!isRecord(raw) || typeof raw.id !== 'string' || typeof raw.name !== 'string' || typeof raw.url !== 'string')
|
|
73
|
+
throw bad('a connector is missing its id, name or url');
|
|
74
|
+
if (!isRecord(raw.config)) throw bad('a connector has no config');
|
|
75
|
+
return { id: raw.id, name: raw.name, url: raw.url, config: raw.config };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function agentOf(raw: unknown): AgentBundle['agent'] {
|
|
79
|
+
if (!isRecord(raw)) throw bad('not a v1 agent bundle');
|
|
80
|
+
const { id, name, stations } = raw;
|
|
81
|
+
if (typeof id !== 'string' || parseId(id) === null) throw bad('agent id is not an id');
|
|
82
|
+
if (!Array.isArray(stations)) throw bad('agent has no station list');
|
|
83
|
+
return { id, name: typeof name === 'string' ? name : '', stations: stations.map(stationOf) };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function parseBundle(raw: unknown): AgentBundle {
|
|
87
|
+
if (!isRecord(raw) || raw.version !== 1) throw bad('not a v1 agent bundle');
|
|
88
|
+
const connectors = Array.isArray(raw.connectors) ? raw.connectors : [];
|
|
89
|
+
return { version: 1, agent: agentOf(raw.agent), connectors: connectors.map(connectorOf) };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function answer(req: IncomingMessage, deps: BundleApiDeps, path: string): Promise<unknown> {
|
|
93
|
+
if (path === RESTORE_PATH) {
|
|
94
|
+
const body: unknown = await readJsonBody(req, BUNDLE_MAX);
|
|
95
|
+
return deps.restore(parseBundle(body), parseMode(body));
|
|
96
|
+
}
|
|
97
|
+
const id = parseId(path.slice(AGENTS.length + 1).split('/')[0] ?? '');
|
|
98
|
+
if (id === null) throw new ApiError('no such agent', 404);
|
|
99
|
+
return deps.bundle(id);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function handleBundleRequest(req: IncomingMessage, res: ServerResponse, deps: BundleApiDeps): boolean {
|
|
103
|
+
const path = (req.url ?? '').split('?')[0] ?? '';
|
|
104
|
+
const isRestore = path === RESTORE_PATH;
|
|
105
|
+
const isBundle = /^\/api\/agents\/[^/]+\/bundle$/.test(path);
|
|
106
|
+
if (!isRestore && !isBundle) return false;
|
|
107
|
+
if (req.method === 'OPTIONS') {
|
|
108
|
+
res.writeHead(204, cors(req)).end();
|
|
109
|
+
return true;
|
|
110
|
+
}
|
|
111
|
+
if (req.method !== (isRestore ? 'POST' : 'GET')) {
|
|
112
|
+
sendJson(req, res, 405, { error: 'method not allowed' });
|
|
113
|
+
return true;
|
|
114
|
+
}
|
|
115
|
+
apiSession(req)
|
|
116
|
+
.then((session) => {
|
|
117
|
+
if (!session) throw new ApiError('unauthorized', 401);
|
|
118
|
+
requireAdmin(session);
|
|
119
|
+
return answer(req, deps, path);
|
|
120
|
+
})
|
|
121
|
+
.then((body) => {
|
|
122
|
+
sendJson(req, res, isRestore ? 201 : 200, body);
|
|
123
|
+
})
|
|
124
|
+
.catch((err: unknown) => {
|
|
125
|
+
if (err instanceof ApiError) apiFailure(req, res, err, 'bundle-api');
|
|
126
|
+
else {
|
|
127
|
+
log.warn({ err: errMsg(err) }, 'bundle-api: request failed');
|
|
128
|
+
if (!res.headersSent) sendJson(req, res, 500, { error: 'bundle api failed' });
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { ApiError } from '@metro-labs/http/api-error';
|
|
4
|
+
import { isOrganizationId } from '@metro-labs/http/workos-token';
|
|
5
|
+
import { ensureSecureDir, writeSecure } from '@metro-labs/core/secure-fs';
|
|
6
|
+
import {
|
|
7
|
+
newApiKey,
|
|
8
|
+
AgentAdminError,
|
|
9
|
+
normalizeAgentName,
|
|
10
|
+
type AgentSummary,
|
|
11
|
+
type CreatedAgent,
|
|
12
|
+
} from './admin.js';
|
|
13
|
+
import type { AccountRef } from './account-attach.js';
|
|
14
|
+
import {
|
|
15
|
+
agentFilePath,
|
|
16
|
+
AgentFileError,
|
|
17
|
+
agentsDir,
|
|
18
|
+
listAgentFiles,
|
|
19
|
+
parseAgentFile,
|
|
20
|
+
readAgentFile,
|
|
21
|
+
type AgentFile,
|
|
22
|
+
} from './files.js';
|
|
23
|
+
import { newId } from '@metro-labs/core/ids';
|
|
24
|
+
import { registerKey } from './keys.js';
|
|
25
|
+
import { MOVABLE_STATIONS, type LoadedAgent } from '../stations/materialize.js';
|
|
26
|
+
import type { StationName } from '@metro-labs/core/station-names';
|
|
27
|
+
import type { ToolPolicy } from '../policy/policy.js';
|
|
28
|
+
|
|
29
|
+
const OWNER_FILE = '.owner';
|
|
30
|
+
|
|
31
|
+
interface Stored {
|
|
32
|
+
path: string;
|
|
33
|
+
file: AgentFile;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const missing = (): AgentAdminError => new AgentAdminError('no such agent', 404);
|
|
37
|
+
|
|
38
|
+
function parseOwner(raw: string): string | null {
|
|
39
|
+
const text = raw.trim();
|
|
40
|
+
return isOrganizationId(text) ? text : null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function localOwner(dir = agentsDir()): string | null {
|
|
44
|
+
try {
|
|
45
|
+
return parseOwner(readFileSync(join(dir, OWNER_FILE), 'utf8'));
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function setLocalOwner(raw: string, dir = agentsDir()): string {
|
|
52
|
+
const owner = parseOwner(raw);
|
|
53
|
+
if (owner === null) throw new ApiError(`'${raw}' is not an organization id`, 400);
|
|
54
|
+
ensureSecureDir(dir);
|
|
55
|
+
writeSecure(join(dir, OWNER_FILE), `${owner}\n`);
|
|
56
|
+
return owner;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function storedAgents(dir: string): Stored[] {
|
|
60
|
+
return listAgentFiles(dir).map((path) => ({ path, file: readAgentFile(path) }));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function save(stored: Stored): void {
|
|
64
|
+
writeSecure(stored.path, `${JSON.stringify(stored.file, null, 2)}\n`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function agentOrThrow(id: string, dir: string): Stored {
|
|
68
|
+
const found = storedAgents(dir).find((s) => s.file.id === id);
|
|
69
|
+
if (found === undefined) throw missing();
|
|
70
|
+
return found;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function localListAgents(dir = agentsDir()): Promise<AgentSummary[]> {
|
|
74
|
+
return Promise.resolve(storedAgents(dir).map(({ file }) => ({ id: file.id, name: file.name })));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function freshId(taken: Set<string>): string {
|
|
78
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
79
|
+
const id = newId();
|
|
80
|
+
if (!taken.has(id)) return id;
|
|
81
|
+
}
|
|
82
|
+
throw new AgentAdminError('could not allocate a free id', 500);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function localCreateAgent(rawName?: string, dir = agentsDir()): Promise<CreatedAgent> {
|
|
86
|
+
const name = rawName === undefined ? null : normalizeAgentName(rawName);
|
|
87
|
+
const existing = storedAgents(dir);
|
|
88
|
+
if (existing.length > 0) throw new AgentAdminError('this box already has its agent', 409);
|
|
89
|
+
const id = freshId(new Set());
|
|
90
|
+
const key = newApiKey();
|
|
91
|
+
ensureSecureDir(dir);
|
|
92
|
+
save({
|
|
93
|
+
path: agentFilePath(dir),
|
|
94
|
+
file: { version: 1, id, name, key, stations: [] },
|
|
95
|
+
});
|
|
96
|
+
registerKey(key, id);
|
|
97
|
+
return Promise.resolve({ id, name, key });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export type Ensured = 'created' | 'present' | 'no-owner';
|
|
101
|
+
|
|
102
|
+
export async function ensureLocalAgent(dir = agentsDir()): Promise<Ensured> {
|
|
103
|
+
if (storedAgents(dir).length > 0) return 'present';
|
|
104
|
+
if (localOwner(dir) === null) return 'no-owner';
|
|
105
|
+
await localCreateAgent(undefined, dir);
|
|
106
|
+
return 'created';
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function assertUnclaimed(existing: Stored[], agent: LoadedAgent): void {
|
|
110
|
+
for (const { file } of existing) {
|
|
111
|
+
if (file.id === agent.id)
|
|
112
|
+
throw new AgentAdminError(`agent ${agent.id} is already on this machine`, 409);
|
|
113
|
+
if (agent.key !== null && file.key === agent.key)
|
|
114
|
+
throw new AgentAdminError('that agent key is already on this machine', 409);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function assertImportable(agent: LoadedAgent): void {
|
|
119
|
+
const immovable = agent.accounts.find((a) => !MOVABLE_STATIONS.has(a.station));
|
|
120
|
+
if (immovable !== undefined)
|
|
121
|
+
throw new AgentAdminError(
|
|
122
|
+
`a ${immovable.station} endpoint needs a public url and cannot live on a local daemon`,
|
|
123
|
+
400,
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
interface ImportTarget {
|
|
128
|
+
path: string;
|
|
129
|
+
previous: AgentFile | undefined;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function importTarget(dir: string, agent: LoadedAgent): ImportTarget {
|
|
133
|
+
const existing = storedAgents(dir);
|
|
134
|
+
const same = existing.find((s) => s.file.id === agent.id);
|
|
135
|
+
if (same === undefined && existing.length > 0)
|
|
136
|
+
throw new AgentAdminError('this box already has its agent; import into it rather than bringing another', 409);
|
|
137
|
+
assertUnclaimed(existing.filter((s) => s !== same), agent);
|
|
138
|
+
return { path: same?.path ?? agentFilePath(dir), previous: same?.file };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export type ImportMode = 'append' | 'overwrite';
|
|
142
|
+
|
|
143
|
+
export async function localImportAgent(
|
|
144
|
+
agent: LoadedAgent,
|
|
145
|
+
dir = agentsDir(),
|
|
146
|
+
mode: ImportMode = 'overwrite',
|
|
147
|
+
): Promise<{ id: string; name: string; key: string; stations: number }> {
|
|
148
|
+
assertImportable(agent);
|
|
149
|
+
const { path, previous } = importTarget(dir, agent);
|
|
150
|
+
const key = previous?.key ?? agent.key ?? newApiKey();
|
|
151
|
+
const file = fileFor({ ...agent, key }, path, previous, mode);
|
|
152
|
+
ensureSecureDir(join(path, '..'));
|
|
153
|
+
save({ path, file });
|
|
154
|
+
registerKey(key, agent.id);
|
|
155
|
+
return Promise.resolve({
|
|
156
|
+
id: agent.id,
|
|
157
|
+
name: agent.name,
|
|
158
|
+
key,
|
|
159
|
+
stations: file.stations.length,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function mergedStations(
|
|
164
|
+
agent: LoadedAgent,
|
|
165
|
+
previous: AgentFile | undefined,
|
|
166
|
+
mode: ImportMode,
|
|
167
|
+
): AgentFile['stations'] {
|
|
168
|
+
const before = previous?.stations ?? [];
|
|
169
|
+
const here = (station: string, id: string): boolean => before.some((s) => s.station === station && s.id === id);
|
|
170
|
+
const fromMetro = agent.accounts
|
|
171
|
+
.map((a) => ({ ...a, allowlist: a.allowlist ?? ['*'] }))
|
|
172
|
+
.filter((m) => mode === 'overwrite' || !here(m.station, m.id));
|
|
173
|
+
const kept = before.filter((s) => !fromMetro.some((m) => m.station === s.station && m.id === s.id));
|
|
174
|
+
return [...fromMetro, ...kept];
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function fileFor(
|
|
178
|
+
agent: LoadedAgent,
|
|
179
|
+
path: string,
|
|
180
|
+
previous: AgentFile | undefined,
|
|
181
|
+
mode: ImportMode,
|
|
182
|
+
): AgentFile {
|
|
183
|
+
try {
|
|
184
|
+
return parseAgentFile(
|
|
185
|
+
JSON.stringify({
|
|
186
|
+
version: 1,
|
|
187
|
+
id: agent.id,
|
|
188
|
+
name: previous?.name ?? null,
|
|
189
|
+
key: agent.key,
|
|
190
|
+
stations: mergedStations(agent, previous, mode),
|
|
191
|
+
}),
|
|
192
|
+
path,
|
|
193
|
+
);
|
|
194
|
+
} catch (err) {
|
|
195
|
+
if (err instanceof AgentFileError)
|
|
196
|
+
throw new AgentAdminError(`metro's copy of this agent cannot be written here: ${err.message}`, 400);
|
|
197
|
+
throw err;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export function readLocalAgentFile(agentId: string, dir = agentsDir()): AgentFile {
|
|
202
|
+
const found = storedAgents(dir).find((s) => s.file.id === agentId);
|
|
203
|
+
if (found === undefined) throw missing();
|
|
204
|
+
return found.file;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const UNIQUE_CREDENTIALS: Record<string, string> = {
|
|
208
|
+
token: 'that bot token is already attached to an agent on this machine',
|
|
209
|
+
accountEmail: 'that mailbox is already connected to an agent on this machine',
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
function assertCredentialFree(all: Stored[], station: StationName, config: Record<string, unknown>): void {
|
|
213
|
+
for (const [key, refusal] of Object.entries(UNIQUE_CREDENTIALS)) {
|
|
214
|
+
const value = config[key];
|
|
215
|
+
if (typeof value !== 'string') continue;
|
|
216
|
+
const taken = all.some((s) => s.file.stations.some((a) => a.station === station && a.config[key] === value));
|
|
217
|
+
if (taken) throw new AgentAdminError(refusal, 409);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export async function localAttachAccount(
|
|
222
|
+
agentId: string,
|
|
223
|
+
station: StationName,
|
|
224
|
+
config: Record<string, unknown>,
|
|
225
|
+
dir = agentsDir(),
|
|
226
|
+
): Promise<AccountRef> {
|
|
227
|
+
const stored = agentOrThrow(agentId, dir);
|
|
228
|
+
if (!MOVABLE_STATIONS.has(station))
|
|
229
|
+
throw new AgentAdminError(
|
|
230
|
+
`a ${station} endpoint needs a public url and cannot live on a local daemon`,
|
|
231
|
+
400,
|
|
232
|
+
);
|
|
233
|
+
assertCredentialFree(storedAgents(dir), station, config);
|
|
234
|
+
const taken = new Set(stored.file.stations.map((a) => a.id));
|
|
235
|
+
const accountId = freshId(taken);
|
|
236
|
+
stored.file.stations.push({ station, id: accountId, allowlist: ['*'], enabled: true, config });
|
|
237
|
+
save(stored);
|
|
238
|
+
return Promise.resolve({ agentId, station, accountId });
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export async function localSetAllowlist(
|
|
242
|
+
agentId: string,
|
|
243
|
+
station: StationName,
|
|
244
|
+
accountId: string,
|
|
245
|
+
allowlist: string[],
|
|
246
|
+
approvers: string[] = [],
|
|
247
|
+
dir = agentsDir(),
|
|
248
|
+
): Promise<string[]> {
|
|
249
|
+
const stored = agentOrThrow(agentId, dir);
|
|
250
|
+
const account = stored.file.stations.find((a) => a.station === station && a.id === accountId);
|
|
251
|
+
if (account === undefined) throw new AgentAdminError('no such account on this agent', 404);
|
|
252
|
+
account.allowlist = allowlist;
|
|
253
|
+
if (approvers.length === 0) delete account.approvers;
|
|
254
|
+
else account.approvers = approvers;
|
|
255
|
+
save(stored);
|
|
256
|
+
return Promise.resolve(allowlist);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export async function localSetPolicy(
|
|
260
|
+
agentId: string,
|
|
261
|
+
station: StationName,
|
|
262
|
+
accountId: string,
|
|
263
|
+
policy: ToolPolicy,
|
|
264
|
+
dir = agentsDir(),
|
|
265
|
+
): Promise<ToolPolicy> {
|
|
266
|
+
const stored = agentOrThrow(agentId, dir);
|
|
267
|
+
const account = stored.file.stations.find((a) => a.station === station && a.id === accountId);
|
|
268
|
+
if (account === undefined) throw new AgentAdminError('no such account on this agent', 404);
|
|
269
|
+
if (policy.read === undefined && policy.write === undefined && policy.tools === undefined) delete account.policy;
|
|
270
|
+
else account.policy = policy;
|
|
271
|
+
save(stored);
|
|
272
|
+
return Promise.resolve(policy);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
export async function localSetAccountEnabled(
|
|
276
|
+
agentId: string,
|
|
277
|
+
station: StationName,
|
|
278
|
+
accountId: string,
|
|
279
|
+
enabled: boolean,
|
|
280
|
+
dir = agentsDir(),
|
|
281
|
+
): Promise<boolean> {
|
|
282
|
+
const stored = agentOrThrow(agentId, dir);
|
|
283
|
+
const account = stored.file.stations.find((a) => a.station === station && a.id === accountId);
|
|
284
|
+
if (account === undefined) throw new AgentAdminError('no such account on this agent', 404);
|
|
285
|
+
account.enabled = enabled;
|
|
286
|
+
save(stored);
|
|
287
|
+
return Promise.resolve(enabled);
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
export async function localDetachAccount(
|
|
291
|
+
agentId: string,
|
|
292
|
+
station: StationName,
|
|
293
|
+
accountId: string,
|
|
294
|
+
dir = agentsDir(),
|
|
295
|
+
): Promise<AccountRef> {
|
|
296
|
+
const stored = agentOrThrow(agentId, dir);
|
|
297
|
+
const before = stored.file.stations.length;
|
|
298
|
+
stored.file.stations = stored.file.stations.filter(
|
|
299
|
+
(a) => !(a.station === station && a.id === accountId),
|
|
300
|
+
);
|
|
301
|
+
if (stored.file.stations.length === before)
|
|
302
|
+
throw new AgentAdminError('no such account on this agent', 404);
|
|
303
|
+
save(stored);
|
|
304
|
+
return Promise.resolve({ agentId, station, accountId });
|
|
305
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { AGENT_NAME_RE, ID_RE } from '@metro-labs/core/ids';
|
|
5
|
+
import { STATIONS, type StationName } from '@metro-labs/core/station-names';
|
|
6
|
+
import type { LoadedAccount, LoadedAgent, StationSource } from '../stations/materialize.js';
|
|
7
|
+
import { isRecord } from '@metro-labs/core/is-record';
|
|
8
|
+
import { parsePolicy } from '../policy/policy.js';
|
|
9
|
+
|
|
10
|
+
const AGENT_FILE = 'agent.json';
|
|
11
|
+
|
|
12
|
+
const KEY_RE = /^[A-Za-z0-9_-]{16,128}$/;
|
|
13
|
+
const STATION_NAMES = new Set<string>(STATIONS);
|
|
14
|
+
|
|
15
|
+
export class AgentFileError extends Error {}
|
|
16
|
+
|
|
17
|
+
export interface AgentFile {
|
|
18
|
+
version: 1;
|
|
19
|
+
id: string;
|
|
20
|
+
name: string | null;
|
|
21
|
+
key: string | null;
|
|
22
|
+
stations: LoadedAccount[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export const agentFilePath = (dir = agentsDir()): string => join(dir, AGENT_FILE);
|
|
26
|
+
|
|
27
|
+
export function agentsDir(): string {
|
|
28
|
+
const explicit = process.env.METRO_AGENTS_DIR?.trim();
|
|
29
|
+
return explicit !== undefined && explicit !== ''
|
|
30
|
+
? explicit
|
|
31
|
+
: join(homedir(), '.metro', 'agents');
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function fail(path: string, reason: string): never {
|
|
35
|
+
throw new AgentFileError(`${path}: ${reason}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function isStationName(value: unknown): value is StationName {
|
|
39
|
+
return typeof value === 'string' && STATION_NAMES.has(value);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function allowlistOf(raw: unknown, path: string, where: string): string[] | null {
|
|
43
|
+
if (raw === null || raw === undefined) return null;
|
|
44
|
+
if (!Array.isArray(raw)) fail(path, `${where}.allowlist is not a list of strings`);
|
|
45
|
+
const list = raw.filter((entry): entry is string => typeof entry === 'string');
|
|
46
|
+
if (list.length !== raw.length)
|
|
47
|
+
fail(path, `${where}.allowlist is not a list of strings`);
|
|
48
|
+
return list;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function approversOf(raw: unknown): { approvers?: string[] } {
|
|
52
|
+
if (!Array.isArray(raw)) return {};
|
|
53
|
+
const list = raw.filter((entry): entry is string => typeof entry === 'string' && entry !== '*' && entry.trim() !== '');
|
|
54
|
+
return list.length === 0 ? {} : { approvers: list };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function stationOf(raw: unknown, path: string, index: number): LoadedAccount {
|
|
58
|
+
const where = `stations[${String(index)}]`;
|
|
59
|
+
if (!isRecord(raw)) fail(path, `${where} is not an object`);
|
|
60
|
+
const { station, id, config } = raw;
|
|
61
|
+
if (!isStationName(station)) fail(path, `${where}.station is not a known station`);
|
|
62
|
+
if (typeof id !== 'string' || !ID_RE.test(id))
|
|
63
|
+
fail(path, `${where}.id is not an 11-character id`);
|
|
64
|
+
if (!isRecord(config)) fail(path, `${where}.config is not an object`);
|
|
65
|
+
const policy = parsePolicy(raw.policy, `${path} ${where}`);
|
|
66
|
+
return {
|
|
67
|
+
station,
|
|
68
|
+
id,
|
|
69
|
+
allowlist: allowlistOf(raw.allowlist, path, where),
|
|
70
|
+
...approversOf(raw.approvers),
|
|
71
|
+
enabled: raw.enabled !== false,
|
|
72
|
+
...(policy === undefined ? {} : { policy }),
|
|
73
|
+
config,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function optionalMatch(
|
|
78
|
+
value: unknown,
|
|
79
|
+
re: RegExp,
|
|
80
|
+
path: string,
|
|
81
|
+
what: string,
|
|
82
|
+
): string | null {
|
|
83
|
+
if (value === null || value === undefined) return null;
|
|
84
|
+
if (typeof value !== 'string' || !re.test(value)) fail(path, what);
|
|
85
|
+
return value;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function nameOf(raw: unknown, path: string): string | null {
|
|
89
|
+
if (raw === undefined || raw === null) return null;
|
|
90
|
+
if (typeof raw !== 'string' || !AGENT_NAME_RE.test(raw)) fail(path, 'name is not a valid agent name');
|
|
91
|
+
return raw;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function parseAgentFile(raw: string, path: string): AgentFile {
|
|
95
|
+
let parsed: unknown;
|
|
96
|
+
try {
|
|
97
|
+
parsed = JSON.parse(raw);
|
|
98
|
+
} catch {
|
|
99
|
+
fail(path, 'is not valid JSON');
|
|
100
|
+
}
|
|
101
|
+
if (!isRecord(parsed)) fail(path, 'is not a JSON object');
|
|
102
|
+
if (parsed.version !== 1) fail(path, 'version must be 1');
|
|
103
|
+
const { id, name, stations } = parsed;
|
|
104
|
+
if (typeof id !== 'string' || !ID_RE.test(id))
|
|
105
|
+
fail(path, 'id is not an 11-character id');
|
|
106
|
+
const label = nameOf(name, path);
|
|
107
|
+
const key = optionalMatch(parsed.key, KEY_RE, path, 'key is not an agent key');
|
|
108
|
+
if (!Array.isArray(stations)) fail(path, 'stations is not a list');
|
|
109
|
+
return {
|
|
110
|
+
version: 1,
|
|
111
|
+
id,
|
|
112
|
+
name: label,
|
|
113
|
+
key,
|
|
114
|
+
stations: stations.map((s, i) => stationOf(s, path, i)),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function readAgentFile(path: string): AgentFile {
|
|
119
|
+
let raw: string;
|
|
120
|
+
try {
|
|
121
|
+
raw = readFileSync(path, 'utf8');
|
|
122
|
+
} catch {
|
|
123
|
+
fail(path, 'cannot be read');
|
|
124
|
+
}
|
|
125
|
+
return parseAgentFile(raw, path);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function listAgentFiles(dir = agentsDir()): string[] {
|
|
129
|
+
const fixed = agentFilePath(dir);
|
|
130
|
+
return existsSync(fixed) ? [fixed] : [];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function loadFileAgents(dir = agentsDir()): LoadedAgent[] {
|
|
134
|
+
return listAgentFiles(dir).map(readAgentFile).map((agent) => ({
|
|
135
|
+
id: agent.id,
|
|
136
|
+
name: agent.name ?? agent.id,
|
|
137
|
+
key: agent.key,
|
|
138
|
+
accounts: agent.stations,
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export const fileSource: StationSource = () => Promise.resolve(loadFileAgents());
|
package/runtime/node_modules/@metro-labs/{mcp/src/db/key-map.ts → daemon/src/agents/keys.ts}
RENAMED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
|
-
import { ID_RE } from '
|
|
2
|
+
import { ID_RE } from '@metro-labs/core/ids';
|
|
3
3
|
|
|
4
4
|
const digest = (token: string): string =>
|
|
5
5
|
createHash('sha256').update(token).digest('hex');
|
|
@@ -25,16 +25,6 @@ export function registerKey(key: string, agentId: string): void {
|
|
|
25
25
|
agentIdByKeyHash.set(digest(key), agentId);
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
export function unregisterAgentKey(agentId: string): void {
|
|
29
|
-
for (const [hash, id] of agentIdByKeyHash)
|
|
30
|
-
if (id === agentId) agentIdByKeyHash.delete(hash);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export function rotateAgentKey(agentId: string, key: string | null): void {
|
|
34
|
-
unregisterAgentKey(agentId);
|
|
35
|
-
if (key !== null) registerKey(key, agentId);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
28
|
export function agentIdForKey(token: string): string | undefined {
|
|
39
29
|
if (token === '') return undefined;
|
|
40
30
|
return agentIdByKeyHash.get(digest(token));
|
package/runtime/node_modules/@metro-labs/{mcp/src/db/agent-map.ts → daemon/src/agents/map.ts}
RENAMED
|
@@ -8,6 +8,8 @@ const mapKey = (station: string, accountId: string): string =>
|
|
|
8
8
|
let agentMap: AgentMap = {};
|
|
9
9
|
let agentNames: AgentNameMap = {};
|
|
10
10
|
let allowlistMap: AllowlistMap = {};
|
|
11
|
+
let approversMap: AllowlistMap = {};
|
|
12
|
+
let disabledAccounts = new Set<string>();
|
|
11
13
|
|
|
12
14
|
export function setAgentMap(map: AgentMap, names: AgentNameMap): void {
|
|
13
15
|
agentMap = map;
|
|
@@ -18,6 +20,28 @@ export function setAllowlistMap(map: AllowlistMap): void {
|
|
|
18
20
|
allowlistMap = map;
|
|
19
21
|
}
|
|
20
22
|
|
|
23
|
+
export function setApproversMap(map: AllowlistMap): void {
|
|
24
|
+
approversMap = map;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const approversForAccount = (station: string, accountId: string): string[] => approversMap[mapKey(station, accountId)] ?? [];
|
|
28
|
+
|
|
29
|
+
export function approversForLine(line: string): string[] {
|
|
30
|
+
const a = accountFromLine(line);
|
|
31
|
+
return a ? approversForAccount(a.station, a.accountId) : [];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const mayApprove = (line: string, from: string): boolean => {
|
|
35
|
+
const approvers = approversForLine(line);
|
|
36
|
+
return approvers.length > 0 && senderMatchesAllowlist(approvers, from);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function setDisabledAccounts(ids: Set<string>): void {
|
|
40
|
+
disabledAccounts = ids;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export const accountEnabled = (station: string, accountId: string): boolean => !disabledAccounts.has(mapKey(station, accountId));
|
|
44
|
+
|
|
21
45
|
export function accountFromLine(
|
|
22
46
|
line: string,
|
|
23
47
|
): { station: string; accountId: string } | undefined {
|
|
@@ -45,6 +69,19 @@ export function agentForLine(line: string): string | undefined {
|
|
|
45
69
|
return id === undefined ? undefined : agentNames[id];
|
|
46
70
|
}
|
|
47
71
|
|
|
72
|
+
export interface KnownAccount {
|
|
73
|
+
station: string;
|
|
74
|
+
id: string;
|
|
75
|
+
agentId: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function knownAccounts(): KnownAccount[] {
|
|
79
|
+
return Object.entries(agentMap).flatMap(([key, agentId]) => {
|
|
80
|
+
const cut = key.indexOf('/');
|
|
81
|
+
return cut === -1 ? [] : [{ station: key.slice(0, cut), id: key.slice(cut + 1), agentId }];
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
|
|
48
85
|
export function stationAgentIds(station: string): string[] {
|
|
49
86
|
const prefix = `${station}/`;
|
|
50
87
|
return Object.entries(agentMap)
|
|
@@ -52,11 +89,24 @@ export function stationAgentIds(station: string): string[] {
|
|
|
52
89
|
.map(([, id]) => id);
|
|
53
90
|
}
|
|
54
91
|
|
|
92
|
+
export function allowlistForAccount(station: string, accountId: string): string[] | undefined {
|
|
93
|
+
return allowlistMap[mapKey(station, accountId)];
|
|
94
|
+
}
|
|
95
|
+
|
|
55
96
|
export function allowlistForLine(line: string): string[] | undefined {
|
|
56
97
|
const a = accountFromLine(line);
|
|
57
98
|
return a ? allowlistMap[mapKey(a.station, a.accountId)] : undefined;
|
|
58
99
|
}
|
|
59
100
|
|
|
101
|
+
export function senderPermitted(
|
|
102
|
+
allowlist: string[] | undefined,
|
|
103
|
+
from: string,
|
|
104
|
+
verified?: boolean,
|
|
105
|
+
): boolean {
|
|
106
|
+
if (allowlist === undefined || allowlist.length === 0 || allowlist.includes('*')) return true;
|
|
107
|
+
return verified !== false && senderMatchesAllowlist(allowlist, from);
|
|
108
|
+
}
|
|
109
|
+
|
|
60
110
|
export function senderMatchesAllowlist(
|
|
61
111
|
allowlist: string[],
|
|
62
112
|
from: string,
|
|
@@ -66,6 +116,13 @@ export function senderMatchesAllowlist(
|
|
|
66
116
|
const id = f.split('/').pop() ?? f;
|
|
67
117
|
return allowlist.some((a) => {
|
|
68
118
|
const v = a.toLowerCase();
|
|
69
|
-
return v === f || v === id;
|
|
119
|
+
return v === f || v === id || domainMatches(v, id);
|
|
70
120
|
});
|
|
71
121
|
}
|
|
122
|
+
|
|
123
|
+
const EMAIL_RE = /^[^@\s/]+@([^@\s/]+)$/;
|
|
124
|
+
|
|
125
|
+
function domainMatches(entry: string, id: string): boolean {
|
|
126
|
+
if (!entry.startsWith('@') || entry.length < 2) return false;
|
|
127
|
+
return EMAIL_RE.exec(id)?.[1] === entry.slice(1);
|
|
128
|
+
}
|