@stage-labs/metro 0.1.0-beta.9 → 0.1.0-beta.90
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 +129 -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/node-name.js +71 -0
- package/dist/plugin.js +80 -0
- package/dist/provider-flags.js +9 -0
- package/dist/runtime-install.js +87 -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 -25
- 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 +82 -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 +12 -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 +107 -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/accounts-api.ts +368 -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 +302 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +111 -0
- package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +345 -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 +88 -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 +193 -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 +176 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +329 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +167 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +117 -0
- package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +154 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +307 -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 +144 -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 +69 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-client.ts +159 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-discovery.ts +117 -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 +157 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth.ts +127 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +97 -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 +295 -0
- package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +277 -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 +330 -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 +252 -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 +220 -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 +182 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +208 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +195 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +198 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +40 -0
- package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +17 -0
- package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +139 -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/http.ts +390 -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 +198 -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/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 +184 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +248 -0
- package/runtime/node_modules/@metro-labs/daemon/src/stations/registry.ts +35 -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/train-call.ts +22 -0
- package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +91 -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/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 +178 -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 +327 -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/station.ts +17 -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 +36 -0
- package/runtime/trains/.keep +0 -0
- package/LICENSE +0 -21
- package/README.md +0 -249
- package/dist/cache.js +0 -75
- package/dist/cli/actions.js +0 -139
- package/dist/cli/config.js +0 -185
- package/dist/cli/index.js +0 -177
- package/dist/cli/skill.js +0 -62
- package/dist/cli/util.js +0 -72
- package/dist/cli/webhook.js +0 -83
- package/dist/codex-rc.js +0 -245
- package/dist/dispatcher.js +0 -122
- package/dist/history.js +0 -99
- package/dist/ipc.js +0 -72
- package/dist/log.js +0 -15
- 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 -200
- package/dist/stations/index.js +0 -133
- package/dist/stations/telegram-md.js +0 -34
- package/dist/stations/telegram-upload.js +0 -113
- package/dist/stations/telegram.js +0 -213
- package/dist/stations/webhook.js +0 -100
- package/dist/tunnel.js +0 -66
- package/dist/webhooks.js +0 -40
- package/docs/agents.md +0 -216
- package/docs/uri-scheme.md +0 -125
- package/skills/metro/SKILL.md +0 -249
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
/** Telegram outgoing sends — single multipart, media groups, and the rich-content dispatch. */
|
|
2
|
-
import { readFile } from 'node:fs/promises';
|
|
3
|
-
import { basename } from 'node:path';
|
|
4
|
-
import { log } from '../log.js';
|
|
5
|
-
import { mdToTelegramHtml } from './telegram-md.js';
|
|
6
|
-
const API_BASE = 'https://api.telegram.org';
|
|
7
|
-
const NO_PREVIEW = { link_preview_options: { is_disabled: true } };
|
|
8
|
-
export const inlineKeyboard = (rows) => ({ inline_keyboard: rows });
|
|
9
|
-
async function post(token, method, form) {
|
|
10
|
-
const res = await fetch(`${API_BASE}/bot${token}/${method}`, {
|
|
11
|
-
method: 'POST', body: form, signal: AbortSignal.timeout(60_000),
|
|
12
|
-
});
|
|
13
|
-
const json = (await res.json());
|
|
14
|
-
if (!json.ok)
|
|
15
|
-
throw new Error(`telegram ${method}: ${json.description ?? 'unknown error'}`);
|
|
16
|
-
return json.result;
|
|
17
|
-
}
|
|
18
|
-
function appendFields(form, fields) {
|
|
19
|
-
for (const [k, v] of Object.entries(fields)) {
|
|
20
|
-
if (v === undefined || v === null)
|
|
21
|
-
continue;
|
|
22
|
-
form.append(k, typeof v === 'string' ? v : JSON.stringify(v));
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
async function appendFile(form, field, filePath) {
|
|
26
|
-
form.append(field, new Blob([new Uint8Array(await readFile(filePath))]), basename(filePath));
|
|
27
|
-
}
|
|
28
|
-
const isParseError = (err) => err instanceof Error && err.message.includes("can't parse entities");
|
|
29
|
-
/** sendPhoto / sendDocument / sendVoice with HTML-caption + plain fallback. */
|
|
30
|
-
async function sendOne(token, base, method, fileField, filePath, caption) {
|
|
31
|
-
const attempt = async (html) => {
|
|
32
|
-
const form = new FormData();
|
|
33
|
-
appendFields(form, {
|
|
34
|
-
...base,
|
|
35
|
-
caption: html ? mdToTelegramHtml(caption) : caption,
|
|
36
|
-
...(html ? { parse_mode: 'HTML' } : {}),
|
|
37
|
-
});
|
|
38
|
-
await appendFile(form, fileField, filePath);
|
|
39
|
-
return post(token, method, form);
|
|
40
|
-
};
|
|
41
|
-
try {
|
|
42
|
-
return String((await attempt(true)).message_id);
|
|
43
|
-
}
|
|
44
|
-
catch (err) {
|
|
45
|
-
if (!isParseError(err))
|
|
46
|
-
throw err;
|
|
47
|
-
log.warn('telegram: HTML rejected, retrying plain');
|
|
48
|
-
return String((await attempt(false)).message_id);
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
/** sendMediaGroup — 2-10 same-kind files. Caption goes on the first item only. */
|
|
52
|
-
async function sendGroup(token, base, kind, paths, caption) {
|
|
53
|
-
const form = new FormData();
|
|
54
|
-
appendFields(form, base);
|
|
55
|
-
const media = await Promise.all(paths.map(async (p, i) => {
|
|
56
|
-
const field = `m${i}`;
|
|
57
|
-
await appendFile(form, field, p);
|
|
58
|
-
return {
|
|
59
|
-
type: kind, media: `attach://${field}`,
|
|
60
|
-
...(i === 0 && caption ? { caption: mdToTelegramHtml(caption), parse_mode: 'HTML' } : {}),
|
|
61
|
-
};
|
|
62
|
-
}));
|
|
63
|
-
form.append('media', JSON.stringify(media));
|
|
64
|
-
const sent = await post(token, 'sendMediaGroup', form);
|
|
65
|
-
return String(sent[0].message_id);
|
|
66
|
-
}
|
|
67
|
-
/** Plain text via sendMessage with HTML + plain fallback. */
|
|
68
|
-
async function sendText(send, base, text) {
|
|
69
|
-
const attempt = (html) => send('sendMessage', html ? { ...base, ...NO_PREVIEW, text: mdToTelegramHtml(text), parse_mode: 'HTML' }
|
|
70
|
-
: { ...base, ...NO_PREVIEW, text });
|
|
71
|
-
try {
|
|
72
|
-
return String((await attempt(true)).message_id);
|
|
73
|
-
}
|
|
74
|
-
catch (err) {
|
|
75
|
-
if (!isParseError(err))
|
|
76
|
-
throw err;
|
|
77
|
-
log.warn('telegram: HTML rejected, sending plain');
|
|
78
|
-
return String((await attempt(false)).message_id);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
/** Dispatch for text + images + documents + voice + buttons. Returns the first message id. */
|
|
82
|
-
export async function tgSendRich(token, send, base, text, opts) {
|
|
83
|
-
const images = opts?.images ?? [], docs = opts?.documents ?? [], voice = opts?.voice;
|
|
84
|
-
const total = images.length + docs.length + (voice ? 1 : 0);
|
|
85
|
-
const baseWithButtons = opts?.buttons?.length
|
|
86
|
-
? { ...base, reply_markup: inlineKeyboard(opts.buttons) } : base;
|
|
87
|
-
if (total === 0)
|
|
88
|
-
return sendText(send, baseWithButtons, text);
|
|
89
|
-
if (total === 1) {
|
|
90
|
-
if (voice)
|
|
91
|
-
return sendOne(token, baseWithButtons, 'sendVoice', 'voice', voice, text);
|
|
92
|
-
if (images.length)
|
|
93
|
-
return sendOne(token, baseWithButtons, 'sendPhoto', 'photo', images[0], text);
|
|
94
|
-
return sendOne(token, baseWithButtons, 'sendDocument', 'document', docs[0], text);
|
|
95
|
-
}
|
|
96
|
-
/* Multi-attachment: media groups don't support reply_markup — buttons are dropped. */
|
|
97
|
-
if (opts?.buttons?.length)
|
|
98
|
-
log.warn('telegram: buttons dropped on multi-attachment send');
|
|
99
|
-
let first;
|
|
100
|
-
let caption = text;
|
|
101
|
-
const claim = async (id) => { first ??= id; caption = ''; };
|
|
102
|
-
if (voice)
|
|
103
|
-
await claim(await sendOne(token, base, 'sendVoice', 'voice', voice, caption));
|
|
104
|
-
if (images.length === 1)
|
|
105
|
-
await claim(await sendOne(token, base, 'sendPhoto', 'photo', images[0], caption));
|
|
106
|
-
else if (images.length)
|
|
107
|
-
await claim(await sendGroup(token, base, 'photo', images, caption));
|
|
108
|
-
if (docs.length === 1)
|
|
109
|
-
await claim(await sendOne(token, base, 'sendDocument', 'document', docs[0], caption));
|
|
110
|
-
else if (docs.length)
|
|
111
|
-
await claim(await sendGroup(token, base, 'document', docs, caption));
|
|
112
|
-
return first;
|
|
113
|
-
}
|
|
@@ -1,213 +0,0 @@
|
|
|
1
|
-
/** Telegram station: long-poll bot API; sends agent-style markdown as HTML with plain-text fallback. */
|
|
2
|
-
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
-
import { writeFile } from 'node:fs/promises';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import { errMsg, log } from '../log.js';
|
|
6
|
-
import { mintId } from '../history.js';
|
|
7
|
-
import { mdToTelegramHtml } from './telegram-md.js';
|
|
8
|
-
import { inlineKeyboard, tgSendRich } from './telegram-upload.js';
|
|
9
|
-
import { Line, } from './index.js';
|
|
10
|
-
import { STATE_DIR } from '../paths.js';
|
|
11
|
-
const API_BASE = 'https://api.telegram.org';
|
|
12
|
-
const NO_PREVIEW = { link_preview_options: { is_disabled: true } };
|
|
13
|
-
const MAX_BYTES = 20 * 1024 * 1024;
|
|
14
|
-
const token = () => {
|
|
15
|
-
const t = process.env.TELEGRAM_BOT_TOKEN;
|
|
16
|
-
if (!t)
|
|
17
|
-
throw new Error('TELEGRAM_BOT_TOKEN is not set');
|
|
18
|
-
return t;
|
|
19
|
-
};
|
|
20
|
-
async function tg(method, body, opts = {}) {
|
|
21
|
-
const signals = [AbortSignal.timeout(opts.timeoutMs ?? 30_000)];
|
|
22
|
-
if (opts.signal)
|
|
23
|
-
signals.push(opts.signal);
|
|
24
|
-
const res = await fetch(`${API_BASE}/bot${token()}/${method}`, {
|
|
25
|
-
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
26
|
-
body: JSON.stringify(body), signal: AbortSignal.any(signals),
|
|
27
|
-
});
|
|
28
|
-
const json = (await res.json());
|
|
29
|
-
if (!json.ok)
|
|
30
|
-
throw new Error(`telegram ${method}: ${json.description ?? 'unknown error'}`);
|
|
31
|
-
return json.result;
|
|
32
|
-
}
|
|
33
|
-
const isParseError = (err) => errMsg(err).includes("can't parse entities");
|
|
34
|
-
const isNoopEdit = (err) => errMsg(err).includes('message is not modified');
|
|
35
|
-
const targetOf = (line) => {
|
|
36
|
-
const t = Line.parseTelegram(line);
|
|
37
|
-
if (!t)
|
|
38
|
-
throw new Error(`not a telegram line: ${line}`);
|
|
39
|
-
return t;
|
|
40
|
-
};
|
|
41
|
-
function attachmentTags(m) {
|
|
42
|
-
const out = [];
|
|
43
|
-
if (m.photo?.length)
|
|
44
|
-
out.push('[image]');
|
|
45
|
-
if (m.document?.mime_type?.startsWith('image/'))
|
|
46
|
-
out.push('[image]');
|
|
47
|
-
else if (m.document)
|
|
48
|
-
out.push(`[file: ${m.document.file_name ?? m.document.file_id}]`);
|
|
49
|
-
if (m.voice)
|
|
50
|
-
out.push('[voice]');
|
|
51
|
-
if (m.audio)
|
|
52
|
-
out.push('[audio]');
|
|
53
|
-
return out;
|
|
54
|
-
}
|
|
55
|
-
const CAPS = {
|
|
56
|
-
in: ['text', 'image'], out: ['text'],
|
|
57
|
-
features: ['reply', 'send', 'edit', 'react', 'download', 'fetch'],
|
|
58
|
-
};
|
|
59
|
-
export class TelegramStation {
|
|
60
|
-
name = 'telegram';
|
|
61
|
-
capabilities = CAPS;
|
|
62
|
-
pollOffset = 0;
|
|
63
|
-
pollAbort = null;
|
|
64
|
-
messageHandler = () => { };
|
|
65
|
-
offsetFile = join(STATE_DIR, 'telegram-offset.json');
|
|
66
|
-
/** Snapshot recent inbounds in memory so `metro download <line> <id>` can resolve them. */
|
|
67
|
-
recent = new Map();
|
|
68
|
-
onMessage(handler) { this.messageHandler = handler; }
|
|
69
|
-
async start() {
|
|
70
|
-
await tg('deleteWebhook', { drop_pending_updates: false }).catch(() => { });
|
|
71
|
-
const persisted = Number(existsSync(this.offsetFile) ? readFileSync(this.offsetFile, 'utf8').trim() : 0) || 0;
|
|
72
|
-
if (persisted > 0)
|
|
73
|
-
this.pollOffset = persisted;
|
|
74
|
-
else {
|
|
75
|
-
/* First run: anchor on latest update id (-1 returns most recent without consuming). */
|
|
76
|
-
const initial = await tg('getUpdates', { offset: -1, timeout: 0 });
|
|
77
|
-
this.pollOffset = initial.length ? initial[0].update_id + 1 : 0;
|
|
78
|
-
this.saveOffset();
|
|
79
|
-
}
|
|
80
|
-
log.info({ offset: this.pollOffset }, 'telegram station: polling started');
|
|
81
|
-
this.pollAbort = new AbortController();
|
|
82
|
-
void this.pollLoop();
|
|
83
|
-
}
|
|
84
|
-
async stop() { this.pollAbort?.abort(); this.pollAbort = null; }
|
|
85
|
-
async getMe() {
|
|
86
|
-
return tg('getMe', {});
|
|
87
|
-
}
|
|
88
|
-
async send(line, text, opts) {
|
|
89
|
-
const { chatId, topicId } = targetOf(line);
|
|
90
|
-
const base = { chat_id: chatId };
|
|
91
|
-
if (topicId !== undefined)
|
|
92
|
-
base.message_thread_id = topicId;
|
|
93
|
-
if (opts?.replyTo)
|
|
94
|
-
base.reply_parameters = { message_id: Number(opts.replyTo) };
|
|
95
|
-
return tgSendRich(token(), tg, base, text, opts);
|
|
96
|
-
}
|
|
97
|
-
async edit(line, messageId, text, opts) {
|
|
98
|
-
const { chatId } = targetOf(line);
|
|
99
|
-
const base = { chat_id: chatId, message_id: Number(messageId), ...NO_PREVIEW };
|
|
100
|
-
if (opts?.buttons)
|
|
101
|
-
base.reply_markup = opts.buttons.length ? inlineKeyboard(opts.buttons) : { inline_keyboard: [] };
|
|
102
|
-
try {
|
|
103
|
-
await tg('editMessageText', { ...base, text: mdToTelegramHtml(text), parse_mode: 'HTML' });
|
|
104
|
-
}
|
|
105
|
-
catch (err) {
|
|
106
|
-
if (isNoopEdit(err))
|
|
107
|
-
return;
|
|
108
|
-
if (!isParseError(err))
|
|
109
|
-
throw err;
|
|
110
|
-
log.warn({ err: errMsg(err) }, 'telegram: HTML edit rejected, retrying plain');
|
|
111
|
-
try {
|
|
112
|
-
await tg('editMessageText', { ...base, text });
|
|
113
|
-
}
|
|
114
|
-
catch (e) {
|
|
115
|
-
if (!isNoopEdit(e))
|
|
116
|
-
throw e;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
async react(line, messageId, emoji) {
|
|
121
|
-
await tg('setMessageReaction', {
|
|
122
|
-
chat_id: targetOf(line).chatId, message_id: Number(messageId),
|
|
123
|
-
reaction: emoji ? [{ type: 'emoji', emoji }] : [],
|
|
124
|
-
});
|
|
125
|
-
}
|
|
126
|
-
async download(line, messageId, outDir) {
|
|
127
|
-
/* Telegram has no "get message by id" — resolve from the in-memory snapshot. */
|
|
128
|
-
const { chatId } = targetOf(line);
|
|
129
|
-
const m = this.recent.get(`${chatId}:${messageId}`);
|
|
130
|
-
if (!m)
|
|
131
|
-
return [];
|
|
132
|
-
const refs = [];
|
|
133
|
-
if (m.photo?.length)
|
|
134
|
-
refs.push({ id: m.photo[m.photo.length - 1].file_id, mime: 'image/jpeg' });
|
|
135
|
-
if (m.document?.mime_type?.startsWith('image/'))
|
|
136
|
-
refs.push({ id: m.document.file_id, mime: m.document.mime_type });
|
|
137
|
-
const out = [];
|
|
138
|
-
for (const [i, { id, mime }] of refs.entries()) {
|
|
139
|
-
try {
|
|
140
|
-
const file = await tg('getFile', { file_id: id });
|
|
141
|
-
const res = await fetch(`${API_BASE}/file/bot${token()}/${file.file_path}`, { signal: AbortSignal.timeout(30_000) });
|
|
142
|
-
if (!res.ok)
|
|
143
|
-
throw new Error(`status ${res.status}`);
|
|
144
|
-
const buf = Buffer.from(await res.arrayBuffer());
|
|
145
|
-
if (buf.byteLength > MAX_BYTES) {
|
|
146
|
-
log.warn({ id, size: buf.byteLength }, 'telegram: attachment too large');
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
const path = join(outDir, `${chatId}-${messageId}-${i}.${mime.split('/')[1] ?? 'bin'}`);
|
|
150
|
-
await writeFile(path, buf);
|
|
151
|
-
out.push({ path, mediaType: mime });
|
|
152
|
-
}
|
|
153
|
-
catch (err) {
|
|
154
|
-
log.warn({ err: errMsg(err), id }, 'telegram: attachment fetch failed');
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
return out;
|
|
158
|
-
}
|
|
159
|
-
/** Bot API has no history endpoint — only the in-memory snapshot is reachable. */
|
|
160
|
-
async fetch() { return []; }
|
|
161
|
-
saveOffset() {
|
|
162
|
-
try {
|
|
163
|
-
writeFileSync(this.offsetFile, String(this.pollOffset));
|
|
164
|
-
}
|
|
165
|
-
catch (err) {
|
|
166
|
-
log.warn({ err: errMsg(err) }, 'telegram offset save failed');
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
async pollLoop() {
|
|
170
|
-
const signal = this.pollAbort?.signal;
|
|
171
|
-
const body = { timeout: 25, allowed_updates: ['message'] };
|
|
172
|
-
while (this.pollAbort && !this.pollAbort.signal.aborted) {
|
|
173
|
-
try {
|
|
174
|
-
const updates = await tg('getUpdates', { offset: this.pollOffset, ...body }, { timeoutMs: 60_000, signal });
|
|
175
|
-
for (const u of updates) {
|
|
176
|
-
this.pollOffset = u.update_id + 1;
|
|
177
|
-
this.dispatchUpdate(u);
|
|
178
|
-
}
|
|
179
|
-
if (updates.length)
|
|
180
|
-
this.saveOffset();
|
|
181
|
-
}
|
|
182
|
-
catch (err) {
|
|
183
|
-
if (this.pollAbort?.signal.aborted)
|
|
184
|
-
break;
|
|
185
|
-
log.warn({ err: errMsg(err) }, 'telegram poll error; backing off');
|
|
186
|
-
await new Promise(r => setTimeout(r, 2000));
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
dispatchUpdate(u) {
|
|
191
|
-
const m = u.message;
|
|
192
|
-
if (!m?.chat?.id || typeof m.message_id !== 'number' || m.from?.is_bot)
|
|
193
|
-
return;
|
|
194
|
-
const text = [m.text ?? m.caption, ...attachmentTags(m)].filter(Boolean).join(' ');
|
|
195
|
-
if (!text)
|
|
196
|
-
return;
|
|
197
|
-
const topicId = m.is_topic_message ? m.message_thread_id : undefined;
|
|
198
|
-
const fromName = m.from?.username ? `@${m.from.username}` : m.from?.first_name;
|
|
199
|
-
log.info({ from: fromName, chat: m.chat.id, topic: topicId, text: text.slice(0, 80) }, 'telegram: inbound');
|
|
200
|
-
if (this.recent.size >= 50) {
|
|
201
|
-
const first = this.recent.keys().next().value;
|
|
202
|
-
if (first)
|
|
203
|
-
this.recent.delete(first);
|
|
204
|
-
}
|
|
205
|
-
this.recent.set(`${m.chat.id}:${m.message_id}`, m);
|
|
206
|
-
this.messageHandler({
|
|
207
|
-
id: mintId(), ts: new Date((m.date ?? Math.floor(Date.now() / 1000)) * 1000).toISOString(),
|
|
208
|
-
station: 'telegram', line: Line.telegram(m.chat.id, topicId), messageId: String(m.message_id),
|
|
209
|
-
lineName: topicId === undefined ? (m.chat.title ?? m.chat.first_name ?? undefined) : undefined,
|
|
210
|
-
from: Line.user('telegram', m.from?.id ?? 'unknown'), fromName, text, payload: m,
|
|
211
|
-
});
|
|
212
|
-
}
|
|
213
|
-
}
|
package/dist/stations/webhook.js
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
/** Receive-only HTTP station. Each registered endpoint = one path `/wh/<id>` → InboundMessage on stdout. */
|
|
2
|
-
import { createHmac, randomUUID, timingSafeEqual } from 'node:crypto';
|
|
3
|
-
import { createServer } from 'node:http';
|
|
4
|
-
import { errMsg, log } from '../log.js';
|
|
5
|
-
import { mintId } from '../history.js';
|
|
6
|
-
import { findEndpoint, listEndpoints } from '../webhooks.js';
|
|
7
|
-
import { Line } from './index.js';
|
|
8
|
-
const DEFAULT_PORT = 8420;
|
|
9
|
-
/** Synthesize an `event` tag from common provider-specific headers (GitHub, Intercom). */
|
|
10
|
-
function pickEvent(headers) {
|
|
11
|
-
return headers['x-github-event'] ?? headers['x-intercom-topic'] ?? 'event';
|
|
12
|
-
}
|
|
13
|
-
/** Constant-time signature check against `X-Hub-Signature-256: sha256=<hex>` (GitHub/Intercom style). */
|
|
14
|
-
function verifySig(secret, raw, header) {
|
|
15
|
-
if (!header?.startsWith('sha256='))
|
|
16
|
-
return false;
|
|
17
|
-
const given = Buffer.from(header.slice(7), 'hex');
|
|
18
|
-
const want = createHmac('sha256', secret).update(raw).digest();
|
|
19
|
-
return given.length === want.length && timingSafeEqual(given, want);
|
|
20
|
-
}
|
|
21
|
-
async function readBody(req) {
|
|
22
|
-
const chunks = [];
|
|
23
|
-
for await (const c of req)
|
|
24
|
-
chunks.push(c);
|
|
25
|
-
return Buffer.concat(chunks);
|
|
26
|
-
}
|
|
27
|
-
export class WebhookStation {
|
|
28
|
-
name = 'webhook';
|
|
29
|
-
server = null;
|
|
30
|
-
handler = null;
|
|
31
|
-
port() { return Number(process.env.METRO_WEBHOOK_PORT) || DEFAULT_PORT; }
|
|
32
|
-
onMessage(h) { this.handler = h; }
|
|
33
|
-
start() {
|
|
34
|
-
return new Promise((resolve, reject) => {
|
|
35
|
-
this.server = createServer((req, res) => this.handle(req, res).catch(err => {
|
|
36
|
-
log.warn({ err: errMsg(err) }, 'webhook handler error');
|
|
37
|
-
if (!res.headersSent)
|
|
38
|
-
res.writeHead(500).end();
|
|
39
|
-
}));
|
|
40
|
-
this.server.on('error', reject);
|
|
41
|
-
this.server.listen(this.port(), '127.0.0.1', () => {
|
|
42
|
-
log.info({ port: this.port(), endpoints: listEndpoints().length }, 'webhook station ready');
|
|
43
|
-
resolve();
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
async stop() {
|
|
48
|
-
const srv = this.server;
|
|
49
|
-
if (!srv)
|
|
50
|
-
return;
|
|
51
|
-
await new Promise(resolve => srv.close(() => resolve()));
|
|
52
|
-
this.server = null;
|
|
53
|
-
}
|
|
54
|
-
async handle(req, res) {
|
|
55
|
-
const m = req.url?.match(/^\/wh\/([A-Za-z0-9_-]+)/);
|
|
56
|
-
if (!m) {
|
|
57
|
-
res.writeHead(404).end();
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
const endpointId = m[1];
|
|
61
|
-
const endpoint = findEndpoint(endpointId);
|
|
62
|
-
if (!endpoint) {
|
|
63
|
-
res.writeHead(404).end();
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
if (req.method === 'GET') {
|
|
67
|
-
res.writeHead(200).end(`metro webhook ${endpointId} ready\n`);
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
if (req.method !== 'POST') {
|
|
71
|
-
res.writeHead(405).end();
|
|
72
|
-
return;
|
|
73
|
-
}
|
|
74
|
-
const raw = await readBody(req);
|
|
75
|
-
const headers = Object.fromEntries(Object.entries(req.headers).map(([k, v]) => [k, Array.isArray(v) ? v.join(',') : v ?? '']));
|
|
76
|
-
if (endpoint.secret && !verifySig(endpoint.secret, raw, headers['x-hub-signature-256'])) {
|
|
77
|
-
log.warn({ endpoint: endpointId }, 'webhook signature mismatch — rejecting');
|
|
78
|
-
res.writeHead(401).end('signature mismatch');
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
let body = raw.toString('utf8');
|
|
82
|
-
try {
|
|
83
|
-
body = JSON.parse(body);
|
|
84
|
-
}
|
|
85
|
-
catch { /* keep as string */ }
|
|
86
|
-
const line = Line.webhook(endpointId);
|
|
87
|
-
this.handler?.({
|
|
88
|
-
id: mintId(),
|
|
89
|
-
ts: new Date().toISOString(),
|
|
90
|
-
station: 'webhook',
|
|
91
|
-
line,
|
|
92
|
-
lineName: endpoint.label,
|
|
93
|
-
from: line,
|
|
94
|
-
messageId: headers['x-github-delivery'] || headers['x-request-id'] || randomUUID(),
|
|
95
|
-
text: `${pickEvent(headers)} ${req.method} ${req.url}`,
|
|
96
|
-
payload: { headers, body },
|
|
97
|
-
});
|
|
98
|
-
res.writeHead(200).end('ok');
|
|
99
|
-
}
|
|
100
|
-
}
|
package/dist/tunnel.js
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
/** Cloudflared tunnel manager. Prefers token-from-env so a missing local credentials JSON does not block startup. */
|
|
2
|
-
import { spawn, spawnSync } from 'node:child_process';
|
|
3
|
-
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import { STATE_DIR } from './paths.js';
|
|
6
|
-
import { errMsg, log } from './log.js';
|
|
7
|
-
const FILE = join(STATE_DIR, 'tunnel.json');
|
|
8
|
-
const RESTART_DELAY_MS = 2_000;
|
|
9
|
-
export const loadTunnelConfig = () => existsSync(FILE)
|
|
10
|
-
? JSON.parse(readFileSync(FILE, 'utf8'))
|
|
11
|
-
: null;
|
|
12
|
-
export function saveTunnelConfig(c) { writeFileSync(FILE, JSON.stringify(c, null, 2)); }
|
|
13
|
-
/** Fetch the tunnel's auth token. Null when CLI is unavailable, not logged in, or no such tunnel. */
|
|
14
|
-
function fetchTunnelToken(name) {
|
|
15
|
-
const r = spawnSync('cloudflared', ['tunnel', 'token', name], { encoding: 'utf8' });
|
|
16
|
-
if (r.status !== 0)
|
|
17
|
-
return null;
|
|
18
|
-
const token = r.stdout.trim();
|
|
19
|
-
return token.length > 0 ? token : null;
|
|
20
|
-
}
|
|
21
|
-
export class Tunnel {
|
|
22
|
-
cfg;
|
|
23
|
-
port;
|
|
24
|
-
child = null;
|
|
25
|
-
closed = false;
|
|
26
|
-
token = null;
|
|
27
|
-
tokenResolved = false;
|
|
28
|
-
constructor(cfg, port) {
|
|
29
|
-
this.cfg = cfg;
|
|
30
|
-
this.port = port;
|
|
31
|
-
}
|
|
32
|
-
get hostname() { return this.cfg.hostname; }
|
|
33
|
-
start() {
|
|
34
|
-
if (this.closed)
|
|
35
|
-
return;
|
|
36
|
-
if (!this.tokenResolved) {
|
|
37
|
-
this.token = fetchTunnelToken(this.cfg.name);
|
|
38
|
-
this.tokenResolved = true;
|
|
39
|
-
}
|
|
40
|
-
const mode = this.token ? 'token' : 'named';
|
|
41
|
-
log.info({ name: this.cfg.name, hostname: this.cfg.hostname, port: this.port, mode }, 'cloudflared tunnel starting');
|
|
42
|
-
/** `--no-autoupdate` is a global cloudflared flag — must come before the `tunnel` subcommand. */
|
|
43
|
-
const args = ['--no-autoupdate', 'tunnel', 'run', '--url', `http://127.0.0.1:${this.port}`];
|
|
44
|
-
/** Token form resolves the tunnel from TUNNEL_TOKEN so the trailing name arg must be omitted. */
|
|
45
|
-
if (!this.token)
|
|
46
|
-
args.push(this.cfg.name);
|
|
47
|
-
const env = this.token
|
|
48
|
-
? { ...process.env, TUNNEL_TOKEN: this.token }
|
|
49
|
-
: process.env;
|
|
50
|
-
this.child = spawn('cloudflared', args, { stdio: ['ignore', 'pipe', 'pipe'], env });
|
|
51
|
-
this.child.stderr?.on('data', d => log.debug({ cloudflared: d.toString().trim() }, 'cloudflared'));
|
|
52
|
-
this.child.on('exit', code => {
|
|
53
|
-
this.child = null;
|
|
54
|
-
if (this.closed)
|
|
55
|
-
return;
|
|
56
|
-
log.warn({ code }, 'cloudflared exited; restarting');
|
|
57
|
-
setTimeout(() => this.start(), RESTART_DELAY_MS);
|
|
58
|
-
});
|
|
59
|
-
this.child.on('error', err => log.warn({ err: errMsg(err) }, 'cloudflared spawn error'));
|
|
60
|
-
}
|
|
61
|
-
stop() {
|
|
62
|
-
this.closed = true;
|
|
63
|
-
this.child?.kill();
|
|
64
|
-
this.child = null;
|
|
65
|
-
}
|
|
66
|
-
}
|
package/dist/webhooks.js
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/** Webhook endpoint registry: persists `(id, label, secret?)` for each receive endpoint. */
|
|
2
|
-
import { randomBytes } from 'node:crypto';
|
|
3
|
-
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
4
|
-
import { join } from 'node:path';
|
|
5
|
-
import { STATE_DIR } from './paths.js';
|
|
6
|
-
const FILE = join(STATE_DIR, 'webhooks.json');
|
|
7
|
-
function read() {
|
|
8
|
-
if (!existsSync(FILE))
|
|
9
|
-
return { endpoints: [] };
|
|
10
|
-
try {
|
|
11
|
-
return JSON.parse(readFileSync(FILE, 'utf8'));
|
|
12
|
-
}
|
|
13
|
-
catch {
|
|
14
|
-
return { endpoints: [] };
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
function write(s) { writeFileSync(FILE, JSON.stringify(s, null, 2)); }
|
|
18
|
-
export const listEndpoints = () => read().endpoints;
|
|
19
|
-
export const findEndpoint = (id) => read().endpoints.find(e => e.id === id);
|
|
20
|
-
/** Mint a 16-char URL-safe random id (~96 bits of entropy — collision-proof for any reasonable count). */
|
|
21
|
-
const mintEndpointId = () => randomBytes(12).toString('base64url');
|
|
22
|
-
export function addEndpoint(label, secret) {
|
|
23
|
-
const s = read();
|
|
24
|
-
const ep = {
|
|
25
|
-
id: mintEndpointId(), label, createdAt: new Date().toISOString(),
|
|
26
|
-
...(secret ? { secret } : {}),
|
|
27
|
-
};
|
|
28
|
-
s.endpoints.push(ep);
|
|
29
|
-
write(s);
|
|
30
|
-
return ep;
|
|
31
|
-
}
|
|
32
|
-
export function removeEndpoint(id) {
|
|
33
|
-
const s = read();
|
|
34
|
-
const before = s.endpoints.length;
|
|
35
|
-
s.endpoints = s.endpoints.filter(e => e.id !== id);
|
|
36
|
-
if (s.endpoints.length === before)
|
|
37
|
-
return false;
|
|
38
|
-
write(s);
|
|
39
|
-
return true;
|
|
40
|
-
}
|