@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/dist/trains/protocol.js
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/** Train stdin/stdout protocol: line buffering + JSON envelope + outbound call bookkeeping. */
|
|
2
|
-
import { readdirSync, statSync } from 'node:fs';
|
|
3
|
-
import { join, parse as parsePath } from 'node:path';
|
|
4
|
-
import { errMsg, log } from '../log.js';
|
|
5
|
-
export const CALL_TIMEOUT_MS = 60_000;
|
|
6
|
-
export const STDOUT_LINE_MAX = 4 * 1024 * 1024; // 4 MiB safeguard per line
|
|
7
|
-
/** Classify a single parsed stdout line from a train. */
|
|
8
|
-
export function parseTrainLine(name, line) {
|
|
9
|
-
let msg;
|
|
10
|
-
try {
|
|
11
|
-
msg = JSON.parse(line);
|
|
12
|
-
}
|
|
13
|
-
catch (err) {
|
|
14
|
-
log.warn({ name, err: errMsg(err), line: line.slice(0, 200) }, 'train: bad JSON');
|
|
15
|
-
return null;
|
|
16
|
-
}
|
|
17
|
-
if (msg.op === 'response') {
|
|
18
|
-
if (typeof msg.id !== 'string')
|
|
19
|
-
return { op: 'ignore' };
|
|
20
|
-
return { op: 'response', id: msg.id, result: msg.result, error: msg.error };
|
|
21
|
-
}
|
|
22
|
-
if (msg.op === 'log')
|
|
23
|
-
return { op: 'log', text: msg.text };
|
|
24
|
-
/** Anything without an `op` (or with `op:"event"`) is an inbound event. */
|
|
25
|
-
/** Defensive shape check: every event needs `line` (string). Log + drop if missing. */
|
|
26
|
-
if (typeof msg.line !== 'string') {
|
|
27
|
-
log.warn({ name, line: line.slice(0, 200) }, 'train: event missing `line` (string) — dropped');
|
|
28
|
-
return { op: 'ignore' };
|
|
29
|
-
}
|
|
30
|
-
return { op: 'event', event: msg };
|
|
31
|
-
}
|
|
32
|
-
/** Consume complete `\n`-terminated lines from a rolling buffer; invoke `onLine` for each. */
|
|
33
|
-
/** Returns the leftover (incomplete) buffer to keep accumulating. */
|
|
34
|
-
export function drainLines(name, buf, onLine) {
|
|
35
|
-
if (buf.length > STDOUT_LINE_MAX && !buf.includes('\n')) {
|
|
36
|
-
log.warn({ name, bytes: buf.length }, 'train: dropping oversized stdout line');
|
|
37
|
-
return '';
|
|
38
|
-
}
|
|
39
|
-
let nl;
|
|
40
|
-
while ((nl = buf.indexOf('\n')) !== -1) {
|
|
41
|
-
const line = buf.slice(0, nl).trim();
|
|
42
|
-
buf = buf.slice(nl + 1);
|
|
43
|
-
if (!line)
|
|
44
|
-
continue;
|
|
45
|
-
onLine(line);
|
|
46
|
-
}
|
|
47
|
-
return buf;
|
|
48
|
-
}
|
|
49
|
-
export function encodeCall(id, action, args) {
|
|
50
|
-
return JSON.stringify({ op: 'call', id, action, args }) + '\n';
|
|
51
|
-
}
|
|
52
|
-
function isTrainFile(name) {
|
|
53
|
-
return /\.(ts|js|mjs)$/.test(name) && !name.startsWith('_') && !name.startsWith('.');
|
|
54
|
-
}
|
|
55
|
-
/** Discover trains under `dir`: regular files with allowed extensions, no `_` / `.` prefix. */
|
|
56
|
-
export function listTrainFiles(dir) {
|
|
57
|
-
return readdirSync(dir).filter(isTrainFile)
|
|
58
|
-
.map(f => ({ name: parsePath(f).name, path: join(dir, f) }))
|
|
59
|
-
.filter(t => { try {
|
|
60
|
-
return statSync(t.path).isFile();
|
|
61
|
-
}
|
|
62
|
-
catch {
|
|
63
|
-
return false;
|
|
64
|
-
} });
|
|
65
|
-
}
|
|
66
|
-
/* ──────────── outbound call bookkeeping (per-train pending map + timeout) ──────────── */
|
|
67
|
-
export function mintCallId(seq) {
|
|
68
|
-
return `req_${seq}_${Math.random().toString(36).slice(2, 8)}`;
|
|
69
|
-
}
|
|
70
|
-
/** Reject + clear all pending calls on a train (used on shutdown/exit). */
|
|
71
|
-
export function failAllPending(pending, reason) {
|
|
72
|
-
for (const p of pending.values()) {
|
|
73
|
-
clearTimeout(p.timer);
|
|
74
|
-
p.reject(new Error(reason));
|
|
75
|
-
}
|
|
76
|
-
pending.clear();
|
|
77
|
-
}
|
|
78
|
-
/** Dispatch `action` to a train's stdin and register a pending entry; returns the awaitable. */
|
|
79
|
-
export function sendCall(t, id, action, args) {
|
|
80
|
-
return new Promise((resolve, reject) => {
|
|
81
|
-
const timer = setTimeout(() => {
|
|
82
|
-
t.pending.delete(id);
|
|
83
|
-
reject(new Error(`train '${t.name}' call '${action}' timed out after ${CALL_TIMEOUT_MS}ms`));
|
|
84
|
-
}, CALL_TIMEOUT_MS);
|
|
85
|
-
t.pending.set(id, { resolve, reject, timer });
|
|
86
|
-
try {
|
|
87
|
-
const stdin = t.proc?.stdin;
|
|
88
|
-
if (!stdin || typeof stdin === 'number')
|
|
89
|
-
throw new Error('stdin not piped');
|
|
90
|
-
stdin.write(encodeCall(id, action, args));
|
|
91
|
-
stdin.flush();
|
|
92
|
-
}
|
|
93
|
-
catch (err) {
|
|
94
|
-
clearTimeout(timer);
|
|
95
|
-
t.pending.delete(id);
|
|
96
|
-
reject(new Error(`train '${t.name}' stdin write failed: ${errMsg(err)}`));
|
|
97
|
-
}
|
|
98
|
-
});
|
|
99
|
-
}
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
/** Train supervisor: spawn `~/.metro/trains/*.{ts,js,mjs}` under `bun run`, multiplex their */
|
|
2
|
-
/** stdout (events + call-responses), route outbound calls to their stdin. Pure transport. */
|
|
3
|
-
import { mkdirSync } from 'node:fs';
|
|
4
|
-
import { homedir } from 'node:os';
|
|
5
|
-
import { join } from 'node:path';
|
|
6
|
-
import { errMsg, log } from '../log.js';
|
|
7
|
-
import { userSelf } from '../history.js';
|
|
8
|
-
import { drainLines, failAllPending, listTrainFiles, mintCallId, parseTrainLine, sendCall, } from './protocol.js';
|
|
9
|
-
const RESTART_BACKOFFS_MS = [1_000, 5_000, 30_000];
|
|
10
|
-
const MAX_CONSECUTIVE_FAILS = 5;
|
|
11
|
-
export const TRAINS_DIR = process.env.METRO_TRAINS_DIR ?? join(homedir(), '.metro', 'trains');
|
|
12
|
-
export class TrainSupervisor {
|
|
13
|
-
dir;
|
|
14
|
-
trains = new Map();
|
|
15
|
-
onEvent = null;
|
|
16
|
-
nextCallId = 1;
|
|
17
|
-
constructor(dir = TRAINS_DIR) {
|
|
18
|
-
this.dir = dir;
|
|
19
|
-
}
|
|
20
|
-
onTrainEvent(handler) { this.onEvent = handler; }
|
|
21
|
-
/** Discover trains under `dir` and spawn one subprocess per file. Creates the dir if missing. */
|
|
22
|
-
start() {
|
|
23
|
-
mkdirSync(this.dir, { recursive: true });
|
|
24
|
-
for (const t of listTrainFiles(this.dir))
|
|
25
|
-
this.startTrain(t.name, t.path);
|
|
26
|
-
log.info({ dir: this.dir, count: this.trains.size }, 'train supervisor: started');
|
|
27
|
-
}
|
|
28
|
-
/** Shut everything down (graceful: send SIGTERM, then SIGKILL after grace period). */
|
|
29
|
-
async stop() {
|
|
30
|
-
const tasks = [];
|
|
31
|
-
for (const t of this.trains.values()) {
|
|
32
|
-
t.stopped = true;
|
|
33
|
-
if (t.restartTimer) {
|
|
34
|
-
clearTimeout(t.restartTimer);
|
|
35
|
-
t.restartTimer = null;
|
|
36
|
-
}
|
|
37
|
-
failAllPending(t.pending, 'train shutting down');
|
|
38
|
-
const wait = killGracefully(t.proc);
|
|
39
|
-
if (wait)
|
|
40
|
-
tasks.push(wait);
|
|
41
|
-
}
|
|
42
|
-
await Promise.all(tasks);
|
|
43
|
-
}
|
|
44
|
-
list() {
|
|
45
|
-
return [...this.trains.values()].map(t => ({
|
|
46
|
-
name: t.name, path: t.path, running: !!(t.proc && t.proc.exitCode === null),
|
|
47
|
-
pid: t.proc?.pid ?? null, startedAt: t.startedAt, failCount: t.failCount,
|
|
48
|
-
}));
|
|
49
|
-
}
|
|
50
|
-
/** Kill + respawn a named train; resets fail counter so backoff starts fresh. */
|
|
51
|
-
async restart(name) {
|
|
52
|
-
const t = this.trains.get(name);
|
|
53
|
-
if (!t)
|
|
54
|
-
throw new Error(`no train named '${name}'`);
|
|
55
|
-
if (t.restartTimer) {
|
|
56
|
-
clearTimeout(t.restartTimer);
|
|
57
|
-
t.restartTimer = null;
|
|
58
|
-
}
|
|
59
|
-
failAllPending(t.pending, `train '${name}' restarting`);
|
|
60
|
-
t.stopped = true;
|
|
61
|
-
const wait = killGracefully(t.proc);
|
|
62
|
-
if (wait)
|
|
63
|
-
await wait;
|
|
64
|
-
t.stopped = false;
|
|
65
|
-
t.failCount = 0;
|
|
66
|
-
t.proc = null;
|
|
67
|
-
this.spawn(t);
|
|
68
|
-
}
|
|
69
|
-
/** Send a call to a named train and await the matching response. */
|
|
70
|
-
async call(name, action, args) {
|
|
71
|
-
const t = this.trains.get(name);
|
|
72
|
-
if (!t)
|
|
73
|
-
throw new Error(`no train named '${name}' (have: ${[...this.trains.keys()].join(', ') || '(none)'})`);
|
|
74
|
-
if (!t.proc || t.proc.exitCode !== null)
|
|
75
|
-
throw new Error(`train '${name}' is not running`);
|
|
76
|
-
return sendCall(t, mintCallId(this.nextCallId++), action, args);
|
|
77
|
-
}
|
|
78
|
-
startTrain(name, path) {
|
|
79
|
-
if (this.trains.has(name)) {
|
|
80
|
-
log.warn({ name }, 'train supervisor: duplicate name, skipping');
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
const state = {
|
|
84
|
-
name, path, proc: null, pending: new Map(), buf: '', errBuf: '',
|
|
85
|
-
failCount: 0, restartTimer: null, startedAt: null, stopped: false,
|
|
86
|
-
};
|
|
87
|
-
this.trains.set(name, state);
|
|
88
|
-
this.spawn(state);
|
|
89
|
-
}
|
|
90
|
-
spawn(state) {
|
|
91
|
-
if (state.stopped)
|
|
92
|
-
return;
|
|
93
|
-
try {
|
|
94
|
-
const proc = Bun.spawn(['bun', 'run', state.path], {
|
|
95
|
-
stdin: 'pipe', stdout: 'pipe', stderr: 'pipe',
|
|
96
|
-
env: { ...process.env, METRO_TRAIN_NAME: state.name, METRO_SELF_URI: userSelf() },
|
|
97
|
-
});
|
|
98
|
-
state.proc = proc;
|
|
99
|
-
state.startedAt = new Date().toISOString();
|
|
100
|
-
state.buf = '';
|
|
101
|
-
state.errBuf = '';
|
|
102
|
-
log.info({ name: state.name, pid: proc.pid }, 'train: spawned');
|
|
103
|
-
void this.pumpStdout(state);
|
|
104
|
-
void this.pumpStderr(state);
|
|
105
|
-
void proc.exited.then(code => this.onExit(state, code ?? 0));
|
|
106
|
-
}
|
|
107
|
-
catch (err) {
|
|
108
|
-
log.warn({ name: state.name, err: errMsg(err) }, 'train: spawn failed');
|
|
109
|
-
this.scheduleRestart(state);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
pumpStdout(state) {
|
|
113
|
-
return pumpStream(state.proc?.stdout, state.name, 'stdout', chunk => {
|
|
114
|
-
state.buf += chunk;
|
|
115
|
-
state.buf = drainLines(state.name, state.buf, line => this.handleLine(state, line));
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
/** Stream train stderr line-by-line into the daemon's logger so users see crashes/warnings. */
|
|
119
|
-
pumpStderr(state) {
|
|
120
|
-
return pumpStream(state.proc?.stderr, state.name, 'stderr', chunk => {
|
|
121
|
-
state.errBuf += chunk;
|
|
122
|
-
let nl;
|
|
123
|
-
while ((nl = state.errBuf.indexOf('\n')) !== -1) {
|
|
124
|
-
const line = state.errBuf.slice(0, nl).trimEnd();
|
|
125
|
-
state.errBuf = state.errBuf.slice(nl + 1);
|
|
126
|
-
if (line)
|
|
127
|
-
log.warn({ train: state.name }, line);
|
|
128
|
-
}
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
handleLine(state, line) {
|
|
132
|
-
const msg = parseTrainLine(state.name, line);
|
|
133
|
-
if (!msg || msg.op === 'ignore')
|
|
134
|
-
return;
|
|
135
|
-
if (msg.op === 'response') {
|
|
136
|
-
const pending = state.pending.get(msg.id);
|
|
137
|
-
if (!pending) {
|
|
138
|
-
log.debug({ name: state.name, id: msg.id }, 'train: stale response');
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
state.pending.delete(msg.id);
|
|
142
|
-
clearTimeout(pending.timer);
|
|
143
|
-
pending.resolve({ result: msg.result, error: msg.error });
|
|
144
|
-
return;
|
|
145
|
-
}
|
|
146
|
-
if (msg.op === 'log') {
|
|
147
|
-
log.info({ name: state.name, msg: msg.text }, 'train log');
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
this.onEvent?.(msg.event, state.name);
|
|
151
|
-
}
|
|
152
|
-
onExit(state, code) {
|
|
153
|
-
log.warn({ name: state.name, code }, 'train: exited');
|
|
154
|
-
state.proc = null;
|
|
155
|
-
state.startedAt = null;
|
|
156
|
-
failAllPending(state.pending, `train '${state.name}' exited (code=${code}) before responding`);
|
|
157
|
-
if (state.stopped)
|
|
158
|
-
return;
|
|
159
|
-
state.failCount++;
|
|
160
|
-
if (state.failCount >= MAX_CONSECUTIVE_FAILS) {
|
|
161
|
-
log.error({ name: state.name, fails: state.failCount }, 'train: too many consecutive failures, giving up (restart metro to retry)');
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
this.scheduleRestart(state);
|
|
165
|
-
}
|
|
166
|
-
scheduleRestart(state) {
|
|
167
|
-
if (state.stopped)
|
|
168
|
-
return;
|
|
169
|
-
const idx = Math.min(state.failCount, RESTART_BACKOFFS_MS.length - 1);
|
|
170
|
-
const delay = RESTART_BACKOFFS_MS[idx];
|
|
171
|
-
log.info({ name: state.name, delay, attempt: state.failCount }, 'train: restart scheduled');
|
|
172
|
-
state.restartTimer = setTimeout(() => {
|
|
173
|
-
state.restartTimer = null;
|
|
174
|
-
/** Any subprocess that survives 30s resets its consecutive-fail counter. */
|
|
175
|
-
this.spawn(state);
|
|
176
|
-
setTimeout(() => { if (state.proc && state.proc.exitCode === null)
|
|
177
|
-
state.failCount = 0; }, 30_000);
|
|
178
|
-
}, delay);
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
function killGracefully(proc) {
|
|
182
|
-
if (!proc || proc.exitCode !== null)
|
|
183
|
-
return null;
|
|
184
|
-
try {
|
|
185
|
-
proc.kill('SIGTERM');
|
|
186
|
-
}
|
|
187
|
-
catch { /* ignore */ }
|
|
188
|
-
const grace = setTimeout(() => { try {
|
|
189
|
-
proc.kill('SIGKILL');
|
|
190
|
-
}
|
|
191
|
-
catch { /* ignore */ } }, 2_000);
|
|
192
|
-
return proc.exited.finally(() => clearTimeout(grace));
|
|
193
|
-
}
|
|
194
|
-
async function pumpStream(stream, name, kind, onChunk) {
|
|
195
|
-
if (!stream || typeof stream === 'number')
|
|
196
|
-
return;
|
|
197
|
-
const reader = stream.getReader();
|
|
198
|
-
const dec = new TextDecoder();
|
|
199
|
-
try {
|
|
200
|
-
while (true) {
|
|
201
|
-
const { value, done } = await reader.read();
|
|
202
|
-
if (done)
|
|
203
|
-
break;
|
|
204
|
-
onChunk(dec.decode(value, { stream: true }));
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
catch (err) {
|
|
208
|
-
log.debug({ name, err: errMsg(err) }, `train: ${kind} pump ended`);
|
|
209
|
-
}
|
|
210
|
-
}
|
package/dist/tunnel.js
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
/** Cloudflared tunnel manager + webhook endpoint registry. Both persist tiny JSON files in STATE_DIR. */
|
|
2
|
-
import { spawn, spawnSync } from 'node:child_process';
|
|
3
|
-
import { randomBytes } from 'node:crypto';
|
|
4
|
-
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
5
|
-
import { join } from 'node:path';
|
|
6
|
-
import { STATE_DIR } from './paths.js';
|
|
7
|
-
import { errMsg, log } from './log.js';
|
|
8
|
-
const FILE = join(STATE_DIR, 'tunnel.json');
|
|
9
|
-
const RESTART_DELAY_MS = 2_000;
|
|
10
|
-
export const loadTunnelConfig = () => existsSync(FILE)
|
|
11
|
-
? JSON.parse(readFileSync(FILE, 'utf8'))
|
|
12
|
-
: null;
|
|
13
|
-
export function saveTunnelConfig(c) { writeFileSync(FILE, JSON.stringify(c, null, 2)); }
|
|
14
|
-
/** Fetch the tunnel's auth token. Null when CLI is unavailable, not logged in, or no such tunnel. */
|
|
15
|
-
function fetchTunnelToken(name) {
|
|
16
|
-
const r = spawnSync('cloudflared', ['tunnel', 'token', name], { encoding: 'utf8' });
|
|
17
|
-
if (r.status !== 0)
|
|
18
|
-
return null;
|
|
19
|
-
const token = r.stdout.trim();
|
|
20
|
-
return token.length > 0 ? token : null;
|
|
21
|
-
}
|
|
22
|
-
export class Tunnel {
|
|
23
|
-
cfg;
|
|
24
|
-
port;
|
|
25
|
-
child = null;
|
|
26
|
-
closed = false;
|
|
27
|
-
/** `undefined` = unresolved; `null` = resolved but unavailable (CLI missing / not logged in / no tunnel). */
|
|
28
|
-
token = undefined;
|
|
29
|
-
constructor(cfg, port) {
|
|
30
|
-
this.cfg = cfg;
|
|
31
|
-
this.port = port;
|
|
32
|
-
}
|
|
33
|
-
get hostname() { return this.cfg.hostname; }
|
|
34
|
-
start() {
|
|
35
|
-
if (this.closed)
|
|
36
|
-
return;
|
|
37
|
-
if (this.token === undefined)
|
|
38
|
-
this.token = fetchTunnelToken(this.cfg.name);
|
|
39
|
-
const mode = this.token ? 'token' : 'named';
|
|
40
|
-
log.info({ name: this.cfg.name, hostname: this.cfg.hostname, port: this.port, mode }, 'cloudflared tunnel starting');
|
|
41
|
-
/** `--no-autoupdate` is a global cloudflared flag — must come before the `tunnel` subcommand. */
|
|
42
|
-
const args = ['--no-autoupdate', 'tunnel', 'run', '--url', `http://127.0.0.1:${this.port}`];
|
|
43
|
-
/** Token form resolves the tunnel from TUNNEL_TOKEN so the trailing name arg must be omitted. */
|
|
44
|
-
if (!this.token)
|
|
45
|
-
args.push(this.cfg.name);
|
|
46
|
-
const env = this.token
|
|
47
|
-
? { ...process.env, TUNNEL_TOKEN: this.token }
|
|
48
|
-
: process.env;
|
|
49
|
-
this.child = spawn('cloudflared', args, { stdio: ['ignore', 'pipe', 'pipe'], env });
|
|
50
|
-
this.child.stderr?.on('data', d => log.debug({ cloudflared: d.toString().trim() }, 'cloudflared'));
|
|
51
|
-
this.child.on('exit', code => {
|
|
52
|
-
this.child = null;
|
|
53
|
-
if (this.closed)
|
|
54
|
-
return;
|
|
55
|
-
log.warn({ code }, 'cloudflared exited; restarting');
|
|
56
|
-
setTimeout(() => this.start(), RESTART_DELAY_MS);
|
|
57
|
-
});
|
|
58
|
-
this.child.on('error', err => log.warn({ err: errMsg(err) }, 'cloudflared spawn error'));
|
|
59
|
-
}
|
|
60
|
-
stop() {
|
|
61
|
-
this.closed = true;
|
|
62
|
-
this.child?.kill();
|
|
63
|
-
this.child = null;
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
/* ──────────── webhook endpoint registry (id, label, optional secret) ──────────── */
|
|
67
|
-
const WEBHOOKS_FILE = join(STATE_DIR, 'webhooks.json');
|
|
68
|
-
/** Local listener port — `127.0.0.1` only; expose publicly via Cloudflare tunnel. */
|
|
69
|
-
export const webhookPort = () => Number(process.env.METRO_WEBHOOK_PORT) || 8420;
|
|
70
|
-
function readWebhooks() {
|
|
71
|
-
if (!existsSync(WEBHOOKS_FILE))
|
|
72
|
-
return { endpoints: [] };
|
|
73
|
-
try {
|
|
74
|
-
return JSON.parse(readFileSync(WEBHOOKS_FILE, 'utf8'));
|
|
75
|
-
}
|
|
76
|
-
catch {
|
|
77
|
-
return { endpoints: [] };
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
const writeWebhooks = (s) => writeFileSync(WEBHOOKS_FILE, JSON.stringify(s, null, 2));
|
|
81
|
-
export const listEndpoints = () => readWebhooks().endpoints;
|
|
82
|
-
export const findEndpoint = (id) => readWebhooks().endpoints.find(e => e.id === id);
|
|
83
|
-
export function addEndpoint(label, secret) {
|
|
84
|
-
const s = readWebhooks();
|
|
85
|
-
/** 16-char URL-safe id (~96 bits — collision-proof for any reasonable count). */
|
|
86
|
-
const ep = {
|
|
87
|
-
id: randomBytes(12).toString('base64url'), label, createdAt: new Date().toISOString(),
|
|
88
|
-
...(secret ? { secret } : {}),
|
|
89
|
-
};
|
|
90
|
-
s.endpoints.push(ep);
|
|
91
|
-
writeWebhooks(s);
|
|
92
|
-
return ep;
|
|
93
|
-
}
|
|
94
|
-
export function removeEndpoint(id) {
|
|
95
|
-
const s = readWebhooks();
|
|
96
|
-
const before = s.endpoints.length;
|
|
97
|
-
s.endpoints = s.endpoints.filter(e => e.id !== id);
|
|
98
|
-
if (s.endpoints.length === before)
|
|
99
|
-
return false;
|
|
100
|
-
writeWebhooks(s);
|
|
101
|
-
return true;
|
|
102
|
-
}
|
package/docs/broker.md
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
# Metro broker
|
|
2
|
-
|
|
3
|
-
Multi-user event routing on top of the event log. Turns "one daemon → one stdout consumer"
|
|
4
|
-
into "one daemon → N independently-subscribed users (Claude Code, Codex, anything) with
|
|
5
|
-
durable, replayable delivery".
|
|
6
|
-
|
|
7
|
-
## Core idea
|
|
8
|
-
|
|
9
|
-
One concept — a **claim** — and three on-disk files you can `cat`:
|
|
10
|
-
|
|
11
|
-
| Concern | File | Role |
|
|
12
|
-
|----------------------|-----------------------------------------|-------------------------------------------------------------------------------------------------------------------|
|
|
13
|
-
| Event log | `$METRO_STATE_DIR/history.jsonl` | Append-only JSONL — every event (chat messages, webhooks, reactions, transcripts, …). Single source of truth. |
|
|
14
|
-
| Claims | `$METRO_STATE_DIR/claims.json` | `{ <line>: <user-id> }` — flat map. A line in here is *exclusively* owned by that user. Absence = broadcast. |
|
|
15
|
-
| Per-mode cursor | `$METRO_STATE_DIR/cursors/<key>` | Byte offset into `history.jsonl` — last-emitted position for one tail mode. Updated atomically after each emit. |
|
|
16
|
-
|
|
17
|
-
Cursor keys are derived from the *effective mode* (not from `userSelf()`), so `--all` and
|
|
18
|
-
`--unclaimed` don't collide with a personal `--as=<id>` tail:
|
|
19
|
-
|
|
20
|
-
| Tail invocation | Cursor key |
|
|
21
|
-
|--------------------------------------------|-----------------------------------------------------------------|
|
|
22
|
-
| `metro tail --as=<id>` | `<userSlug(id)>` |
|
|
23
|
-
| `metro tail --as=<id> --strict` | `<userSlug(id)>--strict` |
|
|
24
|
-
| `metro tail --as=<id> --include-webhooks` | `<userSlug(id)>--with-webhooks` (or `…--strict--with-webhooks`) |
|
|
25
|
-
| `metro tail --unclaimed` | `_unclaimed` |
|
|
26
|
-
| `metro tail --all` | `_all` |
|
|
27
|
-
|
|
28
|
-
The `_` prefix on the mode-keys can't collide with a real `userSelf()` slug. `--chat=<line>`
|
|
29
|
-
and `--station=<name>` are post-filters applied **after** cursor advancement, so they don't
|
|
30
|
-
need their own cursor keys.
|
|
31
|
-
|
|
32
|
-
Subscribers do not register with the daemon. They tail the log; the broker semantics emerge
|
|
33
|
-
from one filtering rule applied at read time:
|
|
34
|
-
|
|
35
|
-
> An event is delivered to a user when its `line` is **claimed by that user** *or* **claimed by no one**.
|
|
36
|
-
|
|
37
|
-
That single rule covers every case the design needs to handle:
|
|
38
|
-
|
|
39
|
-
- **Chat with one responder** — user claims the chat; other tailing users stop receiving it. No race.
|
|
40
|
-
- **Webhook fan-out** — nobody claims; every user tailing a matching filter sees it.
|
|
41
|
-
- **Operator observability** — `metro tail` with no `--as` (or `--all`) shows everything regardless of claims; doesn't take ownership.
|
|
42
|
-
- **Sub-user onboarding** — sub-user claims its assigned chat before reading; parent stops receiving that chat without any coordination.
|
|
43
|
-
|
|
44
|
-
```
|
|
45
|
-
┌──────────────────────────┐
|
|
46
|
-
inbound (Discord/TG/web) ──► │ dispatcher │ ──► history.jsonl ◄── metro tail --as claude-A
|
|
47
|
-
│ (writes log, no routing)│ ◄── metro tail --as codex-B
|
|
48
|
-
└──────────────────────────┘ ◄── metro tail --as claude-sub-1
|
|
49
|
-
(each holds its own cursor)
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
## CLI surface
|
|
53
|
-
|
|
54
|
-
```bash
|
|
55
|
-
# Tail the event log. --follow streams new entries via fs.watch.
|
|
56
|
-
metro tail [--as <user-id>] [--follow] [--strict | --unclaimed | --all] [--include-webhooks]
|
|
57
|
-
[--chat <line>] [--station <name>] [--since <offset|tail>] [--limit <n>]
|
|
58
|
-
|
|
59
|
-
# Claims: assert/release exclusive ownership of a line. Updates claims.json.
|
|
60
|
-
metro claim <line> [--as <user-id>] # add/overwrite — last writer wins
|
|
61
|
-
metro release <line> # remove (line returns to broadcast)
|
|
62
|
-
metro claims # print current claims.json
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
`--as <user-id>` defaults to `userSelf()` ([history.ts](../src/history.ts)) — the same stable
|
|
66
|
-
identity already used in routing-aware code.
|
|
67
|
-
|
|
68
|
-
### Subscription modes
|
|
69
|
-
|
|
70
|
-
The same `metro tail` command serves four distinct callers — a working user, a strict
|
|
71
|
-
subscriber, a router, and a human observer. Each maps to one mutually-exclusive flag:
|
|
72
|
-
|
|
73
|
-
| Mode | Flag | Predicate | Who uses it |
|
|
74
|
-
|--------------------|----------------------------|---------------------------------------------------------------------------------|------------------------------------------------------------|
|
|
75
|
-
| **Mine + free** | `--as <id>` (default) | `(claims[line] == <id> ∨ line ∉ claims) ∧ station ≠ 'webhook'` | Default working user. Zero-config single-user setup. |
|
|
76
|
-
| **Mine only** | `--as <id> --strict` | `claims[line] == <id> ∧ station ≠ 'webhook'` | Disciplined subscriber that won't race on unclaimed events.|
|
|
77
|
-
| **Unclaimed only** | `--unclaimed` | `line ∉ claims` | Router/first-responder pattern that finds work to claim. |
|
|
78
|
-
| **All** | (no `--as`) or `--all` | `true` | Operator/auditor/debugger; never takes ownership. |
|
|
79
|
-
|
|
80
|
-
Webhooks (`station == 'webhook'`) are excluded from the personal modes by default — they're
|
|
81
|
-
broadcast traffic (GitHub pushes, Intercom pings, etc.) that should flow to the *router*
|
|
82
|
-
(`--unclaimed`) or *operator* (`--all`) feed, not firehose into every `--as <id>` tail. Opt
|
|
83
|
-
back in with `metro tail --as <id> --include-webhooks`.
|
|
84
|
-
|
|
85
|
-
Direct messages between users (`event.to == user-line`) always pass the filter regardless of
|
|
86
|
-
mode — they're inherently 1:1 and can't be "claimed" by someone else.
|
|
87
|
-
|
|
88
|
-
### Auto-claim on outbound
|
|
89
|
-
|
|
90
|
-
Outbound paths call `tryAutoClaim` ([broker/claims.ts](../src/broker/claims.ts)) to claim the target `<line>`
|
|
91
|
-
for the actor (`userSelf()`) the first time it's touched, atomically — same lockfile as
|
|
92
|
-
`metro claim`. Auto-claim only fires when **the line topology is 1:1** (DM, or a
|
|
93
|
-
Claude/Codex cross-user line). Shared lines are skipped:
|
|
94
|
-
|
|
95
|
-
| Line | Classification | Auto-claim? | How |
|
|
96
|
-
|-------------------------------------------------|----------------|-------------|--------------------------------------------------------------|
|
|
97
|
-
| `metro://telegram/<positive-id>` (incl. topics) | DM | Yes | Telegram chat-id > 0 ⇒ private chat |
|
|
98
|
-
| `metro://telegram/<negative-id>` / `-100…` | group | **No** | Telegram chat-id < 0 ⇒ group/supergroup |
|
|
99
|
-
| `metro://discord/<channel-id>` (no guild) | DM | Yes | Recent inbound payload `guildId == null` |
|
|
100
|
-
| `metro://discord/<channel-id>` (in guild) | group | **No** | Recent inbound payload `guildId != null` |
|
|
101
|
-
| `metro://discord/<channel-id>` (no inbound) | unknown | Yes | No metadata cached — treat as DM-eligible until proven group |
|
|
102
|
-
| `metro://claude/...` / `metro://codex/...` | 1:1 | Yes | Cross-user notify is inherently 1:1 by construction |
|
|
103
|
-
| `metro://webhook/<id>` | broadcast | **Never** | Webhook lines are a stream, not a conversation |
|
|
104
|
-
|
|
105
|
-
- If the line is already claimed by someone else (and topology check passed), the action
|
|
106
|
-
still proceeds but the claim is **not overwritten**.
|
|
107
|
-
- Auto-claim writes happen after the action succeeds, so a failed call never writes to `claims.json`.
|
|
108
|
-
|
|
109
|
-
### `metro tail` mechanics
|
|
110
|
-
|
|
111
|
-
- Reads `history.jsonl`, applies the mode predicate + any `--chat`/`--station` filters (AND),
|
|
112
|
-
prints one JSONL line per event to stdout.
|
|
113
|
-
- With `--follow`: stays open, watches the file via `fs.watch`, emits new matching lines.
|
|
114
|
-
- Maintains a per-mode cursor (byte offset) at `cursors/<key>`. On startup, resumes from cursor;
|
|
115
|
-
on each emitted line, the offset is advanced *after* the write succeeds. O(1) resume.
|
|
116
|
-
- `--since <offset>` overrides the cursor; `--since=tail` starts from EOF, ignoring backlog.
|
|
117
|
-
- Claim lookups read `claims.json` once per emitted event. Small (~KB), OS-cached; sub-microsecond cost.
|
|
118
|
-
|
|
119
|
-
### `metro claim` semantics
|
|
120
|
-
|
|
121
|
-
- Pure metadata edit on `claims.json`. Does **not** notify the daemon — claims are read by
|
|
122
|
-
tails, not the dispatcher.
|
|
123
|
-
- Re-claiming a line re-assigns it (last writer wins). `metro claims` prints the current map.
|
|
124
|
-
- Releasing a line returns it to broadcast — every matching tail picks it up again.
|
|
125
|
-
- Writes to `claims.json` are wrapped in an `O_EXCL` lockfile to serialize concurrent writes
|
|
126
|
-
on the same host.
|
|
127
|
-
|
|
128
|
-
## Dispatcher
|
|
129
|
-
|
|
130
|
-
The dispatcher stays dumb. `emit()` appends to history, pushes to codex-rc, and writes to
|
|
131
|
-
stdout. All routing intelligence lives in `metro tail`'s filter, which reads two small files
|
|
132
|
-
(`claims.json` + its own cursor) per event. No new sockets, no fan-out bookkeeping, no
|
|
133
|
-
coupling between subscriber count and daemon state.
|
|
134
|
-
|
|
135
|
-
## Concurrency
|
|
136
|
-
|
|
137
|
-
Multiple processes write `history.jsonl`: the daemon's `emit()` and short-lived auto-claim
|
|
138
|
-
writers. `appendFileSync` opens with `O_APPEND`; POSIX guarantees atomic seek-to-end-and-write
|
|
139
|
-
per `write(2)`. Concurrent writers produce whole lines in some order, never interleaved halves.
|
|
140
|
-
Node issues one `write(2)` per `appendFileSync` call, and history entries stay well under
|
|
141
|
-
per-syscall atomicity limits (~2GB on Linux, `INT_MAX` on macOS).
|
|
142
|
-
|
|
143
|
-
`claims.json` is read on every event by every tail; writes are infrequent. An `O_EXCL`
|
|
144
|
-
lockfile around writes is enough; tails do an unlocked read with a malformed-JSON retry.
|
|
145
|
-
|
|
146
|
-
## Isolation
|
|
147
|
-
|
|
148
|
-
`METRO_STATE_DIR` isolates state-dir-scoped artifacts (`history.jsonl`, `claims.json`,
|
|
149
|
-
`cursors/`, `lines.json`, `bot-ids.json`, the daemon socket, the webhook port). It does **not**
|
|
150
|
-
isolate platform credentials — those are owned by the train and read from `~/.metro/.env`.
|
|
151
|
-
|
|
152
|
-
## Failure modes
|
|
153
|
-
|
|
154
|
-
| Failure | Behavior |
|
|
155
|
-
|-------------------------------------|---------------------------------------------------------------------------------------------------|
|
|
156
|
-
| Process crashes mid-event | Cursor not advanced → event redelivered on next `metro tail`. At-least-once. |
|
|
157
|
-
| Two users claim same line | `claims.json` last-write-wins. `metro claims` shows current owner; humans resolve. |
|
|
158
|
-
| No user claims a chat | Event broadcasts to every tail whose filters match. |
|
|
159
|
-
| `history.jsonl` grows unboundedly | Out of scope here. (Rotate by date, prune by age.) |
|
|
160
|
-
|
|
161
|
-
## Non-goals
|
|
162
|
-
|
|
163
|
-
- **Strict ordering across chats**: events within one `line` are ordered by JSONL append order; cross-chat ordering is best-effort.
|
|
164
|
-
- **Exactly-once delivery**: at-least-once via cursor + redelivery. Idempotency is the subscriber's problem (the daemon mints stable `msg_*` ids).
|
|
165
|
-
- **Authn between users**: any process with filesystem access to `$METRO_STATE_DIR` can tail and claim. Same trust model as the host.
|
|
166
|
-
- **Remote users**: broker is local-only. Cross-host fan-out is solved by running metro on each host and bridging at the chat layer.
|