@stage-labs/metro 0.1.0-beta.15 → 0.1.0-beta.151
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +9 -0
- package/dist/claude-settings.js +40 -0
- package/dist/claude.js +182 -0
- package/dist/cli.js +86 -0
- package/dist/control.js +74 -0
- package/dist/hold.js +211 -0
- package/dist/local.js +77 -0
- package/dist/mcp-config.js +25 -0
- package/dist/node-name.js +71 -0
- package/dist/onboarding.js +43 -0
- package/dist/provider-flags.js +9 -0
- package/dist/route.js +50 -0
- package/dist/runtime-install.js +97 -0
- package/dist/runtime.js +74 -0
- package/dist/serve.js +155 -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 -28
- 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 +40 -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/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 +47 -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 +39 -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 +225 -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 +364 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +185 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/keys.ts +40 -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/senders.ts +38 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +200 -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 +30 -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 +380 -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 +354 -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 +263 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +118 -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 +313 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +157 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/health.ts +18 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/model.ts +69 -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 +304 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +299 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +108 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +59 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +101 -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 +247 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-auth.ts +182 -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 +148 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-translate.ts +201 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini.ts +257 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +353 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +232 -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 +242 -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 +186 -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/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 +370 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +210 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +58 -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 +65 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +80 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/owner.ts +48 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +117 -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 +97 -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 +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 +106 -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 +150 -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 +223 -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 +37 -0
- package/runtime/trains/.keep +0 -0
- package/LICENSE +0 -21
- package/README.md +0 -139
- package/dist/broker/claims.js +0 -144
- package/dist/broker/history-stream.js +0 -149
- package/dist/cli/config.js +0 -179
- package/dist/cli/index.js +0 -175
- package/dist/cli/messenger-api.js +0 -214
- package/dist/cli/messenger-transcribe.js +0 -43
- package/dist/cli/messenger-uploads.js +0 -116
- package/dist/cli/monitor-api.js +0 -205
- package/dist/cli/tail.js +0 -95
- package/dist/cli/util.js +0 -72
- package/dist/cli/webhook.js +0 -181
- package/dist/codex-rc/client.js +0 -225
- package/dist/codex-rc/protocol.js +0 -38
- package/dist/dispatcher/server.js +0 -122
- package/dist/dispatcher.js +0 -97
- package/dist/history.js +0 -143
- package/dist/ipc.js +0 -90
- package/dist/lines.js +0 -54
- package/dist/local-identity.js +0 -80
- package/dist/log.js +0 -15
- package/dist/paths.js +0 -114
- package/dist/trains/protocol.js +0 -99
- package/dist/trains/supervisor.js +0 -210
- package/dist/tunnel.js +0 -102
- package/docs/broker.md +0 -166
- package/docs/monitor.md +0 -241
- package/docs/uri-scheme.md +0 -127
- package/examples/README.md +0 -32
- package/examples/telegram.ts +0 -121
- package/skills/metro/SKILL.md +0 -110
package/docs/monitor.md
DELETED
|
@@ -1,241 +0,0 @@
|
|
|
1
|
-
# Metro monitor endpoints
|
|
2
|
-
|
|
3
|
-
Read-only HTTP endpoints for an external observer (the `apps/app` mobile app, an admin
|
|
4
|
-
dashboard, a curl one-liner) to view live daemon state without touching the JSONL files
|
|
5
|
-
directly.
|
|
6
|
-
|
|
7
|
-
These endpoints mount on the **existing** webhook HTTP server (default port `8420`).
|
|
8
|
-
There is no separate daemon, no separate port, no extra process to launch. The
|
|
9
|
-
implementation lives in [`src/cli/monitor-api.ts`](../src/cli/monitor-api.ts) (re-exported
|
|
10
|
-
by `src/cli/tail.ts` for backwards compatibility) and is wired into the HTTP server in
|
|
11
|
-
[`src/dispatcher/server.ts`](../src/dispatcher/server.ts) via `handleMonitorRequest`.
|
|
12
|
-
|
|
13
|
-
## Routes
|
|
14
|
-
|
|
15
|
-
| Method | Path | Returns |
|
|
16
|
-
|--------|---------------------------------|------------------------------------------------------------------------------------------|
|
|
17
|
-
| GET | `/api/state` | JSON snapshot — `{ claims, lines, recent_history (last 100), bot_ids }`. |
|
|
18
|
-
| GET | `/api/tail` | Server-Sent Events stream — `history.jsonl` entries, claim-aware filtered. |
|
|
19
|
-
| POST | `/api/call/<train>/<action>` | Forward an action call to a train via `forward-call` IPC; returns `{result}`. |
|
|
20
|
-
| POST | `/api/messenger/send` | In-daemon chat: emits a history entry on `metro://messenger/owner`. Accepts `{text?, attachments?[], as?}`. |
|
|
21
|
-
| POST | `/api/messenger/register` | Store an Expo push token so agent replies push to the phone. |
|
|
22
|
-
| POST | `/api/messenger/upload` | Raw binary upload (up to 25 MiB). Body = file bytes; headers `Content-Type`, optional `X-Filename`. Returns `{id, url, kind, mime, size, name?}`. |
|
|
23
|
-
| GET | `/api/messenger/files/<name>` | Stream a previously uploaded attachment. Accepts `?token=…` as an alternative to the bearer header for `<img>` / `<audio>` tags. |
|
|
24
|
-
|
|
25
|
-
`/api/state` and `/api/tail` are read-only. `/api/call/<train>/<action>` is the single
|
|
26
|
-
write endpoint — it never touches the on-disk history; the train running on the daemon
|
|
27
|
-
emits its own outbound event after delivering the message, which then flows through the
|
|
28
|
-
normal SSE stream like any other entry.
|
|
29
|
-
|
|
30
|
-
## Authentication
|
|
31
|
-
|
|
32
|
-
Bearer token in env: `METRO_MONITOR_TOKEN`.
|
|
33
|
-
|
|
34
|
-
- If unset, both routes respond **503** with `{"error":"monitor endpoints not configured (METRO_MONITOR_TOKEN unset)"}`. Anonymous access is never allowed by accident.
|
|
35
|
-
- If set, requests **must** carry `Authorization: Bearer <token>`. Missing/wrong/malformed → **401**. The comparison is constant-time (`crypto.timingSafeEqual`).
|
|
36
|
-
|
|
37
|
-
Set in `~/.config/metro/.env`:
|
|
38
|
-
|
|
39
|
-
```
|
|
40
|
-
METRO_MONITOR_TOKEN=<a long random string — `openssl rand -base64 32`>
|
|
41
|
-
```
|
|
42
|
-
|
|
43
|
-
The daemon picks up the env var on next start.
|
|
44
|
-
|
|
45
|
-
## `GET /api/state`
|
|
46
|
-
|
|
47
|
-
Returns a one-shot JSON snapshot:
|
|
48
|
-
|
|
49
|
-
```jsonc
|
|
50
|
-
{
|
|
51
|
-
"claims": {
|
|
52
|
-
"metro://discord/123456789": "metro://claude/user/abc"
|
|
53
|
-
},
|
|
54
|
-
"lines": [
|
|
55
|
-
"metro://discord/123456789",
|
|
56
|
-
"metro://telegram/-100…"
|
|
57
|
-
],
|
|
58
|
-
"recent_history": [/* most-recent-first, up to 100 HistoryEntry objects */],
|
|
59
|
-
"bot_ids": { "discord": "1234567890", "telegram": "987654321" },
|
|
60
|
-
"version": "x.y.z"
|
|
61
|
-
}
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
- `claims` — verbatim contents of `claims.json`.
|
|
65
|
-
- `lines` — the set of conversation URIs seen across recent history and current claims (good-enough proxy for "what lines exist right now"). Subject to refinement; not authoritative.
|
|
66
|
-
- `recent_history` — same shape as `HistoryEntry` in `src/history.ts`, ordered most-recent-first, capped at 100 entries.
|
|
67
|
-
- `bot_ids` — verbatim contents of `bot-ids.json`.
|
|
68
|
-
- `version` — the daemon's package version (handy for clients gating on capabilities).
|
|
69
|
-
|
|
70
|
-
### Pagination
|
|
71
|
-
|
|
72
|
-
For backlog scrolling, pass `?before=<N>&limit=<M>` (both non-negative integers, `limit` clamped to 500):
|
|
73
|
-
|
|
74
|
-
```bash
|
|
75
|
-
curl -H "Authorization: Bearer $METRO_MONITOR_TOKEN" \
|
|
76
|
-
"https://monitor.metro.box/api/state?before=200&limit=100" | jq .recent_history
|
|
77
|
-
```
|
|
78
|
-
|
|
79
|
-
When `before` is set, only `recent_history` is returned (the older slice). Without
|
|
80
|
-
`before`, the full snapshot above is returned.
|
|
81
|
-
|
|
82
|
-
### Example
|
|
83
|
-
|
|
84
|
-
```bash
|
|
85
|
-
curl -H "Authorization: Bearer $METRO_MONITOR_TOKEN" \
|
|
86
|
-
https://monitor.metro.box/api/state | jq
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
## `GET /api/tail` (SSE)
|
|
90
|
-
|
|
91
|
-
Server-Sent Events stream of new `history.jsonl` entries. Each event has:
|
|
92
|
-
|
|
93
|
-
```
|
|
94
|
-
id: <metro-msg-id>
|
|
95
|
-
event: history
|
|
96
|
-
data: <one HistoryEntry as JSON>
|
|
97
|
-
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
The stream stays open until the client disconnects. A `: keepalive` comment is emitted
|
|
101
|
-
every 25 seconds to keep proxies happy.
|
|
102
|
-
|
|
103
|
-
### Query parameters
|
|
104
|
-
|
|
105
|
-
All optional. Mirror the `metro tail` CLI flags.
|
|
106
|
-
|
|
107
|
-
| Param | Default | Effect |
|
|
108
|
-
|--------------------|-------------|-------------------------------------------------------------------------|
|
|
109
|
-
| `as=<line>` | (none) | Self URI — enables "mine + free" claim-aware filtering. |
|
|
110
|
-
| `mode=strict\|unclaimed\|all` | derived | Override the mode (`strict` needs `as=`). |
|
|
111
|
-
| `chat=<line>` | (none) | Only emit events matching this exact `line`. |
|
|
112
|
-
| `station=<name>` | (none) | Only emit events matching this station (`discord`, `telegram`, …). |
|
|
113
|
-
| `include_webhooks=true` | `false` | Include webhook-station events in personal modes. |
|
|
114
|
-
| `since=tail\|0\|<offset>` | `tail` | Where to start in `history.jsonl`. `tail` = EOF; `0` = full replay; or a byte offset. |
|
|
115
|
-
|
|
116
|
-
### Filter rule
|
|
117
|
-
|
|
118
|
-
Same predicate as `metro tail --as=<id> [--strict|--unclaimed|--all] [--include-webhooks]`:
|
|
119
|
-
|
|
120
|
-
> An event is delivered when its `line` is **claimed by `as=`** *or* **claimed by no one**,
|
|
121
|
-
> minus webhooks (unless `include_webhooks=true`), minus anything failing `chat=`/`station=`.
|
|
122
|
-
|
|
123
|
-
See [broker.md](./broker.md) for the underlying broker semantics.
|
|
124
|
-
|
|
125
|
-
### Example: live tail for a claude user
|
|
126
|
-
|
|
127
|
-
```bash
|
|
128
|
-
curl -N \
|
|
129
|
-
-H "Authorization: Bearer $METRO_MONITOR_TOKEN" \
|
|
130
|
-
"https://monitor.metro.box/api/tail?as=metro://claude/user/abc&include_webhooks=true"
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
The `-N` flag disables curl's output buffering so SSE frames appear as they arrive.
|
|
134
|
-
|
|
135
|
-
### Example: full backlog replay for debugging
|
|
136
|
-
|
|
137
|
-
```bash
|
|
138
|
-
curl -N \
|
|
139
|
-
-H "Authorization: Bearer $METRO_MONITOR_TOKEN" \
|
|
140
|
-
"https://monitor.metro.box/api/tail?since=0"
|
|
141
|
-
```
|
|
142
|
-
|
|
143
|
-
## `POST /api/call/<train>/<action>`
|
|
144
|
-
|
|
145
|
-
Forwards an action call to a train (same as `metro call <train> <action> <args>` on the
|
|
146
|
-
command line) via the daemon's existing `forward-call` IPC. Use this from the mobile or
|
|
147
|
-
web app to send a message, react, edit, etc. without needing shell access.
|
|
148
|
-
|
|
149
|
-
### Request
|
|
150
|
-
|
|
151
|
-
```http
|
|
152
|
-
POST /api/call/discord/send HTTP/1.1
|
|
153
|
-
Host: monitor.metro.box
|
|
154
|
-
Authorization: Bearer <METRO_MONITOR_TOKEN>
|
|
155
|
-
Content-Type: application/json
|
|
156
|
-
|
|
157
|
-
{"args": {"line": "metro://discord/123", "text": "hello from the web"}}
|
|
158
|
-
```
|
|
159
|
-
|
|
160
|
-
The body is one of:
|
|
161
|
-
|
|
162
|
-
- `{"args": <object|array|string>}` — explicit `args` wrapper (recommended).
|
|
163
|
-
- `<object>` — any other JSON object is forwarded as the args verbatim (useful for terse
|
|
164
|
-
clients).
|
|
165
|
-
- Empty body — forwarded as `{}`.
|
|
166
|
-
|
|
167
|
-
`<train>` must match a train running under `~/.metro/trains/`; `<action>` is whatever
|
|
168
|
-
that train expects (`send`, `react`, `edit`, …).
|
|
169
|
-
|
|
170
|
-
### Response
|
|
171
|
-
|
|
172
|
-
| Status | Body | Meaning |
|
|
173
|
-
|--------|-----------------------------------------------------|----------------------------------------------------------|
|
|
174
|
-
| 200 | `{"result": <whatever the train returned>}` | Train accepted the call and returned a result. |
|
|
175
|
-
| 400 | `{"error": "bad JSON body: …"}` | Body was not valid JSON. |
|
|
176
|
-
| 401 | `{"error": "unauthorized"}` | Missing / wrong bearer token. |
|
|
177
|
-
| 405 | `{"error": "method not allowed"}` | Wrong verb (only `POST` is accepted on this path). |
|
|
178
|
-
| 500 | `{"error": "…"}` | Daemon IPC unavailable (e.g. socket missing). |
|
|
179
|
-
| 502 | `{"error": "…"}` | Train returned an error or the IPC handshake malformed. |
|
|
180
|
-
|
|
181
|
-
Request bodies larger than 256 KiB are rejected with HTTP 500.
|
|
182
|
-
|
|
183
|
-
### Example: send a message
|
|
184
|
-
|
|
185
|
-
```bash
|
|
186
|
-
curl -X POST \
|
|
187
|
-
-H "Authorization: Bearer $METRO_MONITOR_TOKEN" \
|
|
188
|
-
-H "Content-Type: application/json" \
|
|
189
|
-
-d '{"args":{"line":"metro://discord/123","text":"hi"}}' \
|
|
190
|
-
https://monitor.metro.box/api/call/discord/send
|
|
191
|
-
```
|
|
192
|
-
|
|
193
|
-
Because the daemon's `send` adapter writes a history entry once the message lands, an
|
|
194
|
-
active `/api/tail` subscriber will receive the corresponding event a moment later
|
|
195
|
-
(`from` = local user). UIs typically clear the input on HTTP 200 and let the SSE replay
|
|
196
|
-
show the sent message.
|
|
197
|
-
|
|
198
|
-
## Exposing publicly via Cloudflare tunnel
|
|
199
|
-
|
|
200
|
-
The daemon listens on `127.0.0.1:8420` only. To reach `/api/*` from a phone or a
|
|
201
|
-
remote machine, route a public hostname through the existing `webhook.metro.box`
|
|
202
|
-
cloudflared tunnel.
|
|
203
|
-
|
|
204
|
-
Add a second hostname route to your `cloudflared` config (typically
|
|
205
|
-
`~/.cloudflared/config.yml`):
|
|
206
|
-
|
|
207
|
-
```yaml
|
|
208
|
-
ingress:
|
|
209
|
-
- hostname: webhook.metro.box
|
|
210
|
-
service: http://127.0.0.1:8420
|
|
211
|
-
- hostname: monitor.metro.box # new — same backing service
|
|
212
|
-
service: http://127.0.0.1:8420
|
|
213
|
-
- service: http_status:404
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
Then create the DNS record:
|
|
217
|
-
|
|
218
|
-
```bash
|
|
219
|
-
cloudflared tunnel route dns <tunnel-name> monitor.metro.box
|
|
220
|
-
```
|
|
221
|
-
|
|
222
|
-
Restart the tunnel; both hostnames now reach the same metro daemon. `webhook.metro.box`
|
|
223
|
-
keeps serving inbound webhooks; `monitor.metro.box` serves the bearer-token-gated
|
|
224
|
-
monitor routes.
|
|
225
|
-
|
|
226
|
-
There's no harm in serving `/api/*` from `webhook.metro.box` too — the bearer-token
|
|
227
|
-
gate is the same either way. The separate hostname is purely a routing convenience
|
|
228
|
-
(and lets you put different access policies in front of each, e.g., Cloudflare Access
|
|
229
|
-
on `monitor.metro.box` only).
|
|
230
|
-
|
|
231
|
-
## Failure modes
|
|
232
|
-
|
|
233
|
-
| Condition | Response |
|
|
234
|
-
|------------------------------------|-------------------------------------------------------------------|
|
|
235
|
-
| `METRO_MONITOR_TOKEN` not set | 503 `{"error":"monitor endpoints not configured (...)"}` |
|
|
236
|
-
| Missing `Authorization` header | 401 `{"error":"unauthorized"}` |
|
|
237
|
-
| Wrong / malformed token | 401 `{"error":"unauthorized"}` |
|
|
238
|
-
| Unknown `/api/*` path | 404 `{"error":"not found"}` |
|
|
239
|
-
| `POST` (or any non-`GET`) to `/api/*` | 405 `{"error":"method not allowed"}` |
|
|
240
|
-
| `history.jsonl` doesn't exist yet | 200 with empty `recent_history` / SSE stream w/ no events. |
|
|
241
|
-
| Client disconnects mid-SSE | Handler clears its interval + closes the file watcher cleanly. |
|
package/docs/uri-scheme.md
DELETED
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
# Metro URI scheme
|
|
2
|
-
|
|
3
|
-
Universal identifier for every conversational scope in metro. Lines pass around as opaque strings; only the owning station parses its own paths.
|
|
4
|
-
|
|
5
|
-
## Grammar
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
line = "metro://" station "/" path
|
|
9
|
-
station = lowercase identifier (claude | codex | discord | telegram | webhook | …)
|
|
10
|
-
path = station-specific, "/"-separated segments
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
The URI parses cleanly with the WHATWG `URL` parser: `new URL(line)` gives `protocol="metro:"`, `host=<station>`, `pathname="/<path>"`.
|
|
14
|
-
|
|
15
|
-
## Registered stations
|
|
16
|
-
|
|
17
|
-
| Station | Pattern | Example |
|
|
18
|
-
|------------|----------------------------------------------|------------------------------------------------------------------------|
|
|
19
|
-
| `discord` | `metro://discord/<channel-id>` | `metro://discord/1234567890123456789` |
|
|
20
|
-
| `telegram` | `metro://telegram/<chat-id>[/<topic-id>]` | `metro://telegram/-1001234567890/42` |
|
|
21
|
-
| `claude` | `metro://claude/<user-id>/<session-id>` | `metro://claude/9bfc7af0-…/50b00d11-…` |
|
|
22
|
-
| `codex` | `metro://codex/<user-id>/<session-id>` | `metro://codex/8119ecb1-…/01997d4b-…` |
|
|
23
|
-
| `webhook` | `metro://webhook/<endpoint-id>` | `metro://webhook/fwaCgTKJuLAjS2K0` |
|
|
24
|
-
|
|
25
|
-
Claude / Codex lines mirror the `<root>/<sub>` structure of `metro://telegram/<chat-id>/<topic-id>`: `<user-id>` (the stable account id — same across devices) plays the role of `<chat-id>`, and `<session-id>` (one conversation) plays the role of `<topic-id>`. Both segments are derived per station (see [participants](#participants) below).
|
|
26
|
-
|
|
27
|
-
## Participants
|
|
28
|
-
|
|
29
|
-
Every chat station also exposes participant URIs — used as `from` on inbound/outbound events and history rows.
|
|
30
|
-
|
|
31
|
-
| Kind | Pattern | Example |
|
|
32
|
-
|--------|----------------------------------|---------------------------------------------------------------|
|
|
33
|
-
| user | `metro://<station>/user/<id>` | `metro://discord/user/87654321` |
|
|
34
|
-
| claude | `metro://claude/user/<orgId>` | `metro://claude/user/9bfc7af0-2117-44c5-baf2-d22ba382d065` |
|
|
35
|
-
| codex | `metro://codex/user/<accountId>` | `metro://codex/user/8119ecb1-b05e-48db-aa80-434584439df9` |
|
|
36
|
-
| webhook | `metro://webhook/<endpointId>` | `metro://webhook/fwaCgTKJuLAjS2K0` (line + `from` are the same — no HTTP-side user identity) |
|
|
37
|
-
|
|
38
|
-
`from` and `to` on history entries are always participant URIs. Discord/Telegram inbounds set `from` to the user URI; the daemon sets `to` to the local user identity:
|
|
39
|
-
|
|
40
|
-
- **Claude Code** (`$CLAUDECODE` set) — `metro://claude/user/<orgId>`. `<orgId>` is the stable Anthropic-account UUID, resolved by shelling out to `claude auth status --json`.
|
|
41
|
-
- **Codex** (`$METRO_CODEX_RC` or `$CODEX_HOME` set) — `metro://codex/user/<accountId>`. `<accountId>` is the ChatGPT-account UUID, read from `$CODEX_HOME/auth.json` (default `~/.codex/auth.json`) at the `tokens.account_id` field. Requires `auth_mode=chatgpt`; API-key-only Codex sessions have no account id and metro will error.
|
|
42
|
-
- **Neither** — `to` is the generic `metro://user`.
|
|
43
|
-
|
|
44
|
-
Same account on any machine yields the same URI. Switching accounts via `claude auth login` / `codex login` flips the URI within ~5 s for the long-lived daemon (5 s TTL cache); one-shot CLI invocations re-resolve every run. On outbound, `from` = the same user identity; `to` = the original sender for replies/reacts (looked up from history), or the channel `line` for fresh group sends. A `fromName` field carries the display name (`@alice`, `bonustrack_`).
|
|
45
|
-
|
|
46
|
-
Override with `--from=<uri>` on any write command, or set `$METRO_FROM` to pin a custom identity for the whole session.
|
|
47
|
-
|
|
48
|
-
Chat lines identify a Discord channel / Telegram chat (with optional forum topic). Claude / Codex lines identify a *specific session* of a specific user (`<user-id>/<session-id>`) — posting to one re-emits the message on the daemon's stdout stream and (if configured) pushes it to the Codex app-server. They have no inherent "messages"; only events.
|
|
49
|
-
|
|
50
|
-
### Session derivation per station
|
|
51
|
-
|
|
52
|
-
| Station | `<user-id>` | `<session-id>` |
|
|
53
|
-
|----------|----------------------------|-----------------------------------------------------------------|
|
|
54
|
-
| `claude` | `orgId` from `claude auth status --json` | `$CLAUDE_CODE_SESSION_ID` (set by Claude Code; stable across `--resume`)|
|
|
55
|
-
| `codex` | `tokens.account_id` from `$CODEX_HOME/auth.json` | codex-rc thread id from the JSON-RPC handshake (`thread/loaded/list` → `thread/start`) |
|
|
56
|
-
|
|
57
|
-
Override either segment with `METRO_USER_ID` / `METRO_USER_SESSION_ID` env vars.
|
|
58
|
-
|
|
59
|
-
### User registry
|
|
60
|
-
|
|
61
|
-
The daemon persists every `(station, user-id, session)` tuple it sees to `$METRO_STATE_DIR/user-registry.json`. `metro lines` lists the recently-seen conversation URIs.
|
|
62
|
-
|
|
63
|
-
## Webhook station
|
|
64
|
-
|
|
65
|
-
Receive-only HTTP endpoint for third-party services (GitHub, Intercom, Fireflies, …). Each registered endpoint is one `metro://webhook/<endpoint-id>` line.
|
|
66
|
-
|
|
67
|
-
- **Register:** `metro webhook add <label> [--secret=<shared-secret>]` mints a 16-char endpoint id (96 bits of entropy, persisted to `$METRO_STATE_DIR/webhooks.json`) and prints the receiving URL. `metro webhook list` / `remove <id>` for the obvious.
|
|
68
|
-
- **Listener:** the dispatcher binds `127.0.0.1:8420` (override with `METRO_WEBHOOK_PORT`) when ≥1 endpoint is registered. Routes `POST /wh/<endpoint-id>` to an inbound event with `payload: { headers, body }` — `body` is parsed JSON when the request `Content-Type` is JSON, raw string otherwise. `GET /wh/<endpoint-id>` returns 200 (for provider ping checks).
|
|
69
|
-
- **Envelope:** `messageId` falls back to `X-GitHub-Delivery` / `X-Request-ID` / a generated UUID for idempotency tracking. `text` is synthesized from `X-GitHub-Event` / `X-Intercom-Topic` plus method + path for at-a-glance routing; consumers narrow on `payload.body` for full event details.
|
|
70
|
-
- **HMAC verification:** if `--secret` was set on `metro webhook add`, requests must include a matching `X-Hub-Signature-256: sha256=<hex>` (GitHub/Intercom format) — mismatches are rejected with 401 before reaching the stream.
|
|
71
|
-
- **Public reachability:** provided by a Cloudflare named tunnel — see [Tunneling](#tunneling) below. Without one, the listener stays loopback-only (useful for `curl` testing).
|
|
72
|
-
|
|
73
|
-
## Tunneling
|
|
74
|
-
|
|
75
|
-
Webhook providers need a public URL. Metro integrates with **Cloudflare named tunnels** (free, stable, account-scoped):
|
|
76
|
-
|
|
77
|
-
```bash
|
|
78
|
-
cloudflared tunnel login # one-time OAuth (browser)
|
|
79
|
-
metro tunnel setup metro webhook.yourdomain.com # creates the tunnel + DNS route
|
|
80
|
-
metro # daemon spawns `cloudflared tunnel run`
|
|
81
|
-
```
|
|
82
|
-
|
|
83
|
-
After setup, `metro webhook list` prints `https://webhook.yourdomain.com/wh/<id>` for each endpoint. The URL is stable across restarts (bound to the tunnel UUID in `~/.cloudflared/<uuid>.json`, not the cloudflared process). Tunnel config persists at `$METRO_STATE_DIR/tunnel.json`. Without setup, endpoints fall back to `http://127.0.0.1:8420/wh/<id>` (local-only, useful for curl testing).
|
|
84
|
-
|
|
85
|
-
## Message addressing
|
|
86
|
-
|
|
87
|
-
Messages on chat lines are referenced by **line + message id** (two args), not as part of the URI. So:
|
|
88
|
-
|
|
89
|
-
```bash
|
|
90
|
-
metro call discord send '{"line":"metro://discord/123","text":"ack","replyTo":"4567"}'
|
|
91
|
-
metro call discord edit '{"line":"metro://discord/123","messageId":"9876","text":"fixed typo"}'
|
|
92
|
-
metro call telegram react '{"line":"metro://telegram/-100/42","messageId":"4567","emoji":"👍"}'
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
## Properties
|
|
96
|
-
|
|
97
|
-
- **Stable**: a Line is valid for the lifetime of the scope.
|
|
98
|
-
- **Self-describing**: the station name is encoded; the dispatcher routes by station prefix.
|
|
99
|
-
- **Persistable**: safe as a JSON key on disk (used by `lines.json`).
|
|
100
|
-
- **Branded**: TypeScript type `Line` prevents mixing with arbitrary strings.
|
|
101
|
-
|
|
102
|
-
## API
|
|
103
|
-
|
|
104
|
-
```ts
|
|
105
|
-
import { Line } from './lines.js'; // value namespace + type
|
|
106
|
-
|
|
107
|
-
const l: Line = Line.discord('1234567890'); // typed Line
|
|
108
|
-
Line.parse(l); // { station: 'discord', path: ['1234567890'] } | null
|
|
109
|
-
Line.station(l); // 'discord'
|
|
110
|
-
Line.claude(orgId, sessionId); // metro://claude/<orgId>/<sessionId>
|
|
111
|
-
Line.codex(accountId, threadId); // metro://codex/<accountId>/<threadId>
|
|
112
|
-
Line.parseClaude(l); // { userId, sessionId } | null
|
|
113
|
-
Line.parseCodex(l); // { userId, sessionId } | null
|
|
114
|
-
Line.webhook(endpointId); // metro://webhook/<endpointId>
|
|
115
|
-
Line.parseWebhook(l); // string | null (the endpoint id)
|
|
116
|
-
Line.user(station, id); // metro://<station>/user/<id>
|
|
117
|
-
Line.isLocal(l); // true for any metro://{claude,codex}/...
|
|
118
|
-
```
|
|
119
|
-
|
|
120
|
-
## Adding a new station
|
|
121
|
-
|
|
122
|
-
A "station" in this URI scheme is just a namespace — anything a train emits with
|
|
123
|
-
`metro://<name>/<path>` works. There is no required registration with core:
|
|
124
|
-
|
|
125
|
-
1. Pick a lowercase station name (`slack`, `matrix`, …).
|
|
126
|
-
2. In your train, emit envelopes with `line: "metro://<name>/<id>"`.
|
|
127
|
-
3. Optionally add a `Line.<station>(...)` formatter to `src/lines.ts` for type-safe construction.
|
package/examples/README.md
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# Example train
|
|
2
|
-
|
|
3
|
-
`telegram.ts` is a starting point, not runtime code. Copy to
|
|
4
|
-
`~/.metro/trains/<name>.ts`, edit, save, restart the daemon:
|
|
5
|
-
|
|
6
|
-
```
|
|
7
|
-
cp telegram.ts ~/.metro/trains/telegram.ts
|
|
8
|
-
echo 'TELEGRAM_BOT_TOKEN=…' >> ~/.metro/.env
|
|
9
|
-
metro
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
For a Discord port: swap the API base + auth header (`Bot $TOKEN`), install
|
|
13
|
-
`discord.js` for the gateway, and emit the same envelope shape — the
|
|
14
|
-
stdin/stdout protocol below is platform-independent. Action names and payload
|
|
15
|
-
shapes are entirely up to you.
|
|
16
|
-
|
|
17
|
-
## Protocol (JSON lines over stdio)
|
|
18
|
-
|
|
19
|
-
```
|
|
20
|
-
metro ─── stdin (one JSON line per action call) ──> train
|
|
21
|
-
<── stdout (one JSON line per event OR response) ── train
|
|
22
|
-
```
|
|
23
|
-
|
|
24
|
-
- **Inbound event** (train → metro): `{ kind, station, line, from, from_name?, message_id?, line_name?, reply_to?, text?, is_private?, payload? }` — snake_case on the wire. Metro mints `id` + `display` if absent and translates to camelCase for `history.jsonl` / the broker (`HistoryEntry` in `src/history.ts`).
|
|
25
|
-
- **Call** (metro → train): `{ "op": "call", "id": "req_abc", "action": "send", "args": {...} }`.
|
|
26
|
-
- **Response** (train → metro): `{ "op": "response", "id": "req_abc", "result": {...} }` or `{ ..., "error": "..." }`.
|
|
27
|
-
|
|
28
|
-
Anything on stdout without an `op` is treated as an inbound event.
|
|
29
|
-
|
|
30
|
-
## Lifecycle
|
|
31
|
-
|
|
32
|
-
Metro scans `~/.metro/trains/*.{ts,js,mjs}` at boot — one subprocess per file. Crashed trains restart with backoff (1s → 5s → 30s, up to 5 consecutive failures). `metro trains list` shows state. Restart the daemon to pick up edits. `~/.metro/.env` is auto-loaded into each train's `process.env`.
|
package/examples/telegram.ts
DELETED
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Reference train — Telegram (long-polling, no npm deps). Pattern is identical for any platform.
|
|
3
|
-
*
|
|
4
|
-
* Setup:
|
|
5
|
-
* cp <this-file> ~/.metro/trains/telegram.ts
|
|
6
|
-
* echo 'TELEGRAM_BOT_TOKEN=…' >> ~/.metro/.env
|
|
7
|
-
*
|
|
8
|
-
* Discord-flavoured port: swap the API base + `tg()` helper for `https://discord.com/api/v10`
|
|
9
|
-
* with `Authorization: Bot $TOKEN`, install `discord.js` for the gateway, and emit the same
|
|
10
|
-
* envelope shape. The stdin/stdout protocol is platform-independent. Discord adds `sticker_ids`
|
|
11
|
-
* support on `sendMessage` payloads — preserve those in `payload` so downstream tooling sees them.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
const TOKEN = process.env.TELEGRAM_BOT_TOKEN;
|
|
15
|
-
if (!TOKEN) { process.stderr.write('TELEGRAM_BOT_TOKEN unset\n'); process.exit(2); }
|
|
16
|
-
const API = `https://api.telegram.org/bot${TOKEN}`;
|
|
17
|
-
|
|
18
|
-
const emit = (e: unknown): void => void process.stdout.write(JSON.stringify(e) + '\n');
|
|
19
|
-
const respond = (id: string, body: { result?: unknown; error?: string }): void =>
|
|
20
|
-
void process.stdout.write(JSON.stringify({ op: 'response', id, ...body }) + '\n');
|
|
21
|
-
const mintId = (): string => `msg_${Math.random().toString(36).slice(2, 10)}`;
|
|
22
|
-
/** Self URI for outbound emissions. Set by the supervisor; trains echo their own sends so mobile + tail see them. */
|
|
23
|
-
const SELF_URI = process.env.METRO_SELF_URI ?? '';
|
|
24
|
-
const emitOutbound = (line: string, messageId: string, text: string, replyTo?: string): void => emit({
|
|
25
|
-
kind: 'outbound', id: mintId(), ts: new Date().toISOString(),
|
|
26
|
-
station: 'telegram', line, from: SELF_URI, to: line, message_id: messageId, text, reply_to: replyTo,
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
async function tg<T>(method: string, body: unknown, timeoutMs = 30_000): Promise<T> {
|
|
30
|
-
const res = await fetch(`${API}/${method}`, {
|
|
31
|
-
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
32
|
-
body: JSON.stringify(body), signal: AbortSignal.timeout(timeoutMs),
|
|
33
|
-
});
|
|
34
|
-
const json = (await res.json()) as { ok: boolean; description?: string; result?: T };
|
|
35
|
-
if (!json.ok) throw new Error(`telegram ${method}: ${json.description ?? 'unknown'}`);
|
|
36
|
-
return json.result as T;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
type TgMsg = {
|
|
40
|
-
message_id: number; date: number;
|
|
41
|
-
chat: { id: number; type: string; title?: string; first_name?: string };
|
|
42
|
-
from?: { id: number; username?: string; first_name?: string; is_bot?: boolean };
|
|
43
|
-
text?: string; caption?: string;
|
|
44
|
-
message_thread_id?: number; is_topic_message?: boolean;
|
|
45
|
-
photo?: unknown[]; document?: { file_name?: string };
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
function envelope(m: TgMsg): Record<string, unknown> {
|
|
49
|
-
const tags = [...(m.photo?.length ? ['[image]'] : []), ...(m.document ? [`[file: ${m.document.file_name ?? 'doc'}]`] : [])];
|
|
50
|
-
const text = [m.text ?? m.caption, ...tags].filter(Boolean).join(' ');
|
|
51
|
-
const topicId = m.is_topic_message ? m.message_thread_id : undefined;
|
|
52
|
-
const line = topicId !== undefined ? `metro://telegram/${m.chat.id}/${topicId}` : `metro://telegram/${m.chat.id}`;
|
|
53
|
-
return {
|
|
54
|
-
kind: 'inbound', id: mintId(), ts: new Date(m.date * 1000).toISOString(),
|
|
55
|
-
station: 'telegram', line,
|
|
56
|
-
line_name: topicId === undefined ? (m.chat.title ?? m.chat.first_name ?? undefined) : undefined,
|
|
57
|
-
from: `metro://telegram/user/${m.from?.id ?? 'unknown'}`,
|
|
58
|
-
from_name: m.from?.username ? `@${m.from.username}` : m.from?.first_name,
|
|
59
|
-
message_id: String(m.message_id), text, payload: m, is_private: m.chat.type === 'private',
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
const targetOf = (line: string): { chatId: number; topicId?: number } => {
|
|
64
|
-
const m = line.match(/^metro:\/\/telegram\/(-?\d+)(?:\/(\d+))?/);
|
|
65
|
-
if (!m) throw new Error(`bad telegram line: ${line}`);
|
|
66
|
-
return { chatId: Number(m[1]), topicId: m[2] ? Number(m[2]) : undefined };
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
type CallMsg = { op: 'call'; id: string; action: string; args: Record<string, unknown> };
|
|
70
|
-
async function handleCall({ id, action, args }: CallMsg): Promise<void> {
|
|
71
|
-
try {
|
|
72
|
-
if (action === 'send') {
|
|
73
|
-
const { line, text, replyTo } = args as { line: string; text: string; replyTo?: string };
|
|
74
|
-
const { chatId, topicId } = targetOf(line);
|
|
75
|
-
const body: Record<string, unknown> = { chat_id: chatId, text };
|
|
76
|
-
if (topicId !== undefined) body.message_thread_id = topicId;
|
|
77
|
-
if (replyTo) body.reply_parameters = { message_id: Number(replyTo) };
|
|
78
|
-
const sent = await tg<{ message_id: number }>('sendMessage', body);
|
|
79
|
-
const sentId = String(sent.message_id);
|
|
80
|
-
emitOutbound(line, sentId, text, replyTo);
|
|
81
|
-
respond(id, { result: { messageId: sentId } });
|
|
82
|
-
} else if (action === 'react') {
|
|
83
|
-
const { line, messageId, emoji } = args as { line: string; messageId: string; emoji: string };
|
|
84
|
-
await tg('setMessageReaction', {
|
|
85
|
-
chat_id: targetOf(line).chatId, message_id: Number(messageId),
|
|
86
|
-
reaction: emoji ? [{ type: 'emoji', emoji }] : [],
|
|
87
|
-
});
|
|
88
|
-
respond(id, { result: { ok: true } });
|
|
89
|
-
} else respond(id, { error: `unknown action '${action}' (have: send, react)` });
|
|
90
|
-
} catch (err) { respond(id, { error: (err as Error).message }); }
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
let buf = '';
|
|
94
|
-
process.stdin.setEncoding('utf8');
|
|
95
|
-
process.stdin.on('data', chunk => {
|
|
96
|
-
buf += chunk;
|
|
97
|
-
let nl;
|
|
98
|
-
while ((nl = buf.indexOf('\n')) !== -1) {
|
|
99
|
-
const line = buf.slice(0, nl).trim();
|
|
100
|
-
buf = buf.slice(nl + 1);
|
|
101
|
-
if (!line) continue;
|
|
102
|
-
try { const msg = JSON.parse(line); if (msg.op === 'call') void handleCall(msg); }
|
|
103
|
-
catch (err) { process.stderr.write(`bad stdin line: ${(err as Error).message}\n`); }
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
let offset = 0;
|
|
108
|
-
process.stderr.write('telegram train ready\n');
|
|
109
|
-
while (true) {
|
|
110
|
-
try {
|
|
111
|
-
const updates = await tg<{ update_id: number; message?: TgMsg }[]>('getUpdates',
|
|
112
|
-
{ offset, timeout: 25, allowed_updates: ['message'] }, 60_000);
|
|
113
|
-
for (const u of updates) {
|
|
114
|
-
offset = u.update_id + 1;
|
|
115
|
-
if (u.message && !u.message.from?.is_bot) emit(envelope(u.message));
|
|
116
|
-
}
|
|
117
|
-
} catch (err) {
|
|
118
|
-
process.stderr.write(`telegram poll error: ${(err as Error).message}\n`);
|
|
119
|
-
await new Promise(r => setTimeout(r, 2_000));
|
|
120
|
-
}
|
|
121
|
-
}
|
package/skills/metro/SKILL.md
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: metro
|
|
3
|
-
description: Run the metro train-supervisor in this session — launch `metro` in the background, watch its stdout for inbound JSON events, and act on each. Use when the user asks to start/run/launch metro, when you see JSON lines on stdout shaped `{"station":"…","line":"metro://…","from":"…","to":"…","text":"…"}`, or when handling a chat/webhook reply/edit/react/send via `metro call`.
|
|
4
|
-
---
|
|
5
|
-
|
|
6
|
-
# Metro — event-interception wire
|
|
7
|
-
|
|
8
|
-
Metro is the wire between this session and any number of platforms. Platform code lives in
|
|
9
|
-
**trains** under `~/.metro/trains/` — single TS files that you (the agent) write, edit, or
|
|
10
|
-
replace on demand.
|
|
11
|
-
|
|
12
|
-
## What metro does
|
|
13
|
-
|
|
14
|
-
1. Spawns each file in `~/.metro/trains/*.{ts,js,mjs}` as a long-running Bun subprocess.
|
|
15
|
-
2. Multiplexes their stdout (JSON lines) into one unified event stream on metro's stdout.
|
|
16
|
-
3. Routes `metro call <train> <action> <args>` requests back to the matching train's stdin.
|
|
17
|
-
4. Two builtin event sources stay in core: **webhooks** (HTTP receiver) and **messenger** (in-daemon chat).
|
|
18
|
-
|
|
19
|
-
## Starting metro
|
|
20
|
-
|
|
21
|
-
**Claude Code:** `Bash(command: "metro", run_in_background: true)`, then attach `Monitor` to its stdout.
|
|
22
|
-
|
|
23
|
-
**Codex:** `shell(command: "METRO_CODEX_RC=ws://127.0.0.1:8421 metro", run_in_background: true)` — metro pushes each event via JSON-RPC `turn/start`. The user must run a Codex daemon + TUI on the same WebSocket URL (`codex app-server --listen ws://127.0.0.1:8421` + `codex --remote ws://127.0.0.1:8421`, type "hi" once to seed a thread).
|
|
24
|
-
|
|
25
|
-
`metro doctor` reports trains found, deps installed, dispatcher running, codex-rc, skill install.
|
|
26
|
-
|
|
27
|
-
## Envelope
|
|
28
|
-
|
|
29
|
-
Every event on stdout is a single JSON line:
|
|
30
|
-
|
|
31
|
-
```json
|
|
32
|
-
{"id":"msg_…","ts":"2026-05-17T18:00:00Z","station":"discord","line":"metro://discord/123","line_name":"infra","from":"metro://discord/user/456","from_name":"alice","to":"metro://claude/user/9bfc…","text":"hi","message_id":"789","reply_to":"…","payload":{…}}
|
|
33
|
-
```
|
|
34
|
-
|
|
35
|
-
Wire fields are `snake_case` on the train protocol; the dispatcher translates to camelCase for `history.jsonl`. Trains supply `line`, `from`, `to`, `text`, plus `payload` (the platform's native message). Metro mints `id` + `display` if missing.
|
|
36
|
-
|
|
37
|
-
**No `kind` field.** Direction is derived: `Line.isLocal(from)` → outbound (📤), else inbound (📩). Reactions and edits are train-specific — encode them in `text` (e.g. `[react 👀]`) plus whatever you need in `payload`.
|
|
38
|
-
|
|
39
|
-
## Outbound: `metro call <train> <action> <args>`
|
|
40
|
-
|
|
41
|
-
```
|
|
42
|
-
metro call discord send '{"line":"metro://discord/123","text":"hi","replyTo":"789"}'
|
|
43
|
-
metro call telegram react '{"line":"metro://telegram/-100/1","messageId":"42","emoji":"👀"}'
|
|
44
|
-
metro call discord edit '{"line":"metro://discord/123","messageId":"999","text":"new"}'
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
`[args]` can be JSON, `@path/to/args.json`, `-` (stdin), or a bare string. Action names are whatever the train exposes — metro core knows nothing about them.
|
|
48
|
-
|
|
49
|
-
## Writing a new train
|
|
50
|
-
|
|
51
|
-
1. Start from `node_modules/@stage-labs/metro/examples/telegram.ts`.
|
|
52
|
-
2. Copy → `~/.metro/trains/<name>.ts` and edit. Keep the inbound envelope shape and the `op:"call"` → `op:"response"` protocol.
|
|
53
|
-
3. Deps (if needed): `cd ~/.metro && bun add <pkg>`. Credentials: `echo 'FOO_TOKEN=…' >> ~/.metro/.env`.
|
|
54
|
-
4. Restart the metro daemon (or just `metro trains restart <name>`) to pick up the new train.
|
|
55
|
-
|
|
56
|
-
Trains are throwaway — if the user asks for new functionality, rewrite the train rather than adding glue in core.
|
|
57
|
-
|
|
58
|
-
## First-run setup (once per machine)
|
|
59
|
-
|
|
60
|
-
```
|
|
61
|
-
mkdir -p ~/.metro && cd ~/.metro && bun init -y
|
|
62
|
-
cp node_modules/@stage-labs/metro/examples/telegram.ts ~/.metro/trains/
|
|
63
|
-
echo 'TELEGRAM_BOT_TOKEN=…' >> ~/.metro/.env
|
|
64
|
-
metro setup skill # optional — installs this SKILL.md into ~/.claude / ~/.codex
|
|
65
|
-
metro
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
## Detecting "is this for me?"
|
|
69
|
-
|
|
70
|
-
Trains should set `is_private: true` for DMs. For groups, narrow on `payload`:
|
|
71
|
-
|
|
72
|
-
- **discord** — DM when `payload.guildId == null`; otherwise look at `payload.mentions.users`.
|
|
73
|
-
- **telegram** — DM when `payload.chat.type === 'private'`; otherwise look at `payload.entities` mentions.
|
|
74
|
-
- **webhook** — every POST is for you by design. Route on `payload.headers['x-github-event']` / `x-intercom-topic`.
|
|
75
|
-
- **messenger** — every event on the messenger line is between the user and the agent. No filtering needed.
|
|
76
|
-
|
|
77
|
-
## CLI cheat sheet
|
|
78
|
-
|
|
79
|
-
```
|
|
80
|
-
metro # start the daemon
|
|
81
|
-
metro trains [list] # list trains + state
|
|
82
|
-
metro trains new <name> # scaffold ~/.metro/trains/<name>.ts
|
|
83
|
-
metro trains restart <name> # kill + respawn a train (resets backoff)
|
|
84
|
-
metro call <train> <action> <args> # forward an action call
|
|
85
|
-
metro tail [--as=<user-uri>] [--follow] # subscribe to the event log
|
|
86
|
-
metro history [--limit=N] [--line=…] [--from=…] [--text=…] [--since=…]
|
|
87
|
-
metro webhook add <label> # register an HTTP receive endpoint
|
|
88
|
-
metro tunnel setup <name> <hostname> # configure a Cloudflare named tunnel
|
|
89
|
-
metro doctor # health check
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
## Webhooks (builtin source)
|
|
93
|
-
|
|
94
|
-
Webhooks stay in core because they're shared HTTP infra (one Cloudflare tunnel routes many endpoints). `metro webhook add <label>` issues an endpoint id; the full URL is `https://<tunnel-host>/wh/<id>` (or `http://127.0.0.1:8420/wh/<id>` locally). Events arrive on `metro://webhook/<id>`.
|
|
95
|
-
|
|
96
|
-
## Messenger (builtin source)
|
|
97
|
-
|
|
98
|
-
Direct chat between the agent and the device. Five endpoints — all under the same bearer-token guard as `/api/state`:
|
|
99
|
-
|
|
100
|
-
- `POST /api/messenger/send {text?, attachments?, as?}` — emit an envelope on `metro://messenger/owner`. Either text or attachments required.
|
|
101
|
-
- `POST /api/messenger/react {messageId, emoji, as?}` — emit a slim `payload.reactTo` envelope. If the sender already has an active reaction with this emoji on this target, emits `{removed: true}` instead — reactions are toggleable.
|
|
102
|
-
- `POST /api/messenger/upload` — raw binary upload (≤ 25 MiB, body = file bytes, `Content-Type` + optional `X-Filename` headers). Returns `{id, url, kind, mime, size, name?}`; files land in `$METRO_STATE_DIR/messenger-uploads/`.
|
|
103
|
-
- `GET /api/messenger/files/<name>` — stream a stored upload. Accepts `Authorization: Bearer …` header *or* `?token=…` query (for `<img>` / `<audio>` tags). Content-Type derived from extension.
|
|
104
|
-
- `POST /api/messenger/register {pushToken}` — store an Expo push token so agent replies push to the device.
|
|
105
|
-
|
|
106
|
-
The mobile + web companion apps use these for chat with the agent. The envelope is the standard slim shape — no `kind` / `emoji` fields, direction derived from `Line.isLocal(from)`.
|
|
107
|
-
|
|
108
|
-
## Crashes
|
|
109
|
-
|
|
110
|
-
If a train crashes, metro restarts it with backoff (1s → 5s → 30s, then gives up after 5 consecutive failures). Use `metro trains` to check state.
|