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,105 @@
|
|
|
1
|
+
import { jsonError, toNumber, toStringOrEmpty } from "./utils.js";
|
|
2
|
+
export function registerBrowserTabRoutes(app, ctx) {
|
|
3
|
+
app.get("/tabs", async (_req, res) => {
|
|
4
|
+
try {
|
|
5
|
+
const reachable = await ctx.isReachable(300);
|
|
6
|
+
if (!reachable)
|
|
7
|
+
return res.json({ running: false, tabs: [] });
|
|
8
|
+
const tabs = await ctx.listTabs();
|
|
9
|
+
res.json({ running: true, tabs });
|
|
10
|
+
}
|
|
11
|
+
catch (err) {
|
|
12
|
+
jsonError(res, 500, String(err));
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
app.post("/tabs/open", async (req, res) => {
|
|
16
|
+
const url = toStringOrEmpty(req.body?.url);
|
|
17
|
+
if (!url)
|
|
18
|
+
return jsonError(res, 400, "url is required");
|
|
19
|
+
try {
|
|
20
|
+
await ctx.ensureBrowserAvailable();
|
|
21
|
+
const tab = await ctx.openTab(url);
|
|
22
|
+
res.json(tab);
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
jsonError(res, 500, String(err));
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
app.post("/tabs/focus", async (req, res) => {
|
|
29
|
+
const targetId = toStringOrEmpty(req.body?.targetId);
|
|
30
|
+
if (!targetId)
|
|
31
|
+
return jsonError(res, 400, "targetId is required");
|
|
32
|
+
try {
|
|
33
|
+
if (!(await ctx.isReachable(300)))
|
|
34
|
+
return jsonError(res, 409, "browser not running");
|
|
35
|
+
await ctx.focusTab(targetId);
|
|
36
|
+
res.json({ ok: true });
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
const mapped = ctx.mapTabError(err);
|
|
40
|
+
if (mapped)
|
|
41
|
+
return jsonError(res, mapped.status, mapped.message);
|
|
42
|
+
jsonError(res, 500, String(err));
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
app.delete("/tabs/:targetId", async (req, res) => {
|
|
46
|
+
const targetId = toStringOrEmpty(req.params.targetId);
|
|
47
|
+
if (!targetId)
|
|
48
|
+
return jsonError(res, 400, "targetId is required");
|
|
49
|
+
try {
|
|
50
|
+
if (!(await ctx.isReachable(300)))
|
|
51
|
+
return jsonError(res, 409, "browser not running");
|
|
52
|
+
await ctx.closeTab(targetId);
|
|
53
|
+
res.json({ ok: true });
|
|
54
|
+
}
|
|
55
|
+
catch (err) {
|
|
56
|
+
const mapped = ctx.mapTabError(err);
|
|
57
|
+
if (mapped)
|
|
58
|
+
return jsonError(res, mapped.status, mapped.message);
|
|
59
|
+
jsonError(res, 500, String(err));
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
app.post("/tabs/action", async (req, res) => {
|
|
63
|
+
const action = toStringOrEmpty(req.body?.action);
|
|
64
|
+
const index = toNumber(req.body?.index);
|
|
65
|
+
try {
|
|
66
|
+
if (action === "list") {
|
|
67
|
+
const reachable = await ctx.isReachable(300);
|
|
68
|
+
if (!reachable)
|
|
69
|
+
return res.json({ ok: true, tabs: [] });
|
|
70
|
+
const tabs = await ctx.listTabs();
|
|
71
|
+
return res.json({ ok: true, tabs });
|
|
72
|
+
}
|
|
73
|
+
if (action === "new") {
|
|
74
|
+
await ctx.ensureBrowserAvailable();
|
|
75
|
+
const tab = await ctx.openTab("about:blank");
|
|
76
|
+
return res.json({ ok: true, tab });
|
|
77
|
+
}
|
|
78
|
+
if (action === "close") {
|
|
79
|
+
const tabs = await ctx.listTabs();
|
|
80
|
+
const target = typeof index === "number" ? tabs[index] : tabs.at(0);
|
|
81
|
+
if (!target)
|
|
82
|
+
return jsonError(res, 404, "tab not found");
|
|
83
|
+
await ctx.closeTab(target.targetId);
|
|
84
|
+
return res.json({ ok: true, targetId: target.targetId });
|
|
85
|
+
}
|
|
86
|
+
if (action === "select") {
|
|
87
|
+
if (typeof index !== "number")
|
|
88
|
+
return jsonError(res, 400, "index is required");
|
|
89
|
+
const tabs = await ctx.listTabs();
|
|
90
|
+
const target = tabs[index];
|
|
91
|
+
if (!target)
|
|
92
|
+
return jsonError(res, 404, "tab not found");
|
|
93
|
+
await ctx.focusTab(target.targetId);
|
|
94
|
+
return res.json({ ok: true, targetId: target.targetId });
|
|
95
|
+
}
|
|
96
|
+
return jsonError(res, 400, "unknown tab action");
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
const mapped = ctx.mapTabError(err);
|
|
100
|
+
if (mapped)
|
|
101
|
+
return jsonError(res, mapped.status, mapped.message);
|
|
102
|
+
jsonError(res, 500, String(err));
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract profile name from query string or body and get profile context.
|
|
3
|
+
* Query string takes precedence over body for consistency with GET routes.
|
|
4
|
+
*/
|
|
5
|
+
export function getProfileContext(req, ctx) {
|
|
6
|
+
let profileName;
|
|
7
|
+
// Check query string first (works for GET and POST)
|
|
8
|
+
if (typeof req.query.profile === "string") {
|
|
9
|
+
profileName = req.query.profile.trim() || undefined;
|
|
10
|
+
}
|
|
11
|
+
// Fall back to body for POST requests
|
|
12
|
+
if (!profileName && req.body && typeof req.body === "object") {
|
|
13
|
+
const body = req.body;
|
|
14
|
+
if (typeof body.profile === "string") {
|
|
15
|
+
profileName = body.profile.trim() || undefined;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
return ctx.forProfile(profileName);
|
|
20
|
+
}
|
|
21
|
+
catch (err) {
|
|
22
|
+
return { error: String(err), status: 404 };
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function jsonError(res, status, message) {
|
|
26
|
+
res.status(status).json({ error: message });
|
|
27
|
+
}
|
|
28
|
+
export function toStringOrEmpty(value) {
|
|
29
|
+
if (typeof value === "string")
|
|
30
|
+
return value.trim();
|
|
31
|
+
if (typeof value === "number" || typeof value === "boolean") {
|
|
32
|
+
return String(value).trim();
|
|
33
|
+
}
|
|
34
|
+
return "";
|
|
35
|
+
}
|
|
36
|
+
export function toNumber(value) {
|
|
37
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
38
|
+
return value;
|
|
39
|
+
if (typeof value === "string" && value.trim()) {
|
|
40
|
+
const parsed = Number(value);
|
|
41
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
42
|
+
}
|
|
43
|
+
return undefined;
|
|
44
|
+
}
|
|
45
|
+
export function toBoolean(value) {
|
|
46
|
+
if (typeof value === "boolean")
|
|
47
|
+
return value;
|
|
48
|
+
if (typeof value === "string") {
|
|
49
|
+
const v = value.trim().toLowerCase();
|
|
50
|
+
if (v === "true" || v === "1" || v === "yes")
|
|
51
|
+
return true;
|
|
52
|
+
if (v === "false" || v === "0" || v === "no")
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
return undefined;
|
|
56
|
+
}
|
|
57
|
+
export function toStringArray(value) {
|
|
58
|
+
if (!Array.isArray(value))
|
|
59
|
+
return undefined;
|
|
60
|
+
const strings = value.map((v) => toStringOrEmpty(v)).filter(Boolean);
|
|
61
|
+
return strings.length ? strings : undefined;
|
|
62
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { getImageMetadata, resizeToJpeg } from "../media/image-ops.js";
|
|
2
|
+
export const DEFAULT_BROWSER_SCREENSHOT_MAX_SIDE = 2000;
|
|
3
|
+
export const DEFAULT_BROWSER_SCREENSHOT_MAX_BYTES = 5 * 1024 * 1024;
|
|
4
|
+
export async function normalizeBrowserScreenshot(buffer, opts) {
|
|
5
|
+
const maxSide = Math.max(1, Math.round(opts?.maxSide ?? DEFAULT_BROWSER_SCREENSHOT_MAX_SIDE));
|
|
6
|
+
const maxBytes = Math.max(1, Math.round(opts?.maxBytes ?? DEFAULT_BROWSER_SCREENSHOT_MAX_BYTES));
|
|
7
|
+
const meta = await getImageMetadata(buffer);
|
|
8
|
+
const width = Number(meta?.width ?? 0);
|
|
9
|
+
const height = Number(meta?.height ?? 0);
|
|
10
|
+
const maxDim = Math.max(width, height);
|
|
11
|
+
if (buffer.byteLength <= maxBytes &&
|
|
12
|
+
(maxDim === 0 || (width <= maxSide && height <= maxSide))) {
|
|
13
|
+
return { buffer };
|
|
14
|
+
}
|
|
15
|
+
const qualities = [85, 75, 65, 55, 45, 35];
|
|
16
|
+
const sideStart = maxDim > 0 ? Math.min(maxSide, maxDim) : maxSide;
|
|
17
|
+
const sideGrid = [sideStart, 1800, 1600, 1400, 1200, 1000, 800]
|
|
18
|
+
.map((v) => Math.min(maxSide, v))
|
|
19
|
+
.filter((v, i, arr) => v > 0 && arr.indexOf(v) === i)
|
|
20
|
+
.sort((a, b) => b - a);
|
|
21
|
+
let smallest = null;
|
|
22
|
+
for (const side of sideGrid) {
|
|
23
|
+
for (const quality of qualities) {
|
|
24
|
+
const out = await resizeToJpeg({
|
|
25
|
+
buffer,
|
|
26
|
+
maxSide: side,
|
|
27
|
+
quality,
|
|
28
|
+
withoutEnlargement: true,
|
|
29
|
+
});
|
|
30
|
+
if (!smallest || out.byteLength < smallest.size) {
|
|
31
|
+
smallest = { buffer: out, size: out.byteLength };
|
|
32
|
+
}
|
|
33
|
+
if (out.byteLength <= maxBytes) {
|
|
34
|
+
return { buffer: out, contentType: "image/jpeg" };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
const best = smallest?.buffer ?? buffer;
|
|
39
|
+
throw new Error(`Browser screenshot could not be reduced below ${(maxBytes / (1024 * 1024)).toFixed(0)}MB (got ${(best.byteLength / (1024 * 1024)).toFixed(2)}MB)`);
|
|
40
|
+
}
|
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { createTargetViaCdp, normalizeCdpWsUrl } from "./cdp.js";
|
|
3
|
+
import { isChromeCdpReady, isChromeReachable, launchClawdChrome, resolveClawdUserDataDir, stopClawdChrome, } from "./chrome.js";
|
|
4
|
+
import { resolveProfile } from "./config.js";
|
|
5
|
+
import { resolveTargetIdFromTabs } from "./target-id.js";
|
|
6
|
+
import { movePathToTrash } from "./trash.js";
|
|
7
|
+
/**
|
|
8
|
+
* Normalize a CDP WebSocket URL to use the correct base URL.
|
|
9
|
+
*/
|
|
10
|
+
function normalizeWsUrl(raw, cdpBaseUrl) {
|
|
11
|
+
if (!raw)
|
|
12
|
+
return undefined;
|
|
13
|
+
try {
|
|
14
|
+
return normalizeCdpWsUrl(raw, cdpBaseUrl);
|
|
15
|
+
}
|
|
16
|
+
catch {
|
|
17
|
+
return raw;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
async function fetchJson(url, timeoutMs = 1500, init) {
|
|
21
|
+
const ctrl = new AbortController();
|
|
22
|
+
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
23
|
+
try {
|
|
24
|
+
const res = await fetch(url, { ...init, signal: ctrl.signal });
|
|
25
|
+
if (!res.ok)
|
|
26
|
+
throw new Error(`HTTP ${res.status}`);
|
|
27
|
+
return (await res.json());
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
clearTimeout(t);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async function fetchOk(url, timeoutMs = 1500, init) {
|
|
34
|
+
const ctrl = new AbortController();
|
|
35
|
+
const t = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
36
|
+
try {
|
|
37
|
+
const res = await fetch(url, { ...init, signal: ctrl.signal });
|
|
38
|
+
if (!res.ok)
|
|
39
|
+
throw new Error(`HTTP ${res.status}`);
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
clearTimeout(t);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Create a profile-scoped context for browser operations.
|
|
47
|
+
*/
|
|
48
|
+
function createProfileContext(opts, profile) {
|
|
49
|
+
const state = () => {
|
|
50
|
+
const current = opts.getState();
|
|
51
|
+
if (!current)
|
|
52
|
+
throw new Error("Browser server not started");
|
|
53
|
+
return current;
|
|
54
|
+
};
|
|
55
|
+
const getProfileState = () => {
|
|
56
|
+
const current = state();
|
|
57
|
+
let profileState = current.profiles.get(profile.name);
|
|
58
|
+
if (!profileState) {
|
|
59
|
+
profileState = { profile, running: null };
|
|
60
|
+
current.profiles.set(profile.name, profileState);
|
|
61
|
+
}
|
|
62
|
+
return profileState;
|
|
63
|
+
};
|
|
64
|
+
const setProfileRunning = (running) => {
|
|
65
|
+
const profileState = getProfileState();
|
|
66
|
+
profileState.running = running;
|
|
67
|
+
};
|
|
68
|
+
const listTabs = async () => {
|
|
69
|
+
const raw = await fetchJson(`${profile.cdpUrl.replace(/\/$/, "")}/json/list`);
|
|
70
|
+
return raw
|
|
71
|
+
.map((t) => ({
|
|
72
|
+
targetId: t.id ?? "",
|
|
73
|
+
title: t.title ?? "",
|
|
74
|
+
url: t.url ?? "",
|
|
75
|
+
wsUrl: normalizeWsUrl(t.webSocketDebuggerUrl, profile.cdpUrl),
|
|
76
|
+
type: t.type,
|
|
77
|
+
}))
|
|
78
|
+
.filter((t) => Boolean(t.targetId));
|
|
79
|
+
};
|
|
80
|
+
const openTab = async (url) => {
|
|
81
|
+
const createdViaCdp = await createTargetViaCdp({
|
|
82
|
+
cdpUrl: profile.cdpUrl,
|
|
83
|
+
url,
|
|
84
|
+
})
|
|
85
|
+
.then((r) => r.targetId)
|
|
86
|
+
.catch(() => null);
|
|
87
|
+
if (createdViaCdp) {
|
|
88
|
+
const deadline = Date.now() + 2000;
|
|
89
|
+
while (Date.now() < deadline) {
|
|
90
|
+
const tabs = await listTabs().catch(() => []);
|
|
91
|
+
const found = tabs.find((t) => t.targetId === createdViaCdp);
|
|
92
|
+
if (found)
|
|
93
|
+
return found;
|
|
94
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
95
|
+
}
|
|
96
|
+
return { targetId: createdViaCdp, title: "", url, type: "page" };
|
|
97
|
+
}
|
|
98
|
+
const encoded = encodeURIComponent(url);
|
|
99
|
+
const base = profile.cdpUrl.replace(/\/$/, "");
|
|
100
|
+
const endpoint = `${base}/json/new?${encoded}`;
|
|
101
|
+
const created = await fetchJson(endpoint, 1500, {
|
|
102
|
+
method: "PUT",
|
|
103
|
+
}).catch(async (err) => {
|
|
104
|
+
if (String(err).includes("HTTP 405")) {
|
|
105
|
+
return await fetchJson(endpoint, 1500);
|
|
106
|
+
}
|
|
107
|
+
throw err;
|
|
108
|
+
});
|
|
109
|
+
if (!created.id)
|
|
110
|
+
throw new Error("Failed to open tab (missing id)");
|
|
111
|
+
return {
|
|
112
|
+
targetId: created.id,
|
|
113
|
+
title: created.title ?? "",
|
|
114
|
+
url: created.url ?? url,
|
|
115
|
+
wsUrl: normalizeWsUrl(created.webSocketDebuggerUrl, base),
|
|
116
|
+
type: created.type,
|
|
117
|
+
};
|
|
118
|
+
};
|
|
119
|
+
const isReachable = async (timeoutMs = 300) => {
|
|
120
|
+
const wsTimeout = Math.max(200, Math.min(2000, timeoutMs * 2));
|
|
121
|
+
return await isChromeCdpReady(profile.cdpUrl, timeoutMs, wsTimeout);
|
|
122
|
+
};
|
|
123
|
+
const isHttpReachable = async (timeoutMs = 300) => {
|
|
124
|
+
return await isChromeReachable(profile.cdpUrl, timeoutMs);
|
|
125
|
+
};
|
|
126
|
+
const attachRunning = (running) => {
|
|
127
|
+
setProfileRunning(running);
|
|
128
|
+
running.proc.on("exit", () => {
|
|
129
|
+
// Guard against server teardown (e.g., SIGUSR1 restart)
|
|
130
|
+
if (!opts.getState())
|
|
131
|
+
return;
|
|
132
|
+
const profileState = getProfileState();
|
|
133
|
+
if (profileState.running?.pid === running.pid) {
|
|
134
|
+
setProfileRunning(null);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
};
|
|
138
|
+
const ensureBrowserAvailable = async () => {
|
|
139
|
+
const current = state();
|
|
140
|
+
const remoteCdp = !profile.cdpIsLoopback;
|
|
141
|
+
const profileState = getProfileState();
|
|
142
|
+
const httpReachable = await isHttpReachable();
|
|
143
|
+
if (!httpReachable) {
|
|
144
|
+
if (current.resolved.attachOnly || remoteCdp) {
|
|
145
|
+
throw new Error(remoteCdp
|
|
146
|
+
? `Remote CDP for profile "${profile.name}" is not reachable at ${profile.cdpUrl}.`
|
|
147
|
+
: `Browser attachOnly is enabled and profile "${profile.name}" is not running.`);
|
|
148
|
+
}
|
|
149
|
+
const launched = await launchClawdChrome(current.resolved, profile);
|
|
150
|
+
attachRunning(launched);
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
// Port is reachable - check if we own it
|
|
154
|
+
if (await isReachable())
|
|
155
|
+
return;
|
|
156
|
+
// HTTP responds but WebSocket fails - port in use by something else
|
|
157
|
+
if (!profileState.running) {
|
|
158
|
+
throw new Error(`Port ${profile.cdpPort} is in use for profile "${profile.name}" but not by clawdbot. ` +
|
|
159
|
+
`Run action=reset-profile profile=${profile.name} to kill the process.`);
|
|
160
|
+
}
|
|
161
|
+
// We own it but WebSocket failed - restart
|
|
162
|
+
if (current.resolved.attachOnly || remoteCdp) {
|
|
163
|
+
throw new Error(remoteCdp
|
|
164
|
+
? `Remote CDP websocket for profile "${profile.name}" is not reachable.`
|
|
165
|
+
: `Browser attachOnly is enabled and CDP websocket for profile "${profile.name}" is not reachable.`);
|
|
166
|
+
}
|
|
167
|
+
await stopClawdChrome(profileState.running);
|
|
168
|
+
setProfileRunning(null);
|
|
169
|
+
const relaunched = await launchClawdChrome(current.resolved, profile);
|
|
170
|
+
attachRunning(relaunched);
|
|
171
|
+
if (!(await isReachable(600))) {
|
|
172
|
+
throw new Error(`Chrome CDP websocket for profile "${profile.name}" is not reachable after restart.`);
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
const ensureTabAvailable = async (targetId) => {
|
|
176
|
+
await ensureBrowserAvailable();
|
|
177
|
+
const tabs1 = await listTabs();
|
|
178
|
+
if (tabs1.length === 0) {
|
|
179
|
+
await openTab("about:blank");
|
|
180
|
+
}
|
|
181
|
+
const tabs = await listTabs();
|
|
182
|
+
const chosen = targetId
|
|
183
|
+
? (() => {
|
|
184
|
+
const resolved = resolveTargetIdFromTabs(targetId, tabs);
|
|
185
|
+
if (!resolved.ok) {
|
|
186
|
+
if (resolved.reason === "ambiguous")
|
|
187
|
+
return "AMBIGUOUS";
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
return tabs.find((t) => t.targetId === resolved.targetId) ?? null;
|
|
191
|
+
})()
|
|
192
|
+
: (tabs.at(0) ?? null);
|
|
193
|
+
if (chosen === "AMBIGUOUS") {
|
|
194
|
+
throw new Error("ambiguous target id prefix");
|
|
195
|
+
}
|
|
196
|
+
if (!chosen?.wsUrl)
|
|
197
|
+
throw new Error("tab not found");
|
|
198
|
+
return chosen;
|
|
199
|
+
};
|
|
200
|
+
const focusTab = async (targetId) => {
|
|
201
|
+
const base = profile.cdpUrl.replace(/\/$/, "");
|
|
202
|
+
const tabs = await listTabs();
|
|
203
|
+
const resolved = resolveTargetIdFromTabs(targetId, tabs);
|
|
204
|
+
if (!resolved.ok) {
|
|
205
|
+
if (resolved.reason === "ambiguous") {
|
|
206
|
+
throw new Error("ambiguous target id prefix");
|
|
207
|
+
}
|
|
208
|
+
throw new Error("tab not found");
|
|
209
|
+
}
|
|
210
|
+
await fetchOk(`${base}/json/activate/${resolved.targetId}`);
|
|
211
|
+
};
|
|
212
|
+
const closeTab = async (targetId) => {
|
|
213
|
+
const base = profile.cdpUrl.replace(/\/$/, "");
|
|
214
|
+
const tabs = await listTabs();
|
|
215
|
+
const resolved = resolveTargetIdFromTabs(targetId, tabs);
|
|
216
|
+
if (!resolved.ok) {
|
|
217
|
+
if (resolved.reason === "ambiguous") {
|
|
218
|
+
throw new Error("ambiguous target id prefix");
|
|
219
|
+
}
|
|
220
|
+
throw new Error("tab not found");
|
|
221
|
+
}
|
|
222
|
+
await fetchOk(`${base}/json/close/${resolved.targetId}`);
|
|
223
|
+
};
|
|
224
|
+
const stopRunningBrowser = async () => {
|
|
225
|
+
const profileState = getProfileState();
|
|
226
|
+
if (!profileState.running)
|
|
227
|
+
return { stopped: false };
|
|
228
|
+
await stopClawdChrome(profileState.running);
|
|
229
|
+
setProfileRunning(null);
|
|
230
|
+
return { stopped: true };
|
|
231
|
+
};
|
|
232
|
+
const resetProfile = async () => {
|
|
233
|
+
if (!profile.cdpIsLoopback) {
|
|
234
|
+
throw new Error(`reset-profile is only supported for local profiles (profile "${profile.name}" is remote).`);
|
|
235
|
+
}
|
|
236
|
+
const userDataDir = resolveClawdUserDataDir(profile.name);
|
|
237
|
+
const profileState = getProfileState();
|
|
238
|
+
const httpReachable = await isHttpReachable(300);
|
|
239
|
+
if (httpReachable && !profileState.running) {
|
|
240
|
+
// Port in use but not by us - kill it
|
|
241
|
+
try {
|
|
242
|
+
const mod = await import("./pw-ai.js");
|
|
243
|
+
await mod.closePlaywrightBrowserConnection();
|
|
244
|
+
}
|
|
245
|
+
catch {
|
|
246
|
+
// ignore
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
if (profileState.running) {
|
|
250
|
+
await stopRunningBrowser();
|
|
251
|
+
}
|
|
252
|
+
try {
|
|
253
|
+
const mod = await import("./pw-ai.js");
|
|
254
|
+
await mod.closePlaywrightBrowserConnection();
|
|
255
|
+
}
|
|
256
|
+
catch {
|
|
257
|
+
// ignore
|
|
258
|
+
}
|
|
259
|
+
if (!fs.existsSync(userDataDir)) {
|
|
260
|
+
return { moved: false, from: userDataDir };
|
|
261
|
+
}
|
|
262
|
+
const moved = await movePathToTrash(userDataDir);
|
|
263
|
+
return { moved: true, from: userDataDir, to: moved };
|
|
264
|
+
};
|
|
265
|
+
return {
|
|
266
|
+
profile,
|
|
267
|
+
ensureBrowserAvailable,
|
|
268
|
+
ensureTabAvailable,
|
|
269
|
+
isHttpReachable,
|
|
270
|
+
isReachable,
|
|
271
|
+
listTabs,
|
|
272
|
+
openTab,
|
|
273
|
+
focusTab,
|
|
274
|
+
closeTab,
|
|
275
|
+
stopRunningBrowser,
|
|
276
|
+
resetProfile,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
export function createBrowserRouteContext(opts) {
|
|
280
|
+
const state = () => {
|
|
281
|
+
const current = opts.getState();
|
|
282
|
+
if (!current)
|
|
283
|
+
throw new Error("Browser server not started");
|
|
284
|
+
return current;
|
|
285
|
+
};
|
|
286
|
+
const forProfile = (profileName) => {
|
|
287
|
+
const current = state();
|
|
288
|
+
const name = profileName ?? current.resolved.defaultProfile;
|
|
289
|
+
const profile = resolveProfile(current.resolved, name);
|
|
290
|
+
if (!profile) {
|
|
291
|
+
const available = Object.keys(current.resolved.profiles).join(", ");
|
|
292
|
+
throw new Error(`Profile "${name}" not found. Available profiles: ${available || "(none)"}`);
|
|
293
|
+
}
|
|
294
|
+
return createProfileContext(opts, profile);
|
|
295
|
+
};
|
|
296
|
+
const listProfiles = async () => {
|
|
297
|
+
const current = state();
|
|
298
|
+
const result = [];
|
|
299
|
+
for (const name of Object.keys(current.resolved.profiles)) {
|
|
300
|
+
const profileState = current.profiles.get(name);
|
|
301
|
+
const profile = resolveProfile(current.resolved, name);
|
|
302
|
+
if (!profile)
|
|
303
|
+
continue;
|
|
304
|
+
let tabCount = 0;
|
|
305
|
+
let running = false;
|
|
306
|
+
if (profileState?.running) {
|
|
307
|
+
running = true;
|
|
308
|
+
try {
|
|
309
|
+
const ctx = createProfileContext(opts, profile);
|
|
310
|
+
const tabs = await ctx.listTabs();
|
|
311
|
+
tabCount = tabs.filter((t) => t.type === "page").length;
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
// Browser might not be responsive
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
// Check if something is listening on the port
|
|
319
|
+
try {
|
|
320
|
+
const reachable = await isChromeReachable(profile.cdpUrl, 200);
|
|
321
|
+
if (reachable) {
|
|
322
|
+
running = true;
|
|
323
|
+
const ctx = createProfileContext(opts, profile);
|
|
324
|
+
const tabs = await ctx.listTabs().catch(() => []);
|
|
325
|
+
tabCount = tabs.filter((t) => t.type === "page").length;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
catch {
|
|
329
|
+
// Not reachable
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
result.push({
|
|
333
|
+
name,
|
|
334
|
+
cdpPort: profile.cdpPort,
|
|
335
|
+
cdpUrl: profile.cdpUrl,
|
|
336
|
+
color: profile.color,
|
|
337
|
+
running,
|
|
338
|
+
tabCount,
|
|
339
|
+
isDefault: name === current.resolved.defaultProfile,
|
|
340
|
+
isRemote: !profile.cdpIsLoopback,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
return result;
|
|
344
|
+
};
|
|
345
|
+
// Create default profile context for backward compatibility
|
|
346
|
+
const getDefaultContext = () => forProfile();
|
|
347
|
+
const mapTabError = (err) => {
|
|
348
|
+
const msg = String(err);
|
|
349
|
+
if (msg.includes("ambiguous target id prefix")) {
|
|
350
|
+
return { status: 409, message: "ambiguous target id prefix" };
|
|
351
|
+
}
|
|
352
|
+
if (msg.includes("tab not found")) {
|
|
353
|
+
return { status: 404, message: "tab not found" };
|
|
354
|
+
}
|
|
355
|
+
if (msg.includes("not found")) {
|
|
356
|
+
return { status: 404, message: msg };
|
|
357
|
+
}
|
|
358
|
+
return null;
|
|
359
|
+
};
|
|
360
|
+
return {
|
|
361
|
+
state,
|
|
362
|
+
forProfile,
|
|
363
|
+
listProfiles,
|
|
364
|
+
// Legacy methods delegate to default profile
|
|
365
|
+
ensureBrowserAvailable: () => getDefaultContext().ensureBrowserAvailable(),
|
|
366
|
+
ensureTabAvailable: (targetId) => getDefaultContext().ensureTabAvailable(targetId),
|
|
367
|
+
isHttpReachable: (timeoutMs) => getDefaultContext().isHttpReachable(timeoutMs),
|
|
368
|
+
isReachable: (timeoutMs) => getDefaultContext().isReachable(timeoutMs),
|
|
369
|
+
listTabs: () => getDefaultContext().listTabs(),
|
|
370
|
+
openTab: (url) => getDefaultContext().openTab(url),
|
|
371
|
+
focusTab: (targetId) => getDefaultContext().focusTab(targetId),
|
|
372
|
+
closeTab: (targetId) => getDefaultContext().closeTab(targetId),
|
|
373
|
+
stopRunningBrowser: () => getDefaultContext().stopRunningBrowser(),
|
|
374
|
+
resetProfile: () => getDefaultContext().resetProfile(),
|
|
375
|
+
mapTabError,
|
|
376
|
+
};
|
|
377
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import { loadConfig } from "../config/config.js";
|
|
3
|
+
import { createSubsystemLogger } from "../logging.js";
|
|
4
|
+
import { resolveBrowserConfig, shouldStartLocalBrowserServer, } from "./config.js";
|
|
5
|
+
import { registerBrowserRoutes } from "./routes/index.js";
|
|
6
|
+
import { createBrowserRouteContext, } from "./server-context.js";
|
|
7
|
+
let state = null;
|
|
8
|
+
const log = createSubsystemLogger("browser");
|
|
9
|
+
const logServer = log.child("server");
|
|
10
|
+
export async function startBrowserControlServerFromConfig() {
|
|
11
|
+
if (state)
|
|
12
|
+
return state;
|
|
13
|
+
const cfg = loadConfig();
|
|
14
|
+
const resolved = resolveBrowserConfig(cfg.browser);
|
|
15
|
+
if (!resolved.enabled)
|
|
16
|
+
return null;
|
|
17
|
+
if (!shouldStartLocalBrowserServer(resolved)) {
|
|
18
|
+
logServer.info(`browser control URL is non-loopback (${resolved.controlUrl}); skipping local server start`);
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const app = express();
|
|
22
|
+
app.use(express.json({ limit: "1mb" }));
|
|
23
|
+
const ctx = createBrowserRouteContext({
|
|
24
|
+
getState: () => state,
|
|
25
|
+
});
|
|
26
|
+
registerBrowserRoutes(app, ctx);
|
|
27
|
+
const port = resolved.controlPort;
|
|
28
|
+
const server = await new Promise((resolve, reject) => {
|
|
29
|
+
const s = app.listen(port, "127.0.0.1", () => resolve(s));
|
|
30
|
+
s.once("error", reject);
|
|
31
|
+
}).catch((err) => {
|
|
32
|
+
logServer.error(`clawd browser server failed to bind 127.0.0.1:${port}: ${String(err)}`);
|
|
33
|
+
return null;
|
|
34
|
+
});
|
|
35
|
+
if (!server)
|
|
36
|
+
return null;
|
|
37
|
+
state = {
|
|
38
|
+
server,
|
|
39
|
+
port,
|
|
40
|
+
resolved,
|
|
41
|
+
profiles: new Map(),
|
|
42
|
+
};
|
|
43
|
+
logServer.info(`Browser control listening on http://127.0.0.1:${port}/`);
|
|
44
|
+
return state;
|
|
45
|
+
}
|
|
46
|
+
export async function stopBrowserControlServer() {
|
|
47
|
+
const current = state;
|
|
48
|
+
if (!current)
|
|
49
|
+
return;
|
|
50
|
+
const ctx = createBrowserRouteContext({
|
|
51
|
+
getState: () => state,
|
|
52
|
+
});
|
|
53
|
+
try {
|
|
54
|
+
const current = state;
|
|
55
|
+
if (current) {
|
|
56
|
+
for (const name of Object.keys(current.resolved.profiles)) {
|
|
57
|
+
try {
|
|
58
|
+
await ctx.forProfile(name).stopRunningBrowser();
|
|
59
|
+
}
|
|
60
|
+
catch {
|
|
61
|
+
// ignore
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
logServer.warn(`clawd browser stop failed: ${String(err)}`);
|
|
68
|
+
}
|
|
69
|
+
await new Promise((resolve) => {
|
|
70
|
+
current.server.close(() => resolve());
|
|
71
|
+
});
|
|
72
|
+
state = null;
|
|
73
|
+
// Optional: Playwright is not always available (e.g. embedded gateway builds).
|
|
74
|
+
try {
|
|
75
|
+
const mod = await import("./pw-ai.js");
|
|
76
|
+
await mod.closePlaywrightBrowserConnection();
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
// ignore
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function resolveTargetIdFromTabs(input, tabs) {
|
|
2
|
+
const needle = input.trim();
|
|
3
|
+
if (!needle)
|
|
4
|
+
return { ok: false, reason: "not_found" };
|
|
5
|
+
const exact = tabs.find((t) => t.targetId === needle);
|
|
6
|
+
if (exact)
|
|
7
|
+
return { ok: true, targetId: exact.targetId };
|
|
8
|
+
const lower = needle.toLowerCase();
|
|
9
|
+
const matches = tabs
|
|
10
|
+
.map((t) => t.targetId)
|
|
11
|
+
.filter((id) => id.toLowerCase().startsWith(lower));
|
|
12
|
+
const only = matches.length === 1 ? matches[0] : undefined;
|
|
13
|
+
if (only)
|
|
14
|
+
return { ok: true, targetId: only };
|
|
15
|
+
if (matches.length === 0)
|
|
16
|
+
return { ok: false, reason: "not_found" };
|
|
17
|
+
return { ok: false, reason: "ambiguous", matches };
|
|
18
|
+
}
|