clawdbot 2026.1.4-1
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/CHANGELOG.md +120 -0
- package/LICENSE +21 -0
- package/README-header.png +0 -0
- package/README.md +297 -0
- package/dist/agents/agent-paths.js +17 -0
- package/dist/agents/bash-process-registry.js +126 -0
- package/dist/agents/bash-tools.js +837 -0
- package/dist/agents/clawdbot-tools.js +30 -0
- package/dist/agents/clawdis-tools.js +27 -0
- package/dist/agents/context.js +34 -0
- package/dist/agents/defaults.js +6 -0
- package/dist/agents/model-auth.js +112 -0
- package/dist/agents/model-catalog.js +55 -0
- package/dist/agents/model-fallback.js +191 -0
- package/dist/agents/model-scan.js +263 -0
- package/dist/agents/model-selection.js +116 -0
- package/dist/agents/models-config.js +49 -0
- package/dist/agents/pi-embedded-helpers.js +74 -0
- package/dist/agents/pi-embedded-runner.js +407 -0
- package/dist/agents/pi-embedded-subscribe.js +568 -0
- package/dist/agents/pi-embedded-utils.js +20 -0
- package/dist/agents/pi-embedded.js +1 -0
- package/dist/agents/pi-oauth.js +88 -0
- package/dist/agents/pi-tools.js +433 -0
- package/dist/agents/sandbox-paths.js +68 -0
- package/dist/agents/sandbox.js +644 -0
- package/dist/agents/shell-utils.js +53 -0
- package/dist/agents/skills-install.js +244 -0
- package/dist/agents/skills-status.js +157 -0
- package/dist/agents/skills.js +470 -0
- package/dist/agents/steerable-agent-loop.js +338 -0
- package/dist/agents/steerable-provider-transport.js +48 -0
- package/dist/agents/system-prompt.js +104 -0
- package/dist/agents/tool-display.js +162 -0
- package/dist/agents/tool-images.js +138 -0
- package/dist/agents/tools/browser-tool.js +339 -0
- package/dist/agents/tools/canvas-tool.js +193 -0
- package/dist/agents/tools/common.js +88 -0
- package/dist/agents/tools/cron-tool.js +124 -0
- package/dist/agents/tools/discord-actions-guild.js +186 -0
- package/dist/agents/tools/discord-actions-messaging.js +285 -0
- package/dist/agents/tools/discord-actions-moderation.js +70 -0
- package/dist/agents/tools/discord-actions.js +56 -0
- package/dist/agents/tools/discord-schema.js +199 -0
- package/dist/agents/tools/discord-tool.js +16 -0
- package/dist/agents/tools/gateway-tool.js +46 -0
- package/dist/agents/tools/gateway.js +27 -0
- package/dist/agents/tools/image-tool.js +132 -0
- package/dist/agents/tools/nodes-tool.js +413 -0
- package/dist/agents/tools/nodes-utils.js +92 -0
- package/dist/agents/tools/sessions-helpers.js +88 -0
- package/dist/agents/tools/sessions-history-tool.js +53 -0
- package/dist/agents/tools/sessions-list-tool.js +143 -0
- package/dist/agents/tools/sessions-send-helpers.js +100 -0
- package/dist/agents/tools/sessions-send-tool.js +347 -0
- package/dist/agents/tools/slack-actions.js +129 -0
- package/dist/agents/tools/slack-schema.js +59 -0
- package/dist/agents/tools/slack-tool.js +16 -0
- package/dist/agents/usage.js +39 -0
- package/dist/agents/workspace.js +241 -0
- package/dist/auto-reply/chunk.js +76 -0
- package/dist/auto-reply/envelope.js +38 -0
- package/dist/auto-reply/group-activation.js +20 -0
- package/dist/auto-reply/heartbeat.js +57 -0
- package/dist/auto-reply/model.js +14 -0
- package/dist/auto-reply/reply/abort.js +14 -0
- package/dist/auto-reply/reply/agent-runner.js +371 -0
- package/dist/auto-reply/reply/block-streaming.js +34 -0
- package/dist/auto-reply/reply/body.js +29 -0
- package/dist/auto-reply/reply/commands.js +207 -0
- package/dist/auto-reply/reply/directive-handling.js +361 -0
- package/dist/auto-reply/reply/directives.js +47 -0
- package/dist/auto-reply/reply/followup-runner.js +149 -0
- package/dist/auto-reply/reply/groups.js +91 -0
- package/dist/auto-reply/reply/mentions.js +38 -0
- package/dist/auto-reply/reply/model-selection.js +114 -0
- package/dist/auto-reply/reply/queue.js +399 -0
- package/dist/auto-reply/reply/reply-tags.js +26 -0
- package/dist/auto-reply/reply/session-updates.js +87 -0
- package/dist/auto-reply/reply/session.js +160 -0
- package/dist/auto-reply/reply/typing.js +75 -0
- package/dist/auto-reply/reply.js +535 -0
- package/dist/auto-reply/send-policy.js +28 -0
- package/dist/auto-reply/status.js +158 -0
- package/dist/auto-reply/templating.js +9 -0
- package/dist/auto-reply/thinking.js +49 -0
- package/dist/auto-reply/tokens.js +2 -0
- package/dist/auto-reply/tool-meta.js +74 -0
- package/dist/auto-reply/transcription.js +57 -0
- package/dist/auto-reply/types.js +1 -0
- package/dist/browser/bridge-server.js +37 -0
- package/dist/browser/cdp.js +382 -0
- package/dist/browser/chrome.js +432 -0
- package/dist/browser/client-actions-core.js +67 -0
- package/dist/browser/client-actions-observe.js +24 -0
- package/dist/browser/client-actions-types.js +1 -0
- package/dist/browser/client-actions.js +3 -0
- package/dist/browser/client-fetch.js +43 -0
- package/dist/browser/client.js +105 -0
- package/dist/browser/config.js +140 -0
- package/dist/browser/constants.js +4 -0
- package/dist/browser/profiles-service.js +122 -0
- package/dist/browser/profiles.js +85 -0
- package/dist/browser/pw-ai.js +2 -0
- package/dist/browser/pw-session.js +144 -0
- package/dist/browser/pw-tools-core.js +363 -0
- package/dist/browser/routes/agent.js +535 -0
- package/dist/browser/routes/basic.js +155 -0
- package/dist/browser/routes/index.js +8 -0
- package/dist/browser/routes/tabs.js +105 -0
- package/dist/browser/routes/utils.js +62 -0
- package/dist/browser/screenshot.js +40 -0
- package/dist/browser/server-context.js +377 -0
- package/dist/browser/server.js +81 -0
- package/dist/browser/target-id.js +18 -0
- package/dist/browser/trash.js +21 -0
- package/dist/canvas-host/a2ui/.bundle.hash +1 -0
- package/dist/canvas-host/a2ui/a2ui.bundle.js +17768 -0
- package/dist/canvas-host/a2ui/index.html +246 -0
- package/dist/canvas-host/a2ui.js +187 -0
- package/dist/canvas-host/server.js +382 -0
- package/dist/cli/browser-cli-actions-input.js +459 -0
- package/dist/cli/browser-cli-actions-observe.js +56 -0
- package/dist/cli/browser-cli-examples.js +31 -0
- package/dist/cli/browser-cli-inspect.js +97 -0
- package/dist/cli/browser-cli-manage.js +286 -0
- package/dist/cli/browser-cli-shared.js +1 -0
- package/dist/cli/browser-cli.js +26 -0
- package/dist/cli/canvas-cli.js +416 -0
- package/dist/cli/cron-cli.js +454 -0
- package/dist/cli/deps.js +17 -0
- package/dist/cli/dns-cli.js +180 -0
- package/dist/cli/gateway-cli.js +489 -0
- package/dist/cli/gateway-rpc.js +20 -0
- package/dist/cli/hooks-cli.js +135 -0
- package/dist/cli/models-cli.js +248 -0
- package/dist/cli/nodes-camera.js +57 -0
- package/dist/cli/nodes-canvas.js +26 -0
- package/dist/cli/nodes-cli.js +946 -0
- package/dist/cli/nodes-screen.js +37 -0
- package/dist/cli/parse-duration.js +20 -0
- package/dist/cli/ports.js +97 -0
- package/dist/cli/program.js +406 -0
- package/dist/cli/prompt.js +19 -0
- package/dist/cli/tui-cli.js +35 -0
- package/dist/cli/wait.js +8 -0
- package/dist/commands/agent.js +645 -0
- package/dist/commands/antigravity-oauth.js +327 -0
- package/dist/commands/configure.js +480 -0
- package/dist/commands/doctor.js +484 -0
- package/dist/commands/health.js +108 -0
- package/dist/commands/models/aliases.js +64 -0
- package/dist/commands/models/fallbacks.js +99 -0
- package/dist/commands/models/image-fallbacks.js +99 -0
- package/dist/commands/models/list.js +323 -0
- package/dist/commands/models/scan.js +266 -0
- package/dist/commands/models/set-image.js +23 -0
- package/dist/commands/models/set.js +23 -0
- package/dist/commands/models/shared.js +72 -0
- package/dist/commands/models.js +7 -0
- package/dist/commands/onboard-auth.js +70 -0
- package/dist/commands/onboard-helpers.js +295 -0
- package/dist/commands/onboard-interactive.js +17 -0
- package/dist/commands/onboard-non-interactive.js +202 -0
- package/dist/commands/onboard-providers.js +634 -0
- package/dist/commands/onboard-remote.js +120 -0
- package/dist/commands/onboard-skills.js +148 -0
- package/dist/commands/onboard-types.js +1 -0
- package/dist/commands/onboard.js +12 -0
- package/dist/commands/send.js +124 -0
- package/dist/commands/sessions.js +212 -0
- package/dist/commands/setup.js +58 -0
- package/dist/commands/signal-install.js +135 -0
- package/dist/commands/status.js +207 -0
- package/dist/commands/update.js +16 -0
- package/dist/config/config.js +6 -0
- package/dist/config/defaults.js +61 -0
- package/dist/config/io.js +147 -0
- package/dist/config/legacy-migrate.js +13 -0
- package/dist/config/legacy.js +159 -0
- package/dist/config/paths.js +71 -0
- package/dist/config/schema.js +150 -0
- package/dist/config/sessions.js +282 -0
- package/dist/config/talk.js +31 -0
- package/dist/config/types.js +1 -0
- package/dist/config/validation.js +29 -0
- package/dist/config/zod-schema.js +831 -0
- package/dist/control-ui/assets/index-BFID3yAA.css +1 -0
- package/dist/control-ui/assets/index-CE_axlTS.js +2235 -0
- package/dist/control-ui/assets/index-CE_axlTS.js.map +1 -0
- package/dist/control-ui/index.html +15 -0
- package/dist/cron/isolated-agent.js +499 -0
- package/dist/cron/run-log.js +72 -0
- package/dist/cron/schedule.js +24 -0
- package/dist/cron/service.js +471 -0
- package/dist/cron/store.js +43 -0
- package/dist/cron/types.js +1 -0
- package/dist/daemon/constants.js +10 -0
- package/dist/daemon/launchd.js +276 -0
- package/dist/daemon/legacy.js +63 -0
- package/dist/daemon/program-args.js +76 -0
- package/dist/daemon/schtasks.js +257 -0
- package/dist/daemon/service.js +60 -0
- package/dist/daemon/systemd.js +266 -0
- package/dist/discord/index.js +2 -0
- package/dist/discord/monitor.js +1188 -0
- package/dist/discord/probe.js +54 -0
- package/dist/discord/send.js +577 -0
- package/dist/discord/token.js +8 -0
- package/dist/gateway/auth.js +121 -0
- package/dist/gateway/call.js +94 -0
- package/dist/gateway/chat-attachments.js +41 -0
- package/dist/gateway/client.js +180 -0
- package/dist/gateway/config-reload.js +274 -0
- package/dist/gateway/control-ui.js +184 -0
- package/dist/gateway/hooks-mapping.js +282 -0
- package/dist/gateway/hooks.js +168 -0
- package/dist/gateway/net.js +29 -0
- package/dist/gateway/protocol/index.js +61 -0
- package/dist/gateway/protocol/schema.js +560 -0
- package/dist/gateway/server-bridge-subscriptions.js +93 -0
- package/dist/gateway/server-bridge.js +1013 -0
- package/dist/gateway/server-browser.js +12 -0
- package/dist/gateway/server-chat.js +159 -0
- package/dist/gateway/server-constants.js +8 -0
- package/dist/gateway/server-discovery.js +62 -0
- package/dist/gateway/server-http.js +165 -0
- package/dist/gateway/server-methods/agent-job.js +125 -0
- package/dist/gateway/server-methods/agent.js +250 -0
- package/dist/gateway/server-methods/chat.js +200 -0
- package/dist/gateway/server-methods/config.js +50 -0
- package/dist/gateway/server-methods/connect.js +6 -0
- package/dist/gateway/server-methods/cron.js +83 -0
- package/dist/gateway/server-methods/health.js +28 -0
- package/dist/gateway/server-methods/models.js +16 -0
- package/dist/gateway/server-methods/nodes.js +294 -0
- package/dist/gateway/server-methods/providers.js +217 -0
- package/dist/gateway/server-methods/send.js +166 -0
- package/dist/gateway/server-methods/sessions.js +305 -0
- package/dist/gateway/server-methods/skills.js +83 -0
- package/dist/gateway/server-methods/system.js +118 -0
- package/dist/gateway/server-methods/talk.js +22 -0
- package/dist/gateway/server-methods/types.js +1 -0
- package/dist/gateway/server-methods/voicewake.js +30 -0
- package/dist/gateway/server-methods/web.js +58 -0
- package/dist/gateway/server-methods/wizard.js +100 -0
- package/dist/gateway/server-methods.js +53 -0
- package/dist/gateway/server-providers.js +644 -0
- package/dist/gateway/server-shared.js +1 -0
- package/dist/gateway/server-utils.js +35 -0
- package/dist/gateway/server.js +1437 -0
- package/dist/gateway/session-utils.js +216 -0
- package/dist/gateway/ws-log.js +349 -0
- package/dist/gateway/ws-logging.js +8 -0
- package/dist/globals.js +41 -0
- package/dist/hooks/gmail-ops.js +236 -0
- package/dist/hooks/gmail-setup-utils.js +278 -0
- package/dist/hooks/gmail-watcher.js +175 -0
- package/dist/hooks/gmail.js +177 -0
- package/dist/imessage/client.js +165 -0
- package/dist/imessage/index.js +3 -0
- package/dist/imessage/monitor.js +272 -0
- package/dist/imessage/probe.js +26 -0
- package/dist/imessage/send.js +83 -0
- package/dist/imessage/targets.js +176 -0
- package/dist/index.js +50 -0
- package/dist/infra/agent-events.js +46 -0
- package/dist/infra/binaries.js +9 -0
- package/dist/infra/bonjour-discovery.js +163 -0
- package/dist/infra/bonjour.js +200 -0
- package/dist/infra/bridge/server.js +562 -0
- package/dist/infra/canvas-host-url.js +54 -0
- package/dist/infra/env.js +8 -0
- package/dist/infra/errors.js +28 -0
- package/dist/infra/gateway-lock.js +8 -0
- package/dist/infra/heartbeat-events.js +21 -0
- package/dist/infra/heartbeat-runner.js +453 -0
- package/dist/infra/heartbeat-wake.js +61 -0
- package/dist/infra/is-main.js +37 -0
- package/dist/infra/machine-name.js +40 -0
- package/dist/infra/node-pairing.js +211 -0
- package/dist/infra/pam.js +42 -0
- package/dist/infra/path-env.js +92 -0
- package/dist/infra/ports.js +87 -0
- package/dist/infra/provider-summary.js +80 -0
- package/dist/infra/restart.js +29 -0
- package/dist/infra/retry.js +16 -0
- package/dist/infra/runtime-guard.js +59 -0
- package/dist/infra/system-events.js +44 -0
- package/dist/infra/system-presence.js +216 -0
- package/dist/infra/tailnet.js +46 -0
- package/dist/infra/tailscale.js +149 -0
- package/dist/infra/voicewake.js +77 -0
- package/dist/infra/widearea-dns.js +123 -0
- package/dist/infra/ws.js +13 -0
- package/dist/logger.js +52 -0
- package/dist/logging.js +490 -0
- package/dist/macos/gateway-daemon.js +141 -0
- package/dist/macos/relay.js +46 -0
- package/dist/media/constants.js +33 -0
- package/dist/media/host.js +42 -0
- package/dist/media/image-ops.js +121 -0
- package/dist/media/mime.js +115 -0
- package/dist/media/parse.js +81 -0
- package/dist/media/server.js +64 -0
- package/dist/media/store.js +139 -0
- package/dist/process/command-queue.js +97 -0
- package/dist/process/exec.js +75 -0
- package/dist/protocol.schema.json +2918 -0
- package/dist/provider-web.js +8 -0
- package/dist/providers/web/index.js +2 -0
- package/dist/runtime.js +8 -0
- package/dist/sessions/send-policy.js +68 -0
- package/dist/signal/client.js +134 -0
- package/dist/signal/daemon.js +69 -0
- package/dist/signal/index.js +3 -0
- package/dist/signal/monitor.js +336 -0
- package/dist/signal/probe.js +46 -0
- package/dist/signal/send.js +91 -0
- package/dist/slack/actions.js +97 -0
- package/dist/slack/index.js +5 -0
- package/dist/slack/monitor.js +1029 -0
- package/dist/slack/probe.js +47 -0
- package/dist/slack/send.js +131 -0
- package/dist/slack/token.js +10 -0
- package/dist/telegram/bot.js +394 -0
- package/dist/telegram/download.js +34 -0
- package/dist/telegram/index.js +4 -0
- package/dist/telegram/monitor.js +47 -0
- package/dist/telegram/probe.js +63 -0
- package/dist/telegram/proxy.js +9 -0
- package/dist/telegram/send.js +138 -0
- package/dist/telegram/token.js +30 -0
- package/dist/telegram/webhook-set.js +12 -0
- package/dist/telegram/webhook.js +56 -0
- package/dist/tui/commands.js +74 -0
- package/dist/tui/components/assistant-message.js +16 -0
- package/dist/tui/components/chat-log.js +92 -0
- package/dist/tui/components/custom-editor.js +53 -0
- package/dist/tui/components/selectors.js +8 -0
- package/dist/tui/components/tool-execution.js +111 -0
- package/dist/tui/components/user-message.js +17 -0
- package/dist/tui/gateway-chat.js +140 -0
- package/dist/tui/layout.js +41 -0
- package/dist/tui/message-list.js +57 -0
- package/dist/tui/theme/theme.js +80 -0
- package/dist/tui/theme.js +25 -0
- package/dist/tui/tui.js +708 -0
- package/dist/utils.js +133 -0
- package/dist/version.js +18 -0
- package/dist/web/active-listener.js +7 -0
- package/dist/web/auto-reply.js +1203 -0
- package/dist/web/inbound.js +481 -0
- package/dist/web/login-qr.js +204 -0
- package/dist/web/login.js +59 -0
- package/dist/web/media.js +148 -0
- package/dist/web/outbound.js +67 -0
- package/dist/web/qr-image.js +97 -0
- package/dist/web/reconnect.js +60 -0
- package/dist/web/reply-heartbeat-wake.js +61 -0
- package/dist/web/session.js +346 -0
- package/dist/wizard/clack-prompter.js +56 -0
- package/dist/wizard/onboarding.js +452 -0
- package/dist/wizard/prompts.js +6 -0
- package/dist/wizard/session.js +203 -0
- package/docs/AGENTS.default.md +116 -0
- package/docs/CNAME +1 -0
- package/docs/RELEASING.md +64 -0
- package/docs/_config.yml +51 -0
- package/docs/_layouts/default.html +145 -0
- package/docs/agent-send.md +21 -0
- package/docs/agent.md +104 -0
- package/docs/android/connect.md +131 -0
- package/docs/architecture.md +89 -0
- package/docs/assets/markdown.css +130 -0
- package/docs/assets/pixel-lobster.svg +60 -0
- package/docs/assets/terminal.css +497 -0
- package/docs/assets/theme.js +55 -0
- package/docs/audio.md +50 -0
- package/docs/background-process.md +74 -0
- package/docs/bash.md +32 -0
- package/docs/bonjour.md +159 -0
- package/docs/browser.md +289 -0
- package/docs/camera.md +152 -0
- package/docs/clawd.md +199 -0
- package/docs/clawdbot-mac.md +104 -0
- package/docs/configuration.md +1177 -0
- package/docs/control-api.md +49 -0
- package/docs/control-ui.md +83 -0
- package/docs/cron.md +374 -0
- package/docs/dashboard.md +17 -0
- package/docs/device-models.md +46 -0
- package/docs/discord.md +293 -0
- package/docs/discovery.md +112 -0
- package/docs/docker.md +251 -0
- package/docs/docs.json +86 -0
- package/docs/doctor.md +47 -0
- package/docs/elevated.md +31 -0
- package/docs/faq.md +640 -0
- package/docs/gateway/pairing.md +109 -0
- package/docs/gateway-lock.md +28 -0
- package/docs/gateway.md +174 -0
- package/docs/gmail-pubsub.md +191 -0
- package/docs/grammy.md +27 -0
- package/docs/group-messages.md +71 -0
- package/docs/groups.md +78 -0
- package/docs/health.md +28 -0
- package/docs/heartbeat.md +64 -0
- package/docs/images.md +52 -0
- package/docs/imessage.md +63 -0
- package/docs/index.md +182 -0
- package/docs/ios/connect.md +177 -0
- package/docs/ios/spec.md +236 -0
- package/docs/location-command.md +95 -0
- package/docs/logging.md +99 -0
- package/docs/lore.md +131 -0
- package/docs/mac/bun.md +133 -0
- package/docs/mac/canvas.md +161 -0
- package/docs/mac/child-process.md +72 -0
- package/docs/mac/dev-setup.md +81 -0
- package/docs/mac/health.md +28 -0
- package/docs/mac/icon.md +26 -0
- package/docs/mac/logging.md +51 -0
- package/docs/mac/menu-bar.md +69 -0
- package/docs/mac/peekaboo.md +170 -0
- package/docs/mac/permissions.md +40 -0
- package/docs/mac/release.md +76 -0
- package/docs/mac/remote.md +57 -0
- package/docs/mac/signing.md +41 -0
- package/docs/mac/skills.md +27 -0
- package/docs/mac/voice-overlay.md +52 -0
- package/docs/mac/voicewake.md +56 -0
- package/docs/mac/webchat.md +27 -0
- package/docs/mac/xpc.md +40 -0
- package/docs/models.md +90 -0
- package/docs/nix.md +49 -0
- package/docs/nodes.md +157 -0
- package/docs/onboarding-config-protocol.md +29 -0
- package/docs/onboarding.md +185 -0
- package/docs/presence.md +133 -0
- package/docs/queue.md +78 -0
- package/docs/refactor/browser-control-simplification.md +58 -0
- package/docs/refactor/canvas-a2ui.md +93 -0
- package/docs/refactor/cli-unification.md +64 -0
- package/docs/refactor/gateway-client.md +31 -0
- package/docs/refactor/gateway.md +99 -0
- package/docs/refactor/new-arch.md +171 -0
- package/docs/refactor/tui.md +26 -0
- package/docs/refactor/web-gateway-troubleshooting.md +37 -0
- package/docs/refactor/webagent-session.md +46 -0
- package/docs/remote-gateway-readme.md +148 -0
- package/docs/remote.md +66 -0
- package/docs/research/memory.md +227 -0
- package/docs/rpc.md +35 -0
- package/docs/security.md +168 -0
- package/docs/session-tool.md +119 -0
- package/docs/session.md +84 -0
- package/docs/sessions.md +8 -0
- package/docs/setup.md +118 -0
- package/docs/signal.md +113 -0
- package/docs/skills-config.md +58 -0
- package/docs/skills.md +149 -0
- package/docs/slack.md +158 -0
- package/docs/surface.md +20 -0
- package/docs/tailscale.md +71 -0
- package/docs/talk.md +79 -0
- package/docs/telegram.md +90 -0
- package/docs/templates/AGENTS.md +126 -0
- package/docs/templates/BOOTSTRAP.md +53 -0
- package/docs/templates/IDENTITY.md +17 -0
- package/docs/templates/SOUL.md +41 -0
- package/docs/templates/TOOLS.md +41 -0
- package/docs/templates/USER.md +22 -0
- package/docs/test.md +35 -0
- package/docs/thinking.md +46 -0
- package/docs/tools.md +248 -0
- package/docs/troubleshooting.md +227 -0
- package/docs/tui.md +69 -0
- package/docs/typebox.md +42 -0
- package/docs/voicewake.md +61 -0
- package/docs/web.md +115 -0
- package/docs/webchat.md +34 -0
- package/docs/webhook.md +132 -0
- package/docs/whatsapp-clawd.jpg +0 -0
- package/docs/whatsapp.md +142 -0
- package/docs/wizard.md +158 -0
- package/package.json +186 -0
- package/skills/apple-notes/SKILL.md +50 -0
- package/skills/apple-reminders/SKILL.md +67 -0
- package/skills/bear-notes/SKILL.md +79 -0
- package/skills/bird/SKILL.md +25 -0
- package/skills/blogwatcher/SKILL.md +46 -0
- package/skills/blucli/SKILL.md +27 -0
- package/skills/brave-search/SKILL.md +30 -0
- package/skills/brave-search/scripts/content.mjs +53 -0
- package/skills/brave-search/scripts/search.mjs +79 -0
- package/skills/camsnap/SKILL.md +25 -0
- package/skills/clawdhub/SKILL.md +53 -0
- package/skills/coding-agent/SKILL.md +275 -0
- package/skills/discord/SKILL.md +369 -0
- package/skills/eightctl/SKILL.md +29 -0
- package/skills/food-order/SKILL.md +41 -0
- package/skills/gemini/SKILL.md +23 -0
- package/skills/gifgrep/SKILL.md +47 -0
- package/skills/github/SKILL.md +47 -0
- package/skills/gog/SKILL.md +36 -0
- package/skills/goplaces/SKILL.md +30 -0
- package/skills/imsg/SKILL.md +25 -0
- package/skills/local-places/SERVER_README.md +101 -0
- package/skills/local-places/SKILL.md +91 -0
- package/skills/local-places/pyproject.toml +27 -0
- package/skills/local-places/src/local_places/__init__.py +2 -0
- package/skills/local-places/src/local_places/__pycache__/__init__.cpython-314.pyc +0 -0
- package/skills/local-places/src/local_places/__pycache__/google_places.cpython-314.pyc +0 -0
- package/skills/local-places/src/local_places/__pycache__/main.cpython-314.pyc +0 -0
- package/skills/local-places/src/local_places/__pycache__/schemas.cpython-314.pyc +0 -0
- package/skills/local-places/src/local_places/google_places.py +314 -0
- package/skills/local-places/src/local_places/main.py +65 -0
- package/skills/local-places/src/local_places/schemas.py +107 -0
- package/skills/mcporter/SKILL.md +38 -0
- package/skills/nano-banana-pro/SKILL.md +29 -0
- package/skills/nano-banana-pro/scripts/generate_image.py +167 -0
- package/skills/nano-pdf/SKILL.md +20 -0
- package/skills/notion/SKILL.md +156 -0
- package/skills/obsidian/SKILL.md +55 -0
- package/skills/openai-image-gen/SKILL.md +31 -0
- package/skills/openai-image-gen/scripts/gen.py +173 -0
- package/skills/openai-whisper/SKILL.md +19 -0
- package/skills/openai-whisper-api/SKILL.md +43 -0
- package/skills/openai-whisper-api/scripts/transcribe.sh +85 -0
- package/skills/openhue/SKILL.md +30 -0
- package/skills/oracle/SKILL.md +105 -0
- package/skills/ordercli/SKILL.md +47 -0
- package/skills/peekaboo/SKILL.md +153 -0
- package/skills/qmd/SKILL.md +26 -0
- package/skills/sag/SKILL.md +62 -0
- package/skills/slack/SKILL.md +143 -0
- package/skills/songsee/SKILL.md +29 -0
- package/skills/sonoscli/SKILL.md +26 -0
- package/skills/spotify-player/SKILL.md +34 -0
- package/skills/summarize/SKILL.md +49 -0
- package/skills/things-mac/SKILL.md +61 -0
- package/skills/tmux/SKILL.md +121 -0
- package/skills/tmux/scripts/find-sessions.sh +112 -0
- package/skills/tmux/scripts/wait-for-text.sh +83 -0
- package/skills/trello/SKILL.md +84 -0
- package/skills/video-frames/SKILL.md +29 -0
- package/skills/video-frames/scripts/frame.sh +81 -0
- package/skills/wacli/SKILL.md +42 -0
- package/skills/weather/SKILL.md +49 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { WebClient } from "@slack/web-api";
|
|
2
|
+
function withTimeout(promise, timeoutMs) {
|
|
3
|
+
if (!timeoutMs || timeoutMs <= 0)
|
|
4
|
+
return promise;
|
|
5
|
+
let timer = null;
|
|
6
|
+
const timeout = new Promise((_, reject) => {
|
|
7
|
+
timer = setTimeout(() => reject(new Error("timeout")), timeoutMs);
|
|
8
|
+
});
|
|
9
|
+
return Promise.race([promise, timeout]).finally(() => {
|
|
10
|
+
if (timer)
|
|
11
|
+
clearTimeout(timer);
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
export async function probeSlack(token, timeoutMs = 2500) {
|
|
15
|
+
const client = new WebClient(token);
|
|
16
|
+
const start = Date.now();
|
|
17
|
+
try {
|
|
18
|
+
const result = await withTimeout(client.auth.test(), timeoutMs);
|
|
19
|
+
if (!result.ok) {
|
|
20
|
+
return {
|
|
21
|
+
ok: false,
|
|
22
|
+
status: 200,
|
|
23
|
+
error: result.error ?? "unknown",
|
|
24
|
+
elapsedMs: Date.now() - start,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
ok: true,
|
|
29
|
+
status: 200,
|
|
30
|
+
elapsedMs: Date.now() - start,
|
|
31
|
+
bot: { id: result.user_id ?? undefined, name: result.user ?? undefined },
|
|
32
|
+
team: { id: result.team_id ?? undefined, name: result.team ?? undefined },
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
37
|
+
const status = typeof err.status === "number"
|
|
38
|
+
? err.status
|
|
39
|
+
: null;
|
|
40
|
+
return {
|
|
41
|
+
ok: false,
|
|
42
|
+
status,
|
|
43
|
+
error: message,
|
|
44
|
+
elapsedMs: Date.now() - start,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { WebClient } from "@slack/web-api";
|
|
2
|
+
import { chunkText, resolveTextChunkLimit } from "../auto-reply/chunk.js";
|
|
3
|
+
import { loadConfig } from "../config/config.js";
|
|
4
|
+
import { loadWebMedia } from "../web/media.js";
|
|
5
|
+
import { resolveSlackBotToken } from "./token.js";
|
|
6
|
+
const SLACK_TEXT_LIMIT = 4000;
|
|
7
|
+
function resolveToken(explicit) {
|
|
8
|
+
const cfgToken = loadConfig().slack?.botToken;
|
|
9
|
+
const token = resolveSlackBotToken(explicit ?? process.env.SLACK_BOT_TOKEN ?? cfgToken ?? undefined);
|
|
10
|
+
if (!token) {
|
|
11
|
+
throw new Error("SLACK_BOT_TOKEN or slack.botToken is required for Slack sends");
|
|
12
|
+
}
|
|
13
|
+
return token;
|
|
14
|
+
}
|
|
15
|
+
function parseRecipient(raw) {
|
|
16
|
+
const trimmed = raw.trim();
|
|
17
|
+
if (!trimmed) {
|
|
18
|
+
throw new Error("Recipient is required for Slack sends");
|
|
19
|
+
}
|
|
20
|
+
const mentionMatch = trimmed.match(/^<@([A-Z0-9]+)>$/i);
|
|
21
|
+
if (mentionMatch) {
|
|
22
|
+
return { kind: "user", id: mentionMatch[1] };
|
|
23
|
+
}
|
|
24
|
+
if (trimmed.startsWith("user:")) {
|
|
25
|
+
return { kind: "user", id: trimmed.slice("user:".length) };
|
|
26
|
+
}
|
|
27
|
+
if (trimmed.startsWith("channel:")) {
|
|
28
|
+
return { kind: "channel", id: trimmed.slice("channel:".length) };
|
|
29
|
+
}
|
|
30
|
+
if (trimmed.startsWith("slack:")) {
|
|
31
|
+
return { kind: "user", id: trimmed.slice("slack:".length) };
|
|
32
|
+
}
|
|
33
|
+
if (trimmed.startsWith("@")) {
|
|
34
|
+
const candidate = trimmed.slice(1);
|
|
35
|
+
if (!/^[A-Z0-9]+$/i.test(candidate)) {
|
|
36
|
+
throw new Error("Slack DMs require a user id (use user:<id> or <@id>)");
|
|
37
|
+
}
|
|
38
|
+
return { kind: "user", id: candidate };
|
|
39
|
+
}
|
|
40
|
+
if (trimmed.startsWith("#")) {
|
|
41
|
+
const candidate = trimmed.slice(1);
|
|
42
|
+
if (!/^[A-Z0-9]+$/i.test(candidate)) {
|
|
43
|
+
throw new Error("Slack channels require a channel id (use channel:<id>)");
|
|
44
|
+
}
|
|
45
|
+
return { kind: "channel", id: candidate };
|
|
46
|
+
}
|
|
47
|
+
return { kind: "channel", id: trimmed };
|
|
48
|
+
}
|
|
49
|
+
async function resolveChannelId(client, recipient) {
|
|
50
|
+
if (recipient.kind === "channel") {
|
|
51
|
+
return { channelId: recipient.id };
|
|
52
|
+
}
|
|
53
|
+
const response = await client.conversations.open({ users: recipient.id });
|
|
54
|
+
const channelId = response.channel?.id;
|
|
55
|
+
if (!channelId) {
|
|
56
|
+
throw new Error("Failed to open Slack DM channel");
|
|
57
|
+
}
|
|
58
|
+
return { channelId, isDm: true };
|
|
59
|
+
}
|
|
60
|
+
async function uploadSlackFile(params) {
|
|
61
|
+
const { buffer, contentType, fileName } = await loadWebMedia(params.mediaUrl, params.maxBytes);
|
|
62
|
+
const basePayload = {
|
|
63
|
+
channel_id: params.channelId,
|
|
64
|
+
file: buffer,
|
|
65
|
+
filename: fileName,
|
|
66
|
+
...(params.caption ? { initial_comment: params.caption } : {}),
|
|
67
|
+
...(contentType ? { filetype: contentType } : {}),
|
|
68
|
+
};
|
|
69
|
+
const payload = params.threadTs
|
|
70
|
+
? { ...basePayload, thread_ts: params.threadTs }
|
|
71
|
+
: basePayload;
|
|
72
|
+
const response = await params.client.files.uploadV2(payload);
|
|
73
|
+
const parsed = response;
|
|
74
|
+
const fileId = parsed.files?.[0]?.id ??
|
|
75
|
+
parsed.file?.id ??
|
|
76
|
+
parsed.files?.[0]?.name ??
|
|
77
|
+
parsed.file?.name ??
|
|
78
|
+
"unknown";
|
|
79
|
+
return fileId;
|
|
80
|
+
}
|
|
81
|
+
export async function sendMessageSlack(to, message, opts = {}) {
|
|
82
|
+
const trimmedMessage = message?.trim() ?? "";
|
|
83
|
+
if (!trimmedMessage && !opts.mediaUrl) {
|
|
84
|
+
throw new Error("Slack send requires text or media");
|
|
85
|
+
}
|
|
86
|
+
const token = resolveToken(opts.token);
|
|
87
|
+
const client = opts.client ?? new WebClient(token);
|
|
88
|
+
const recipient = parseRecipient(to);
|
|
89
|
+
const { channelId } = await resolveChannelId(client, recipient);
|
|
90
|
+
const cfg = loadConfig();
|
|
91
|
+
const textLimit = resolveTextChunkLimit(cfg, "slack");
|
|
92
|
+
const chunkLimit = Math.min(textLimit, SLACK_TEXT_LIMIT);
|
|
93
|
+
const chunks = chunkText(trimmedMessage, chunkLimit);
|
|
94
|
+
const mediaMaxBytes = typeof cfg.slack?.mediaMaxMb === "number"
|
|
95
|
+
? cfg.slack.mediaMaxMb * 1024 * 1024
|
|
96
|
+
: undefined;
|
|
97
|
+
let lastMessageId = "";
|
|
98
|
+
if (opts.mediaUrl) {
|
|
99
|
+
const [firstChunk, ...rest] = chunks;
|
|
100
|
+
lastMessageId = await uploadSlackFile({
|
|
101
|
+
client,
|
|
102
|
+
channelId,
|
|
103
|
+
mediaUrl: opts.mediaUrl,
|
|
104
|
+
caption: firstChunk,
|
|
105
|
+
threadTs: opts.threadTs,
|
|
106
|
+
maxBytes: mediaMaxBytes,
|
|
107
|
+
});
|
|
108
|
+
for (const chunk of rest) {
|
|
109
|
+
const response = await client.chat.postMessage({
|
|
110
|
+
channel: channelId,
|
|
111
|
+
text: chunk,
|
|
112
|
+
thread_ts: opts.threadTs,
|
|
113
|
+
});
|
|
114
|
+
lastMessageId = response.ts ?? lastMessageId;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
for (const chunk of chunks.length ? chunks : [""]) {
|
|
119
|
+
const response = await client.chat.postMessage({
|
|
120
|
+
channel: channelId,
|
|
121
|
+
text: chunk,
|
|
122
|
+
thread_ts: opts.threadTs,
|
|
123
|
+
});
|
|
124
|
+
lastMessageId = response.ts ?? lastMessageId;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
messageId: lastMessageId || "unknown",
|
|
129
|
+
channelId,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export function normalizeSlackToken(raw) {
|
|
2
|
+
const trimmed = raw?.trim();
|
|
3
|
+
return trimmed ? trimmed : undefined;
|
|
4
|
+
}
|
|
5
|
+
export function resolveSlackBotToken(raw) {
|
|
6
|
+
return normalizeSlackToken(raw);
|
|
7
|
+
}
|
|
8
|
+
export function resolveSlackAppToken(raw) {
|
|
9
|
+
return normalizeSlackToken(raw);
|
|
10
|
+
}
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
// @ts-nocheck
|
|
2
|
+
import { Buffer } from "node:buffer";
|
|
3
|
+
import { apiThrottler } from "@grammyjs/transformer-throttler";
|
|
4
|
+
import { Bot, InputFile, webhookCallback } from "grammy";
|
|
5
|
+
import { chunkText, resolveTextChunkLimit } from "../auto-reply/chunk.js";
|
|
6
|
+
import { formatAgentEnvelope } from "../auto-reply/envelope.js";
|
|
7
|
+
import { getReplyFromConfig } from "../auto-reply/reply.js";
|
|
8
|
+
import { loadConfig } from "../config/config.js";
|
|
9
|
+
import { resolveStorePath, updateLastRoute } from "../config/sessions.js";
|
|
10
|
+
import { danger, logVerbose, shouldLogVerbose } from "../globals.js";
|
|
11
|
+
import { formatErrorMessage } from "../infra/errors.js";
|
|
12
|
+
import { getChildLogger } from "../logging.js";
|
|
13
|
+
import { mediaKindFromMime } from "../media/constants.js";
|
|
14
|
+
import { detectMime } from "../media/mime.js";
|
|
15
|
+
import { saveMediaBuffer } from "../media/store.js";
|
|
16
|
+
import { loadWebMedia } from "../web/media.js";
|
|
17
|
+
const PARSE_ERR_RE = /can't parse entities|parse entities|find end of the entity/i;
|
|
18
|
+
export function createTelegramBot(opts) {
|
|
19
|
+
const runtime = opts.runtime ?? {
|
|
20
|
+
log: console.log,
|
|
21
|
+
error: console.error,
|
|
22
|
+
exit: (code) => {
|
|
23
|
+
throw new Error(`exit ${code}`);
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
const client = opts.proxyFetch
|
|
27
|
+
? { fetch: opts.proxyFetch }
|
|
28
|
+
: undefined;
|
|
29
|
+
const bot = new Bot(opts.token, { client });
|
|
30
|
+
bot.api.config.use(apiThrottler());
|
|
31
|
+
const cfg = loadConfig();
|
|
32
|
+
const textLimit = resolveTextChunkLimit(cfg, "telegram");
|
|
33
|
+
const allowFrom = opts.allowFrom ?? cfg.telegram?.allowFrom;
|
|
34
|
+
const replyToMode = opts.replyToMode ?? cfg.telegram?.replyToMode ?? "off";
|
|
35
|
+
const mediaMaxBytes = (opts.mediaMaxMb ?? cfg.telegram?.mediaMaxMb ?? 5) * 1024 * 1024;
|
|
36
|
+
const logger = getChildLogger({ module: "telegram-auto-reply" });
|
|
37
|
+
const resolveGroupRequireMention = (chatId) => {
|
|
38
|
+
const groupId = String(chatId);
|
|
39
|
+
const groupConfig = cfg.telegram?.groups?.[groupId];
|
|
40
|
+
if (typeof groupConfig?.requireMention === "boolean") {
|
|
41
|
+
return groupConfig.requireMention;
|
|
42
|
+
}
|
|
43
|
+
const groupDefault = cfg.telegram?.groups?.["*"]?.requireMention;
|
|
44
|
+
if (typeof groupDefault === "boolean")
|
|
45
|
+
return groupDefault;
|
|
46
|
+
if (typeof opts.requireMention === "boolean")
|
|
47
|
+
return opts.requireMention;
|
|
48
|
+
return true;
|
|
49
|
+
};
|
|
50
|
+
bot.on("message", async (ctx) => {
|
|
51
|
+
try {
|
|
52
|
+
const msg = ctx.message;
|
|
53
|
+
if (!msg)
|
|
54
|
+
return;
|
|
55
|
+
const chatId = msg.chat.id;
|
|
56
|
+
const isGroup = msg.chat.type === "group" || msg.chat.type === "supergroup";
|
|
57
|
+
const sendTyping = async () => {
|
|
58
|
+
try {
|
|
59
|
+
await bot.api.sendChatAction(chatId, "typing");
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
logVerbose(`telegram typing cue failed for chat ${chatId}: ${String(err)}`);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
// allowFrom for direct chats
|
|
66
|
+
if (!isGroup && Array.isArray(allowFrom) && allowFrom.length > 0) {
|
|
67
|
+
const candidate = String(chatId);
|
|
68
|
+
const allowed = allowFrom.map(String);
|
|
69
|
+
const allowedWithPrefix = allowFrom.map((v) => `telegram:${String(v)}`);
|
|
70
|
+
const permitted = allowed.includes(candidate) ||
|
|
71
|
+
allowedWithPrefix.includes(`telegram:${candidate}`) ||
|
|
72
|
+
allowed.includes("*");
|
|
73
|
+
if (!permitted) {
|
|
74
|
+
logVerbose(`Blocked unauthorized telegram sender ${candidate} (not in allowFrom)`);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
const botUsername = ctx.me?.username?.toLowerCase();
|
|
79
|
+
const wasMentioned = Boolean(botUsername) && hasBotMention(msg, botUsername);
|
|
80
|
+
if (isGroup && resolveGroupRequireMention(chatId) && botUsername) {
|
|
81
|
+
if (!wasMentioned) {
|
|
82
|
+
logger.info({ chatId, reason: "no-mention" }, "skipping group message");
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const media = await resolveMedia(ctx, mediaMaxBytes, opts.token, opts.proxyFetch);
|
|
87
|
+
const replyTarget = describeReplyTarget(msg);
|
|
88
|
+
const rawBody = (msg.text ??
|
|
89
|
+
msg.caption ??
|
|
90
|
+
media?.placeholder ??
|
|
91
|
+
"").trim();
|
|
92
|
+
if (!rawBody)
|
|
93
|
+
return;
|
|
94
|
+
const replySuffix = replyTarget
|
|
95
|
+
? `\n\n[Replying to ${replyTarget.sender}${replyTarget.id ? ` id:${replyTarget.id}` : ""}]\n${replyTarget.body}\n[/Replying]`
|
|
96
|
+
: "";
|
|
97
|
+
const body = formatAgentEnvelope({
|
|
98
|
+
surface: "Telegram",
|
|
99
|
+
from: isGroup
|
|
100
|
+
? buildGroupLabel(msg, chatId)
|
|
101
|
+
: buildSenderLabel(msg, chatId),
|
|
102
|
+
timestamp: msg.date ? msg.date * 1000 : undefined,
|
|
103
|
+
body: `${rawBody}${replySuffix}`,
|
|
104
|
+
});
|
|
105
|
+
const ctxPayload = {
|
|
106
|
+
Body: body,
|
|
107
|
+
From: isGroup ? `group:${chatId}` : `telegram:${chatId}`,
|
|
108
|
+
To: `telegram:${chatId}`,
|
|
109
|
+
ChatType: isGroup ? "group" : "direct",
|
|
110
|
+
GroupSubject: isGroup ? (msg.chat.title ?? undefined) : undefined,
|
|
111
|
+
SenderName: buildSenderName(msg),
|
|
112
|
+
Surface: "telegram",
|
|
113
|
+
MessageSid: String(msg.message_id),
|
|
114
|
+
ReplyToId: replyTarget?.id,
|
|
115
|
+
ReplyToBody: replyTarget?.body,
|
|
116
|
+
ReplyToSender: replyTarget?.sender,
|
|
117
|
+
Timestamp: msg.date ? msg.date * 1000 : undefined,
|
|
118
|
+
WasMentioned: isGroup && botUsername ? wasMentioned : undefined,
|
|
119
|
+
MediaPath: media?.path,
|
|
120
|
+
MediaType: media?.contentType,
|
|
121
|
+
MediaUrl: media?.path,
|
|
122
|
+
};
|
|
123
|
+
if (replyTarget && shouldLogVerbose()) {
|
|
124
|
+
const preview = replyTarget.body.replace(/\s+/g, " ").slice(0, 120);
|
|
125
|
+
logVerbose(`telegram reply-context: replyToId=${replyTarget.id} replyToSender=${replyTarget.sender} replyToBody="${preview}"`);
|
|
126
|
+
}
|
|
127
|
+
if (!isGroup) {
|
|
128
|
+
const sessionCfg = cfg.session;
|
|
129
|
+
const mainKey = (sessionCfg?.mainKey ?? "main").trim() || "main";
|
|
130
|
+
const storePath = resolveStorePath(sessionCfg?.store);
|
|
131
|
+
await updateLastRoute({
|
|
132
|
+
storePath,
|
|
133
|
+
sessionKey: mainKey,
|
|
134
|
+
channel: "telegram",
|
|
135
|
+
to: String(chatId),
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (shouldLogVerbose()) {
|
|
139
|
+
const preview = body.slice(0, 200).replace(/\n/g, "\\n");
|
|
140
|
+
logVerbose(`telegram inbound: chatId=${chatId} from=${ctxPayload.From} len=${body.length} preview="${preview}"`);
|
|
141
|
+
}
|
|
142
|
+
let blockSendChain = Promise.resolve();
|
|
143
|
+
const sendBlockReply = (payload) => {
|
|
144
|
+
if (!payload?.text &&
|
|
145
|
+
!payload?.mediaUrl &&
|
|
146
|
+
!(payload?.mediaUrls?.length ?? 0)) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
blockSendChain = blockSendChain
|
|
150
|
+
.then(async () => {
|
|
151
|
+
await deliverReplies({
|
|
152
|
+
replies: [payload],
|
|
153
|
+
chatId: String(chatId),
|
|
154
|
+
token: opts.token,
|
|
155
|
+
runtime,
|
|
156
|
+
bot,
|
|
157
|
+
replyToMode,
|
|
158
|
+
textLimit,
|
|
159
|
+
});
|
|
160
|
+
})
|
|
161
|
+
.catch((err) => {
|
|
162
|
+
runtime.error?.(danger(`telegram block reply failed: ${String(err)}`));
|
|
163
|
+
});
|
|
164
|
+
};
|
|
165
|
+
const replyResult = await getReplyFromConfig(ctxPayload, { onReplyStart: sendTyping, onBlockReply: sendBlockReply }, cfg);
|
|
166
|
+
const replies = replyResult
|
|
167
|
+
? Array.isArray(replyResult)
|
|
168
|
+
? replyResult
|
|
169
|
+
: [replyResult]
|
|
170
|
+
: [];
|
|
171
|
+
await blockSendChain;
|
|
172
|
+
if (replies.length === 0)
|
|
173
|
+
return;
|
|
174
|
+
await deliverReplies({
|
|
175
|
+
replies,
|
|
176
|
+
chatId: String(chatId),
|
|
177
|
+
token: opts.token,
|
|
178
|
+
runtime,
|
|
179
|
+
bot,
|
|
180
|
+
replyToMode,
|
|
181
|
+
textLimit,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
catch (err) {
|
|
185
|
+
runtime.error?.(danger(`handler failed: ${String(err)}`));
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
return bot;
|
|
189
|
+
}
|
|
190
|
+
export function createTelegramWebhookCallback(bot, path = "/telegram-webhook") {
|
|
191
|
+
return { path, handler: webhookCallback(bot, "http") };
|
|
192
|
+
}
|
|
193
|
+
async function deliverReplies(params) {
|
|
194
|
+
const { replies, chatId, runtime, bot, replyToMode, textLimit } = params;
|
|
195
|
+
let hasReplied = false;
|
|
196
|
+
for (const reply of replies) {
|
|
197
|
+
if (!reply?.text && !reply?.mediaUrl && !(reply?.mediaUrls?.length ?? 0)) {
|
|
198
|
+
runtime.error?.(danger("reply missing text/media"));
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
const replyToId = replyToMode === "off"
|
|
202
|
+
? undefined
|
|
203
|
+
: resolveTelegramReplyId(reply.replyToId);
|
|
204
|
+
const mediaList = reply.mediaUrls?.length
|
|
205
|
+
? reply.mediaUrls
|
|
206
|
+
: reply.mediaUrl
|
|
207
|
+
? [reply.mediaUrl]
|
|
208
|
+
: [];
|
|
209
|
+
if (mediaList.length === 0) {
|
|
210
|
+
for (const chunk of chunkText(reply.text || "", textLimit)) {
|
|
211
|
+
await sendTelegramText(bot, chatId, chunk, runtime, {
|
|
212
|
+
replyToMessageId: replyToId && (replyToMode === "all" || !hasReplied)
|
|
213
|
+
? replyToId
|
|
214
|
+
: undefined,
|
|
215
|
+
});
|
|
216
|
+
if (replyToId && !hasReplied) {
|
|
217
|
+
hasReplied = true;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
continue;
|
|
221
|
+
}
|
|
222
|
+
// media with optional caption on first item
|
|
223
|
+
let first = true;
|
|
224
|
+
for (const mediaUrl of mediaList) {
|
|
225
|
+
const media = await loadWebMedia(mediaUrl);
|
|
226
|
+
const kind = mediaKindFromMime(media.contentType ?? undefined);
|
|
227
|
+
const file = new InputFile(media.buffer, media.fileName ?? "file");
|
|
228
|
+
const caption = first ? (reply.text ?? undefined) : undefined;
|
|
229
|
+
first = false;
|
|
230
|
+
const replyToMessageId = replyToId && (replyToMode === "all" || !hasReplied)
|
|
231
|
+
? replyToId
|
|
232
|
+
: undefined;
|
|
233
|
+
if (kind === "image") {
|
|
234
|
+
await bot.api.sendPhoto(chatId, file, {
|
|
235
|
+
caption,
|
|
236
|
+
reply_to_message_id: replyToMessageId,
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
else if (kind === "video") {
|
|
240
|
+
await bot.api.sendVideo(chatId, file, {
|
|
241
|
+
caption,
|
|
242
|
+
reply_to_message_id: replyToMessageId,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
else if (kind === "audio") {
|
|
246
|
+
await bot.api.sendAudio(chatId, file, {
|
|
247
|
+
caption,
|
|
248
|
+
reply_to_message_id: replyToMessageId,
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
await bot.api.sendDocument(chatId, file, {
|
|
253
|
+
caption,
|
|
254
|
+
reply_to_message_id: replyToMessageId,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
if (replyToId && !hasReplied) {
|
|
258
|
+
hasReplied = true;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
function buildSenderName(msg) {
|
|
264
|
+
const name = [msg.from?.first_name, msg.from?.last_name]
|
|
265
|
+
.filter(Boolean)
|
|
266
|
+
.join(" ")
|
|
267
|
+
.trim() || msg.from?.username;
|
|
268
|
+
return name || undefined;
|
|
269
|
+
}
|
|
270
|
+
function buildSenderLabel(msg, chatId) {
|
|
271
|
+
const name = buildSenderName(msg);
|
|
272
|
+
const username = msg.from?.username ? `@${msg.from.username}` : undefined;
|
|
273
|
+
let label = name;
|
|
274
|
+
if (name && username) {
|
|
275
|
+
label = `${name} (${username})`;
|
|
276
|
+
}
|
|
277
|
+
else if (!name && username) {
|
|
278
|
+
label = username;
|
|
279
|
+
}
|
|
280
|
+
const idPart = `id:${chatId}`;
|
|
281
|
+
return label ? `${label} ${idPart}` : idPart;
|
|
282
|
+
}
|
|
283
|
+
function buildGroupLabel(msg, chatId) {
|
|
284
|
+
const title = msg.chat?.title;
|
|
285
|
+
if (title)
|
|
286
|
+
return `${title} id:${chatId}`;
|
|
287
|
+
return `group:${chatId}`;
|
|
288
|
+
}
|
|
289
|
+
function hasBotMention(msg, botUsername) {
|
|
290
|
+
const text = (msg.text ?? msg.caption ?? "").toLowerCase();
|
|
291
|
+
if (text.includes(`@${botUsername}`))
|
|
292
|
+
return true;
|
|
293
|
+
const entities = msg.entities ?? msg.caption_entities ?? [];
|
|
294
|
+
for (const ent of entities) {
|
|
295
|
+
if (ent.type !== "mention")
|
|
296
|
+
continue;
|
|
297
|
+
const slice = (msg.text ?? msg.caption ?? "").slice(ent.offset, ent.offset + ent.length);
|
|
298
|
+
if (slice.toLowerCase() === `@${botUsername}`)
|
|
299
|
+
return true;
|
|
300
|
+
}
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
function resolveTelegramReplyId(raw) {
|
|
304
|
+
if (!raw)
|
|
305
|
+
return undefined;
|
|
306
|
+
const parsed = Number(raw);
|
|
307
|
+
if (!Number.isFinite(parsed))
|
|
308
|
+
return undefined;
|
|
309
|
+
return parsed;
|
|
310
|
+
}
|
|
311
|
+
async function resolveMedia(ctx, maxBytes, token, proxyFetch) {
|
|
312
|
+
const msg = ctx.message;
|
|
313
|
+
const m = msg.photo?.[msg.photo.length - 1] ??
|
|
314
|
+
msg.video ??
|
|
315
|
+
msg.document ??
|
|
316
|
+
msg.audio ??
|
|
317
|
+
msg.voice;
|
|
318
|
+
if (!m?.file_id)
|
|
319
|
+
return null;
|
|
320
|
+
const file = await ctx.getFile();
|
|
321
|
+
if (!file.file_path) {
|
|
322
|
+
throw new Error("Telegram getFile returned no file_path");
|
|
323
|
+
}
|
|
324
|
+
const fetchImpl = proxyFetch ?? globalThis.fetch;
|
|
325
|
+
if (!fetchImpl) {
|
|
326
|
+
throw new Error("fetch is not available; set telegram.proxy in config");
|
|
327
|
+
}
|
|
328
|
+
const url = `https://api.telegram.org/file/bot${token}/${file.file_path}`;
|
|
329
|
+
const res = await fetchImpl(url);
|
|
330
|
+
if (!res.ok) {
|
|
331
|
+
throw new Error(`Failed to download telegram file: HTTP ${res.status} ${res.statusText}`);
|
|
332
|
+
}
|
|
333
|
+
const data = Buffer.from(await res.arrayBuffer());
|
|
334
|
+
const mime = await detectMime({
|
|
335
|
+
buffer: data,
|
|
336
|
+
headerMime: res.headers.get("content-type"),
|
|
337
|
+
filePath: file.file_path,
|
|
338
|
+
});
|
|
339
|
+
const saved = await saveMediaBuffer(data, mime, "inbound", maxBytes);
|
|
340
|
+
let placeholder = "<media:document>";
|
|
341
|
+
if (msg.photo)
|
|
342
|
+
placeholder = "<media:image>";
|
|
343
|
+
else if (msg.video)
|
|
344
|
+
placeholder = "<media:video>";
|
|
345
|
+
else if (msg.audio || msg.voice)
|
|
346
|
+
placeholder = "<media:audio>";
|
|
347
|
+
return { path: saved.path, contentType: saved.contentType, placeholder };
|
|
348
|
+
}
|
|
349
|
+
async function sendTelegramText(bot, chatId, text, runtime, opts) {
|
|
350
|
+
try {
|
|
351
|
+
const res = await bot.api.sendMessage(chatId, text, {
|
|
352
|
+
parse_mode: "Markdown",
|
|
353
|
+
reply_to_message_id: opts?.replyToMessageId,
|
|
354
|
+
});
|
|
355
|
+
return res.message_id;
|
|
356
|
+
}
|
|
357
|
+
catch (err) {
|
|
358
|
+
const errText = formatErrorMessage(err);
|
|
359
|
+
if (PARSE_ERR_RE.test(errText)) {
|
|
360
|
+
runtime.log?.(`telegram markdown parse failed; retrying without formatting: ${errText}`);
|
|
361
|
+
const res = await bot.api.sendMessage(chatId, text, {
|
|
362
|
+
reply_to_message_id: opts?.replyToMessageId,
|
|
363
|
+
});
|
|
364
|
+
return res.message_id;
|
|
365
|
+
}
|
|
366
|
+
throw err;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
function describeReplyTarget(msg) {
|
|
370
|
+
const reply = msg.reply_to_message;
|
|
371
|
+
if (!reply)
|
|
372
|
+
return null;
|
|
373
|
+
const replyBody = (reply.text ?? reply.caption ?? "").trim();
|
|
374
|
+
let body = replyBody;
|
|
375
|
+
if (!body) {
|
|
376
|
+
if (reply.photo)
|
|
377
|
+
body = "<media:image>";
|
|
378
|
+
else if (reply.video)
|
|
379
|
+
body = "<media:video>";
|
|
380
|
+
else if (reply.audio || reply.voice)
|
|
381
|
+
body = "<media:audio>";
|
|
382
|
+
else if (reply.document)
|
|
383
|
+
body = "<media:document>";
|
|
384
|
+
}
|
|
385
|
+
if (!body)
|
|
386
|
+
return null;
|
|
387
|
+
const sender = buildSenderName(reply);
|
|
388
|
+
const senderLabel = sender ? `${sender}` : "unknown sender";
|
|
389
|
+
return {
|
|
390
|
+
id: reply.message_id ? String(reply.message_id) : undefined,
|
|
391
|
+
sender: senderLabel,
|
|
392
|
+
body,
|
|
393
|
+
};
|
|
394
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { detectMime } from "../media/mime.js";
|
|
2
|
+
import { saveMediaBuffer } from "../media/store.js";
|
|
3
|
+
export async function getTelegramFile(token, fileId) {
|
|
4
|
+
const res = await fetch(`https://api.telegram.org/bot${token}/getFile?file_id=${encodeURIComponent(fileId)}`);
|
|
5
|
+
if (!res.ok) {
|
|
6
|
+
throw new Error(`getFile failed: ${res.status} ${res.statusText}`);
|
|
7
|
+
}
|
|
8
|
+
const json = (await res.json());
|
|
9
|
+
if (!json.ok || !json.result?.file_path) {
|
|
10
|
+
throw new Error("getFile returned no file_path");
|
|
11
|
+
}
|
|
12
|
+
return json.result;
|
|
13
|
+
}
|
|
14
|
+
export async function downloadTelegramFile(token, info, maxBytes) {
|
|
15
|
+
if (!info.file_path)
|
|
16
|
+
throw new Error("file_path missing");
|
|
17
|
+
const url = `https://api.telegram.org/file/bot${token}/${info.file_path}`;
|
|
18
|
+
const res = await fetch(url);
|
|
19
|
+
if (!res.ok || !res.body) {
|
|
20
|
+
throw new Error(`Failed to download telegram file: HTTP ${res.status}`);
|
|
21
|
+
}
|
|
22
|
+
const array = Buffer.from(await res.arrayBuffer());
|
|
23
|
+
const mime = await detectMime({
|
|
24
|
+
buffer: array,
|
|
25
|
+
headerMime: res.headers.get("content-type"),
|
|
26
|
+
filePath: info.file_path,
|
|
27
|
+
});
|
|
28
|
+
// save with inbound subdir
|
|
29
|
+
const saved = await saveMediaBuffer(array, mime, "inbound", maxBytes);
|
|
30
|
+
// Ensure extension matches mime if possible
|
|
31
|
+
if (!saved.contentType && mime)
|
|
32
|
+
saved.contentType = mime;
|
|
33
|
+
return saved;
|
|
34
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { loadConfig } from "../config/config.js";
|
|
2
|
+
import { createTelegramBot } from "./bot.js";
|
|
3
|
+
import { makeProxyFetch } from "./proxy.js";
|
|
4
|
+
import { resolveTelegramToken } from "./token.js";
|
|
5
|
+
import { startTelegramWebhook } from "./webhook.js";
|
|
6
|
+
export async function monitorTelegramProvider(opts = {}) {
|
|
7
|
+
const { token } = resolveTelegramToken(loadConfig(), {
|
|
8
|
+
envToken: opts.token,
|
|
9
|
+
});
|
|
10
|
+
if (!token) {
|
|
11
|
+
throw new Error("TELEGRAM_BOT_TOKEN or telegram.botToken/tokenFile is required for Telegram gateway");
|
|
12
|
+
}
|
|
13
|
+
const proxyFetch = opts.proxyFetch ??
|
|
14
|
+
(loadConfig().telegram?.proxy
|
|
15
|
+
? makeProxyFetch(loadConfig().telegram?.proxy)
|
|
16
|
+
: undefined);
|
|
17
|
+
const bot = createTelegramBot({
|
|
18
|
+
token,
|
|
19
|
+
runtime: opts.runtime,
|
|
20
|
+
proxyFetch,
|
|
21
|
+
});
|
|
22
|
+
if (opts.useWebhook) {
|
|
23
|
+
await startTelegramWebhook({
|
|
24
|
+
token,
|
|
25
|
+
path: opts.webhookPath,
|
|
26
|
+
port: opts.webhookPort,
|
|
27
|
+
secret: opts.webhookSecret,
|
|
28
|
+
runtime: opts.runtime,
|
|
29
|
+
fetch: proxyFetch,
|
|
30
|
+
abortSignal: opts.abortSignal,
|
|
31
|
+
publicUrl: opts.webhookUrl,
|
|
32
|
+
});
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
// Long polling
|
|
36
|
+
const stopOnAbort = () => {
|
|
37
|
+
if (opts.abortSignal?.aborted)
|
|
38
|
+
void bot.stop();
|
|
39
|
+
};
|
|
40
|
+
opts.abortSignal?.addEventListener("abort", stopOnAbort, { once: true });
|
|
41
|
+
try {
|
|
42
|
+
await bot.start();
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
opts.abortSignal?.removeEventListener("abort", stopOnAbort);
|
|
46
|
+
}
|
|
47
|
+
}
|