@stage-labs/metro 0.1.0-beta.2 → 0.1.0-beta.200
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +11 -0
- package/dist/claude-settings.js +40 -0
- package/dist/claude.js +163 -0
- package/dist/cli.js +77 -567
- package/dist/control.js +74 -0
- package/dist/hold.js +211 -0
- package/dist/local.js +43 -0
- package/dist/mcp-config.js +25 -0
- package/dist/node-name.js +71 -0
- package/dist/onboarding.js +44 -0
- package/dist/provider-flags.js +9 -0
- package/dist/route.js +58 -0
- package/dist/runtime-install.js +97 -0
- package/dist/runtime.js +74 -0
- package/dist/serve.js +158 -0
- package/dist/service.js +243 -0
- package/dist/tail.js +102 -144
- package/dist/update.js +45 -0
- package/dist/version.js +97 -0
- package/package.json +14 -23
- package/runtime/marketplace/.claude-plugin/marketplace.json +12 -0
- package/runtime/marketplace/plugin/.claude-plugin/plugin.json +17 -0
- package/runtime/marketplace/plugin/.mcp.json +1 -0
- package/runtime/marketplace/plugin/bin/guard.mjs +71 -0
- package/runtime/marketplace/plugin/bin/metro-plugin.mjs +30 -0
- package/runtime/marketplace/plugin/bin/policy.mjs +97 -0
- package/runtime/marketplace/plugin/bin/session-start.mjs +31 -0
- package/runtime/marketplace/plugin/hooks/hooks.json +25 -0
- package/runtime/marketplace/plugin/orchestrator.md +24 -0
- package/runtime/node_modules/@metro-labs/core/package.json +47 -0
- package/runtime/node_modules/@metro-labs/core/src/endpoints.ts +7 -0
- package/runtime/node_modules/@metro-labs/core/src/events.ts +119 -0
- package/runtime/node_modules/@metro-labs/core/src/identity.ts +7 -0
- package/runtime/node_modules/@metro-labs/core/src/ids.ts +24 -0
- package/runtime/node_modules/@metro-labs/core/src/is-record.ts +2 -0
- package/runtime/node_modules/@metro-labs/core/src/lines.ts +91 -0
- package/runtime/node_modules/@metro-labs/core/src/log.ts +42 -0
- package/runtime/node_modules/@metro-labs/core/src/protocol.ts +227 -0
- package/runtime/node_modules/@metro-labs/core/src/secure-fs.ts +74 -0
- package/runtime/node_modules/@metro-labs/core/src/station-names.ts +13 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/account-files.ts +41 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/account-store.ts +60 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/attachments.ts +196 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +88 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/profile.ts +58 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/sender-profile.ts +64 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/station-runtime.ts +61 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/train-boot.ts +42 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/train-events.ts +89 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/types.ts +123 -0
- package/runtime/node_modules/@metro-labs/core/src/str.ts +5 -0
- package/runtime/node_modules/@metro-labs/core/src/tickets.ts +37 -0
- package/runtime/node_modules/@metro-labs/core/src/train-error.ts +53 -0
- package/runtime/node_modules/@metro-labs/core/src/version.ts +2 -0
- package/runtime/node_modules/@metro-labs/daemon/package.json +48 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/agent-fs.ts +134 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/files-api.ts +12 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/files.ts +119 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/home-fs.ts +113 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/job-env.ts +51 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/provision.ts +91 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedule-detail.ts +100 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules-api.ts +27 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules.ts +252 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/unit-files.ts +61 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/user.ts +91 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agent-user/view.ts +87 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/account-attach.ts +19 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/account-lookups.ts +55 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/account-routes.ts +76 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +357 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +30 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/allowlist.ts +59 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +131 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +132 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +305 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +168 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/keys.ts +35 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/map.ts +128 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/policy-route.ts +27 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/scope.ts +64 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/sender-cards.ts +86 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/senders.ts +38 -0
- package/runtime/node_modules/@metro-labs/daemon/src/approvals/api.ts +55 -0
- package/runtime/node_modules/@metro-labs/daemon/src/approvals/needs.ts +2 -0
- package/runtime/node_modules/@metro-labs/daemon/src/approvals/pending.ts +149 -0
- package/runtime/node_modules/@metro-labs/daemon/src/approvals/preview.ts +66 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +221 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +39 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/crash-guard.ts +49 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +21 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/paths.ts +106 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +26 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/dedupe.ts +12 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +370 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/media-note.ts +90 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +78 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/react-note.ts +13 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/relay.ts +129 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/reply-id.ts +1 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/webhook-note.ts +51 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +355 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +359 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +208 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/memory.ts +82 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +60 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +149 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +75 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +283 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +109 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +271 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +120 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +113 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +306 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +176 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/gates.ts +72 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/health.ts +18 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-client.ts +159 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-discovery.ts +135 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-routes.ts +158 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth.ts +166 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +131 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/reach.ts +27 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-policy.ts +90 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-target.ts +29 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +348 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +358 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +81 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +37 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +106 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +99 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-owner.ts +32 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-reaper.ts +174 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +123 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +278 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/upload-store.ts +181 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +233 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +208 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +173 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +194 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +137 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/effort.ts +77 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +123 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +237 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/frames.ts +118 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +274 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-auth.ts +160 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-client.ts +46 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-setup.ts +150 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-stream.ts +139 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-translate.ts +223 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini.ts +208 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +199 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +299 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-signin.ts +193 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-store.ts +70 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +90 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/pkce.ts +36 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +66 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +18 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/subscription.ts +120 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/text.ts +15 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +244 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +156 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/attachment-schema.ts +91 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +226 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/ctx.ts +62 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +90 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +129 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +137 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/instructions.ts +19 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/member-tools.ts +72 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-prompt.ts +87 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +79 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-gate.ts +83 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-snapshot.ts +79 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/profile-lookup.ts +60 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/profile-tool.ts +96 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/raw-get-stream.ts +207 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/read-tool.ts +87 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +46 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-slot.ts +48 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +202 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-catalog.ts +88 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-def.ts +9 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-dispatch.ts +225 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-schemas.ts +333 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/upload-tool.ts +61 -0
- package/runtime/node_modules/@metro-labs/daemon/src/metro-user/api.ts +43 -0
- package/runtime/node_modules/@metro-labs/daemon/src/metro-user/helper-script.ts +126 -0
- package/runtime/node_modules/@metro-labs/daemon/src/metro-user/move.ts +165 -0
- package/runtime/node_modules/@metro-labs/daemon/src/metro-user/privilege.ts +48 -0
- package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +132 -0
- package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +316 -0
- package/runtime/node_modules/@metro-labs/daemon/src/policy/policy.ts +118 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/bearer.ts +35 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/body.ts +24 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +365 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +208 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +71 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/threema-callback.ts +103 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +39 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +58 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/owner.ts +26 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +90 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-driver.ts +42 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-inline.ts +128 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-interactive.ts +134 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-outlook.ts +63 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +225 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-session.ts +288 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-xmtp.ts +111 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +248 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +265 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/registry.ts +51 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor-io.ts +97 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +320 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/train-call.ts +22 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/webhook-endpoints.ts +51 -0
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +84 -0
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +124 -0
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +15 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/api.ts +45 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/config.ts +51 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/firewall.ts +48 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/index.ts +96 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/install.ts +56 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/paths.ts +26 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/proxy.ts +148 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/store.ts +130 -0
- package/runtime/node_modules/@metro-labs/daemon/src/vault/trust.ts +61 -0
- package/runtime/node_modules/@metro-labs/discord-bot/package.json +26 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +117 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +240 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +174 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +122 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/profile.ts +68 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +22 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +1 -0
- package/runtime/node_modules/@metro-labs/http/package.json +29 -0
- package/runtime/node_modules/@metro-labs/http/src/api-error.ts +8 -0
- package/runtime/node_modules/@metro-labs/http/src/api-http.ts +138 -0
- package/runtime/node_modules/@metro-labs/http/src/cors.ts +29 -0
- package/runtime/node_modules/@metro-labs/http/src/mode-api.ts +32 -0
- package/runtime/node_modules/@metro-labs/http/src/return-to.ts +21 -0
- package/runtime/node_modules/@metro-labs/http/src/workos-token.ts +149 -0
- package/runtime/node_modules/@metro-labs/outlook/package.json +25 -0
- package/runtime/node_modules/@metro-labs/outlook/src/accounts.ts +107 -0
- package/runtime/node_modules/@metro-labs/outlook/src/actions.ts +20 -0
- package/runtime/node_modules/@metro-labs/outlook/src/attachments.ts +97 -0
- package/runtime/node_modules/@metro-labs/outlook/src/auth.ts +150 -0
- package/runtime/node_modules/@metro-labs/outlook/src/browser.ts +53 -0
- package/runtime/node_modules/@metro-labs/outlook/src/config.ts +36 -0
- package/runtime/node_modules/@metro-labs/outlook/src/format.ts +141 -0
- package/runtime/node_modules/@metro-labs/outlook/src/graph.ts +60 -0
- package/runtime/node_modules/@metro-labs/outlook/src/html.ts +72 -0
- package/runtime/node_modules/@metro-labs/outlook/src/inbound.ts +104 -0
- package/runtime/node_modules/@metro-labs/outlook/src/index.ts +12 -0
- package/runtime/node_modules/@metro-labs/outlook/src/login.ts +150 -0
- package/runtime/node_modules/@metro-labs/outlook/src/me.ts +41 -0
- package/runtime/node_modules/@metro-labs/outlook/src/outbound.ts +116 -0
- package/runtime/node_modules/@metro-labs/outlook/src/query.ts +119 -0
- package/runtime/node_modules/@metro-labs/outlook/src/state.ts +43 -0
- package/runtime/node_modules/@metro-labs/outlook/src/station.ts +15 -0
- package/runtime/node_modules/@metro-labs/outlook/src/trust.ts +70 -0
- package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
- package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
- package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +256 -0
- package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
- package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
- package/runtime/node_modules/@metro-labs/telegram/src/format.ts +128 -0
- package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
- package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
- package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +57 -0
- package/runtime/node_modules/@metro-labs/telegram/src/index.ts +7 -0
- package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
- package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
- package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
- package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
- package/runtime/node_modules/@metro-labs/telegram/src/profile.ts +70 -0
- package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
- package/runtime/node_modules/@metro-labs/telegram/src/station.ts +27 -0
- package/runtime/node_modules/@metro-labs/telegram/src/types.ts +6 -0
- package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +119 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +246 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +173 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +103 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +95 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/profile.ts +65 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +20 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +56 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +1 -0
- package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
- package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +106 -0
- package/runtime/node_modules/@metro-labs/threema/src/actions.ts +134 -0
- package/runtime/node_modules/@metro-labs/threema/src/api.ts +152 -0
- package/runtime/node_modules/@metro-labs/threema/src/crypto.ts +100 -0
- package/runtime/node_modules/@metro-labs/threema/src/files.ts +58 -0
- package/runtime/node_modules/@metro-labs/threema/src/format.ts +169 -0
- package/runtime/node_modules/@metro-labs/threema/src/groups.ts +96 -0
- package/runtime/node_modules/@metro-labs/threema/src/ids.ts +21 -0
- package/runtime/node_modules/@metro-labs/threema/src/inbound.ts +89 -0
- package/runtime/node_modules/@metro-labs/threema/src/index.ts +24 -0
- package/runtime/node_modules/@metro-labs/threema/src/messages.ts +264 -0
- package/runtime/node_modules/@metro-labs/threema/src/outbound.ts +160 -0
- package/runtime/node_modules/@metro-labs/threema/src/station.ts +13 -0
- package/runtime/node_modules/@metro-labs/threema/src/verify.ts +117 -0
- package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
- package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
- package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +213 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +388 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +83 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +40 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +7 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/names.ts +28 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/resolve.ts +52 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +24 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +94 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +5 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +1 -0
- package/runtime/node_modules/@metro-labs/xmtp/package.json +34 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +114 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +101 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +118 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +244 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +316 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +69 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +47 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +122 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +139 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/names.ts +61 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/profile.ts +153 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/sender.ts +112 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/smart.ts +72 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +17 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +79 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +198 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +88 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +98 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +51 -0
- package/runtime/runtime.json +3 -0
- package/runtime/server.ts +1 -0
- package/runtime/stations.json +37 -0
- package/runtime/trains/.keep +0 -0
- package/LICENSE +0 -21
- package/README.md +0 -78
- package/dist/channels/discord.js +0 -148
- package/dist/channels/telegram.js +0 -137
- package/dist/lib/address.js +0 -21
- package/dist/lib/codex-rc.js +0 -274
- package/dist/lib/dotenv.js +0 -31
- package/dist/log.js +0 -12
- package/dist/paths.js +0 -45
- package/skills/metro/SKILL.md +0 -122
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { TrainError } from '@metro-labs/core/train-error';
|
|
2
|
+
import type { GroupResult } from '@metro-labs/core/stations/types';
|
|
3
|
+
import { accountForCall, convOf, lineOf } from './accounts.js';
|
|
4
|
+
import {
|
|
5
|
+
applyMemberOp,
|
|
6
|
+
createGroupWithMembers,
|
|
7
|
+
warmGroupName,
|
|
8
|
+
} from './conv-helpers.js';
|
|
9
|
+
import { memberOutcomes, splitMembers } from './group-members.js';
|
|
10
|
+
|
|
11
|
+
type Args = Record<string, unknown>;
|
|
12
|
+
|
|
13
|
+
export async function groupCreate(args: Args): Promise<GroupResult> {
|
|
14
|
+
const name = typeof args.name === 'string' ? args.name : '';
|
|
15
|
+
if (!name) throw new TrainError('INVALID_ARGS', 'groupCreate requires a `name`');
|
|
16
|
+
const acct = accountForCall(args);
|
|
17
|
+
const { addrs, inboxes } = splitMembers(args);
|
|
18
|
+
const group = await createGroupWithMembers(acct, addrs, inboxes, {
|
|
19
|
+
groupName: name,
|
|
20
|
+
});
|
|
21
|
+
warmGroupName(group.id, name);
|
|
22
|
+
return {
|
|
23
|
+
capability: { supported: true },
|
|
24
|
+
line: lineOf(acct.cfg.id, group.id),
|
|
25
|
+
id: group.id,
|
|
26
|
+
name,
|
|
27
|
+
members: memberOutcomes(addrs, inboxes, 'added'),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function memberOp(
|
|
32
|
+
args: Args,
|
|
33
|
+
mode: 'add' | 'remove',
|
|
34
|
+
verb: string,
|
|
35
|
+
): Promise<GroupResult> {
|
|
36
|
+
const line = typeof args.line === 'string' ? args.line : '';
|
|
37
|
+
if (!line) throw new TrainError('INVALID_ARGS', `${verb} requires a \`line\``);
|
|
38
|
+
const { addrs, inboxes } = splitMembers(args);
|
|
39
|
+
if (!addrs.length && !inboxes.length)
|
|
40
|
+
throw new TrainError('INVALID_ARGS', `${verb} requires members`);
|
|
41
|
+
const { conv } = await convOf(line);
|
|
42
|
+
if (!conv) throw new TrainError('NOT_FOUND', `conversation not found for ${line}`);
|
|
43
|
+
await applyMemberOp(conv, addrs, inboxes, mode);
|
|
44
|
+
return {
|
|
45
|
+
capability: { supported: true },
|
|
46
|
+
line,
|
|
47
|
+
id: conv.id,
|
|
48
|
+
members: memberOutcomes(addrs, inboxes, mode === 'add' ? 'added' : 'removed'),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const groupAddMembers = (args: Args): Promise<GroupResult> =>
|
|
53
|
+
memberOp(args, 'add', 'groupAddMembers');
|
|
54
|
+
|
|
55
|
+
export const groupRemoveMembers = (args: Args): Promise<GroupResult> =>
|
|
56
|
+
memberOp(args, 'remove', 'groupRemoveMembers');
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { IdentifierKind, type Signer } from '@xmtp/node-sdk';
|
|
2
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { SMART_CHAIN_ID, smartAccountFor, type SmartAccount } from './smart.js';
|
|
6
|
+
|
|
7
|
+
export const XMTP_ENV = 'production' as const;
|
|
8
|
+
|
|
9
|
+
export const expandHome = (p: string): string =>
|
|
10
|
+
p.startsWith('~') ? join(homedir(), p.slice(1)) : p;
|
|
11
|
+
|
|
12
|
+
export interface Identity {
|
|
13
|
+
signer: Signer;
|
|
14
|
+
address: string;
|
|
15
|
+
smart: SmartAccount | null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const bytesOf = (sig: string): Uint8Array => {
|
|
19
|
+
const hex = sig.slice(2);
|
|
20
|
+
const out = new Uint8Array(hex.length / 2);
|
|
21
|
+
for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
22
|
+
return out;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function signerFor(privateKey: string): Identity {
|
|
26
|
+
const acct = privateKeyToAccount(privateKey as `0x${string}`);
|
|
27
|
+
const signer: Signer = {
|
|
28
|
+
type: 'EOA',
|
|
29
|
+
getIdentifier: () => Promise.resolve({ identifier: acct.address, identifierKind: IdentifierKind.Ethereum }),
|
|
30
|
+
signMessage: async (msg: string) => bytesOf(await acct.signMessage({ message: msg })),
|
|
31
|
+
};
|
|
32
|
+
return { signer, address: acct.address, smart: null };
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function smartSignerFor(privateKey: string, rpc?: string): Promise<Identity> {
|
|
36
|
+
const smart = await smartAccountFor(privateKey, rpc);
|
|
37
|
+
const signer: Signer = {
|
|
38
|
+
type: 'SCW',
|
|
39
|
+
getIdentifier: () => Promise.resolve({ identifier: smart.address, identifierKind: IdentifierKind.Ethereum }),
|
|
40
|
+
signMessage: async (msg: string) => bytesOf(await smart.signMessage(msg)),
|
|
41
|
+
getChainId: () => BigInt(SMART_CHAIN_ID),
|
|
42
|
+
};
|
|
43
|
+
return { signer, address: smart.address, smart };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export const identityFor = (privateKey: string, smart: boolean): Promise<Identity> =>
|
|
47
|
+
smart ? smartSignerFor(privateKey) : Promise.resolve(signerFor(privateKey));
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { errMsg } from '@metro-labs/core/log';
|
|
2
|
+
import { ConsentState, type DecodedMessage } from '@xmtp/node-sdk';
|
|
3
|
+
import {
|
|
4
|
+
accounts,
|
|
5
|
+
bootAccount,
|
|
6
|
+
loadAccounts,
|
|
7
|
+
type Account,
|
|
8
|
+
} from './accounts.js';
|
|
9
|
+
import { emitInbound } from '@metro-labs/core/stations/train-events';
|
|
10
|
+
import { announceAccounts } from '@metro-labs/core/stations/train-boot';
|
|
11
|
+
import { envelope } from './emit.js';
|
|
12
|
+
import { groupNameFor } from './conv-helpers.js';
|
|
13
|
+
import { senderFieldsNow } from './sender.js';
|
|
14
|
+
import { readCalls } from '@metro-labs/core/trains/protocol';
|
|
15
|
+
import { handleCall } from './actions.js';
|
|
16
|
+
|
|
17
|
+
readCalls('xmtp', handleCall);
|
|
18
|
+
|
|
19
|
+
const SYNC_MS = Number(process.env.XMTP_SYNC_MS ?? '60000');
|
|
20
|
+
const SILENT_TYPES = new Set([
|
|
21
|
+
'readReceipt',
|
|
22
|
+
'transactionReference',
|
|
23
|
+
'walletSendCalls',
|
|
24
|
+
'groupUpdated',
|
|
25
|
+
'group_updated',
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
async function bootSync(acct: Account): Promise<void> {
|
|
29
|
+
const { id } = acct.cfg;
|
|
30
|
+
try {
|
|
31
|
+
await acct.client.conversations.syncAll([
|
|
32
|
+
ConsentState.Allowed,
|
|
33
|
+
ConsentState.Unknown,
|
|
34
|
+
]);
|
|
35
|
+
const initial = await acct.client.conversations.list();
|
|
36
|
+
process.stderr.write(
|
|
37
|
+
`xmtp[${id}]: synced ${initial.length} conversation(s) at boot\n`,
|
|
38
|
+
);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
process.stderr.write(
|
|
41
|
+
`xmtp[${id}] boot sync error: ${errMsg(err)}\n`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function startPeriodicSync(acct: Account): void {
|
|
47
|
+
const { id } = acct.cfg;
|
|
48
|
+
setInterval(() => {
|
|
49
|
+
acct.client.conversations
|
|
50
|
+
.syncAll([ConsentState.Allowed, ConsentState.Unknown])
|
|
51
|
+
.catch((err: unknown) => {
|
|
52
|
+
process.stderr.write(`xmtp[${id}] sync error: ${errMsg(err)}\n`);
|
|
53
|
+
});
|
|
54
|
+
}, SYNC_MS).unref();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function handleStreamMessage(
|
|
58
|
+
acct: Account,
|
|
59
|
+
msg: DecodedMessage,
|
|
60
|
+
): Promise<void> {
|
|
61
|
+
const { id } = acct.cfg;
|
|
62
|
+
if (msg.senderInboxId === acct.client.inboxId) return;
|
|
63
|
+
if (SILENT_TYPES.has(msg.contentType?.typeId ?? '')) return;
|
|
64
|
+
const conv = await acct.client.conversations.getConversationById(
|
|
65
|
+
msg.conversationId,
|
|
66
|
+
);
|
|
67
|
+
if (!conv) return;
|
|
68
|
+
const env = envelope(id, msg, conv);
|
|
69
|
+
Object.assign(env, senderFieldsNow(acct, msg.senderInboxId));
|
|
70
|
+
const name = await groupNameFor(msg.conversationId, conv);
|
|
71
|
+
if (name) {
|
|
72
|
+
env.line_name = name;
|
|
73
|
+
env.lineName = name;
|
|
74
|
+
const p = (env.payload ?? {}) as Record<string, unknown>;
|
|
75
|
+
env.payload = { ...p, lineName: name };
|
|
76
|
+
}
|
|
77
|
+
emitInbound(id, env);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async function runAccount(acct: Account): Promise<void> {
|
|
81
|
+
const { id } = acct.cfg;
|
|
82
|
+
await bootSync(acct);
|
|
83
|
+
startPeriodicSync(acct);
|
|
84
|
+
|
|
85
|
+
for (;;) {
|
|
86
|
+
try {
|
|
87
|
+
const stream = await acct.client.conversations.streamAllMessages({
|
|
88
|
+
consentStates: [ConsentState.Allowed, ConsentState.Unknown],
|
|
89
|
+
});
|
|
90
|
+
for await (const msg of stream) {
|
|
91
|
+
if (!msg) continue;
|
|
92
|
+
await handleStreamMessage(acct, msg).catch((err: unknown) => {
|
|
93
|
+
process.stderr.write(`xmtp[${id}] one message failed and was skipped: ${errMsg(err)}\n`);
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
} catch (err) {
|
|
97
|
+
process.stderr.write(
|
|
98
|
+
`xmtp[${id}] stream error (retry 5s): ${errMsg(err)}\n`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
await new Promise((r) => setTimeout(r, 5000));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const cfgs = loadAccounts();
|
|
106
|
+
for (const cfg of cfgs) {
|
|
107
|
+
try {
|
|
108
|
+
await bootAccount(cfg);
|
|
109
|
+
} catch (err) {
|
|
110
|
+
process.stderr.write(
|
|
111
|
+
`xmtp[${cfg.id}] boot FAILED: ${errMsg(err)}\n`,
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
announceAccounts('xmtp', accounts.keys());
|
|
116
|
+
|
|
117
|
+
for (const acct of accounts.values())
|
|
118
|
+
runAccount(acct).catch((err: unknown) => {
|
|
119
|
+
process.stderr.write(
|
|
120
|
+
`xmtp[${acct.cfg.id}] account loop failed: ${errMsg(err)}\n`,
|
|
121
|
+
);
|
|
122
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
export interface GroupLike {
|
|
2
|
+
id: string;
|
|
3
|
+
appData?: string;
|
|
4
|
+
updateAppData?: (s: string) => Promise<void>;
|
|
5
|
+
updateName?: (s: string) => Promise<void>;
|
|
6
|
+
updateDescription?: (s: string) => Promise<void>;
|
|
7
|
+
removeMembers?: (inboxIds: string[]) => Promise<void>;
|
|
8
|
+
sync?: () => Promise<unknown>;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const MAX_LABELS = 16;
|
|
12
|
+
const MAX_LABEL_LEN = 24;
|
|
13
|
+
|
|
14
|
+
function cleanLabels(raw: unknown): string[] {
|
|
15
|
+
if (!Array.isArray(raw)) return [];
|
|
16
|
+
const out: string[] = [];
|
|
17
|
+
const seen = new Set<string>();
|
|
18
|
+
for (const item of raw) {
|
|
19
|
+
if (typeof item !== 'string') continue;
|
|
20
|
+
const label = item.trim().replace(/\s+/g, ' ').slice(0, MAX_LABEL_LEN);
|
|
21
|
+
const key = label.toLowerCase();
|
|
22
|
+
if (!label || seen.has(key)) continue;
|
|
23
|
+
seen.add(key);
|
|
24
|
+
out.push(label);
|
|
25
|
+
if (out.length >= MAX_LABELS) break;
|
|
26
|
+
}
|
|
27
|
+
return out;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function parseAppDataObject(
|
|
31
|
+
existingAppData: string | undefined,
|
|
32
|
+
): Record<string, unknown> {
|
|
33
|
+
if (!existingAppData?.trim()) return {};
|
|
34
|
+
try {
|
|
35
|
+
const p: unknown = JSON.parse(existingAppData);
|
|
36
|
+
if (p && typeof p === 'object' && !Array.isArray(p))
|
|
37
|
+
return p as Record<string, unknown>;
|
|
38
|
+
} catch {
|
|
39
|
+
return {};
|
|
40
|
+
}
|
|
41
|
+
return {};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function trimmedString(v: unknown): string | undefined {
|
|
45
|
+
return typeof v === 'string' && v.trim() ? v.trim() : undefined;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function readAppData(appData: string | undefined): {
|
|
49
|
+
labels: string[];
|
|
50
|
+
github?: string;
|
|
51
|
+
preview?: string;
|
|
52
|
+
} {
|
|
53
|
+
if (!appData?.trim()) return { labels: [] };
|
|
54
|
+
try {
|
|
55
|
+
const p: unknown = JSON.parse(appData);
|
|
56
|
+
if (!p || typeof p !== 'object' || Array.isArray(p)) return { labels: [] };
|
|
57
|
+
const rec = p as Record<string, unknown>;
|
|
58
|
+
return {
|
|
59
|
+
labels: cleanLabels(rec.labels),
|
|
60
|
+
github: trimmedString(rec.github),
|
|
61
|
+
preview: trimmedString(rec.preview),
|
|
62
|
+
};
|
|
63
|
+
} catch {
|
|
64
|
+
return { labels: [] };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function applyMergeKey(
|
|
69
|
+
merged: Record<string, unknown>,
|
|
70
|
+
k: string,
|
|
71
|
+
v: unknown,
|
|
72
|
+
): void {
|
|
73
|
+
if (k === 'labels') {
|
|
74
|
+
merged.labels = cleanLabels(v);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (k === 'github') {
|
|
78
|
+
const g = normalizeGithubUrl(v);
|
|
79
|
+
if (g) merged.github = g;
|
|
80
|
+
else delete merged.github;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (k === 'preview') {
|
|
84
|
+
const p = normalizePreviewUrl(v);
|
|
85
|
+
if (p) merged.preview = p;
|
|
86
|
+
else delete merged.preview;
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (v === undefined || v === null) Reflect.deleteProperty(merged, k);
|
|
90
|
+
else merged[k] = v;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export function mergeAppData(
|
|
94
|
+
existingAppData: string | undefined,
|
|
95
|
+
patch: Record<string, unknown>,
|
|
96
|
+
): { blob: string; merged: Record<string, unknown> } {
|
|
97
|
+
const merged: Record<string, unknown> = {
|
|
98
|
+
...parseAppDataObject(existingAppData),
|
|
99
|
+
v: 1,
|
|
100
|
+
};
|
|
101
|
+
for (const [k, v] of Object.entries(patch)) applyMergeKey(merged, k, v);
|
|
102
|
+
return { blob: JSON.stringify(merged), merged };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function normalizeGithubUrl(url: unknown): string {
|
|
106
|
+
if (typeof url !== 'string')
|
|
107
|
+
throw new Error('setGithub requires a `url` string');
|
|
108
|
+
const trimmed = url.trim();
|
|
109
|
+
if (!trimmed) return '';
|
|
110
|
+
let parsed: URL;
|
|
111
|
+
try {
|
|
112
|
+
parsed = new URL(trimmed);
|
|
113
|
+
} catch {
|
|
114
|
+
throw new Error(`invalid url: ${trimmed}`);
|
|
115
|
+
}
|
|
116
|
+
if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
|
|
117
|
+
throw new Error(`github url must be http(s): ${trimmed}`);
|
|
118
|
+
}
|
|
119
|
+
if (
|
|
120
|
+
parsed.hostname !== 'github.com' &&
|
|
121
|
+
parsed.hostname !== 'www.github.com'
|
|
122
|
+
) {
|
|
123
|
+
throw new Error(`url must be a github.com URL: ${trimmed}`);
|
|
124
|
+
}
|
|
125
|
+
return trimmed;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function normalizePreviewUrl(url: unknown): string {
|
|
129
|
+
if (typeof url !== 'string')
|
|
130
|
+
throw new Error('setPreview requires a `preview` string');
|
|
131
|
+
const trimmed = url.trim();
|
|
132
|
+
if (!trimmed) return '';
|
|
133
|
+
try {
|
|
134
|
+
new URL(trimmed);
|
|
135
|
+
} catch {
|
|
136
|
+
throw new Error(`invalid preview url: ${trimmed}`);
|
|
137
|
+
}
|
|
138
|
+
return trimmed;
|
|
139
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { TrainError } from '@metro-labs/core/train-error';
|
|
2
|
+
|
|
3
|
+
type Args = Record<string, unknown>;
|
|
4
|
+
|
|
5
|
+
export function filterStrings(value: unknown): string[] {
|
|
6
|
+
if (!Array.isArray(value)) return [];
|
|
7
|
+
return value.filter((a) => typeof a === 'string' && a.length > 0) as string[];
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function resolveMembers(args: Args): {
|
|
11
|
+
addrs: string[];
|
|
12
|
+
inboxes: string[];
|
|
13
|
+
} {
|
|
14
|
+
const { addresses, inboxIds, memberAddresses, memberInboxIds } = args as {
|
|
15
|
+
addresses?: string[];
|
|
16
|
+
inboxIds?: string[];
|
|
17
|
+
memberAddresses?: string[];
|
|
18
|
+
memberInboxIds?: string[];
|
|
19
|
+
};
|
|
20
|
+
return {
|
|
21
|
+
addrs: filterStrings(addresses ?? memberAddresses),
|
|
22
|
+
inboxes: filterStrings(inboxIds ?? memberInboxIds),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function parseMemberArgs(
|
|
27
|
+
args: Args,
|
|
28
|
+
verb: string,
|
|
29
|
+
): { line: string; addrs: string[]; inboxes: string[] } {
|
|
30
|
+
const { line } = args as { line: string };
|
|
31
|
+
if (!line || typeof line !== 'string')
|
|
32
|
+
throw new TrainError('INVALID_ARGS', `${verb} requires a \`line\``);
|
|
33
|
+
const { addrs, inboxes } = resolveMembers(args);
|
|
34
|
+
if (addrs.length === 0 && inboxes.length === 0)
|
|
35
|
+
throw new TrainError(
|
|
36
|
+
'INVALID_ARGS',
|
|
37
|
+
`${verb} requires addresses[] or inboxIds[]`,
|
|
38
|
+
);
|
|
39
|
+
return { line, addrs, inboxes };
|
|
40
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { TrainError } from '@metro-labs/core/train-error';
|
|
2
|
+
|
|
3
|
+
export const STAGE_NAMES_PARENT = 'stage.base.eth';
|
|
4
|
+
export const STAGE_PROXY = 'https://proxy.stage.box';
|
|
5
|
+
const LABEL_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
6
|
+
const MIN = 6;
|
|
7
|
+
const MAX = 32;
|
|
8
|
+
const HEADERS = { 'content-type': 'application/json', 'x-stage-client': '1' };
|
|
9
|
+
|
|
10
|
+
export const proxyBase = (): string => process.env.METRO_STAGE_PROXY ?? STAGE_PROXY;
|
|
11
|
+
|
|
12
|
+
export const stageNameOf = (label: string): string => `${label}.${STAGE_NAMES_PARENT}`;
|
|
13
|
+
|
|
14
|
+
export function parseLabel(raw: unknown): string {
|
|
15
|
+
const label = typeof raw === 'string' ? raw.trim().toLowerCase() : '';
|
|
16
|
+
if (label.length < MIN) throw new TrainError('bad_request', `a name needs at least ${String(MIN)} characters`);
|
|
17
|
+
if (label.length > MAX) throw new TrainError('bad_request', `a name can have at most ${String(MAX)} characters`);
|
|
18
|
+
if (!LABEL_RE.test(label)) throw new TrainError('bad_request', 'a name is lowercase letters, digits and single inner hyphens');
|
|
19
|
+
return label;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export const claimMessage = (label: string, address: string, issuedAt: number): string =>
|
|
23
|
+
`Claim ${stageNameOf(label)} for ${address.toLowerCase()} at ${String(issuedAt)}`;
|
|
24
|
+
|
|
25
|
+
async function ask(path: string, init: RequestInit, fetchImpl: typeof fetch): Promise<Record<string, unknown>> {
|
|
26
|
+
let res: Response;
|
|
27
|
+
try {
|
|
28
|
+
res = await fetchImpl(`${proxyBase()}/names/${path}`, { ...init, headers: HEADERS, signal: AbortSignal.timeout(30_000) });
|
|
29
|
+
} catch (err) {
|
|
30
|
+
throw new TrainError('names_unreachable', `could not reach the stage name service: ${err instanceof Error ? err.message : String(err)}`);
|
|
31
|
+
}
|
|
32
|
+
const body: unknown = await res.json().catch(() => null);
|
|
33
|
+
const record = typeof body === 'object' && body !== null ? (body as Record<string, unknown>) : {};
|
|
34
|
+
if (!res.ok) throw new TrainError('names_refused', typeof record.error === 'string' ? record.error : `the stage name service answered ${String(res.status)}`);
|
|
35
|
+
return record;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export async function nameOf(address: string, fetchImpl: typeof fetch = fetch): Promise<string | null> {
|
|
39
|
+
const body = await ask(`status?address=${encodeURIComponent(address)}`, {}, fetchImpl);
|
|
40
|
+
return typeof body.name === 'string' && body.name !== '' ? body.name : null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function checkLabel(label: string, fetchImpl: typeof fetch = fetch): Promise<void> {
|
|
44
|
+
const body = await ask(`check?label=${encodeURIComponent(label)}`, {}, fetchImpl);
|
|
45
|
+
if (body.valid !== true) throw new TrainError('bad_request', typeof body.reason === 'string' ? body.reason : `${stageNameOf(label)} is not a valid name`);
|
|
46
|
+
if (body.available !== true) throw new TrainError('name_taken', `${stageNameOf(label)} is already taken`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function claimName(
|
|
50
|
+
label: string,
|
|
51
|
+
address: string,
|
|
52
|
+
sign: (message: string) => Promise<string>,
|
|
53
|
+
fetchImpl: typeof fetch = fetch,
|
|
54
|
+
now = Date.now(),
|
|
55
|
+
): Promise<string> {
|
|
56
|
+
await checkLabel(label, fetchImpl);
|
|
57
|
+
const signature = await sign(claimMessage(label, address, now));
|
|
58
|
+
const body = await ask('claim', { method: 'POST', body: JSON.stringify({ label, address, issuedAt: now, signature }) }, fetchImpl);
|
|
59
|
+
if (typeof body.name !== 'string' || body.name === '') throw new TrainError('names_refused', 'the stage name service issued no name');
|
|
60
|
+
return body.name;
|
|
61
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises';
|
|
2
|
+
import { encodeFunctionData, encodePacked, keccak256, namehash, stringToBytes, type Hex } from 'viem';
|
|
3
|
+
import { normalize } from 'viem/ens';
|
|
4
|
+
import { TrainError } from '@metro-labs/core/train-error';
|
|
5
|
+
import {
|
|
6
|
+
assertImage,
|
|
7
|
+
fieldsOf,
|
|
8
|
+
parseProfileChange,
|
|
9
|
+
type ProfileApplied,
|
|
10
|
+
type ProfileAvatar,
|
|
11
|
+
type ProfileChange,
|
|
12
|
+
} from '@metro-labs/core/stations/profile';
|
|
13
|
+
import { accountForCall } from './accounts.js';
|
|
14
|
+
import { claimName, nameOf, parseLabel } from './names.js';
|
|
15
|
+
import { sendSponsored, type SmartAccount } from './smart.js';
|
|
16
|
+
import { respond } from './wire.js';
|
|
17
|
+
|
|
18
|
+
export const BASENAME_REGISTRY = '0xB94704422c2a1E396835A571837Aa5AE53285a95' as const;
|
|
19
|
+
export const BASENAME_L2_RESOLVER = '0xC6d566A56A1aFf6508b41f6c90ff131615583BCD' as const;
|
|
20
|
+
export const BASENAME_REVERSE_REGISTRAR = '0x79EA96012eEa67A83431F1701B3dFf7e37F9E282' as const;
|
|
21
|
+
export const PINEAPPLE_UPLOAD_URL = 'https://pineapple.fyi/upload';
|
|
22
|
+
const STAMP_CLEAR_URL = 'https://stamp.fyi/clear/';
|
|
23
|
+
const ZERO = '0x0000000000000000000000000000000000000000';
|
|
24
|
+
const TEXT_KEYS = { name: 'name', bio: 'description', avatar: 'avatar' } as const;
|
|
25
|
+
|
|
26
|
+
export const REGISTRY_ABI = [
|
|
27
|
+
{ name: 'resolver', type: 'function', stateMutability: 'view', inputs: [{ name: 'node', type: 'bytes32' }], outputs: [{ name: '', type: 'address' }] },
|
|
28
|
+
] as const;
|
|
29
|
+
|
|
30
|
+
const RESOLVER_ABI = [
|
|
31
|
+
{
|
|
32
|
+
name: 'setText',
|
|
33
|
+
type: 'function',
|
|
34
|
+
stateMutability: 'nonpayable',
|
|
35
|
+
inputs: [{ name: 'node', type: 'bytes32' }, { name: 'key', type: 'string' }, { name: 'value', type: 'string' }],
|
|
36
|
+
outputs: [],
|
|
37
|
+
},
|
|
38
|
+
{ name: 'multicall', type: 'function', stateMutability: 'nonpayable', inputs: [{ name: 'data', type: 'bytes[]' }], outputs: [{ name: 'results', type: 'bytes[]' }] },
|
|
39
|
+
] as const;
|
|
40
|
+
|
|
41
|
+
const REVERSE_ABI = [
|
|
42
|
+
{ name: 'setName', type: 'function', stateMutability: 'nonpayable', inputs: [{ name: 'name', type: 'string' }], outputs: [{ name: '', type: 'bytes32' }] },
|
|
43
|
+
] as const;
|
|
44
|
+
|
|
45
|
+
export const NAME_ABI = [
|
|
46
|
+
{ name: 'name', type: 'function', stateMutability: 'view', inputs: [{ name: 'node', type: 'bytes32' }], outputs: [{ name: '', type: 'string' }] },
|
|
47
|
+
] as const;
|
|
48
|
+
|
|
49
|
+
const BASE_REVERSE_ROOT = namehash('80002105.reverse');
|
|
50
|
+
|
|
51
|
+
type Args = Record<string, unknown>;
|
|
52
|
+
|
|
53
|
+
const NO_NAME = 'this XMTP account has no name yet, so it has no profile: claim a <label>.stage.base.eth name on its channel page first';
|
|
54
|
+
const NOT_SMART = 'this XMTP account was attached before names existed and cannot hold one; attach XMTP again to get an account that can';
|
|
55
|
+
|
|
56
|
+
export async function pinAvatar(avatar: ProfileAvatar, fetchImpl: typeof fetch = fetch): Promise<string> {
|
|
57
|
+
assertImage(avatar);
|
|
58
|
+
const form = new FormData();
|
|
59
|
+
form.append('file', new Blob([await readFile(avatar.path)], { type: avatar.mime }), avatar.name);
|
|
60
|
+
const res = await fetchImpl(PINEAPPLE_UPLOAD_URL, { method: 'POST', body: form, signal: AbortSignal.timeout(60_000) });
|
|
61
|
+
const body = (await res.json().catch(() => ({}))) as { result?: { cid?: string }; error?: { message?: string } };
|
|
62
|
+
if (!res.ok) throw new TrainError('avatar_upload', `the image upload answered ${String(res.status)}`);
|
|
63
|
+
if (typeof body.error?.message === 'string') throw new TrainError('avatar_upload', body.error.message);
|
|
64
|
+
if (typeof body.result?.cid !== 'string' || body.result.cid === '') throw new TrainError('avatar_upload', 'the image upload returned no CID');
|
|
65
|
+
return `ipfs://${body.result.cid}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export const encodePrimaryName = (name: string): Hex => encodeFunctionData({ abi: REVERSE_ABI, functionName: 'setName', args: [name] });
|
|
69
|
+
|
|
70
|
+
export const reverseNodeOf = (address: string): Hex =>
|
|
71
|
+
keccak256(encodePacked(['bytes32', 'bytes32'], [BASE_REVERSE_ROOT, keccak256(stringToBytes(address.slice(2).toLowerCase()))]));
|
|
72
|
+
|
|
73
|
+
async function resolverOf(smart: SmartAccount, node: Hex): Promise<Hex> {
|
|
74
|
+
const found: Hex = await smart.publicClient.readContract({ address: BASENAME_REGISTRY, abi: REGISTRY_ABI, functionName: 'resolver', args: [node] });
|
|
75
|
+
return found === ZERO ? BASENAME_L2_RESOLVER : found;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function primaryNameOf(smart: SmartAccount): Promise<string | null> {
|
|
79
|
+
const node = reverseNodeOf(smart.address);
|
|
80
|
+
const found = await smart.publicClient.readContract({ address: await resolverOf(smart, node), abi: NAME_ABI, functionName: 'name', args: [node] }).catch(() => '');
|
|
81
|
+
return found === '' ? null : found;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export async function setPrimaryName(smart: SmartAccount, name: string): Promise<boolean> {
|
|
85
|
+
try {
|
|
86
|
+
await sendSponsored(smart, BASENAME_REVERSE_REGISTRAR, encodePrimaryName(name));
|
|
87
|
+
return true;
|
|
88
|
+
} catch (err) {
|
|
89
|
+
process.stderr.write(`xmtp: the name ${name} is claimed but not set as the primary name of ${smart.address}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
export function encodeTextRecords(name: string, records: Record<string, string>): Hex {
|
|
95
|
+
const node = namehash(normalize(name));
|
|
96
|
+
const calls = Object.entries(records).map(([key, value]) => encodeFunctionData({ abi: RESOLVER_ABI, functionName: 'setText', args: [node, key, value] }));
|
|
97
|
+
return encodeFunctionData({ abi: RESOLVER_ABI, functionName: 'multicall', args: [calls] });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const resolverFor = (smart: SmartAccount, name: string): Promise<Hex> => resolverOf(smart, namehash(normalize(name)));
|
|
101
|
+
|
|
102
|
+
async function recordsFor(change: ProfileChange, fetchImpl: typeof fetch): Promise<Record<string, string>> {
|
|
103
|
+
const records: Record<string, string> = {};
|
|
104
|
+
if (change.name !== undefined) records[TEXT_KEYS.name] = change.name;
|
|
105
|
+
if (change.bio !== undefined) records[TEXT_KEYS.bio] = change.bio;
|
|
106
|
+
if (change.avatar !== undefined) records[TEXT_KEYS.avatar] = await pinAvatar(change.avatar, fetchImpl);
|
|
107
|
+
return records;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export async function writeProfile(smart: SmartAccount, name: string, change: ProfileChange, fetchImpl: typeof fetch = fetch): Promise<Hex> {
|
|
111
|
+
const records = await recordsFor(change, fetchImpl);
|
|
112
|
+
const hash = await sendSponsored(smart, await resolverFor(smart, name), encodeTextRecords(name, records));
|
|
113
|
+
const id = smart.address.toLowerCase();
|
|
114
|
+
await Promise.all([fetchImpl(`${STAMP_CLEAR_URL}address/${id}`), fetchImpl(`${STAMP_CLEAR_URL}avatar/eth:${id}`)]).catch(() => undefined);
|
|
115
|
+
return hash;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const smartOf = (args: Args): { accountId: string; address: string; smart: SmartAccount } => {
|
|
119
|
+
const acct = accountForCall(args);
|
|
120
|
+
if (acct.smart === null) throw new TrainError('no_name', NOT_SMART);
|
|
121
|
+
return { accountId: acct.cfg.id, address: acct.address, smart: acct.smart };
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export async function setProfile(id: string, args: Args): Promise<void> {
|
|
125
|
+
const { accountId, address, smart } = smartOf(args);
|
|
126
|
+
const change = parseProfileChange(args);
|
|
127
|
+
const name = await nameOf(address);
|
|
128
|
+
if (name === null) throw new TrainError('no_name', NO_NAME);
|
|
129
|
+
const tx = await writeProfile(smart, name, change);
|
|
130
|
+
const result: ProfileApplied & { name: string; tx: string } = { account: accountId, applied: fieldsOf(change), name, tx };
|
|
131
|
+
respond(id, { result });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export async function claimNameAction(id: string, args: Args): Promise<void> {
|
|
135
|
+
const { accountId, address, smart } = smartOf(args);
|
|
136
|
+
const held = await nameOf(address);
|
|
137
|
+
if (held !== null) throw new TrainError('name_held', `this account already holds ${held}`);
|
|
138
|
+
const name = await claimName(parseLabel(args.label), address, (message) => smart.signMessage(message));
|
|
139
|
+
const primary = await setPrimaryName(smart, name);
|
|
140
|
+
respond(id, { result: { account: accountId, name, primary } });
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
export async function ensurePrimaryName(smart: SmartAccount, name: string): Promise<boolean> {
|
|
144
|
+
if ((await primaryNameOf(smart)) === name) return true;
|
|
145
|
+
return setPrimaryName(smart, name);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export async function nameAction(id: string, args: Args): Promise<void> {
|
|
149
|
+
const acct = accountForCall(args);
|
|
150
|
+
const name = acct.smart === null ? null : await nameOf(acct.address);
|
|
151
|
+
const primary = acct.smart !== null && name !== null ? await ensurePrimaryName(acct.smart, name) : false;
|
|
152
|
+
respond(id, { result: { account: acct.cfg.id, name, primary, canClaim: acct.smart !== null && name === null } });
|
|
153
|
+
}
|