@stage-labs/metro 0.1.0-beta.13 → 0.1.0-beta.131
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 +14 -0
- package/dist/claude-settings.js +40 -0
- package/dist/claude.js +176 -0
- package/dist/cli.js +101 -0
- package/dist/control.js +74 -0
- package/dist/hold.js +211 -0
- package/dist/local.js +93 -0
- package/dist/mcp-config.js +25 -0
- package/dist/node-name.js +71 -0
- package/dist/onboarding.js +43 -0
- package/dist/plugin.js +120 -0
- package/dist/provider-flags.js +9 -0
- package/dist/route.js +38 -0
- package/dist/runtime-install.js +97 -0
- package/dist/runtime.js +74 -0
- package/dist/serve.js +150 -0
- package/dist/service.js +242 -0
- package/dist/tail.js +119 -0
- package/dist/update.js +45 -0
- package/dist/version.js +97 -0
- package/package.json +14 -27
- package/runtime/marketplace/.claude-plugin/marketplace.json +12 -0
- package/runtime/marketplace/plugin/.claude-plugin/plugin.json +18 -0
- package/runtime/marketplace/plugin/.mcp.json +1 -0
- package/runtime/marketplace/plugin/bin/guard.mjs +55 -0
- package/runtime/marketplace/plugin/bin/metro-plugin.mjs +122 -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 +16 -0
- package/runtime/marketplace/plugin/skills/refresh/SKILL.md +15 -0
- package/runtime/node_modules/@metro-labs/core/package.json +43 -0
- package/runtime/node_modules/@metro-labs/core/src/address.ts +6 -0
- package/runtime/node_modules/@metro-labs/core/src/endpoints.ts +7 -0
- package/runtime/node_modules/@metro-labs/core/src/events.ts +120 -0
- package/runtime/node_modules/@metro-labs/core/src/identity.ts +7 -0
- package/runtime/node_modules/@metro-labs/core/src/ids.ts +21 -0
- package/runtime/node_modules/@metro-labs/core/src/is-record.ts +2 -0
- package/runtime/node_modules/@metro-labs/core/src/lines.ts +87 -0
- package/runtime/node_modules/@metro-labs/core/src/log.ts +42 -0
- package/runtime/node_modules/@metro-labs/core/src/protocol.ts +226 -0
- package/runtime/node_modules/@metro-labs/core/src/secure-fs.ts +57 -0
- package/runtime/node_modules/@metro-labs/core/src/station-names.ts +13 -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/station-runtime.ts +64 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/types.ts +108 -0
- package/runtime/node_modules/@metro-labs/core/src/str.ts +5 -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/agents/account-attach.ts +19 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/account-routes.ts +70 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +365 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +43 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/allowlist.ts +37 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +300 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +122 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +371 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +155 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/keys.ts +45 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/map.ts +95 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/scope.ts +64 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/seed.ts +33 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/senders.ts +38 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +209 -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 +14 -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 +25 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/dedupe.ts +24 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +377 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/media-note.ts +85 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +75 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/relay.ts +129 -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 +309 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +358 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +169 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +56 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +140 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +90 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +253 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +117 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +239 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +154 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +109 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +310 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/cli-api.ts +63 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +175 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/json.ts +21 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/model.ts +77 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-client.ts +166 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-discovery.ts +139 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-pending.ts +65 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-routes.ts +171 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth.ts +155 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +122 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/reach.ts +27 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-target.ts +39 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +296 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +286 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +98 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +70 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +332 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/watch.ts +54 -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-serve.ts +121 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +294 -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 +259 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +231 -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 +288 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +203 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +231 -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 +187 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +229 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +269 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +207 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +90 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +65 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +17 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +229 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +151 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +216 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/channel-owner.ts +29 -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 +91 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/frame-scope.ts +21 -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 +191 -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-relay.ts +66 -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/request-identity.ts +47 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-registry.ts +172 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-route.ts +43 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +215 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-dispatch.ts +215 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-schemas.ts +400 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/upload-tool.ts +66 -0
- package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +237 -0
- package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +389 -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 +372 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/identity.ts +54 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +223 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +60 -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 +64 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +61 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +116 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-inline.ts +100 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-interactive.ts +161 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +202 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-session.ts +280 -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 +261 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/registry.ts +37 -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/terminal/api.ts +96 -0
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +123 -0
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +40 -0
- package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -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 +220 -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 +127 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -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 +19 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
- package/runtime/node_modules/@metro-labs/http/package.json +33 -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 +95 -0
- package/runtime/node_modules/@metro-labs/http/src/cors.ts +29 -0
- package/runtime/node_modules/@metro-labs/http/src/identity-key.ts +33 -0
- package/runtime/node_modules/@metro-labs/http/src/identity-registry.ts +13 -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/signed-identity.ts +36 -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 +254 -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 +156 -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 +78 -0
- package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -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/reactions.ts +167 -0
- package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
- package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -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 +120 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -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 +201 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +109 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +58 -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 +3 -0
- package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
- package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +100 -0
- package/runtime/node_modules/@metro-labs/threema/src/actions.ts +219 -0
- package/runtime/node_modules/@metro-labs/threema/src/api.ts +130 -0
- package/runtime/node_modules/@metro-labs/threema/src/crypto.ts +132 -0
- package/runtime/node_modules/@metro-labs/threema/src/format.ts +123 -0
- package/runtime/node_modules/@metro-labs/threema/src/ids.ts +21 -0
- package/runtime/node_modules/@metro-labs/threema/src/index.ts +29 -0
- package/runtime/node_modules/@metro-labs/threema/src/station.ts +10 -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 +190 -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 +347 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +138 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -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/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 +18 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
- package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +112 -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 +299 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -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-core.ts +55 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +335 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +80 -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 +33 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +53 -0
- package/runtime/runtime.json +3 -0
- package/runtime/server.ts +1 -0
- package/runtime/stations.json +39 -0
- package/runtime/trains/.keep +0 -0
- package/LICENSE +0 -21
- package/README.md +0 -252
- package/dist/broker.js +0 -202
- package/dist/cache.js +0 -69
- package/dist/cli/actions.js +0 -206
- package/dist/cli/config.js +0 -185
- package/dist/cli/index.js +0 -188
- package/dist/cli/skill.js +0 -62
- package/dist/cli/tail.js +0 -164
- package/dist/cli/util.js +0 -72
- package/dist/cli/webhook.js +0 -81
- package/dist/codex-rc.js +0 -245
- package/dist/dispatcher.js +0 -128
- package/dist/history.js +0 -121
- package/dist/ipc.js +0 -72
- package/dist/log.js +0 -15
- package/dist/monitor.js +0 -194
- package/dist/paths.js +0 -68
- package/dist/registry.js +0 -48
- package/dist/stations/claude.js +0 -45
- package/dist/stations/codex.js +0 -68
- package/dist/stations/discord.js +0 -216
- package/dist/stations/index.js +0 -129
- package/dist/stations/telegram-md.js +0 -34
- package/dist/stations/telegram-upload.js +0 -113
- package/dist/stations/telegram.js +0 -234
- package/dist/stations/webhook.js +0 -103
- package/dist/tunnel.js +0 -64
- package/dist/webhooks.js +0 -41
- package/docs/broker.md +0 -214
- package/docs/monitor.md +0 -163
- package/docs/uri-scheme.md +0 -124
- package/docs/users.md +0 -226
- package/skills/metro/SKILL.md +0 -256
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { rmSync } from 'node:fs';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { errMsg, log } from '@metro-labs/core/log';
|
|
5
|
+
|
|
6
|
+
export class XmtpAttachError extends Error {}
|
|
7
|
+
|
|
8
|
+
export interface XmtpIdentity {
|
|
9
|
+
inboxId: string;
|
|
10
|
+
address: string;
|
|
11
|
+
installationId: string;
|
|
12
|
+
dbPath: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export type VerifyXmtpKey = (
|
|
16
|
+
privateKey: string,
|
|
17
|
+
dbPath: string,
|
|
18
|
+
) => Promise<XmtpIdentity>;
|
|
19
|
+
|
|
20
|
+
const VERIFY_TIMEOUT_MS = 90_000;
|
|
21
|
+
const UNREACHABLE =
|
|
22
|
+
'Metro could not open an XMTP inbox with the key it generated, so nothing was attached';
|
|
23
|
+
|
|
24
|
+
export function newXmtpDbPath(): string {
|
|
25
|
+
return `~/.metro/xmtp-production-${randomBytes(8).toString('hex')}.db3`;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function discardXmtpDb(dbPath: string): void {
|
|
29
|
+
for (const suffix of ['', '-wal', '-shm'])
|
|
30
|
+
rmSync(`${dbPath}${suffix}`, { force: true });
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function withoutKey(message: string, privateKey: string): string {
|
|
34
|
+
return message.split(privateKey).join('[redacted]');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface VerifyReply {
|
|
38
|
+
ok?: unknown;
|
|
39
|
+
inboxId?: unknown;
|
|
40
|
+
address?: unknown;
|
|
41
|
+
installationId?: unknown;
|
|
42
|
+
dbPath?: unknown;
|
|
43
|
+
error?: unknown;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const str = (value: unknown): string => (typeof value === 'string' ? value : '');
|
|
47
|
+
|
|
48
|
+
function readReply(raw: string): VerifyReply {
|
|
49
|
+
const line = raw.trim().split('\n').at(-1) ?? '';
|
|
50
|
+
try {
|
|
51
|
+
const parsed: unknown = JSON.parse(line);
|
|
52
|
+
return typeof parsed === 'object' && parsed !== null ? parsed : {};
|
|
53
|
+
} catch {
|
|
54
|
+
return {};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function runVerifier(payload: string): Promise<string> {
|
|
59
|
+
const script = fileURLToPath(import.meta.resolve('@metro-labs/xmtp/verify'));
|
|
60
|
+
const proc = Bun.spawn(['bun', 'run', script], {
|
|
61
|
+
stdin: 'pipe',
|
|
62
|
+
stdout: 'pipe',
|
|
63
|
+
stderr: 'pipe',
|
|
64
|
+
});
|
|
65
|
+
await proc.stdin.write(payload);
|
|
66
|
+
await proc.stdin.end();
|
|
67
|
+
const timer = setTimeout(() => {
|
|
68
|
+
proc.kill();
|
|
69
|
+
}, VERIFY_TIMEOUT_MS);
|
|
70
|
+
try {
|
|
71
|
+
const [out, err, code] = await Promise.all([
|
|
72
|
+
new Response(proc.stdout).text(),
|
|
73
|
+
new Response(proc.stderr).text(),
|
|
74
|
+
proc.exited,
|
|
75
|
+
]);
|
|
76
|
+
if (code !== 0)
|
|
77
|
+
log.warn(
|
|
78
|
+
{ code, stderr: err.trim().slice(-500) },
|
|
79
|
+
'attach-xmtp: the inbox check exited badly',
|
|
80
|
+
);
|
|
81
|
+
return out;
|
|
82
|
+
} finally {
|
|
83
|
+
clearTimeout(timer);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export async function verifyXmtpKeyOutOfProcess(
|
|
88
|
+
privateKey: string,
|
|
89
|
+
dbPath: string,
|
|
90
|
+
): Promise<XmtpIdentity> {
|
|
91
|
+
const raw = await runVerifier(
|
|
92
|
+
JSON.stringify({ privateKey, dbPath }),
|
|
93
|
+
).catch((err: unknown) => {
|
|
94
|
+
log.warn({ err: errMsg(err) }, 'attach-xmtp: could not run the inbox check');
|
|
95
|
+
throw new XmtpAttachError(UNREACHABLE);
|
|
96
|
+
});
|
|
97
|
+
const reply = readReply(raw);
|
|
98
|
+
if (reply.ok !== true)
|
|
99
|
+
throw new XmtpAttachError(
|
|
100
|
+
withoutKey(str(reply.error) || UNREACHABLE, privateKey),
|
|
101
|
+
);
|
|
102
|
+
const identity = {
|
|
103
|
+
inboxId: str(reply.inboxId),
|
|
104
|
+
address: str(reply.address),
|
|
105
|
+
installationId: str(reply.installationId),
|
|
106
|
+
dbPath: str(reply.dbPath),
|
|
107
|
+
};
|
|
108
|
+
if (identity.inboxId === '' || identity.address === '')
|
|
109
|
+
throw new XmtpAttachError(UNREACHABLE);
|
|
110
|
+
return identity;
|
|
111
|
+
}
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import {
|
|
3
|
+
DiscordTokenError,
|
|
4
|
+
verifyDiscordBotToken,
|
|
5
|
+
} from '@metro-labs/discord-bot/verify';
|
|
6
|
+
import {
|
|
7
|
+
TelegramTokenError,
|
|
8
|
+
verifyTelegramBotToken,
|
|
9
|
+
} from '@metro-labs/telegram-bot/verify';
|
|
10
|
+
import {
|
|
11
|
+
parsePrivateKey,
|
|
12
|
+
ThreemaGatewayError,
|
|
13
|
+
verifyThreemaGateway,
|
|
14
|
+
} from '@metro-labs/threema/verify';
|
|
15
|
+
import { ApiError } from '@metro-labs/http/api-error';
|
|
16
|
+
import { bodyField } from '@metro-labs/http/api-http';
|
|
17
|
+
import { ensureStationDeps } from './runtime-deps.js';
|
|
18
|
+
import { publicBaseOrDefault } from '../files/attach-serve.js';
|
|
19
|
+
import {
|
|
20
|
+
discardXmtpDb,
|
|
21
|
+
newXmtpDbPath,
|
|
22
|
+
verifyXmtpKeyOutOfProcess,
|
|
23
|
+
XmtpAttachError,
|
|
24
|
+
type VerifyXmtpKey,
|
|
25
|
+
} from './attach-xmtp.js';
|
|
26
|
+
|
|
27
|
+
export const ATTACHABLE_STATIONS = [
|
|
28
|
+
'discord-bot',
|
|
29
|
+
'telegram-bot',
|
|
30
|
+
'threema',
|
|
31
|
+
'xmtp',
|
|
32
|
+
'webhook',
|
|
33
|
+
] as const;
|
|
34
|
+
|
|
35
|
+
export type AttachStation = (typeof ATTACHABLE_STATIONS)[number];
|
|
36
|
+
|
|
37
|
+
export class StationAttachError extends ApiError {}
|
|
38
|
+
|
|
39
|
+
export interface AttachInput {
|
|
40
|
+
station: AttachStation;
|
|
41
|
+
token?: unknown;
|
|
42
|
+
gatewayId?: unknown;
|
|
43
|
+
secret?: unknown;
|
|
44
|
+
privateKey?: unknown;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface OneTimeSecret {
|
|
48
|
+
label: string;
|
|
49
|
+
value: string;
|
|
50
|
+
note: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface PreparedAccount {
|
|
54
|
+
config: Record<string, unknown>;
|
|
55
|
+
identity: Record<string, string>;
|
|
56
|
+
secret?: OneTimeSecret;
|
|
57
|
+
discard?: () => void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const TOKEN_RE = /^[A-Za-z0-9._:-]{8,256}$/;
|
|
61
|
+
|
|
62
|
+
const SECP256K1_ORDER = BigInt(
|
|
63
|
+
'0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141',
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
export function attachInputOf(station: AttachStation, body: unknown): AttachInput {
|
|
67
|
+
return {
|
|
68
|
+
station,
|
|
69
|
+
token: bodyField(body, 'token'),
|
|
70
|
+
gatewayId: bodyField(body, 'gatewayId'),
|
|
71
|
+
secret: bodyField(body, 'secret'),
|
|
72
|
+
privateKey: bodyField(body, 'privateKey'),
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function isAttachStation(raw: unknown): raw is AttachStation {
|
|
77
|
+
return (
|
|
78
|
+
typeof raw === 'string' &&
|
|
79
|
+
(ATTACHABLE_STATIONS as readonly string[]).includes(raw)
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function requireToken(raw: unknown, label: string): string {
|
|
84
|
+
const token = typeof raw === 'string' ? raw.trim() : '';
|
|
85
|
+
if (token === '')
|
|
86
|
+
throw new StationAttachError(`a ${label} bot token is required`, 400);
|
|
87
|
+
if (!TOKEN_RE.test(token))
|
|
88
|
+
throw new StationAttachError(
|
|
89
|
+
`that does not look like a ${label} bot token`,
|
|
90
|
+
400,
|
|
91
|
+
);
|
|
92
|
+
return token;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function rejected(err: unknown, own: boolean, fallback: string): never {
|
|
96
|
+
throw new StationAttachError(
|
|
97
|
+
own && err instanceof Error ? err.message : fallback,
|
|
98
|
+
400,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function prepareDiscord(raw: unknown): Promise<PreparedAccount> {
|
|
103
|
+
const token = requireToken(raw, 'Discord');
|
|
104
|
+
try {
|
|
105
|
+
const bot = await verifyDiscordBotToken(token);
|
|
106
|
+
if (!bot.messageContent)
|
|
107
|
+
throw new StationAttachError(
|
|
108
|
+
'this Discord application does not have the Message Content intent enabled. Turn it on under Bot / Privileged Gateway Intents in the developer portal, then attach it again',
|
|
109
|
+
400,
|
|
110
|
+
);
|
|
111
|
+
return {
|
|
112
|
+
config: { token },
|
|
113
|
+
identity: { userId: bot.userId, username: bot.username },
|
|
114
|
+
};
|
|
115
|
+
} catch (err) {
|
|
116
|
+
if (err instanceof StationAttachError) throw err;
|
|
117
|
+
return rejected(
|
|
118
|
+
err,
|
|
119
|
+
err instanceof DiscordTokenError,
|
|
120
|
+
'Discord rejected that bot token',
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async function prepareTelegram(raw: unknown): Promise<PreparedAccount> {
|
|
126
|
+
const token = requireToken(raw, 'Telegram');
|
|
127
|
+
try {
|
|
128
|
+
const bot = await verifyTelegramBotToken(token);
|
|
129
|
+
return {
|
|
130
|
+
config: { token },
|
|
131
|
+
identity: { botId: String(bot.botId), username: bot.username },
|
|
132
|
+
};
|
|
133
|
+
} catch (err) {
|
|
134
|
+
return rejected(
|
|
135
|
+
err,
|
|
136
|
+
err instanceof TelegramTokenError,
|
|
137
|
+
'Telegram rejected that bot token',
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function newXmtpPrivateKey(): string {
|
|
143
|
+
for (;;) {
|
|
144
|
+
const hex = randomBytes(32).toString('hex');
|
|
145
|
+
const value = BigInt(`0x${hex}`);
|
|
146
|
+
if (value > 0n && value < SECP256K1_ORDER) return `0x${hex}`;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function prepareXmtp(verify: VerifyXmtpKey): Promise<PreparedAccount> {
|
|
151
|
+
const privateKey = newXmtpPrivateKey();
|
|
152
|
+
const dbPath = newXmtpDbPath();
|
|
153
|
+
const identity = await verify(privateKey, dbPath).catch((err: unknown) =>
|
|
154
|
+
rejected(
|
|
155
|
+
err,
|
|
156
|
+
err instanceof XmtpAttachError,
|
|
157
|
+
'Metro could not open an XMTP inbox with the key it generated, so nothing was attached',
|
|
158
|
+
),
|
|
159
|
+
);
|
|
160
|
+
return {
|
|
161
|
+
config: { privateKey, dbPath },
|
|
162
|
+
identity: { inboxId: identity.inboxId, address: identity.address },
|
|
163
|
+
secret: {
|
|
164
|
+
label: 'xmtp private key',
|
|
165
|
+
value: privateKey,
|
|
166
|
+
note: 'Metro generated this key, opened inbox ' +
|
|
167
|
+
`${identity.inboxId} with it, and stores it with the account. ` +
|
|
168
|
+
'It is shown once, here, and is never returned by any API again.',
|
|
169
|
+
},
|
|
170
|
+
discard: () => {
|
|
171
|
+
discardXmtpDb(identity.dbPath);
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export const hookUrl = (webhookId: string, secret: string): string =>
|
|
177
|
+
`${publicBaseOrDefault().replace(/\/+$/, '')}/api/webhooks/${webhookId}/${secret}`;
|
|
178
|
+
|
|
179
|
+
const WEBHOOK_ID_FLOOR = 10n ** 18n;
|
|
180
|
+
|
|
181
|
+
export function newWebhookId(): string {
|
|
182
|
+
const raw = BigInt(`0x${randomBytes(8).toString('hex')}`);
|
|
183
|
+
return String(WEBHOOK_ID_FLOOR + (raw % (9n * WEBHOOK_ID_FLOOR)));
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export const threemaCallbackUrl = (callbackId: string, token: string): string =>
|
|
187
|
+
`${publicBaseOrDefault().replace(/\/+$/, '')}/api/threema/${callbackId}/${token}`;
|
|
188
|
+
|
|
189
|
+
function requireText(raw: unknown, label: string): string {
|
|
190
|
+
const text = typeof raw === 'string' ? raw.trim() : '';
|
|
191
|
+
if (text === '')
|
|
192
|
+
throw new StationAttachError(`the Threema ${label} is required`, 400);
|
|
193
|
+
return text;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function prepareThreema(input: AttachInput): Promise<PreparedAccount> {
|
|
197
|
+
const gatewayId = requireText(input.gatewayId, 'Gateway ID');
|
|
198
|
+
const secret = requireText(input.secret, 'API secret');
|
|
199
|
+
const privateKey = requireText(input.privateKey, 'private key');
|
|
200
|
+
try {
|
|
201
|
+
const identity = await verifyThreemaGateway({ gatewayId, secret, privateKey });
|
|
202
|
+
const callbackId = newWebhookId();
|
|
203
|
+
const callbackToken = randomBytes(48).toString('base64url');
|
|
204
|
+
return {
|
|
205
|
+
config: {
|
|
206
|
+
gatewayId: identity.gatewayId,
|
|
207
|
+
secret,
|
|
208
|
+
privateKey: parsePrivateKey(privateKey) ?? privateKey,
|
|
209
|
+
callbackId,
|
|
210
|
+
callbackToken,
|
|
211
|
+
createdAt: new Date().toISOString(),
|
|
212
|
+
},
|
|
213
|
+
identity: {
|
|
214
|
+
gatewayId: identity.gatewayId,
|
|
215
|
+
credits: String(identity.credits),
|
|
216
|
+
callback: threemaCallbackUrl(callbackId, callbackToken),
|
|
217
|
+
},
|
|
218
|
+
};
|
|
219
|
+
} catch (err) {
|
|
220
|
+
if (err instanceof StationAttachError) throw err;
|
|
221
|
+
return rejected(
|
|
222
|
+
err,
|
|
223
|
+
err instanceof ThreemaGatewayError,
|
|
224
|
+
'Threema rejected those Gateway credentials',
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function prepareWebhook(): PreparedAccount {
|
|
230
|
+
const secret = randomBytes(48).toString('base64url');
|
|
231
|
+
const webhookId = newWebhookId();
|
|
232
|
+
return {
|
|
233
|
+
config: { secret, webhookId, createdAt: new Date().toISOString() },
|
|
234
|
+
identity: { endpoint: hookUrl(webhookId, secret) },
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
export async function prepareAccount(
|
|
239
|
+
input: AttachInput,
|
|
240
|
+
verify: VerifyXmtpKey = verifyXmtpKeyOutOfProcess,
|
|
241
|
+
): Promise<PreparedAccount> {
|
|
242
|
+
ensureStationDeps(input.station);
|
|
243
|
+
if (input.station === 'discord-bot') return prepareDiscord(input.token);
|
|
244
|
+
if (input.station === 'telegram-bot') return prepareTelegram(input.token);
|
|
245
|
+
if (input.station === 'webhook') return prepareWebhook();
|
|
246
|
+
if (input.station === 'threema') return prepareThreema(input);
|
|
247
|
+
return prepareXmtp(verify);
|
|
248
|
+
}
|
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
rmSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
} from 'node:fs';
|
|
8
|
+
import { homedir } from 'node:os';
|
|
9
|
+
import { join } from 'node:path';
|
|
10
|
+
import { log } from '@metro-labs/core/log';
|
|
11
|
+
import { writeSecure } from '@metro-labs/core/secure-fs';
|
|
12
|
+
import { trainsDir } from '../boot/paths.js';
|
|
13
|
+
import {
|
|
14
|
+
setAgentMap,
|
|
15
|
+
setAllowlistMap,
|
|
16
|
+
setDisabledAccounts,
|
|
17
|
+
type AgentMap,
|
|
18
|
+
type AgentNameMap,
|
|
19
|
+
type AllowlistMap,
|
|
20
|
+
} from '../agents/map.js';
|
|
21
|
+
import { setKeyMap } from '../agents/keys.js';
|
|
22
|
+
import { STATIONS, type StationName } from '@metro-labs/core/station-names';
|
|
23
|
+
|
|
24
|
+
interface StationTarget {
|
|
25
|
+
file: string;
|
|
26
|
+
fileEnv: string;
|
|
27
|
+
trainImport: string | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface LoadedAccount {
|
|
31
|
+
station: StationName;
|
|
32
|
+
id: string;
|
|
33
|
+
allowlist: string[] | null;
|
|
34
|
+
enabled?: boolean;
|
|
35
|
+
config: Record<string, unknown>;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export interface LoadedConnector {
|
|
39
|
+
id: string;
|
|
40
|
+
name: string;
|
|
41
|
+
url: string;
|
|
42
|
+
transport: 'http';
|
|
43
|
+
config: Record<string, unknown>;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface LoadedAgent {
|
|
47
|
+
id: string;
|
|
48
|
+
name: string;
|
|
49
|
+
accounts: LoadedAccount[];
|
|
50
|
+
key: string | null;
|
|
51
|
+
connectors?: LoadedConnector[];
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const METRO_DIR = join(homedir(), '.metro');
|
|
55
|
+
|
|
56
|
+
const STATION_TARGETS: Record<StationName, StationTarget> = {
|
|
57
|
+
xmtp: {
|
|
58
|
+
file: 'xmtp-accounts.json',
|
|
59
|
+
fileEnv: 'XMTP_ACCOUNTS_FILE',
|
|
60
|
+
trainImport: '@metro-labs/xmtp/train',
|
|
61
|
+
},
|
|
62
|
+
'telegram-bot': {
|
|
63
|
+
file: 'telegram-bot-accounts.json',
|
|
64
|
+
fileEnv: 'TELEGRAM_BOT_ACCOUNTS_FILE',
|
|
65
|
+
trainImport: '@metro-labs/telegram-bot/train',
|
|
66
|
+
},
|
|
67
|
+
'telegram': {
|
|
68
|
+
file: 'telegram-accounts.json',
|
|
69
|
+
fileEnv: 'TELEGRAM_ACCOUNTS_FILE',
|
|
70
|
+
trainImport: '@metro-labs/telegram/train',
|
|
71
|
+
},
|
|
72
|
+
'discord-bot': {
|
|
73
|
+
file: 'discord-bot-accounts.json',
|
|
74
|
+
fileEnv: 'DISCORD_BOT_ACCOUNTS_FILE',
|
|
75
|
+
trainImport: '@metro-labs/discord-bot/train',
|
|
76
|
+
},
|
|
77
|
+
whatsapp: {
|
|
78
|
+
file: 'whatsapp-accounts.json',
|
|
79
|
+
fileEnv: 'WHATSAPP_ACCOUNTS_FILE',
|
|
80
|
+
trainImport: '@metro-labs/whatsapp/train',
|
|
81
|
+
},
|
|
82
|
+
threema: {
|
|
83
|
+
file: 'threema-accounts.json',
|
|
84
|
+
fileEnv: 'THREEMA_ACCOUNTS_FILE',
|
|
85
|
+
trainImport: '@metro-labs/threema/train',
|
|
86
|
+
},
|
|
87
|
+
webhook: {
|
|
88
|
+
file: 'webhook-accounts.json',
|
|
89
|
+
fileEnv: 'WEBHOOK_ACCOUNTS_FILE',
|
|
90
|
+
trainImport: null,
|
|
91
|
+
},
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
export function accountFilePath(station: StationName): string {
|
|
95
|
+
const target = STATION_TARGETS[station];
|
|
96
|
+
return process.env[target.fileEnv] ?? join(METRO_DIR, target.file);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export type StationSource = () => Promise<LoadedAgent[]>;
|
|
100
|
+
|
|
101
|
+
export const MOVABLE_STATIONS = new Set<StationName>(
|
|
102
|
+
STATIONS.filter((s) => s !== 'webhook'),
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
function trainStubPath(station: StationName): string {
|
|
106
|
+
return join(trainsDir(), `${station}.ts`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function currentText(path: string): string | null {
|
|
110
|
+
try {
|
|
111
|
+
return readFileSync(path, 'utf8');
|
|
112
|
+
} catch {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export function writeIfChanged(path: string, content: string): boolean {
|
|
118
|
+
if (currentText(path) === content) return false;
|
|
119
|
+
writeFileSync(path, content);
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
interface WrittenStations {
|
|
124
|
+
active: Map<StationName, number>;
|
|
125
|
+
changed: StationName[];
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function writeStations(list: LoadedAgent[]): WrittenStations {
|
|
129
|
+
mkdirSync(METRO_DIR, { recursive: true });
|
|
130
|
+
mkdirSync(trainsDir(), { recursive: true });
|
|
131
|
+
|
|
132
|
+
const byStation = new Map<StationName, LoadedAccount[]>();
|
|
133
|
+
const map: AgentMap = {};
|
|
134
|
+
const names: AgentNameMap = {};
|
|
135
|
+
const allow: AllowlistMap = {};
|
|
136
|
+
const disabled = new Set<string>();
|
|
137
|
+
for (const agent of list) {
|
|
138
|
+
names[agent.id] = agent.name;
|
|
139
|
+
for (const a of agent.accounts) {
|
|
140
|
+
if (!(a.station in STATION_TARGETS)) {
|
|
141
|
+
log.error(
|
|
142
|
+
{ station: a.station, id: a.id },
|
|
143
|
+
'unknown station — this metro is older or newer than the row; skipping it',
|
|
144
|
+
);
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
map[`${a.station}/${a.id}`] = agent.id;
|
|
148
|
+
if (a.allowlist) allow[`${a.station}/${a.id}`] = a.allowlist;
|
|
149
|
+
if (a.enabled === false) {
|
|
150
|
+
disabled.add(`${a.station}/${a.id}`);
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const cur = byStation.get(a.station);
|
|
154
|
+
if (cur) cur.push(a);
|
|
155
|
+
else byStation.set(a.station, [a]);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
setAgentMap(map, names);
|
|
159
|
+
setAllowlistMap(allow);
|
|
160
|
+
setDisabledAccounts(disabled);
|
|
161
|
+
|
|
162
|
+
const active = new Map<StationName, number>();
|
|
163
|
+
const changed: StationName[] = [];
|
|
164
|
+
for (const [station, accts] of byStation) {
|
|
165
|
+
const records = accts.map((a) => ({ id: a.id, ...a.config }));
|
|
166
|
+
const path = accountFilePath(station);
|
|
167
|
+
const content = JSON.stringify(records, null, 2);
|
|
168
|
+
if (currentText(path) !== content) {
|
|
169
|
+
writeSecure(path, content);
|
|
170
|
+
changed.push(station);
|
|
171
|
+
}
|
|
172
|
+
const { trainImport } = STATION_TARGETS[station];
|
|
173
|
+
if (trainImport !== null)
|
|
174
|
+
writeIfChanged(trainStubPath(station), `import '${trainImport}';\n`);
|
|
175
|
+
active.set(station, accts.length);
|
|
176
|
+
}
|
|
177
|
+
return { active, changed };
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const BLANKED = '[]\n';
|
|
181
|
+
|
|
182
|
+
function pruneStations(present: Map<StationName, number>): StationName[] {
|
|
183
|
+
const removed: StationName[] = [];
|
|
184
|
+
for (const station of Object.keys(STATION_TARGETS) as StationName[]) {
|
|
185
|
+
if (present.has(station)) continue;
|
|
186
|
+
const hasTrain = STATION_TARGETS[station].trainImport !== null;
|
|
187
|
+
const stub = trainStubPath(station);
|
|
188
|
+
const stubGone = !hasTrain || !existsSync(stub);
|
|
189
|
+
const file = currentText(accountFilePath(station));
|
|
190
|
+
const fileInert = file === null || file === BLANKED;
|
|
191
|
+
if (stubGone && fileInert) continue;
|
|
192
|
+
if (!fileInert) writeSecure(accountFilePath(station), BLANKED);
|
|
193
|
+
if (hasTrain) rmSync(stub, { force: true });
|
|
194
|
+
removed.push(station);
|
|
195
|
+
}
|
|
196
|
+
return removed;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const stationLabels = (m: Map<StationName, number>): string[] =>
|
|
200
|
+
[...m].map(([station, n]) => `${station}(${n})`);
|
|
201
|
+
|
|
202
|
+
function applyKeyMap(list: LoadedAgent[]): void {
|
|
203
|
+
lastKey = list.find((a) => a.key !== null)?.key ?? null;
|
|
204
|
+
setKeyMap(
|
|
205
|
+
list.flatMap((agent) =>
|
|
206
|
+
agent.key === null ? [] : [{ key: agent.key, agentId: agent.id }],
|
|
207
|
+
),
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
let lastKey: string | null = null;
|
|
212
|
+
|
|
213
|
+
export function localAgentKey(): string | null {
|
|
214
|
+
return lastKey;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async function loadAndWrite(
|
|
218
|
+
source: StationSource,
|
|
219
|
+
): Promise<WrittenStations & { agents: number }> {
|
|
220
|
+
const list = await source();
|
|
221
|
+
applyKeyMap(list);
|
|
222
|
+
return { ...writeStations(list), agents: list.length };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export interface MaterializeOptions {
|
|
226
|
+
allowEmpty?: boolean;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
export async function materializeFrom(
|
|
230
|
+
source: StationSource,
|
|
231
|
+
opts: MaterializeOptions = {},
|
|
232
|
+
): Promise<void> {
|
|
233
|
+
const { active, agents: found } = await loadAndWrite(source);
|
|
234
|
+
|
|
235
|
+
if (found === 0 && opts.allowEmpty !== true)
|
|
236
|
+
throw new Error('no agents found — nothing to materialize');
|
|
237
|
+
const removed = pruneStations(active);
|
|
238
|
+
log.info(
|
|
239
|
+
{ stations: stationLabels(active), agents: found, removed },
|
|
240
|
+
'materialized station accounts',
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export interface ReloadedStations {
|
|
245
|
+
active: StationName[];
|
|
246
|
+
removed: StationName[];
|
|
247
|
+
changed: StationName[];
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
export async function reloadFrom(
|
|
251
|
+
source: StationSource,
|
|
252
|
+
): Promise<ReloadedStations> {
|
|
253
|
+
const { active, changed } = await loadAndWrite(source);
|
|
254
|
+
const removed = pruneStations(active);
|
|
255
|
+
if (removed.length > 0 || changed.length > 0)
|
|
256
|
+
log.info(
|
|
257
|
+
{ stations: stationLabels(active), removed, changed },
|
|
258
|
+
'reloaded station accounts',
|
|
259
|
+
);
|
|
260
|
+
return { active: [...active.keys()], removed, changed };
|
|
261
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Station } from '@metro-labs/core/stations/types';
|
|
2
|
+
import { Line } from '@metro-labs/core/lines';
|
|
3
|
+
import { xmtpStation } from '@metro-labs/xmtp';
|
|
4
|
+
import { telegramBotStation } from '@metro-labs/telegram-bot';
|
|
5
|
+
import { telegramStation } from '@metro-labs/telegram';
|
|
6
|
+
import { discordBotStation } from '@metro-labs/discord-bot';
|
|
7
|
+
import { whatsappStation } from '@metro-labs/whatsapp';
|
|
8
|
+
import { threemaStation } from '@metro-labs/threema';
|
|
9
|
+
import { webhookStation } from '@metro-labs/webhook';
|
|
10
|
+
|
|
11
|
+
export const STATIONS: readonly Station[] = [
|
|
12
|
+
xmtpStation,
|
|
13
|
+
telegramBotStation,
|
|
14
|
+
telegramStation,
|
|
15
|
+
discordBotStation,
|
|
16
|
+
whatsappStation,
|
|
17
|
+
threemaStation,
|
|
18
|
+
webhookStation,
|
|
19
|
+
];
|
|
20
|
+
|
|
21
|
+
export const stationByName = (name: string): Station | undefined =>
|
|
22
|
+
STATIONS.find((s) => s.name === name);
|
|
23
|
+
|
|
24
|
+
export const stationForLine = (line: string): Station | undefined => {
|
|
25
|
+
const s = Line.station(line);
|
|
26
|
+
return s ? stationByName(s) : undefined;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const accountStationNames = (): string[] =>
|
|
30
|
+
STATIONS.filter((s) => s.hasAccounts).map((s) => s.name);
|
|
31
|
+
|
|
32
|
+
export const accountStationCapabilities = (): Record<string, string[]> => {
|
|
33
|
+
const out: Record<string, string[]> = {};
|
|
34
|
+
for (const s of STATIONS)
|
|
35
|
+
if (s.hasAccounts) out[s.name] = [...s.messageVerbs].sort();
|
|
36
|
+
return out;
|
|
37
|
+
};
|