@stage-labs/metro 0.1.0-beta.13 → 0.1.0-beta.130
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api.js +14 -0
- package/dist/claude-settings.js +40 -0
- package/dist/claude.js +176 -0
- package/dist/cli.js +101 -0
- package/dist/control.js +74 -0
- package/dist/hold.js +211 -0
- package/dist/local.js +93 -0
- package/dist/mcp-config.js +25 -0
- package/dist/node-name.js +71 -0
- package/dist/onboarding.js +43 -0
- package/dist/plugin.js +120 -0
- package/dist/provider-flags.js +9 -0
- package/dist/route.js +38 -0
- package/dist/runtime-install.js +97 -0
- package/dist/runtime.js +74 -0
- package/dist/serve.js +150 -0
- package/dist/service.js +242 -0
- package/dist/tail.js +119 -0
- package/dist/update.js +45 -0
- package/dist/version.js +97 -0
- package/package.json +14 -27
- package/runtime/marketplace/.claude-plugin/marketplace.json +12 -0
- package/runtime/marketplace/plugin/.claude-plugin/plugin.json +18 -0
- package/runtime/marketplace/plugin/.mcp.json +1 -0
- package/runtime/marketplace/plugin/bin/guard.mjs +55 -0
- package/runtime/marketplace/plugin/bin/metro-plugin.mjs +122 -0
- package/runtime/marketplace/plugin/bin/session-start.mjs +31 -0
- package/runtime/marketplace/plugin/hooks/hooks.json +25 -0
- package/runtime/marketplace/plugin/orchestrator.md +16 -0
- package/runtime/marketplace/plugin/skills/refresh/SKILL.md +15 -0
- package/runtime/node_modules/@metro-labs/core/package.json +43 -0
- package/runtime/node_modules/@metro-labs/core/src/address.ts +6 -0
- package/runtime/node_modules/@metro-labs/core/src/endpoints.ts +7 -0
- package/runtime/node_modules/@metro-labs/core/src/events.ts +120 -0
- package/runtime/node_modules/@metro-labs/core/src/identity.ts +7 -0
- package/runtime/node_modules/@metro-labs/core/src/ids.ts +21 -0
- package/runtime/node_modules/@metro-labs/core/src/is-record.ts +2 -0
- package/runtime/node_modules/@metro-labs/core/src/lines.ts +87 -0
- package/runtime/node_modules/@metro-labs/core/src/log.ts +42 -0
- package/runtime/node_modules/@metro-labs/core/src/protocol.ts +226 -0
- package/runtime/node_modules/@metro-labs/core/src/secure-fs.ts +57 -0
- package/runtime/node_modules/@metro-labs/core/src/station-names.ts +13 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/account-store.ts +60 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/attachments.ts +196 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +88 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/station-runtime.ts +64 -0
- package/runtime/node_modules/@metro-labs/core/src/stations/types.ts +108 -0
- package/runtime/node_modules/@metro-labs/core/src/str.ts +5 -0
- package/runtime/node_modules/@metro-labs/core/src/train-error.ts +53 -0
- package/runtime/node_modules/@metro-labs/core/src/version.ts +2 -0
- package/runtime/node_modules/@metro-labs/daemon/package.json +48 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/account-attach.ts +19 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/account-routes.ts +70 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +365 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +43 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/allowlist.ts +37 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +300 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +122 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +371 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +155 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/keys.ts +45 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/map.ts +95 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/scope.ts +64 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/senders.ts +38 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +207 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +39 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/crash-guard.ts +49 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +14 -0
- package/runtime/node_modules/@metro-labs/daemon/src/boot/paths.ts +106 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +25 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/dedupe.ts +24 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +377 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/media-note.ts +85 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +75 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/relay.ts +129 -0
- package/runtime/node_modules/@metro-labs/daemon/src/channels/webhook-note.ts +51 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +309 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +358 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +169 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +56 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +140 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +90 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +253 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +117 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +239 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +154 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +109 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +310 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/cli-api.ts +63 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +175 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/json.ts +21 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/model.ts +77 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-client.ts +166 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-discovery.ts +139 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-pending.ts +65 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-routes.ts +171 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth.ts +155 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +122 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/reach.ts +27 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-target.ts +39 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +296 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +286 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +98 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +70 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +332 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/watch.ts +54 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +99 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-owner.ts +32 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +121 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +294 -0
- package/runtime/node_modules/@metro-labs/daemon/src/files/upload-store.ts +181 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +259 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +231 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +288 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +203 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +231 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +123 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +187 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +229 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +269 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +207 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +90 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +65 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +17 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +229 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +151 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +216 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/channel-owner.ts +29 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/ctx.ts +62 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +91 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/frame-scope.ts +21 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +129 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +191 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/member-tools.ts +72 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +66 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/raw-get-stream.ts +207 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +47 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-registry.ts +172 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-route.ts +43 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +215 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-dispatch.ts +215 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-schemas.ts +400 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/upload-tool.ts +66 -0
- package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +237 -0
- package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +389 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/body.ts +24 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +372 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/identity.ts +54 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +223 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +60 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
- package/runtime/node_modules/@metro-labs/daemon/src/routes/threema-callback.ts +103 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +64 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +61 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +116 -0
- package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-inline.ts +100 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-interactive.ts +161 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +202 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-session.ts +280 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-xmtp.ts +111 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +248 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +261 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/registry.ts +37 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor-io.ts +97 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +320 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/train-call.ts +22 -0
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +96 -0
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +123 -0
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +40 -0
- package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +220 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
- package/runtime/node_modules/@metro-labs/http/package.json +33 -0
- package/runtime/node_modules/@metro-labs/http/src/api-error.ts +8 -0
- package/runtime/node_modules/@metro-labs/http/src/api-http.ts +95 -0
- package/runtime/node_modules/@metro-labs/http/src/cors.ts +29 -0
- package/runtime/node_modules/@metro-labs/http/src/identity-key.ts +33 -0
- package/runtime/node_modules/@metro-labs/http/src/identity-registry.ts +13 -0
- package/runtime/node_modules/@metro-labs/http/src/mode-api.ts +32 -0
- package/runtime/node_modules/@metro-labs/http/src/return-to.ts +21 -0
- package/runtime/node_modules/@metro-labs/http/src/signed-identity.ts +36 -0
- package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
- package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
- package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +254 -0
- package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
- package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
- package/runtime/node_modules/@metro-labs/telegram/src/format.ts +156 -0
- package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
- package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
- package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
- package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
- package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
- package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
- package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
- package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
- package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
- package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
- package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
- package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +201 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +109 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +58 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
- package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
- package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +100 -0
- package/runtime/node_modules/@metro-labs/threema/src/actions.ts +219 -0
- package/runtime/node_modules/@metro-labs/threema/src/api.ts +130 -0
- package/runtime/node_modules/@metro-labs/threema/src/crypto.ts +132 -0
- package/runtime/node_modules/@metro-labs/threema/src/format.ts +123 -0
- package/runtime/node_modules/@metro-labs/threema/src/ids.ts +21 -0
- package/runtime/node_modules/@metro-labs/threema/src/index.ts +29 -0
- package/runtime/node_modules/@metro-labs/threema/src/station.ts +10 -0
- package/runtime/node_modules/@metro-labs/threema/src/verify.ts +117 -0
- package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
- package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
- package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +190 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +347 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +138 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/resolve.ts +52 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +18 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
- package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +112 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +335 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +80 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +53 -0
- package/runtime/runtime.json +3 -0
- package/runtime/server.ts +1 -0
- package/runtime/stations.json +39 -0
- package/runtime/trains/.keep +0 -0
- package/LICENSE +0 -21
- package/README.md +0 -252
- package/dist/broker.js +0 -202
- package/dist/cache.js +0 -69
- package/dist/cli/actions.js +0 -206
- package/dist/cli/config.js +0 -185
- package/dist/cli/index.js +0 -188
- package/dist/cli/skill.js +0 -62
- package/dist/cli/tail.js +0 -164
- package/dist/cli/util.js +0 -72
- package/dist/cli/webhook.js +0 -81
- package/dist/codex-rc.js +0 -245
- package/dist/dispatcher.js +0 -128
- package/dist/history.js +0 -121
- package/dist/ipc.js +0 -72
- package/dist/log.js +0 -15
- package/dist/monitor.js +0 -194
- package/dist/paths.js +0 -68
- package/dist/registry.js +0 -48
- package/dist/stations/claude.js +0 -45
- package/dist/stations/codex.js +0 -68
- package/dist/stations/discord.js +0 -216
- package/dist/stations/index.js +0 -129
- package/dist/stations/telegram-md.js +0 -34
- package/dist/stations/telegram-upload.js +0 -113
- package/dist/stations/telegram.js +0 -234
- package/dist/stations/webhook.js +0 -103
- package/dist/tunnel.js +0 -64
- package/dist/webhooks.js +0 -41
- package/docs/broker.md +0 -214
- package/docs/monitor.md +0 -163
- package/docs/uri-scheme.md +0 -124
- package/docs/users.md +0 -226
- package/skills/metro/SKILL.md +0 -256
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import { handleSessionApiRequest } from './session.js';
|
|
3
|
+
import { handleAgentApiRequest, type AgentApiDeps } from '../agents/api.js';
|
|
4
|
+
import {
|
|
5
|
+
handleConnectorApiRequest,
|
|
6
|
+
type ConnectorApiDeps,
|
|
7
|
+
} from '../connectors/api.js';
|
|
8
|
+
import type { RelayApiDeps } from '../connectors/relay.js';
|
|
9
|
+
import type { IdentityRouteDeps } from './identity.js';
|
|
10
|
+
import { handleLocalCliRequest, type LocalCliDeps } from '../connectors/cli-api.js';
|
|
11
|
+
import { handleClaudeRequest, type ClaudeApiDeps } from '../claude/api.js';
|
|
12
|
+
import type { ModeInfo } from '@metro-labs/http/mode-api';
|
|
13
|
+
import { handleBundleRequest, type BundleApiDeps } from '../agents/bundle.js';
|
|
14
|
+
import { handleUpdateRequest, type UpdateApiDeps } from '../server/update.js';
|
|
15
|
+
import { handleControlRequest, type ControlApiDeps } from '../server/control.js';
|
|
16
|
+
import { handleMachineRequest, type MachineApiDeps } from '../server/machine.js';
|
|
17
|
+
import { handleModelRequest, type ModelApiDeps } from '../gateway/model-api.js';
|
|
18
|
+
import type { GatewayDeps } from '../gateway/gateway.js';
|
|
19
|
+
import { handleTerminalRequest, type TerminalApiDeps } from '../terminal/api.js';
|
|
20
|
+
|
|
21
|
+
export interface SessionApis {
|
|
22
|
+
agentApi?: AgentApiDeps;
|
|
23
|
+
bundleApi?: BundleApiDeps;
|
|
24
|
+
updateApi?: UpdateApiDeps;
|
|
25
|
+
controlApi?: ControlApiDeps;
|
|
26
|
+
machineApi?: MachineApiDeps;
|
|
27
|
+
modelApi?: ModelApiDeps;
|
|
28
|
+
gateway?: GatewayDeps;
|
|
29
|
+
terminalApi?: TerminalApiDeps;
|
|
30
|
+
localCli?: LocalCliDeps;
|
|
31
|
+
claudeApi?: ClaudeApiDeps;
|
|
32
|
+
connectorApi?: ConnectorApiDeps;
|
|
33
|
+
relayApi?: RelayApiDeps;
|
|
34
|
+
identity?: IdentityRouteDeps;
|
|
35
|
+
mode?: () => ModeInfo;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const when = <T>(dep: T | undefined, run: (dep: T) => boolean): (() => boolean)[] =>
|
|
39
|
+
dep === undefined ? [] : [() => run(dep)];
|
|
40
|
+
|
|
41
|
+
export function handleSessionApis(
|
|
42
|
+
req: IncomingMessage,
|
|
43
|
+
res: ServerResponse,
|
|
44
|
+
apis: SessionApis,
|
|
45
|
+
): boolean {
|
|
46
|
+
const routes: (() => boolean)[] = [
|
|
47
|
+
() => handleSessionApiRequest(req, res),
|
|
48
|
+
...when(apis.localCli, (d) => handleLocalCliRequest(req, res, d)),
|
|
49
|
+
...when(apis.connectorApi, (d) => handleConnectorApiRequest(req, res, d)),
|
|
50
|
+
...when(apis.bundleApi, (d) => handleBundleRequest(req, res, d)),
|
|
51
|
+
...when(apis.updateApi, (d) => handleUpdateRequest(req, res, d)),
|
|
52
|
+
...when(apis.controlApi, (d) => handleControlRequest(req, res, d)),
|
|
53
|
+
...when(apis.machineApi, (d) => handleMachineRequest(req, res, d)),
|
|
54
|
+
...when(apis.modelApi, (d) => handleModelRequest(req, res, d)),
|
|
55
|
+
...when(apis.terminalApi, (d) => handleTerminalRequest(req, res, d)),
|
|
56
|
+
...when(apis.claudeApi, (d) => handleClaudeRequest(req, res, d)),
|
|
57
|
+
...when(apis.agentApi, (d) => handleAgentApiRequest(req, res, d)),
|
|
58
|
+
];
|
|
59
|
+
return routes.some((run) => run());
|
|
60
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import { apiFailure, apiSession, cors, sendJson } from '@metro-labs/http/api-http';
|
|
3
|
+
|
|
4
|
+
const PATH = '/api/session';
|
|
5
|
+
|
|
6
|
+
export function handleSessionApiRequest(
|
|
7
|
+
req: IncomingMessage,
|
|
8
|
+
res: ServerResponse,
|
|
9
|
+
): boolean {
|
|
10
|
+
const path = (req.url ?? '').split('?')[0] ?? '';
|
|
11
|
+
if (path !== PATH) return false;
|
|
12
|
+
if (req.method === 'OPTIONS') {
|
|
13
|
+
res.writeHead(204, cors(req)).end();
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
if (req.method !== 'GET') {
|
|
17
|
+
sendJson(req, res, 405, { error: 'method not allowed' });
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
apiSession(req)
|
|
21
|
+
.then((session) => {
|
|
22
|
+
if (!session) sendJson(req, res, 401, { error: 'unauthorized' });
|
|
23
|
+
else sendJson(req, res, 200, { subject: session.subject });
|
|
24
|
+
})
|
|
25
|
+
.catch((err: unknown) => {
|
|
26
|
+
apiFailure(req, res, err, 'session');
|
|
27
|
+
});
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import { errMsg, log } from '@metro-labs/core/log';
|
|
3
|
+
import { tokenMatches } from '../net/tunnel.js';
|
|
4
|
+
import {
|
|
5
|
+
findThreemaCallback,
|
|
6
|
+
type ThreemaCallback,
|
|
7
|
+
} from '../stations/threema-callbacks.js';
|
|
8
|
+
import { forwardTrainCall, type TrainCallBackend } from '../stations/train-call.js';
|
|
9
|
+
import { BodyTooLargeError, readBody } from './body.js';
|
|
10
|
+
|
|
11
|
+
const PREFIX = '/api/threema/';
|
|
12
|
+
const CALLBACK_PATH = /^\/api\/threema\/([0-9]{17,20})\/([A-Za-z0-9_-]{32,128})$/;
|
|
13
|
+
const BODY_MAX = 64 * 1024;
|
|
14
|
+
const REQUIRED = ['from', 'to', 'messageId', 'date', 'nonce', 'box', 'mac'] as const;
|
|
15
|
+
|
|
16
|
+
function callbackTarget(path: string): ThreemaCallback | null {
|
|
17
|
+
const m = CALLBACK_PATH.exec(path);
|
|
18
|
+
const callbackId = m?.[1];
|
|
19
|
+
const token = m?.[2];
|
|
20
|
+
if (callbackId === undefined || token === undefined) return null;
|
|
21
|
+
const row = findThreemaCallback(callbackId);
|
|
22
|
+
return row !== undefined && tokenMatches(row.callbackToken, token) ? row : null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function parseFields(raw: Buffer): Record<string, string> | null {
|
|
26
|
+
const params = new URLSearchParams(raw.toString('utf8'));
|
|
27
|
+
const out: Record<string, string> = {};
|
|
28
|
+
for (const key of REQUIRED) {
|
|
29
|
+
const value = params.get(key)?.trim() ?? '';
|
|
30
|
+
if (value === '') return null;
|
|
31
|
+
out[key] = value;
|
|
32
|
+
}
|
|
33
|
+
const nickname = params.get('nickname')?.trim() ?? '';
|
|
34
|
+
if (nickname !== '') out.nickname = nickname;
|
|
35
|
+
return out;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function deliver(
|
|
39
|
+
res: ServerResponse,
|
|
40
|
+
target: ThreemaCallback,
|
|
41
|
+
fields: Record<string, string>,
|
|
42
|
+
forward: TrainCallBackend,
|
|
43
|
+
): Promise<void> {
|
|
44
|
+
let response;
|
|
45
|
+
try {
|
|
46
|
+
response = await forward('threema', 'callback', { account: target.id, ...fields });
|
|
47
|
+
} catch (err) {
|
|
48
|
+
log.warn(
|
|
49
|
+
{ account: target.id, err: errMsg(err) },
|
|
50
|
+
'threema: the callback could not reach the train, so Threema is asked to retry',
|
|
51
|
+
);
|
|
52
|
+
res.writeHead(503).end('threema train unavailable');
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
if (response.error !== undefined) {
|
|
56
|
+
log.warn({ account: target.id, err: response.error }, 'threema: callback refused');
|
|
57
|
+
res.writeHead(400).end('refused');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
res.writeHead(200).end('ok');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function readFields(
|
|
64
|
+
req: IncomingMessage,
|
|
65
|
+
res: ServerResponse,
|
|
66
|
+
): Promise<Record<string, string> | null> {
|
|
67
|
+
let raw: Buffer;
|
|
68
|
+
try {
|
|
69
|
+
raw = await readBody(req, BODY_MAX);
|
|
70
|
+
} catch (err) {
|
|
71
|
+
if (!(err instanceof BodyTooLargeError)) throw err;
|
|
72
|
+
res.writeHead(413).end('payload too large');
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const fields = parseFields(raw);
|
|
76
|
+
if (fields === null) res.writeHead(400).end('missing callback fields');
|
|
77
|
+
return fields;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export async function handleThreemaCallback(
|
|
81
|
+
req: IncomingMessage,
|
|
82
|
+
res: ServerResponse,
|
|
83
|
+
forward: TrainCallBackend = forwardTrainCall,
|
|
84
|
+
): Promise<boolean> {
|
|
85
|
+
const path = (req.url ?? '').split('?')[0] ?? '';
|
|
86
|
+
if (!path.startsWith(PREFIX)) return false;
|
|
87
|
+
const target = callbackTarget(path);
|
|
88
|
+
if (target === null) {
|
|
89
|
+
res.writeHead(404).end();
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
if (req.method === 'GET') {
|
|
93
|
+
res.writeHead(200).end(`metro threema callback ${target.callbackId} ready\n`);
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
if (req.method !== 'POST') {
|
|
97
|
+
res.writeHead(405).end();
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
const fields = await readFields(req, res);
|
|
101
|
+
if (fields !== null) await deliver(res, target, fields, forward);
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import { ApiError } from '@metro-labs/http/api-error';
|
|
3
|
+
import { apiFailure, apiSession, cors, sendJson } from '@metro-labs/http/api-http';
|
|
4
|
+
import { errMsg, log } from '@metro-labs/core/log';
|
|
5
|
+
|
|
6
|
+
const STOP = '/api/stop';
|
|
7
|
+
const RESTART = '/api/restart';
|
|
8
|
+
const EXIT_DELAY_MS = 500;
|
|
9
|
+
|
|
10
|
+
export interface ControlApiDeps {
|
|
11
|
+
authorize: (subject: string) => void;
|
|
12
|
+
restart: () => void;
|
|
13
|
+
stop: () => void;
|
|
14
|
+
served?: () => boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
interface Action {
|
|
18
|
+
run: () => void;
|
|
19
|
+
body: Record<string, boolean>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const servedByCli = (): boolean => (process.env.METRO_CLI_BIN?.trim() ?? '') !== '';
|
|
23
|
+
|
|
24
|
+
function actionFor(path: string, deps: ControlApiDeps): Action | null {
|
|
25
|
+
if (path === STOP) return { run: deps.stop, body: { stopping: true } };
|
|
26
|
+
if (path === RESTART) return { run: deps.restart, body: { restarting: true } };
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function handleControlRequest(req: IncomingMessage, res: ServerResponse, deps: ControlApiDeps): boolean {
|
|
31
|
+
const path = (req.url ?? '').split('?')[0] ?? '';
|
|
32
|
+
const action = actionFor(path, deps);
|
|
33
|
+
if (action === null) return false;
|
|
34
|
+
if (req.method === 'OPTIONS') {
|
|
35
|
+
res.writeHead(204, cors(req)).end();
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
if (req.method !== 'POST') {
|
|
39
|
+
sendJson(req, res, 405, { error: 'method not allowed' });
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
Promise.resolve()
|
|
43
|
+
.then(async () => {
|
|
44
|
+
const session = await apiSession(req);
|
|
45
|
+
if (!session) throw new ApiError('unauthorized', 401);
|
|
46
|
+
deps.authorize(session.subject);
|
|
47
|
+
if (!(deps.served ?? servedByCli)())
|
|
48
|
+
throw new ApiError(
|
|
49
|
+
'this daemon was not started by metro serve, so nothing on the machine would bring it back: use the shell instead',
|
|
50
|
+
400,
|
|
51
|
+
);
|
|
52
|
+
log.info({ path, subject: session.subject }, 'control-api: requested from the page');
|
|
53
|
+
setTimeout(action.run, EXIT_DELAY_MS).unref();
|
|
54
|
+
sendJson(req, res, 200, action.body);
|
|
55
|
+
})
|
|
56
|
+
.catch((err: unknown) => {
|
|
57
|
+
if (err instanceof ApiError) apiFailure(req, res, err, 'control-api');
|
|
58
|
+
else {
|
|
59
|
+
log.warn({ err: errMsg(err) }, 'control-api: request failed');
|
|
60
|
+
if (!res.headersSent) sendJson(req, res, 500, { error: 'control api failed' });
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
import { hostname } from 'node:os';
|
|
3
|
+
import { ApiError } from '@metro-labs/http/api-error';
|
|
4
|
+
import { apiFailure, apiSession, cors, sendJson } from '@metro-labs/http/api-http';
|
|
5
|
+
import { publicBaseUrl } from '../files/attach-serve.js';
|
|
6
|
+
import { claudeDir } from '../claude/files.js';
|
|
7
|
+
import { webhookPort } from '../net/tunnel.js';
|
|
8
|
+
import { METRO_VERSION } from '@metro-labs/core/version';
|
|
9
|
+
import { agentsDir } from '../agents/files.js';
|
|
10
|
+
import { localOwner } from '../agents/file-admin.js';
|
|
11
|
+
|
|
12
|
+
const PATH = '/api/server';
|
|
13
|
+
|
|
14
|
+
export interface MachineApiDeps {
|
|
15
|
+
authorize: (subject: string) => void;
|
|
16
|
+
startedAt?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const bootedAt = new Date(Date.now() - process.uptime() * 1000).toISOString();
|
|
20
|
+
|
|
21
|
+
export function machineInfo(startedAt = bootedAt): Record<string, unknown> {
|
|
22
|
+
const store = process.env.METRO_RUNTIME_STORE?.trim() ?? '';
|
|
23
|
+
return {
|
|
24
|
+
version: METRO_VERSION,
|
|
25
|
+
owner: localOwner(),
|
|
26
|
+
hostname: hostname(),
|
|
27
|
+
platform: process.platform,
|
|
28
|
+
arch: process.arch,
|
|
29
|
+
port: webhookPort(),
|
|
30
|
+
publicUrl: publicBaseUrl(),
|
|
31
|
+
uptimeSeconds: Math.round(process.uptime()),
|
|
32
|
+
startedAt,
|
|
33
|
+
bun: process.versions.bun ?? null,
|
|
34
|
+
agentsDir: agentsDir(),
|
|
35
|
+
claudeDir: claudeDir(),
|
|
36
|
+
runtimeStore: store === '' ? null : store,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function handleMachineRequest(req: IncomingMessage, res: ServerResponse, deps: MachineApiDeps): boolean {
|
|
41
|
+
const path = (req.url ?? '').split('?')[0] ?? '';
|
|
42
|
+
if (path !== PATH) return false;
|
|
43
|
+
if (req.method === 'OPTIONS') {
|
|
44
|
+
res.writeHead(204, cors(req)).end();
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
if (req.method !== 'GET') {
|
|
48
|
+
sendJson(req, res, 405, { error: 'method not allowed' });
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
apiSession(req)
|
|
52
|
+
.then((session) => {
|
|
53
|
+
if (!session) throw new ApiError('unauthorized', 401);
|
|
54
|
+
deps.authorize(session.subject);
|
|
55
|
+
sendJson(req, res, 200, machineInfo(deps.startedAt));
|
|
56
|
+
})
|
|
57
|
+
.catch((err: unknown) => {
|
|
58
|
+
apiFailure(req, res, err, 'machine-api');
|
|
59
|
+
});
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { execFile } from 'node:child_process';
|
|
2
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
3
|
+
import { errMsg, log } from '@metro-labs/core/log';
|
|
4
|
+
import { ApiError } from '@metro-labs/http/api-error';
|
|
5
|
+
import { apiFailure, apiSession, cors, sendJson } from '@metro-labs/http/api-http';
|
|
6
|
+
import { isRecord } from '@metro-labs/core/is-record';
|
|
7
|
+
import { METRO_VERSION } from '@metro-labs/core/version';
|
|
8
|
+
|
|
9
|
+
const PATH = '/api/update';
|
|
10
|
+
const CHECK_TTL_MS = 10 * 60_000;
|
|
11
|
+
const CHECK_TIMEOUT_MS = 30_000;
|
|
12
|
+
const UPDATE_TIMEOUT_MS = 5 * 60_000;
|
|
13
|
+
const RESTART_DELAY_MS = 500;
|
|
14
|
+
const OUTPUT_TAIL = 800;
|
|
15
|
+
|
|
16
|
+
export interface UpdateCheck {
|
|
17
|
+
current: string;
|
|
18
|
+
latest: string;
|
|
19
|
+
newer: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface UpdateResult {
|
|
23
|
+
updated: boolean;
|
|
24
|
+
version: string;
|
|
25
|
+
restarting: boolean;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface UpdateApiDeps {
|
|
29
|
+
authorize: (subject: string) => void;
|
|
30
|
+
restart: () => void;
|
|
31
|
+
cliBin?: () => string;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const cliBinFromEnv = (): string => process.env.METRO_CLI_BIN?.trim() ?? '';
|
|
35
|
+
|
|
36
|
+
function runCli(bin: string, args: string[], timeout: number): Promise<{ stdout: string; stderr: string }> {
|
|
37
|
+
return new Promise((resolve, reject) => {
|
|
38
|
+
execFile(process.execPath, [bin, ...args], { timeout, maxBuffer: 4 * 1024 * 1024 }, (err, stdout, stderr) => {
|
|
39
|
+
if (err === null) resolve({ stdout, stderr });
|
|
40
|
+
else reject(new ApiError(`metro update failed: ${(stderr || err.message).trim().slice(-OUTPUT_TAIL)}`, 502));
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
let cached: { at: number; check: UpdateCheck } | null = null;
|
|
46
|
+
|
|
47
|
+
function parseCheck(raw: string): UpdateCheck {
|
|
48
|
+
let body: unknown;
|
|
49
|
+
try {
|
|
50
|
+
body = JSON.parse(raw.trim().split('\n').at(-1) ?? '');
|
|
51
|
+
} catch {
|
|
52
|
+
throw new ApiError('metro update --check returned no report', 502);
|
|
53
|
+
}
|
|
54
|
+
if (!isRecord(body) || typeof body.current !== 'string' || typeof body.latest !== 'string')
|
|
55
|
+
throw new ApiError('metro update --check returned an unexpected report', 502);
|
|
56
|
+
return { current: body.current, latest: body.latest, newer: body.newer === true };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function check(bin: string, now = Date.now()): Promise<UpdateCheck> {
|
|
60
|
+
if (cached !== null && now - cached.at < CHECK_TTL_MS) return cached.check;
|
|
61
|
+
const out = await runCli(bin, ['update', '--check'], CHECK_TIMEOUT_MS);
|
|
62
|
+
cached = { at: now, check: parseCheck(out.stdout) };
|
|
63
|
+
return cached.check;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function apply(bin: string, deps: UpdateApiDeps): Promise<UpdateResult> {
|
|
67
|
+
const before = await check(bin);
|
|
68
|
+
if (!before.newer) return { updated: false, version: before.current, restarting: false };
|
|
69
|
+
const out = await runCli(bin, ['update'], UPDATE_TIMEOUT_MS);
|
|
70
|
+
cached = null;
|
|
71
|
+
log.info({ from: before.current, to: before.latest, output: out.stderr.trim().slice(-OUTPUT_TAIL) }, 'update-api: metro updated, restarting');
|
|
72
|
+
setTimeout(() => {
|
|
73
|
+
deps.restart();
|
|
74
|
+
}, RESTART_DELAY_MS).unref();
|
|
75
|
+
return { updated: true, version: before.latest, restarting: true };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function binOrThrow(deps: UpdateApiDeps): string {
|
|
79
|
+
const bin = (deps.cliBin ?? cliBinFromEnv)();
|
|
80
|
+
if (bin === '')
|
|
81
|
+
throw new ApiError('this daemon was not started by metro serve, so it cannot update itself: run metro update on the machine', 400);
|
|
82
|
+
return bin;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export function handleUpdateRequest(req: IncomingMessage, res: ServerResponse, deps: UpdateApiDeps): boolean {
|
|
86
|
+
const path = (req.url ?? '').split('?')[0] ?? '';
|
|
87
|
+
if (path !== PATH) return false;
|
|
88
|
+
if (req.method === 'OPTIONS') {
|
|
89
|
+
res.writeHead(204, cors(req)).end();
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
if (req.method !== 'GET' && req.method !== 'POST') {
|
|
93
|
+
sendJson(req, res, 405, { error: 'method not allowed' });
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
Promise.resolve()
|
|
97
|
+
.then(async (): Promise<unknown> => {
|
|
98
|
+
const session = await apiSession(req);
|
|
99
|
+
if (!session) throw new ApiError('unauthorized', 401);
|
|
100
|
+
deps.authorize(session.subject);
|
|
101
|
+
const bin = binOrThrow(deps);
|
|
102
|
+
if (req.method === 'GET') return { ...(await check(bin)), running: METRO_VERSION };
|
|
103
|
+
return apply(bin, deps);
|
|
104
|
+
})
|
|
105
|
+
.then((body) => {
|
|
106
|
+
sendJson(req, res, 200, body);
|
|
107
|
+
})
|
|
108
|
+
.catch((err: unknown) => {
|
|
109
|
+
if (err instanceof ApiError) apiFailure(req, res, err, 'update-api');
|
|
110
|
+
else {
|
|
111
|
+
log.warn({ err: errMsg(err) }, 'update-api: request failed');
|
|
112
|
+
if (!res.headersSent) sendJson(req, res, 500, { error: 'update api failed' });
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
2
|
+
import { tmpdir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
|
|
5
|
+
export const MAX_INLINE_BYTES = 8 * 1024 * 1024;
|
|
6
|
+
|
|
7
|
+
const INLINE_TEMP_PREFIX = 'metro-inline-';
|
|
8
|
+
|
|
9
|
+
const BASE64_RE = /^[A-Za-z0-9+/_-]*={0,2}$/;
|
|
10
|
+
|
|
11
|
+
const mib = (n: number): string => `${(n / (1024 * 1024)).toFixed(2)} MiB`;
|
|
12
|
+
|
|
13
|
+
const LIMIT_HINT =
|
|
14
|
+
`inline \`data\` is capped at ${mib(MAX_INLINE_BYTES)} (${MAX_INLINE_BYTES} bytes) of decoded ` +
|
|
15
|
+
'bytes per attachment and per send, because the base64 travels inside the MCP request body. ' +
|
|
16
|
+
'Pass `url`, or a `path` already on the daemon host, for anything larger.';
|
|
17
|
+
|
|
18
|
+
function assertInlineSize(bytes: number, label: string): void {
|
|
19
|
+
if (bytes > MAX_INLINE_BYTES)
|
|
20
|
+
throw new Error(`inline attachment ${label} is ${mib(bytes)}; ${LIMIT_HINT}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function assertInlineTotal(bytes: number): void {
|
|
24
|
+
if (bytes > MAX_INLINE_BYTES)
|
|
25
|
+
throw new Error(
|
|
26
|
+
`inline attachments total ${mib(bytes)} in one send; ${LIMIT_HINT}`,
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const padOf = (b64: string): number =>
|
|
31
|
+
b64.endsWith('==') ? 2 : b64.endsWith('=') ? 1 : 0;
|
|
32
|
+
|
|
33
|
+
export const decodedLengthOf = (b64: string): number =>
|
|
34
|
+
Math.max(0, Math.floor((b64.length * 3) / 4) - padOf(b64));
|
|
35
|
+
|
|
36
|
+
const malformed = (why: string): Error =>
|
|
37
|
+
new Error(
|
|
38
|
+
`inline \`data\` ${why}; pass the file's bytes base64-encoded ` +
|
|
39
|
+
'(standard or url-safe alphabet, optionally behind a `data:<mime>;base64,` prefix)',
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
const DATA_URL_RE = /^data:([^,]*),/i;
|
|
43
|
+
|
|
44
|
+
export interface InlineSource {
|
|
45
|
+
data: string;
|
|
46
|
+
mime?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function splitInlineData(raw: string): InlineSource {
|
|
50
|
+
const compact = raw.replace(/\s+/g, '');
|
|
51
|
+
const match = DATA_URL_RE.exec(compact);
|
|
52
|
+
if (!match) return { data: raw };
|
|
53
|
+
const params = (match[1] ?? '').split(';');
|
|
54
|
+
if (!params.slice(1).some((p) => p.toLowerCase() === 'base64'))
|
|
55
|
+
throw malformed('is a `data:` url whose payload is not base64');
|
|
56
|
+
const mime = params[0]?.toLowerCase();
|
|
57
|
+
const data = compact.slice(match[0].length);
|
|
58
|
+
return mime === undefined || mime === '' ? { data } : { data, mime };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function decodeInline(raw: string, label: string): Uint8Array {
|
|
62
|
+
const b64 = raw.replace(/\s+/g, '');
|
|
63
|
+
if (!b64) throw malformed('is empty');
|
|
64
|
+
const expected = decodedLengthOf(b64);
|
|
65
|
+
assertInlineSize(expected, label);
|
|
66
|
+
if (!BASE64_RE.test(b64)) throw malformed('is not base64');
|
|
67
|
+
if (b64.length % 4 === 1 || (padOf(b64) > 0 && b64.length % 4 !== 0))
|
|
68
|
+
throw malformed('is not a whole number of base64 quanta');
|
|
69
|
+
const buf = Buffer.from(b64, 'base64');
|
|
70
|
+
if (buf.length !== expected)
|
|
71
|
+
throw malformed(`decoded to ${buf.length} bytes instead of ${expected}`);
|
|
72
|
+
return new Uint8Array(buf);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export const safeFileName = (name: string | undefined): string => {
|
|
76
|
+
const base = (name ?? '').split(/[\\/]/).pop() ?? '';
|
|
77
|
+
const clean = base
|
|
78
|
+
.replace(/[^A-Za-z0-9._-]/g, '_')
|
|
79
|
+
.replace(/^\.+/, '')
|
|
80
|
+
.slice(0, 64);
|
|
81
|
+
return clean || 'attachment';
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
export interface InlineTemp {
|
|
85
|
+
dir: string;
|
|
86
|
+
path: string;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function writeInlineTemp(
|
|
90
|
+
data: Uint8Array,
|
|
91
|
+
name: string | undefined,
|
|
92
|
+
): Promise<InlineTemp> {
|
|
93
|
+
const dir = await mkdtemp(join(tmpdir(), INLINE_TEMP_PREFIX));
|
|
94
|
+
const path = join(dir, safeFileName(name));
|
|
95
|
+
await writeFile(path, data, { mode: 0o600 });
|
|
96
|
+
return { dir, path };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export const removeInlineTemp = (dir: string): Promise<void> =>
|
|
100
|
+
rm(dir, { recursive: true, force: true });
|