vilvona 1.0.0
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 +12331 -0
- package/LICENSE +24 -0
- package/README.md +314 -0
- package/THIRD_PARTY_NOTICES.md +37 -0
- package/docs/.i18n/README.md +81 -0
- package/docs/.i18n/ar-navigation.json +18 -0
- package/docs/.i18n/de-navigation.json +18 -0
- package/docs/.i18n/es-navigation.json +18 -0
- package/docs/.i18n/fr-navigation.json +18 -0
- package/docs/.i18n/glossary.ar.json +82 -0
- package/docs/.i18n/glossary.de.json +82 -0
- package/docs/.i18n/glossary.es.json +82 -0
- package/docs/.i18n/glossary.fa.json +82 -0
- package/docs/.i18n/glossary.fr.json +82 -0
- package/docs/.i18n/glossary.id.json +82 -0
- package/docs/.i18n/glossary.it.json +82 -0
- package/docs/.i18n/glossary.ja-JP.json +102 -0
- package/docs/.i18n/glossary.ko.json +82 -0
- package/docs/.i18n/glossary.nl.json +82 -0
- package/docs/.i18n/glossary.pl.json +82 -0
- package/docs/.i18n/glossary.pt-BR.json +82 -0
- package/docs/.i18n/glossary.th.json +82 -0
- package/docs/.i18n/glossary.tr.json +82 -0
- package/docs/.i18n/glossary.uk.json +82 -0
- package/docs/.i18n/glossary.vi.json +82 -0
- package/docs/.i18n/glossary.zh-CN.json +1182 -0
- package/docs/.i18n/glossary.zh-TW.json +162 -0
- package/docs/.i18n/id-navigation.json +18 -0
- package/docs/.i18n/it-navigation.json +18 -0
- package/docs/.i18n/ja-navigation.json +18 -0
- package/docs/.i18n/ko-navigation.json +18 -0
- package/docs/.i18n/pl-navigation.json +18 -0
- package/docs/.i18n/pt-BR-navigation.json +18 -0
- package/docs/.i18n/tr-navigation.json +18 -0
- package/docs/.i18n/translation-workflow.md +111 -0
- package/docs/.i18n/zh-Hans-navigation.json +552 -0
- package/docs/AGENTS.md +36 -0
- package/docs/agent-runtime-architecture.md +48 -0
- package/docs/announcements/bluebubbles-imessage.md +79 -0
- package/docs/auth-credential-semantics.md +124 -0
- package/docs/automation/auth-monitoring.md +11 -0
- package/docs/automation/clawflow.md +12 -0
- package/docs/automation/cron-jobs.md +569 -0
- package/docs/automation/cron-vs-heartbeat.md +11 -0
- package/docs/automation/gmail-pubsub.md +11 -0
- package/docs/automation/hooks.md +387 -0
- package/docs/automation/index.md +135 -0
- package/docs/automation/poll.md +12 -0
- package/docs/automation/standing-orders.md +250 -0
- package/docs/automation/taskflow.md +155 -0
- package/docs/automation/tasks.md +374 -0
- package/docs/automation/troubleshooting.md +12 -0
- package/docs/automation/webhook.md +12 -0
- package/docs/brave-search.md +11 -0
- package/docs/channels/access-groups.md +201 -0
- package/docs/channels/ambient-room-events.md +214 -0
- package/docs/channels/bot-loop-protection.md +131 -0
- package/docs/channels/broadcast-groups.md +472 -0
- package/docs/channels/channel-routing.md +162 -0
- package/docs/channels/clickclack.md +146 -0
- package/docs/channels/discord.md +1758 -0
- package/docs/channels/feishu.md +654 -0
- package/docs/channels/googlechat.md +286 -0
- package/docs/channels/group-messages.md +95 -0
- package/docs/channels/groups.md +610 -0
- package/docs/channels/imessage-from-bluebubbles.md +259 -0
- package/docs/channels/imessage.md +864 -0
- package/docs/channels/index.md +65 -0
- package/docs/channels/irc.md +253 -0
- package/docs/channels/line.md +243 -0
- package/docs/channels/location.md +71 -0
- package/docs/channels/matrix-migration.md +375 -0
- package/docs/channels/matrix-presentation.md +77 -0
- package/docs/channels/matrix-push-rules.md +150 -0
- package/docs/channels/matrix.md +935 -0
- package/docs/channels/mattermost.md +542 -0
- package/docs/channels/msteams.md +1097 -0
- package/docs/channels/nextcloud-talk.md +176 -0
- package/docs/channels/nostr.md +253 -0
- package/docs/channels/pairing.md +214 -0
- package/docs/channels/qqbot.md +317 -0
- package/docs/channels/signal.md +417 -0
- package/docs/channels/slack.md +1623 -0
- package/docs/channels/sms.md +380 -0
- package/docs/channels/synology-chat.md +187 -0
- package/docs/channels/telegram.md +1121 -0
- package/docs/channels/tlon.md +296 -0
- package/docs/channels/troubleshooting.md +162 -0
- package/docs/channels/twitch.md +431 -0
- package/docs/channels/wechat.md +171 -0
- package/docs/channels/whatsapp.md +796 -0
- package/docs/channels/yuanbao.md +416 -0
- package/docs/channels/zalo.md +253 -0
- package/docs/channels/zalouser.md +217 -0
- package/docs/ci.md +665 -0
- package/docs/clawhub/cli.md +82 -0
- package/docs/clawhub/publishing.md +96 -0
- package/docs/cli/acp.md +370 -0
- package/docs/cli/agent.md +109 -0
- package/docs/cli/agents.md +253 -0
- package/docs/cli/approvals.md +193 -0
- package/docs/cli/backup.md +98 -0
- package/docs/cli/browser.md +322 -0
- package/docs/cli/channels.md +154 -0
- package/docs/cli/clawbot.md +25 -0
- package/docs/cli/commitments.md +90 -0
- package/docs/cli/completion.md +39 -0
- package/docs/cli/config.md +504 -0
- package/docs/cli/configure.md +77 -0
- package/docs/cli/crestodian.md +337 -0
- package/docs/cli/cron.md +344 -0
- package/docs/cli/daemon.md +67 -0
- package/docs/cli/dashboard.md +33 -0
- package/docs/cli/devices.md +240 -0
- package/docs/cli/directory.md +68 -0
- package/docs/cli/dns.md +53 -0
- package/docs/cli/docs.md +63 -0
- package/docs/cli/doctor.md +254 -0
- package/docs/cli/flows.md +52 -0
- package/docs/cli/gateway.md +572 -0
- package/docs/cli/health.md +43 -0
- package/docs/cli/hooks.md +345 -0
- package/docs/cli/index.md +406 -0
- package/docs/cli/infer.md +364 -0
- package/docs/cli/logs.md +68 -0
- package/docs/cli/mcp.md +851 -0
- package/docs/cli/memory.md +183 -0
- package/docs/cli/message.md +317 -0
- package/docs/cli/migrate.md +334 -0
- package/docs/cli/models.md +239 -0
- package/docs/cli/node.md +178 -0
- package/docs/cli/nodes.md +76 -0
- package/docs/cli/onboard.md +251 -0
- package/docs/cli/pairing.md +77 -0
- package/docs/cli/path.md +511 -0
- package/docs/cli/plugins.md +480 -0
- package/docs/cli/policy.md +929 -0
- package/docs/cli/proxy.md +89 -0
- package/docs/cli/qr.md +56 -0
- package/docs/cli/reset.md +39 -0
- package/docs/cli/sandbox.md +208 -0
- package/docs/cli/secrets.md +202 -0
- package/docs/cli/security.md +135 -0
- package/docs/cli/sessions.md +178 -0
- package/docs/cli/setup.md +60 -0
- package/docs/cli/skills.md +156 -0
- package/docs/cli/status.md +45 -0
- package/docs/cli/system.md +89 -0
- package/docs/cli/tasks.md +111 -0
- package/docs/cli/transcripts.md +151 -0
- package/docs/cli/tui.md +92 -0
- package/docs/cli/uninstall.md +45 -0
- package/docs/cli/update.md +283 -0
- package/docs/cli/voicecall.md +204 -0
- package/docs/cli/webhooks.md +117 -0
- package/docs/cli/wiki.md +256 -0
- package/docs/cli/workboard.md +228 -0
- package/docs/concepts/active-memory.md +856 -0
- package/docs/concepts/agent-loop.md +185 -0
- package/docs/concepts/agent-runtimes.md +276 -0
- package/docs/concepts/agent-workspace.md +230 -0
- package/docs/concepts/agent.md +142 -0
- package/docs/concepts/architecture.md +154 -0
- package/docs/concepts/channel-docking.md +145 -0
- package/docs/concepts/commitments.md +150 -0
- package/docs/concepts/compaction.md +203 -0
- package/docs/concepts/context-engine.md +347 -0
- package/docs/concepts/context.md +199 -0
- package/docs/concepts/delegate-architecture.md +319 -0
- package/docs/concepts/dreaming.md +279 -0
- package/docs/concepts/experimental-features.md +111 -0
- package/docs/concepts/features.md +91 -0
- package/docs/concepts/mantis-slack-desktop-runbook.md +231 -0
- package/docs/concepts/mantis.md +744 -0
- package/docs/concepts/markdown-formatting.md +139 -0
- package/docs/concepts/memory-builtin.md +151 -0
- package/docs/concepts/memory-honcho.md +144 -0
- package/docs/concepts/memory-qmd.md +277 -0
- package/docs/concepts/memory-search.md +175 -0
- package/docs/concepts/memory.md +299 -0
- package/docs/concepts/message-lifecycle-refactor.md +1126 -0
- package/docs/concepts/messages.md +216 -0
- package/docs/concepts/model-failover.md +405 -0
- package/docs/concepts/model-providers.md +719 -0
- package/docs/concepts/models.md +371 -0
- package/docs/concepts/multi-agent.md +625 -0
- package/docs/concepts/oauth.md +218 -0
- package/docs/concepts/parallel-specialist-lanes.md +127 -0
- package/docs/concepts/personal-agent-benchmark-pack.md +74 -0
- package/docs/concepts/presence.md +117 -0
- package/docs/concepts/progress-drafts.md +406 -0
- package/docs/concepts/qa-e2e-automation.md +979 -0
- package/docs/concepts/qa-matrix.md +139 -0
- package/docs/concepts/queue-steering.md +90 -0
- package/docs/concepts/queue.md +136 -0
- package/docs/concepts/retry.md +86 -0
- package/docs/concepts/session-pruning.md +104 -0
- package/docs/concepts/session-tool.md +201 -0
- package/docs/concepts/session.md +164 -0
- package/docs/concepts/soul.md +116 -0
- package/docs/concepts/streaming.md +258 -0
- package/docs/concepts/system-prompt.md +330 -0
- package/docs/concepts/timezone.md +47 -0
- package/docs/concepts/typebox.md +309 -0
- package/docs/concepts/typing-indicators.md +88 -0
- package/docs/concepts/usage-tracking.md +72 -0
- package/docs/date-time.md +126 -0
- package/docs/debug/node-issue.md +90 -0
- package/docs/diagnostics/flags.md +182 -0
- package/docs/docs.json +1885 -0
- package/docs/gateway/authentication.md +286 -0
- package/docs/gateway/background-process.md +147 -0
- package/docs/gateway/bonjour.md +303 -0
- package/docs/gateway/bridge-protocol.md +97 -0
- package/docs/gateway/cli-backends.md +463 -0
- package/docs/gateway/config-agents.md +1525 -0
- package/docs/gateway/config-channels.md +942 -0
- package/docs/gateway/config-tools.md +806 -0
- package/docs/gateway/configuration-examples.md +706 -0
- package/docs/gateway/configuration-reference.md +1449 -0
- package/docs/gateway/configuration.md +748 -0
- package/docs/gateway/diagnostics.md +213 -0
- package/docs/gateway/discovery.md +154 -0
- package/docs/gateway/doctor.md +576 -0
- package/docs/gateway/external-apps.md +86 -0
- package/docs/gateway/gateway-lock.md +37 -0
- package/docs/gateway/health.md +73 -0
- package/docs/gateway/heartbeat.md +498 -0
- package/docs/gateway/index.md +385 -0
- package/docs/gateway/local-model-services.md +205 -0
- package/docs/gateway/local-models.md +355 -0
- package/docs/gateway/logging.md +149 -0
- package/docs/gateway/multiple-gateways.md +178 -0
- package/docs/gateway/network-model.md +15 -0
- package/docs/gateway/openai-http-api.md +379 -0
- package/docs/gateway/openresponses-http-api.md +349 -0
- package/docs/gateway/openshell.md +316 -0
- package/docs/gateway/opentelemetry.md +440 -0
- package/docs/gateway/operator-scopes.md +119 -0
- package/docs/gateway/pairing.md +207 -0
- package/docs/gateway/prometheus.md +249 -0
- package/docs/gateway/protocol.md +839 -0
- package/docs/gateway/remote-gateway-readme.md +169 -0
- package/docs/gateway/remote.md +280 -0
- package/docs/gateway/sandbox-vs-tool-policy-vs-elevated.md +148 -0
- package/docs/gateway/sandboxing.md +546 -0
- package/docs/gateway/secrets-plan-contract.md +159 -0
- package/docs/gateway/secrets.md +805 -0
- package/docs/gateway/security/audit-checks.md +127 -0
- package/docs/gateway/security/exposure-runbook.md +212 -0
- package/docs/gateway/security/index.md +1344 -0
- package/docs/gateway/security/secure-file-operations.md +76 -0
- package/docs/gateway/security/shrinkwrap.md +111 -0
- package/docs/gateway/tailscale.md +180 -0
- package/docs/gateway/tools-invoke-http-api.md +175 -0
- package/docs/gateway/troubleshooting.md +881 -0
- package/docs/gateway/trusted-proxy-auth.md +483 -0
- package/docs/help/debugging.md +341 -0
- package/docs/help/environment.md +240 -0
- package/docs/help/faq-first-run.md +872 -0
- package/docs/help/faq-models.md +557 -0
- package/docs/help/faq.md +2115 -0
- package/docs/help/index.md +39 -0
- package/docs/help/scripts.md +56 -0
- package/docs/help/testing-live.md +592 -0
- package/docs/help/testing-updates-plugins.md +299 -0
- package/docs/help/testing.md +977 -0
- package/docs/help/troubleshooting.md +500 -0
- package/docs/index.md +196 -0
- package/docs/install/ansible.md +233 -0
- package/docs/install/azure.md +315 -0
- package/docs/install/bun.md +59 -0
- package/docs/install/clawdock.md +112 -0
- package/docs/install/development-channels.md +148 -0
- package/docs/install/digitalocean.md +174 -0
- package/docs/install/docker-vm-runtime.md +154 -0
- package/docs/install/docker.md +564 -0
- package/docs/install/exe-dev.md +201 -0
- package/docs/install/fly.md +524 -0
- package/docs/install/gcp.md +418 -0
- package/docs/install/hetzner.md +285 -0
- package/docs/install/hostinger.md +98 -0
- package/docs/install/index.md +236 -0
- package/docs/install/installer.md +447 -0
- package/docs/install/kubernetes.md +196 -0
- package/docs/install/macos-vm.md +281 -0
- package/docs/install/migrating-claude.md +165 -0
- package/docs/install/migrating-hermes.md +178 -0
- package/docs/install/migrating.md +137 -0
- package/docs/install/nix.md +112 -0
- package/docs/install/node.md +142 -0
- package/docs/install/northflank.mdx +44 -0
- package/docs/install/oracle.md +218 -0
- package/docs/install/podman.md +216 -0
- package/docs/install/railway.mdx +92 -0
- package/docs/install/raspberry-pi.md +234 -0
- package/docs/install/render.mdx +167 -0
- package/docs/install/uninstall.md +140 -0
- package/docs/install/updating.md +284 -0
- package/docs/install/upstash.md +96 -0
- package/docs/logging.md +322 -0
- package/docs/maturity-scores.yaml +5361 -0
- package/docs/nav-tabs-underline.js +104 -0
- package/docs/network.md +72 -0
- package/docs/nodes/audio.md +216 -0
- package/docs/nodes/camera.md +166 -0
- package/docs/nodes/images.md +77 -0
- package/docs/nodes/index.md +444 -0
- package/docs/nodes/location-command.md +102 -0
- package/docs/nodes/media-understanding.md +495 -0
- package/docs/nodes/talk.md +160 -0
- package/docs/nodes/troubleshooting.md +123 -0
- package/docs/nodes/voicewake.md +93 -0
- package/docs/openclaw-agent-runtime.md +82 -0
- package/docs/perplexity.md +11 -0
- package/docs/plan/codex-context-engine-harness.md +624 -0
- package/docs/plan/ui-channels.md +284 -0
- package/docs/platforms/android.md +286 -0
- package/docs/platforms/digitalocean.md +12 -0
- package/docs/platforms/easyrunner.md +109 -0
- package/docs/platforms/index.md +65 -0
- package/docs/platforms/ios.md +287 -0
- package/docs/platforms/linux.md +141 -0
- package/docs/platforms/mac/bundled-gateway.md +79 -0
- package/docs/platforms/mac/canvas.md +128 -0
- package/docs/platforms/mac/child-process.md +72 -0
- package/docs/platforms/mac/dev-setup.md +112 -0
- package/docs/platforms/mac/health.md +39 -0
- package/docs/platforms/mac/icon.md +36 -0
- package/docs/platforms/mac/logging.md +62 -0
- package/docs/platforms/mac/menu-bar.md +93 -0
- package/docs/platforms/mac/peekaboo.md +96 -0
- package/docs/platforms/mac/permissions.md +73 -0
- package/docs/platforms/mac/remote.md +123 -0
- package/docs/platforms/mac/signing.md +52 -0
- package/docs/platforms/mac/skills.md +45 -0
- package/docs/platforms/mac/voice-overlay.md +66 -0
- package/docs/platforms/mac/voicewake.md +79 -0
- package/docs/platforms/mac/webchat.md +54 -0
- package/docs/platforms/mac/xpc.md +66 -0
- package/docs/platforms/macos.md +228 -0
- package/docs/platforms/oracle.md +12 -0
- package/docs/platforms/raspberry-pi.md +13 -0
- package/docs/platforms/windows.md +304 -0
- package/docs/plugins/adding-capabilities.md +146 -0
- package/docs/plugins/admin-http-rpc.md +216 -0
- package/docs/plugins/agent-tools.md +13 -0
- package/docs/plugins/architecture-internals.md +1203 -0
- package/docs/plugins/architecture.md +483 -0
- package/docs/plugins/building-extensions.md +13 -0
- package/docs/plugins/building-plugins.md +343 -0
- package/docs/plugins/bundles.md +310 -0
- package/docs/plugins/cli-backend-plugins.md +328 -0
- package/docs/plugins/codex-computer-use.md +297 -0
- package/docs/plugins/codex-harness-reference.md +480 -0
- package/docs/plugins/codex-harness-runtime.md +268 -0
- package/docs/plugins/codex-harness.md +800 -0
- package/docs/plugins/codex-native-plugins.md +280 -0
- package/docs/plugins/community.md +77 -0
- package/docs/plugins/compatibility.md +198 -0
- package/docs/plugins/copilot.md +355 -0
- package/docs/plugins/dependency-resolution.md +176 -0
- package/docs/plugins/google-meet.md +1737 -0
- package/docs/plugins/hooks.md +525 -0
- package/docs/plugins/install-overrides.md +80 -0
- package/docs/plugins/llama-cpp.md +58 -0
- package/docs/plugins/manage-plugins.md +214 -0
- package/docs/plugins/manifest.md +1468 -0
- package/docs/plugins/memory-lancedb.md +385 -0
- package/docs/plugins/memory-wiki.md +529 -0
- package/docs/plugins/message-presentation.md +487 -0
- package/docs/plugins/oc-path.md +166 -0
- package/docs/plugins/plugin-inventory.md +318 -0
- package/docs/plugins/plugin-permission-requests.md +193 -0
- package/docs/plugins/reference/acpx.md +23 -0
- package/docs/plugins/reference/admin-http-rpc.md +23 -0
- package/docs/plugins/reference/alibaba.md +23 -0
- package/docs/plugins/reference/amazon-bedrock-mantle.md +23 -0
- package/docs/plugins/reference/amazon-bedrock.md +23 -0
- package/docs/plugins/reference/anthropic-vertex.md +29 -0
- package/docs/plugins/reference/anthropic.md +23 -0
- package/docs/plugins/reference/arcee.md +23 -0
- package/docs/plugins/reference/azure-speech.md +23 -0
- package/docs/plugins/reference/bonjour.md +19 -0
- package/docs/plugins/reference/brave.md +23 -0
- package/docs/plugins/reference/browser.md +23 -0
- package/docs/plugins/reference/byteplus.md +19 -0
- package/docs/plugins/reference/canvas.md +19 -0
- package/docs/plugins/reference/cerebras.md +23 -0
- package/docs/plugins/reference/chutes.md +23 -0
- package/docs/plugins/reference/clickclack.md +23 -0
- package/docs/plugins/reference/cloudflare-ai-gateway.md +23 -0
- package/docs/plugins/reference/codex-supervisor.md +27 -0
- package/docs/plugins/reference/codex.md +23 -0
- package/docs/plugins/reference/comfy.md +23 -0
- package/docs/plugins/reference/copilot-proxy.md +19 -0
- package/docs/plugins/reference/copilot.md +23 -0
- package/docs/plugins/reference/deepgram.md +23 -0
- package/docs/plugins/reference/deepinfra.md +23 -0
- package/docs/plugins/reference/deepseek.md +23 -0
- package/docs/plugins/reference/diagnostics-otel.md +19 -0
- package/docs/plugins/reference/diagnostics-prometheus.md +19 -0
- package/docs/plugins/reference/diffs-language-pack.md +31 -0
- package/docs/plugins/reference/diffs.md +19 -0
- package/docs/plugins/reference/discord.md +23 -0
- package/docs/plugins/reference/document-extract.md +23 -0
- package/docs/plugins/reference/duckduckgo.md +23 -0
- package/docs/plugins/reference/elevenlabs.md +23 -0
- package/docs/plugins/reference/exa.md +23 -0
- package/docs/plugins/reference/fal.md +23 -0
- package/docs/plugins/reference/feishu.md +23 -0
- package/docs/plugins/reference/file-transfer.md +19 -0
- package/docs/plugins/reference/firecrawl.md +23 -0
- package/docs/plugins/reference/fireworks.md +23 -0
- package/docs/plugins/reference/github-copilot.md +23 -0
- package/docs/plugins/reference/gmi.md +23 -0
- package/docs/plugins/reference/google-meet.md +23 -0
- package/docs/plugins/reference/google.md +23 -0
- package/docs/plugins/reference/googlechat.md +23 -0
- package/docs/plugins/reference/gradium.md +23 -0
- package/docs/plugins/reference/groq.md +23 -0
- package/docs/plugins/reference/huggingface.md +23 -0
- package/docs/plugins/reference/imessage.md +23 -0
- package/docs/plugins/reference/inworld.md +23 -0
- package/docs/plugins/reference/irc.md +23 -0
- package/docs/plugins/reference/kilocode.md +23 -0
- package/docs/plugins/reference/kimi.md +23 -0
- package/docs/plugins/reference/line.md +23 -0
- package/docs/plugins/reference/litellm.md +23 -0
- package/docs/plugins/reference/llama-cpp.md +23 -0
- package/docs/plugins/reference/llm-task.md +19 -0
- package/docs/plugins/reference/lmstudio.md +23 -0
- package/docs/plugins/reference/lobster.md +19 -0
- package/docs/plugins/reference/matrix.md +23 -0
- package/docs/plugins/reference/mattermost.md +23 -0
- package/docs/plugins/reference/memory-core.md +19 -0
- package/docs/plugins/reference/memory-lancedb.md +23 -0
- package/docs/plugins/reference/memory-wiki.md +23 -0
- package/docs/plugins/reference/microsoft-foundry.md +113 -0
- package/docs/plugins/reference/microsoft.md +19 -0
- package/docs/plugins/reference/migrate-claude.md +19 -0
- package/docs/plugins/reference/migrate-hermes.md +19 -0
- package/docs/plugins/reference/minimax.md +23 -0
- package/docs/plugins/reference/mistral.md +23 -0
- package/docs/plugins/reference/moonshot.md +23 -0
- package/docs/plugins/reference/msteams.md +23 -0
- package/docs/plugins/reference/nextcloud-talk.md +23 -0
- package/docs/plugins/reference/nostr.md +23 -0
- package/docs/plugins/reference/novita.md +23 -0
- package/docs/plugins/reference/nvidia.md +23 -0
- package/docs/plugins/reference/oc-path.md +23 -0
- package/docs/plugins/reference/ollama.md +24 -0
- package/docs/plugins/reference/open-prose.md +19 -0
- package/docs/plugins/reference/openai.md +23 -0
- package/docs/plugins/reference/opencode-go.md +23 -0
- package/docs/plugins/reference/opencode.md +23 -0
- package/docs/plugins/reference/openrouter.md +23 -0
- package/docs/plugins/reference/openshell.md +19 -0
- package/docs/plugins/reference/perplexity.md +23 -0
- package/docs/plugins/reference/pixverse.md +23 -0
- package/docs/plugins/reference/policy.md +79 -0
- package/docs/plugins/reference/qa-channel.md +23 -0
- package/docs/plugins/reference/qa-lab.md +19 -0
- package/docs/plugins/reference/qa-matrix.md +19 -0
- package/docs/plugins/reference/qianfan.md +23 -0
- package/docs/plugins/reference/qqbot.md +23 -0
- package/docs/plugins/reference/qwen.md +24 -0
- package/docs/plugins/reference/runway.md +23 -0
- package/docs/plugins/reference/searxng.md +19 -0
- package/docs/plugins/reference/senseaudio.md +23 -0
- package/docs/plugins/reference/sglang.md +23 -0
- package/docs/plugins/reference/signal.md +23 -0
- package/docs/plugins/reference/slack.md +23 -0
- package/docs/plugins/reference/sms.md +23 -0
- package/docs/plugins/reference/stepfun.md +23 -0
- package/docs/plugins/reference/synology-chat.md +23 -0
- package/docs/plugins/reference/synthetic.md +23 -0
- package/docs/plugins/reference/tavily.md +23 -0
- package/docs/plugins/reference/telegram.md +23 -0
- package/docs/plugins/reference/tencent.md +23 -0
- package/docs/plugins/reference/tlon.md +23 -0
- package/docs/plugins/reference/together.md +23 -0
- package/docs/plugins/reference/tokenjuice.md +23 -0
- package/docs/plugins/reference/tts-local-cli.md +19 -0
- package/docs/plugins/reference/twitch.md +23 -0
- package/docs/plugins/reference/venice.md +23 -0
- package/docs/plugins/reference/vercel-ai-gateway.md +23 -0
- package/docs/plugins/reference/vllm.md +23 -0
- package/docs/plugins/reference/voice-call.md +23 -0
- package/docs/plugins/reference/volcengine.md +23 -0
- package/docs/plugins/reference/voyage.md +19 -0
- package/docs/plugins/reference/vydra.md +23 -0
- package/docs/plugins/reference/web-readability.md +19 -0
- package/docs/plugins/reference/webhooks.md +23 -0
- package/docs/plugins/reference/whatsapp.md +23 -0
- package/docs/plugins/reference/workboard.md +23 -0
- package/docs/plugins/reference/xai.md +23 -0
- package/docs/plugins/reference/xiaomi.md +23 -0
- package/docs/plugins/reference/zai.md +23 -0
- package/docs/plugins/reference/zalo.md +23 -0
- package/docs/plugins/reference/zalouser.md +24 -0
- package/docs/plugins/reference.md +19 -0
- package/docs/plugins/sdk-agent-harness.md +339 -0
- package/docs/plugins/sdk-channel-inbound.md +70 -0
- package/docs/plugins/sdk-channel-ingress.md +137 -0
- package/docs/plugins/sdk-channel-message.md +18 -0
- package/docs/plugins/sdk-channel-outbound.md +113 -0
- package/docs/plugins/sdk-channel-plugins.md +770 -0
- package/docs/plugins/sdk-channel-turn.md +9 -0
- package/docs/plugins/sdk-entrypoints.md +344 -0
- package/docs/plugins/sdk-migration.md +1011 -0
- package/docs/plugins/sdk-overview.md +525 -0
- package/docs/plugins/sdk-provider-plugins.md +1019 -0
- package/docs/plugins/sdk-runtime.md +683 -0
- package/docs/plugins/sdk-setup.md +550 -0
- package/docs/plugins/sdk-subpaths.md +401 -0
- package/docs/plugins/sdk-testing.md +403 -0
- package/docs/plugins/tool-plugins.md +411 -0
- package/docs/plugins/voice-call.md +942 -0
- package/docs/plugins/webhooks.md +192 -0
- package/docs/plugins/workboard.md +402 -0
- package/docs/plugins/zalouser.md +86 -0
- package/docs/prose.md +191 -0
- package/docs/providers/alibaba.md +158 -0
- package/docs/providers/anthropic.md +386 -0
- package/docs/providers/arcee.md +144 -0
- package/docs/providers/azure-speech.md +119 -0
- package/docs/providers/bedrock-mantle.md +224 -0
- package/docs/providers/bedrock.md +433 -0
- package/docs/providers/cerebras.md +130 -0
- package/docs/providers/chutes.md +153 -0
- package/docs/providers/claude-max-api-proxy.md +191 -0
- package/docs/providers/cloudflare-ai-gateway.md +119 -0
- package/docs/providers/comfy.md +362 -0
- package/docs/providers/deepgram.md +184 -0
- package/docs/providers/deepinfra.md +92 -0
- package/docs/providers/deepseek.md +146 -0
- package/docs/providers/ds4.md +309 -0
- package/docs/providers/elevenlabs.md +130 -0
- package/docs/providers/fal.md +240 -0
- package/docs/providers/fireworks.md +144 -0
- package/docs/providers/github-copilot.md +257 -0
- package/docs/providers/gmi.md +92 -0
- package/docs/providers/google.md +472 -0
- package/docs/providers/gradium.md +123 -0
- package/docs/providers/groq.md +171 -0
- package/docs/providers/huggingface.md +235 -0
- package/docs/providers/index.md +105 -0
- package/docs/providers/inferrs.md +272 -0
- package/docs/providers/inworld.md +120 -0
- package/docs/providers/kilocode.md +135 -0
- package/docs/providers/litellm.md +234 -0
- package/docs/providers/lmstudio.md +224 -0
- package/docs/providers/minimax.md +518 -0
- package/docs/providers/mistral.md +235 -0
- package/docs/providers/models.md +64 -0
- package/docs/providers/moonshot.md +413 -0
- package/docs/providers/novita.md +92 -0
- package/docs/providers/nvidia.md +208 -0
- package/docs/providers/ollama-cloud.md +115 -0
- package/docs/providers/ollama.md +1225 -0
- package/docs/providers/openai.md +1091 -0
- package/docs/providers/opencode-go.md +123 -0
- package/docs/providers/opencode.md +149 -0
- package/docs/providers/openrouter.md +390 -0
- package/docs/providers/perplexity-provider.md +123 -0
- package/docs/providers/pixverse.md +165 -0
- package/docs/providers/qianfan.md +132 -0
- package/docs/providers/qwen-oauth.md +115 -0
- package/docs/providers/qwen.md +364 -0
- package/docs/providers/runway.md +103 -0
- package/docs/providers/senseaudio.md +68 -0
- package/docs/providers/sglang.md +161 -0
- package/docs/providers/stepfun.md +229 -0
- package/docs/providers/synthetic.md +154 -0
- package/docs/providers/tencent.md +130 -0
- package/docs/providers/together.md +140 -0
- package/docs/providers/venice.md +312 -0
- package/docs/providers/vercel-ai-gateway.md +128 -0
- package/docs/providers/vllm.md +407 -0
- package/docs/providers/volcengine.md +199 -0
- package/docs/providers/vydra.md +180 -0
- package/docs/providers/xai.md +571 -0
- package/docs/providers/xiaomi.md +286 -0
- package/docs/providers/zai.md +224 -0
- package/docs/refactor/access.md +9 -0
- package/docs/refactor/acp.md +298 -0
- package/docs/refactor/canvas.md +131 -0
- package/docs/refactor/database-first.md +2263 -0
- package/docs/refactor/ingress-core.md +341 -0
- package/docs/reference/AGENTS.default.md +131 -0
- package/docs/reference/RELEASING.md +844 -0
- package/docs/reference/api-usage-costs.md +208 -0
- package/docs/reference/application-modernization-plan.md +208 -0
- package/docs/reference/code-mode.md +1058 -0
- package/docs/reference/credits.md +33 -0
- package/docs/reference/device-models.md +50 -0
- package/docs/reference/full-release-validation.md +206 -0
- package/docs/reference/memory-config.md +630 -0
- package/docs/reference/prompt-caching.md +358 -0
- package/docs/reference/release-performance-sweep.md +347 -0
- package/docs/reference/rich-output-protocol.md +93 -0
- package/docs/reference/rpc.md +43 -0
- package/docs/reference/secret-placeholder-conventions.md +33 -0
- package/docs/reference/secretref-credential-surface.md +163 -0
- package/docs/reference/secretref-user-supplied-credentials-matrix.json +691 -0
- package/docs/reference/session-management-compaction.md +474 -0
- package/docs/reference/templates/AGENTS.dev.md +90 -0
- package/docs/reference/templates/AGENTS.md +227 -0
- package/docs/reference/templates/BOOT.md +16 -0
- package/docs/reference/templates/BOOTSTRAP.md +66 -0
- package/docs/reference/templates/HEARTBEAT.md +24 -0
- package/docs/reference/templates/IDENTITY.dev.md +52 -0
- package/docs/reference/templates/IDENTITY.md +34 -0
- package/docs/reference/templates/SOUL.dev.md +82 -0
- package/docs/reference/templates/SOUL.md +49 -0
- package/docs/reference/templates/TOOLS.dev.md +29 -0
- package/docs/reference/templates/TOOLS.md +51 -0
- package/docs/reference/templates/USER.dev.md +23 -0
- package/docs/reference/templates/USER.md +28 -0
- package/docs/reference/test.md +248 -0
- package/docs/reference/token-use.md +246 -0
- package/docs/reference/transcript-hygiene.md +226 -0
- package/docs/reference/wizard.md +252 -0
- package/docs/security/CONTRIBUTING-THREAT-MODEL.md +101 -0
- package/docs/security/THREAT-MODEL-ATLAS.md +611 -0
- package/docs/security/formal-verification.md +170 -0
- package/docs/security/incident-response.md +59 -0
- package/docs/security/network-proxy.md +268 -0
- package/docs/snippets/plugin-publish/minimal-openclaw.plugin.json +12 -0
- package/docs/snippets/plugin-publish/minimal-package.json +16 -0
- package/docs/specs/claw-supervisor.md +247 -0
- package/docs/start/bootstrapping.md +49 -0
- package/docs/start/docs-directory.md +69 -0
- package/docs/start/getting-started.md +152 -0
- package/docs/start/hubs.md +201 -0
- package/docs/start/lore.md +223 -0
- package/docs/start/onboarding-overview.md +72 -0
- package/docs/start/onboarding.md +98 -0
- package/docs/start/openclaw.md +246 -0
- package/docs/start/quickstart.md +25 -0
- package/docs/start/setup.md +178 -0
- package/docs/start/showcase.md +371 -0
- package/docs/start/wizard-cli-automation.md +232 -0
- package/docs/start/wizard-cli-reference.md +331 -0
- package/docs/start/wizard.md +142 -0
- package/docs/style.css +137 -0
- package/docs/tools/acp-agents-setup.md +356 -0
- package/docs/tools/acp-agents.md +865 -0
- package/docs/tools/agent-send.md +130 -0
- package/docs/tools/apply-patch.md +64 -0
- package/docs/tools/brave-search.md +139 -0
- package/docs/tools/browser-control.md +405 -0
- package/docs/tools/browser-linux-troubleshooting.md +173 -0
- package/docs/tools/browser-login.md +77 -0
- package/docs/tools/browser-wsl2-windows-remote-cdp-troubleshooting.md +219 -0
- package/docs/tools/browser.md +866 -0
- package/docs/tools/btw.md +159 -0
- package/docs/tools/capability-cookbook.md +12 -0
- package/docs/tools/clawhub.md +5 -0
- package/docs/tools/code-execution.md +173 -0
- package/docs/tools/creating-skills.md +271 -0
- package/docs/tools/diffs.md +527 -0
- package/docs/tools/duckduckgo-search.md +109 -0
- package/docs/tools/elevated.md +128 -0
- package/docs/tools/exa-search.md +152 -0
- package/docs/tools/exec-approvals-advanced.md +457 -0
- package/docs/tools/exec-approvals.md +520 -0
- package/docs/tools/exec.md +294 -0
- package/docs/tools/firecrawl.md +155 -0
- package/docs/tools/gemini-search.md +114 -0
- package/docs/tools/goal.md +217 -0
- package/docs/tools/grok-search.md +129 -0
- package/docs/tools/image-generation.md +538 -0
- package/docs/tools/index.md +176 -0
- package/docs/tools/kimi-search.md +105 -0
- package/docs/tools/llm-task.md +137 -0
- package/docs/tools/lobster.md +365 -0
- package/docs/tools/loop-detection.md +154 -0
- package/docs/tools/media-overview.md +161 -0
- package/docs/tools/minimax-search.md +102 -0
- package/docs/tools/multi-agent-sandbox-tools.md +409 -0
- package/docs/tools/music-generation.md +372 -0
- package/docs/tools/ollama-search.md +153 -0
- package/docs/tools/parallel-search.md +154 -0
- package/docs/tools/pdf.md +213 -0
- package/docs/tools/permission-modes.md +111 -0
- package/docs/tools/perplexity-search.md +220 -0
- package/docs/tools/plugin.md +378 -0
- package/docs/tools/reactions.md +100 -0
- package/docs/tools/searxng-search.md +141 -0
- package/docs/tools/skill-workshop.md +283 -0
- package/docs/tools/skills-config.md +457 -0
- package/docs/tools/skills.md +591 -0
- package/docs/tools/slash-commands.md +518 -0
- package/docs/tools/steer.md +77 -0
- package/docs/tools/subagents.md +652 -0
- package/docs/tools/tavily.md +162 -0
- package/docs/tools/thinking.md +142 -0
- package/docs/tools/tokenjuice.md +84 -0
- package/docs/tools/tool-search.md +269 -0
- package/docs/tools/trajectory.md +229 -0
- package/docs/tools/tts.md +1013 -0
- package/docs/tools/video-generation.md +555 -0
- package/docs/tools/web-fetch.md +210 -0
- package/docs/tools/web.md +476 -0
- package/docs/tts.md +11 -0
- package/docs/vps.md +139 -0
- package/docs/web/control-ui.md +530 -0
- package/docs/web/dashboard.md +107 -0
- package/docs/web/index.md +133 -0
- package/docs/web/tui.md +258 -0
- package/docs/web/webchat.md +102 -0
- package/npm-shrinkwrap.json +3485 -0
- package/openclaw.mjs +661 -0
- package/package.json +1971 -0
- package/patches/.gitkeep +0 -0
- package/patches/@agentclientprotocol__claude-agent-acp@0.39.0.patch +40 -0
- package/pnpm-workspace.yaml +124 -0
- package/scripts/crabbox-wrapper.mjs +2470 -0
- package/scripts/lib/official-external-channel-catalog.json +570 -0
- package/scripts/lib/official-external-plugin-catalog.json +284 -0
- package/scripts/lib/official-external-provider-catalog.json +158 -0
- package/scripts/lib/package-dist-imports.mjs +174 -0
- package/scripts/npm-runner.mjs +95 -0
- package/scripts/postinstall-bundled-plugins.mjs +978 -0
- package/scripts/preinstall-package-manager-warning.mjs +74 -0
- package/scripts/prepare-git-hooks.mjs +73 -0
- package/scripts/windows-cmd-helpers.mjs +29 -0
- package/skills/1password/SKILL.md +70 -0
- package/skills/1password/references/cli-examples.md +29 -0
- package/skills/1password/references/get-started.md +17 -0
- package/skills/apple-notes/SKILL.md +77 -0
- package/skills/apple-reminders/SKILL.md +118 -0
- package/skills/bear-notes/SKILL.md +107 -0
- package/skills/blogwatcher/SKILL.md +69 -0
- package/skills/blucli/SKILL.md +47 -0
- package/skills/camsnap/SKILL.md +45 -0
- package/skills/canvas/SKILL.md +78 -0
- package/skills/clawhub/SKILL.md +77 -0
- package/skills/coding-agent/SKILL.md +143 -0
- package/skills/diagram-maker/SKILL.md +53 -0
- package/skills/diagram-maker/references/excalidraw-patterns.md +85 -0
- package/skills/diagram-maker/references/svg-template.md +112 -0
- package/skills/discord/SKILL.md +136 -0
- package/skills/eightctl/SKILL.md +50 -0
- package/skills/gemini/SKILL.md +47 -0
- package/skills/gh-issues/SKILL.md +213 -0
- package/skills/gifgrep/SKILL.md +85 -0
- package/skills/github/SKILL.md +84 -0
- package/skills/gog/SKILL.md +116 -0
- package/skills/goplaces/SKILL.md +52 -0
- package/skills/healthcheck/SKILL.md +105 -0
- package/skills/himalaya/SKILL.md +80 -0
- package/skills/himalaya/references/configuration.md +184 -0
- package/skills/himalaya/references/message-composition.md +199 -0
- package/skills/imsg/SKILL.md +122 -0
- package/skills/mcporter/SKILL.md +61 -0
- package/skills/meme-maker/SKILL.md +42 -0
- package/skills/meme-maker/references/templates.json +358 -0
- package/skills/meme-maker/scripts/meme.mjs +398 -0
- package/skills/model-usage/SKILL.md +71 -0
- package/skills/model-usage/references/codexbar-cli.md +33 -0
- package/skills/model-usage/scripts/model_usage.py +319 -0
- package/skills/model-usage/scripts/test_model_usage.py +40 -0
- package/skills/nano-pdf/SKILL.md +38 -0
- package/skills/node-connect/SKILL.md +143 -0
- package/skills/node-inspect-debugger/SKILL.md +85 -0
- package/skills/notion/SKILL.md +150 -0
- package/skills/obsidian/SKILL.md +119 -0
- package/skills/openai-whisper/SKILL.md +38 -0
- package/skills/openai-whisper-api/SKILL.md +71 -0
- package/skills/openai-whisper-api/scripts/transcribe.sh +154 -0
- package/skills/openhue/SKILL.md +112 -0
- package/skills/oracle/SKILL.md +126 -0
- package/skills/ordercli/SKILL.md +78 -0
- package/skills/peekaboo/SKILL.md +198 -0
- package/skills/pyproject.toml +10 -0
- package/skills/python-debugpy/SKILL.md +73 -0
- package/skills/sag/SKILL.md +87 -0
- package/skills/session-logs/SKILL.md +151 -0
- package/skills/sherpa-onnx-tts/SKILL.md +109 -0
- package/skills/sherpa-onnx-tts/bin/sherpa-onnx-tts +178 -0
- package/skills/skill-creator/SKILL.md +78 -0
- package/skills/skill-creator/license.txt +202 -0
- package/skills/skill-creator/scripts/init_skill.py +378 -0
- package/skills/skill-creator/scripts/package_skill.py +144 -0
- package/skills/skill-creator/scripts/quick_validate.py +169 -0
- package/skills/skill-creator/scripts/test_package_skill.py +199 -0
- package/skills/skill-creator/scripts/test_quick_validate.py +116 -0
- package/skills/slack/SKILL.md +78 -0
- package/skills/songsee/SKILL.md +49 -0
- package/skills/sonoscli/SKILL.md +65 -0
- package/skills/spike/SKILL.md +51 -0
- package/skills/spotify-player/SKILL.md +64 -0
- package/skills/summarize/SKILL.md +87 -0
- package/skills/taskflow/SKILL.md +149 -0
- package/skills/taskflow/examples/inbox-triage.lobster +33 -0
- package/skills/taskflow/examples/pr-intake.lobster +32 -0
- package/skills/taskflow-inbox-triage/SKILL.md +119 -0
- package/skills/things-mac/SKILL.md +86 -0
- package/skills/tmux/SKILL.md +91 -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 +108 -0
- package/skills/video-frames/SKILL.md +46 -0
- package/skills/video-frames/scripts/frame.sh +81 -0
- package/skills/voice-call/SKILL.md +45 -0
- package/skills/wacli/SKILL.md +72 -0
- package/skills/weather/SKILL.md +87 -0
- package/skills/xurl/SKILL.md +120 -0
- package/src/agents/templates/HEARTBEAT.md +5 -0
|
@@ -0,0 +1,2470 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Resolves and delegates to the repo-local or PATH crabbox binary.
|
|
3
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
4
|
+
import {
|
|
5
|
+
accessSync,
|
|
6
|
+
chmodSync,
|
|
7
|
+
constants,
|
|
8
|
+
cpSync,
|
|
9
|
+
mkdirSync,
|
|
10
|
+
mkdtempSync,
|
|
11
|
+
readFileSync,
|
|
12
|
+
readdirSync,
|
|
13
|
+
rmSync,
|
|
14
|
+
statfsSync,
|
|
15
|
+
statSync,
|
|
16
|
+
utimesSync,
|
|
17
|
+
writeFileSync,
|
|
18
|
+
} from "node:fs";
|
|
19
|
+
import { homedir, tmpdir } from "node:os";
|
|
20
|
+
import { delimiter, dirname, extname, isAbsolute, relative, resolve } from "node:path";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
22
|
+
import { resolvePathEnvKey } from "./windows-cmd-helpers.mjs";
|
|
23
|
+
|
|
24
|
+
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
25
|
+
const ignoreRepoBinary = process.env.OPENCLAW_CRABBOX_WRAPPER_IGNORE_REPO_BINARY === "1";
|
|
26
|
+
const repoLocal = ignoreRepoBinary ? null : resolveCrabboxBinary(process.env, process.platform);
|
|
27
|
+
const pathLocal = resolvePathBinary("crabbox", process.env, process.platform);
|
|
28
|
+
const binary =
|
|
29
|
+
repoLocal ??
|
|
30
|
+
pathLocal ??
|
|
31
|
+
resolveGitCommonCrabboxBinary(process.env, process.platform) ??
|
|
32
|
+
"crabbox";
|
|
33
|
+
const args = process.argv.slice(2);
|
|
34
|
+
|
|
35
|
+
if (args[0] === "--") {
|
|
36
|
+
args.shift();
|
|
37
|
+
}
|
|
38
|
+
const userArgStart = args[0] === "actions" && args[1] === "hydrate" ? 2 : 1;
|
|
39
|
+
if (args[userArgStart] === "--") {
|
|
40
|
+
args.splice(userArgStart, 1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function commandCandidates(command, platform) {
|
|
44
|
+
if (platform !== "win32") {
|
|
45
|
+
return [command];
|
|
46
|
+
}
|
|
47
|
+
if (extname(command)) {
|
|
48
|
+
return [command];
|
|
49
|
+
}
|
|
50
|
+
return [`${command}.exe`, `${command}.cmd`, `${command}.bat`, `${command}.com`, command];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function resolveCrabboxBinary(env, platform) {
|
|
54
|
+
const base = resolve(repoRoot, "../crabbox/bin/crabbox");
|
|
55
|
+
for (const candidate of commandCandidates(base, platform)) {
|
|
56
|
+
if (isExecutableFile(candidate, platform)) {
|
|
57
|
+
return candidate;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function resolvePathBinary(command, env, platform) {
|
|
64
|
+
const pathValue = env[resolvePathEnvKey(env)] ?? "";
|
|
65
|
+
for (const dir of pathValue.split(delimiter).filter(Boolean)) {
|
|
66
|
+
for (const candidate of commandCandidates(command, platform)) {
|
|
67
|
+
const fullPath = resolve(dir, candidate);
|
|
68
|
+
if (isExecutableFile(fullPath, platform)) {
|
|
69
|
+
return fullPath;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function resolveGitCommonCrabboxBinary(env, platform) {
|
|
77
|
+
const gitBinary = resolvePathBinary("git", env, platform) ?? "git";
|
|
78
|
+
const invocation = spawnInvocation(gitBinary, ["rev-parse", "--git-common-dir"], env, platform);
|
|
79
|
+
const result = spawnSync(invocation.command, invocation.args, {
|
|
80
|
+
cwd: repoRoot,
|
|
81
|
+
encoding: "utf8",
|
|
82
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
83
|
+
windowsVerbatimArguments: invocation.windowsVerbatimArguments,
|
|
84
|
+
});
|
|
85
|
+
if ((result.status ?? 1) !== 0) {
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
const gitCommonDir = result.stdout.trim();
|
|
89
|
+
if (!gitCommonDir) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
const absoluteGitCommonDir = isAbsolute(gitCommonDir)
|
|
93
|
+
? gitCommonDir
|
|
94
|
+
: resolve(repoRoot, gitCommonDir);
|
|
95
|
+
const base = resolve(absoluteGitCommonDir, "../..", "crabbox/bin/crabbox");
|
|
96
|
+
for (const candidate of commandCandidates(base, platform)) {
|
|
97
|
+
if (isExecutableFile(candidate, platform)) {
|
|
98
|
+
return candidate;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isExecutableFile(path, platform) {
|
|
105
|
+
try {
|
|
106
|
+
if (!statSync(path).isFile()) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
if (platform !== "win32") {
|
|
110
|
+
accessSync(path, constants.X_OK);
|
|
111
|
+
}
|
|
112
|
+
return true;
|
|
113
|
+
} catch {
|
|
114
|
+
return false;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function spawnInvocation(command, commandArgs, env, platform) {
|
|
119
|
+
const extension = extname(command).toLowerCase();
|
|
120
|
+
if (platform === "win32" && (extension === ".cmd" || extension === ".bat")) {
|
|
121
|
+
return {
|
|
122
|
+
command: env.ComSpec ?? "cmd.exe",
|
|
123
|
+
args: ["/d", "/s", "/c", buildBatchCommandLine(command, commandArgs)],
|
|
124
|
+
windowsVerbatimArguments: true,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
return { command, args: commandArgs };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const cmdMetaCharactersRe = /([()\][%!^"`<>&|;, *?])/g;
|
|
131
|
+
const jsRuntimeEntrypoints = new Set(["pnpm", "npm", "npx", "corepack", "node", "yarn", "bun"]);
|
|
132
|
+
const awsMacosCorepackEntrypoints = new Set(["pnpm", "yarn", "corepack"]);
|
|
133
|
+
const minimumBlacksmithCrabboxVersion = [0, 22, 0];
|
|
134
|
+
const shellControlCommandPrefixes = new Set([
|
|
135
|
+
"if",
|
|
136
|
+
"while",
|
|
137
|
+
"until",
|
|
138
|
+
"then",
|
|
139
|
+
"do",
|
|
140
|
+
"else",
|
|
141
|
+
"elif",
|
|
142
|
+
"!",
|
|
143
|
+
]);
|
|
144
|
+
const shellCommandExecutionPrefixes = new Set(["exec"]);
|
|
145
|
+
const shellInlineCommandInterpreters = new Set(["bash", "dash", "ksh", "sh", "zsh"]);
|
|
146
|
+
const remoteChangedGateEnv = [
|
|
147
|
+
"OPENCLAW_CHECK_CHANGED_REMOTE_CHILD=1",
|
|
148
|
+
"OPENCLAW_CHANGED_LANES_RAW_SYNC=1",
|
|
149
|
+
"CI=1",
|
|
150
|
+
];
|
|
151
|
+
const shellInlineCommandOptionsWithNextValue = new Set([
|
|
152
|
+
"+O",
|
|
153
|
+
"+o",
|
|
154
|
+
"-O",
|
|
155
|
+
"-o",
|
|
156
|
+
"--init-file",
|
|
157
|
+
"--rcfile",
|
|
158
|
+
]);
|
|
159
|
+
|
|
160
|
+
function escapeBatchCommand(command) {
|
|
161
|
+
return `${command}`.replace(cmdMetaCharactersRe, "^$1");
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function escapeBatchArgument(arg) {
|
|
165
|
+
let escaped = `${arg}`;
|
|
166
|
+
escaped = escaped.replace(/(?=(\\+?)?)\1"/g, '$1$1\\"');
|
|
167
|
+
escaped = escaped.replace(/(?=(\\+?)?)\1$/, "$1$1");
|
|
168
|
+
escaped = `"${escaped}"`;
|
|
169
|
+
escaped = escaped.replace(cmdMetaCharactersRe, "^$1");
|
|
170
|
+
return escaped.replace(cmdMetaCharactersRe, "^$1");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function buildBatchCommandLine(command, commandArgs) {
|
|
174
|
+
const escapedCommand = escapeBatchCommand(command);
|
|
175
|
+
const escapedArgs = commandArgs.map(escapeBatchArgument);
|
|
176
|
+
return `"${[escapedCommand, ...escapedArgs].join(" ")}"`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function checkedOutput(command, commandArgs) {
|
|
180
|
+
const invocation = spawnInvocation(command, commandArgs, process.env, process.platform);
|
|
181
|
+
const result = spawnSync(invocation.command, invocation.args, {
|
|
182
|
+
cwd: repoRoot,
|
|
183
|
+
encoding: "utf8",
|
|
184
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
185
|
+
windowsVerbatimArguments: invocation.windowsVerbatimArguments,
|
|
186
|
+
timeout: 5_000,
|
|
187
|
+
killSignal: "SIGKILL",
|
|
188
|
+
});
|
|
189
|
+
const timedOut = result.error?.name === "Error" && result.signal === "SIGKILL";
|
|
190
|
+
return {
|
|
191
|
+
status: timedOut ? 124 : (result.status ?? 1),
|
|
192
|
+
text: `${result.stdout ?? ""}${result.stderr ?? ""}`.trim(),
|
|
193
|
+
stdout: (result.stdout ?? "").trim(),
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function parseCrabboxVersion(value) {
|
|
198
|
+
const match = `${value}`.match(/\bv?(\d+)\.(\d+)\.(\d+)(?:-([^\s+]+))?(?:\+[^\s]+)?\b/u);
|
|
199
|
+
if (!match) {
|
|
200
|
+
return null;
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
tuple: match.slice(1, 4).map((part) => Number.parseInt(part, 10)),
|
|
204
|
+
suffix: match[4] ?? "",
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function compareVersionTuples(left, right) {
|
|
209
|
+
for (let index = 0; index < 3; index += 1) {
|
|
210
|
+
const diff = left[index] - right[index];
|
|
211
|
+
if (diff !== 0) {
|
|
212
|
+
return diff;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return 0;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function formatVersionTuple(version) {
|
|
219
|
+
return version.join(".");
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function isPostReleaseDescribeSuffix(suffix) {
|
|
223
|
+
return /^\d+-g[0-9a-f]+(?:-dirty)?$/iu.test(suffix);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function satisfiesMinimumCrabboxVersion(version, minimum) {
|
|
227
|
+
const parsed = parseCrabboxVersion(version);
|
|
228
|
+
if (!parsed) {
|
|
229
|
+
return false;
|
|
230
|
+
}
|
|
231
|
+
const comparison = compareVersionTuples(parsed.tuple, minimum);
|
|
232
|
+
if (comparison !== 0) {
|
|
233
|
+
return comparison > 0;
|
|
234
|
+
}
|
|
235
|
+
return !parsed.suffix || isPostReleaseDescribeSuffix(parsed.suffix);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function gitOutput(commandArgs) {
|
|
239
|
+
const gitBinary = resolvePathBinary("git", process.env, process.platform) ?? "git";
|
|
240
|
+
const invocation = spawnInvocation(gitBinary, commandArgs, process.env, process.platform);
|
|
241
|
+
const result = spawnSync(invocation.command, invocation.args, {
|
|
242
|
+
cwd: repoRoot,
|
|
243
|
+
encoding: "utf8",
|
|
244
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
245
|
+
windowsVerbatimArguments: invocation.windowsVerbatimArguments,
|
|
246
|
+
});
|
|
247
|
+
return {
|
|
248
|
+
status: result.status ?? 1,
|
|
249
|
+
text: `${result.stdout ?? ""}${result.stderr ?? ""}`.trim(),
|
|
250
|
+
stdout: (result.stdout ?? "").trim(),
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function envProvider() {
|
|
255
|
+
const envProviderValue = process.env.CRABBOX_PROVIDER?.trim();
|
|
256
|
+
if (envProviderValue) {
|
|
257
|
+
return envProviderValue;
|
|
258
|
+
}
|
|
259
|
+
return "";
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function configProvider() {
|
|
263
|
+
try {
|
|
264
|
+
const config = readFileSync(resolve(repoRoot, ".crabbox.yaml"), "utf8");
|
|
265
|
+
const match = config.match(/^provider:\s*([^\s#]+)/m);
|
|
266
|
+
return match?.[1] ?? "aws";
|
|
267
|
+
} catch {
|
|
268
|
+
return "aws";
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function configuredProvider() {
|
|
273
|
+
return envProvider() || configProvider();
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const runValueOptions = new Set([
|
|
277
|
+
"allow-env",
|
|
278
|
+
"artifact-glob",
|
|
279
|
+
"azure-location",
|
|
280
|
+
"azure-os-disk",
|
|
281
|
+
"azure-resource-group",
|
|
282
|
+
"azure-subnet",
|
|
283
|
+
"azure-vnet",
|
|
284
|
+
"blacksmith-job",
|
|
285
|
+
"blacksmith-org",
|
|
286
|
+
"blacksmith-ref",
|
|
287
|
+
"blacksmith-workflow",
|
|
288
|
+
"capture-stderr",
|
|
289
|
+
"capture-stdout",
|
|
290
|
+
"class",
|
|
291
|
+
"cloudflare-url",
|
|
292
|
+
"cloudflare-workdir",
|
|
293
|
+
"daytona-api-url",
|
|
294
|
+
"daytona-snapshot",
|
|
295
|
+
"daytona-ssh-access-minutes",
|
|
296
|
+
"daytona-ssh-gateway-host",
|
|
297
|
+
"daytona-target",
|
|
298
|
+
"daytona-user",
|
|
299
|
+
"daytona-work-root",
|
|
300
|
+
"download",
|
|
301
|
+
"env-from-profile",
|
|
302
|
+
"env-helper",
|
|
303
|
+
"e2b-api-url",
|
|
304
|
+
"e2b-domain",
|
|
305
|
+
"e2b-template",
|
|
306
|
+
"e2b-user",
|
|
307
|
+
"e2b-workdir",
|
|
308
|
+
"fresh-pr",
|
|
309
|
+
"id",
|
|
310
|
+
"idle-timeout",
|
|
311
|
+
"islo-base-url",
|
|
312
|
+
"islo-disk-gb",
|
|
313
|
+
"islo-gateway-profile",
|
|
314
|
+
"islo-image",
|
|
315
|
+
"islo-memory-mb",
|
|
316
|
+
"islo-snapshot-name",
|
|
317
|
+
"islo-vcpus",
|
|
318
|
+
"islo-workdir",
|
|
319
|
+
"junit",
|
|
320
|
+
"label",
|
|
321
|
+
"market",
|
|
322
|
+
"modal-app",
|
|
323
|
+
"modal-image",
|
|
324
|
+
"modal-python",
|
|
325
|
+
"modal-workdir",
|
|
326
|
+
"namespace-auto-stop-idle-timeout",
|
|
327
|
+
"namespace-image",
|
|
328
|
+
"namespace-repository",
|
|
329
|
+
"namespace-site",
|
|
330
|
+
"namespace-size",
|
|
331
|
+
"namespace-volume-size-gb",
|
|
332
|
+
"namespace-work-root",
|
|
333
|
+
"network",
|
|
334
|
+
"preflight-tools",
|
|
335
|
+
"profile",
|
|
336
|
+
"proof-template",
|
|
337
|
+
"provider",
|
|
338
|
+
"proxmox-api-url",
|
|
339
|
+
"proxmox-bridge",
|
|
340
|
+
"proxmox-node",
|
|
341
|
+
"proxmox-pool",
|
|
342
|
+
"proxmox-storage",
|
|
343
|
+
"proxmox-template-id",
|
|
344
|
+
"proxmox-user",
|
|
345
|
+
"proxmox-work-root",
|
|
346
|
+
"script",
|
|
347
|
+
"scenario",
|
|
348
|
+
"semaphore-host",
|
|
349
|
+
"semaphore-idle-timeout",
|
|
350
|
+
"semaphore-machine",
|
|
351
|
+
"semaphore-os-image",
|
|
352
|
+
"semaphore-project",
|
|
353
|
+
"sprites-api-url",
|
|
354
|
+
"sprites-work-root",
|
|
355
|
+
"static-host",
|
|
356
|
+
"static-port",
|
|
357
|
+
"static-user",
|
|
358
|
+
"static-work-root",
|
|
359
|
+
"stop-after",
|
|
360
|
+
"tailscale-auth-key-env",
|
|
361
|
+
"tailscale-exit-node",
|
|
362
|
+
"tailscale-hostname-template",
|
|
363
|
+
"tailscale-tags",
|
|
364
|
+
"target",
|
|
365
|
+
"tensorlake-api-url",
|
|
366
|
+
"tensorlake-cli",
|
|
367
|
+
"tensorlake-cpus",
|
|
368
|
+
"tensorlake-disk-mb",
|
|
369
|
+
"tensorlake-image",
|
|
370
|
+
"tensorlake-memory-mb",
|
|
371
|
+
"tensorlake-namespace",
|
|
372
|
+
"tensorlake-organization-id",
|
|
373
|
+
"tensorlake-project-id",
|
|
374
|
+
"tensorlake-snapshot",
|
|
375
|
+
"tensorlake-timeout-secs",
|
|
376
|
+
"tensorlake-workdir",
|
|
377
|
+
"ttl",
|
|
378
|
+
"type",
|
|
379
|
+
"emit-proof",
|
|
380
|
+
"preset",
|
|
381
|
+
"preset-var",
|
|
382
|
+
"windows-mode",
|
|
383
|
+
]);
|
|
384
|
+
|
|
385
|
+
let runValueOptionsFromHelp;
|
|
386
|
+
|
|
387
|
+
function parseRunValueOptionsFromHelp(text) {
|
|
388
|
+
const names = new Set();
|
|
389
|
+
for (const line of text.split(/\r?\n/u)) {
|
|
390
|
+
const match = line.match(
|
|
391
|
+
/^\s+-{1,2}([a-z0-9][a-z0-9-]*)\s+(?:string|duration|int|float|value)\b/u,
|
|
392
|
+
);
|
|
393
|
+
if (match) {
|
|
394
|
+
names.add(match[1]);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
return names;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
function currentRunValueOptions() {
|
|
401
|
+
if (!runValueOptionsFromHelp) {
|
|
402
|
+
runValueOptionsFromHelp = new Set([
|
|
403
|
+
...runValueOptions,
|
|
404
|
+
...parseRunValueOptionsFromHelp(help.text),
|
|
405
|
+
]);
|
|
406
|
+
}
|
|
407
|
+
return runValueOptionsFromHelp;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
function runOptionName(arg) {
|
|
411
|
+
return arg.replace(/^-+/u, "").split("=", 1)[0];
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function runCommandBounds(commandArgs) {
|
|
415
|
+
if (commandArgs[0] !== "run") {
|
|
416
|
+
return { start: -1, optionEnd: commandArgs.length };
|
|
417
|
+
}
|
|
418
|
+
for (let index = 1; index < commandArgs.length; index += 1) {
|
|
419
|
+
const arg = commandArgs[index];
|
|
420
|
+
if (arg === "--") {
|
|
421
|
+
return { start: index + 1, optionEnd: index };
|
|
422
|
+
}
|
|
423
|
+
if (!arg.startsWith("-")) {
|
|
424
|
+
return { start: index, optionEnd: index };
|
|
425
|
+
}
|
|
426
|
+
if (!arg.includes("=") && currentRunValueOptions().has(runOptionName(arg))) {
|
|
427
|
+
index += 1;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return { start: -1, optionEnd: commandArgs.length };
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function crabboxOptionArgs(commandArgs) {
|
|
434
|
+
const bounds = runCommandBounds(commandArgs);
|
|
435
|
+
if (commandArgs[0] === "run") {
|
|
436
|
+
return commandArgs.slice(0, bounds.optionEnd);
|
|
437
|
+
}
|
|
438
|
+
const delimiterCandidate = commandArgs.indexOf("--");
|
|
439
|
+
return delimiterCandidate >= 0 ? commandArgs.slice(0, delimiterCandidate) : commandArgs;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function commandProvider(commandArgsInput) {
|
|
443
|
+
let commandArgs = commandArgsInput;
|
|
444
|
+
commandArgs = crabboxOptionArgs(commandArgs);
|
|
445
|
+
for (let index = 0; index < commandArgs.length; index += 1) {
|
|
446
|
+
const arg = commandArgs[index];
|
|
447
|
+
if (arg === "--provider" || arg === "-provider") {
|
|
448
|
+
return commandArgs[index + 1] ?? "";
|
|
449
|
+
}
|
|
450
|
+
if (arg.startsWith("--provider=") || arg.startsWith("-provider=")) {
|
|
451
|
+
return arg.slice(arg.indexOf("=") + 1);
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
return "";
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function selectedProvider(commandArgs, advertisedProviders = []) {
|
|
458
|
+
const explicitProvider = commandProvider(commandArgs);
|
|
459
|
+
if (explicitProvider) {
|
|
460
|
+
return explicitProvider;
|
|
461
|
+
}
|
|
462
|
+
if (shouldPreferAzureForWindows(commandArgs, advertisedProviders)) {
|
|
463
|
+
return "azure";
|
|
464
|
+
}
|
|
465
|
+
return configuredProvider();
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function shouldRequireBrokeredAws(commandArgs, providerName) {
|
|
469
|
+
if (process.env.OPENCLAW_CRABBOX_ALLOW_DIRECT_AWS === "1") {
|
|
470
|
+
return false;
|
|
471
|
+
}
|
|
472
|
+
const canonicalProvider = providerAliases.get(providerName) ?? providerName;
|
|
473
|
+
if (canonicalProvider !== "aws") {
|
|
474
|
+
return false;
|
|
475
|
+
}
|
|
476
|
+
if (commandArgs[0] === "run" || commandArgs[0] === "warmup") {
|
|
477
|
+
return true;
|
|
478
|
+
}
|
|
479
|
+
return commandArgs[0] === "actions" && commandArgs[1] === "hydrate";
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
function brokerAuthConfigured() {
|
|
483
|
+
const config = checkedOutput(binary, ["config", "show", "--json"]);
|
|
484
|
+
if (config.status !== 0) {
|
|
485
|
+
return false;
|
|
486
|
+
}
|
|
487
|
+
let parsed;
|
|
488
|
+
try {
|
|
489
|
+
parsed = JSON.parse(config.stdout || config.text);
|
|
490
|
+
} catch {
|
|
491
|
+
return false;
|
|
492
|
+
}
|
|
493
|
+
if (!parsed?.coordinator || parsed?.brokerAuth !== "configured") {
|
|
494
|
+
return false;
|
|
495
|
+
}
|
|
496
|
+
return checkedOutput(binary, ["whoami"]).status === 0;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
function enforceBrokeredAws(commandArgs, providerName) {
|
|
500
|
+
if (!shouldRequireBrokeredAws(commandArgs, providerName) || brokerAuthConfigured()) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
console.error(
|
|
504
|
+
[
|
|
505
|
+
"[crabbox] provider=aws requires a configured Crabbox broker for OpenClaw proof.",
|
|
506
|
+
"[crabbox] run `crabbox login --url https://crabbox.openclaw.ai --provider aws`, then retry.",
|
|
507
|
+
"[crabbox] for intentional direct AWS provider debugging, set OPENCLAW_CRABBOX_ALLOW_DIRECT_AWS=1.",
|
|
508
|
+
].join("\n"),
|
|
509
|
+
);
|
|
510
|
+
process.exit(2);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function optionValue(commandArgsInput, name) {
|
|
514
|
+
let commandArgs = commandArgsInput;
|
|
515
|
+
commandArgs = crabboxOptionArgs(commandArgs);
|
|
516
|
+
for (let index = 0; index < commandArgs.length; index += 1) {
|
|
517
|
+
const arg = commandArgs[index];
|
|
518
|
+
if (arg === name || arg === name.replace(/^--/u, "-")) {
|
|
519
|
+
return commandArgs[index + 1] ?? "";
|
|
520
|
+
}
|
|
521
|
+
if (arg.startsWith(`${name}=`) || arg.startsWith(`${name.replace(/^--/u, "-")}=`)) {
|
|
522
|
+
return arg.slice(arg.indexOf("=") + 1);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return "";
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function hasOption(commandArgsInput, name) {
|
|
529
|
+
let commandArgs = commandArgsInput;
|
|
530
|
+
commandArgs = crabboxOptionArgs(commandArgs);
|
|
531
|
+
const shortName = name.replace(/^--/u, "-");
|
|
532
|
+
for (const arg of commandArgs) {
|
|
533
|
+
if (
|
|
534
|
+
arg === name ||
|
|
535
|
+
arg === shortName ||
|
|
536
|
+
arg.startsWith(`${name}=`) ||
|
|
537
|
+
arg.startsWith(`${shortName}=`)
|
|
538
|
+
) {
|
|
539
|
+
return true;
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return false;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
function commandOptionEnd(commandArgs) {
|
|
546
|
+
if (commandArgs[0] === "run") {
|
|
547
|
+
return runCommandBounds(commandArgs).optionEnd;
|
|
548
|
+
}
|
|
549
|
+
const delimiterEntry = commandArgs.indexOf("--");
|
|
550
|
+
return delimiterEntry >= 0 ? delimiterEntry : commandArgs.length;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
function shouldPreferAzureForWindows(commandArgs, advertisedProviders = []) {
|
|
554
|
+
return (
|
|
555
|
+
["run", "warmup"].includes(commandArgs[0]) &&
|
|
556
|
+
isWindowsRemoteTarget(commandArgs) &&
|
|
557
|
+
!commandProvider(commandArgs) &&
|
|
558
|
+
!envProvider() &&
|
|
559
|
+
!hasOption(commandArgs, "--id") &&
|
|
560
|
+
advertisedProviders.includes("azure")
|
|
561
|
+
);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function ensureAzureWindowsProvider(commandArgs, providerName, advertisedProviders = []) {
|
|
565
|
+
if (providerName !== "azure" || !shouldPreferAzureForWindows(commandArgs, advertisedProviders)) {
|
|
566
|
+
return commandArgs;
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const optionEnd = commandOptionEnd(commandArgs);
|
|
570
|
+
const normalizedArgs = [...commandArgs];
|
|
571
|
+
normalizedArgs.splice(optionEnd, 0, "--provider", "azure");
|
|
572
|
+
return normalizedArgs;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function ensureAwsMacOnDemandMarket(commandArgs, providerName) {
|
|
576
|
+
if (
|
|
577
|
+
!["run", "warmup"].includes(commandArgs[0]) ||
|
|
578
|
+
providerName !== "aws" ||
|
|
579
|
+
optionValue(commandArgs, "--target") !== "macos" ||
|
|
580
|
+
hasOption(commandArgs, "--market") ||
|
|
581
|
+
hasOption(commandArgs, "--id")
|
|
582
|
+
) {
|
|
583
|
+
return commandArgs;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
const optionEnd = commandOptionEnd(commandArgs);
|
|
587
|
+
const normalizedArgs = [...commandArgs];
|
|
588
|
+
normalizedArgs.splice(optionEnd, 0, "--market", "on-demand");
|
|
589
|
+
return normalizedArgs;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const localPathRunOptions = new Set([
|
|
593
|
+
"capture-stderr",
|
|
594
|
+
"capture-stdout",
|
|
595
|
+
"emit-proof",
|
|
596
|
+
"env-from-profile",
|
|
597
|
+
"script",
|
|
598
|
+
]);
|
|
599
|
+
|
|
600
|
+
function repoRelativePath(value) {
|
|
601
|
+
if (!value || value === "-" || isAbsolute(value)) {
|
|
602
|
+
return value;
|
|
603
|
+
}
|
|
604
|
+
return resolve(repoRoot, value);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function repoRelativeDownload(value) {
|
|
608
|
+
const split = value.indexOf("=");
|
|
609
|
+
if (split < 0) {
|
|
610
|
+
return value;
|
|
611
|
+
}
|
|
612
|
+
const remote = value.slice(0, split + 1);
|
|
613
|
+
const local = value.slice(split + 1);
|
|
614
|
+
return `${remote}${repoRelativePath(local)}`;
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
function absolutizeLocalRunPaths(commandArgs) {
|
|
618
|
+
if (commandArgs[0] !== "run") {
|
|
619
|
+
return commandArgs;
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const normalizedArgs = [...commandArgs];
|
|
623
|
+
const { optionEnd } = runCommandBounds(normalizedArgs);
|
|
624
|
+
for (let index = 1; index < optionEnd; index += 1) {
|
|
625
|
+
const arg = normalizedArgs[index];
|
|
626
|
+
if (!arg.startsWith("-")) {
|
|
627
|
+
continue;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
const optionName = runOptionName(arg);
|
|
631
|
+
const absolutize = optionName === "download" ? repoRelativeDownload : repoRelativePath;
|
|
632
|
+
if (localPathRunOptions.has(optionName) || optionName === "download") {
|
|
633
|
+
const equals = arg.indexOf("=");
|
|
634
|
+
if (equals >= 0) {
|
|
635
|
+
normalizedArgs[index] = `${arg.slice(0, equals + 1)}${absolutize(arg.slice(equals + 1))}`;
|
|
636
|
+
} else if (index + 1 < optionEnd) {
|
|
637
|
+
normalizedArgs[index + 1] = absolutize(normalizedArgs[index + 1]);
|
|
638
|
+
index += 1;
|
|
639
|
+
}
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
if (!arg.includes("=") && currentRunValueOptions().has(optionName)) {
|
|
644
|
+
index += 1;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
return normalizedArgs;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function pathExists(path) {
|
|
651
|
+
try {
|
|
652
|
+
statSync(path);
|
|
653
|
+
return true;
|
|
654
|
+
} catch {
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
function preserveTemporaryCrabboxRuns() {
|
|
660
|
+
if (childCwd === repoRoot) {
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
const sourceRuns = resolve(childCwd, ".crabbox", "runs");
|
|
665
|
+
if (!pathExists(sourceRuns)) {
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
const targetRuns = resolve(repoRoot, ".crabbox", "runs");
|
|
670
|
+
mkdirSync(targetRuns, { recursive: true });
|
|
671
|
+
let preserved = 0;
|
|
672
|
+
for (const entry of readdirSync(sourceRuns)) {
|
|
673
|
+
cpSync(resolve(sourceRuns, entry), resolve(targetRuns, entry), {
|
|
674
|
+
recursive: true,
|
|
675
|
+
force: true,
|
|
676
|
+
});
|
|
677
|
+
preserved += 1;
|
|
678
|
+
}
|
|
679
|
+
if (preserved > 0) {
|
|
680
|
+
console.error(
|
|
681
|
+
`[crabbox] preserved ${preserved} temporary run artifact ${preserved === 1 ? "directory" : "directories"} under ${relative(repoRoot, targetRuns)}`,
|
|
682
|
+
);
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
function shellQuote(value) {
|
|
687
|
+
const text = `${value}`;
|
|
688
|
+
if (text === "") {
|
|
689
|
+
return "''";
|
|
690
|
+
}
|
|
691
|
+
if (/^[A-Za-z0-9_./:=@%+-]+$/u.test(text)) {
|
|
692
|
+
return text;
|
|
693
|
+
}
|
|
694
|
+
return `'${text.replaceAll("'", "'\\''")}'`;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
function shellJoin(commandArgs) {
|
|
698
|
+
return commandArgs.map(shellQuote).join(" ");
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
function powershellQuote(value) {
|
|
702
|
+
const text = `${value}`;
|
|
703
|
+
if (text === "") {
|
|
704
|
+
return "''";
|
|
705
|
+
}
|
|
706
|
+
if (/^[A-Za-z0-9_./:=%+-]+$/u.test(text)) {
|
|
707
|
+
return text;
|
|
708
|
+
}
|
|
709
|
+
return `'${text.replaceAll("'", "''")}'`;
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
function powershellJoin(commandArgs) {
|
|
713
|
+
return commandArgs.map(powershellQuote).join(" ");
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
function isLocalContainerProvider(providerName) {
|
|
717
|
+
return ["local-container", "docker", "container", "local-docker"].includes(providerName);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
function runCommandArgs(commandArgs) {
|
|
721
|
+
const { start } = runCommandBounds(commandArgs);
|
|
722
|
+
return start >= 0 ? commandArgs.slice(start) : [];
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
function normalizedCommandWords(commandArgs) {
|
|
726
|
+
const words = commandArgs.length === 1 ? commandArgs[0].split(/\s+/u) : [...commandArgs];
|
|
727
|
+
while (/^[A-Za-z_][A-Za-z0-9_]*=/.test(words[0] ?? "")) {
|
|
728
|
+
words.shift();
|
|
729
|
+
}
|
|
730
|
+
return words.map((word) => word.replace(/^['"]|['";|&()]+$/g, ""));
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
function commandRuntimeEntrypoint(commandArgs) {
|
|
734
|
+
if (commandArgs.length === 1) {
|
|
735
|
+
for (const candidateWords of shellCommandWordCandidates(commandArgs[0])) {
|
|
736
|
+
const shellRuntime = commandWordsRuntimeEntrypoint(candidateWords);
|
|
737
|
+
if (shellRuntime) {
|
|
738
|
+
return shellRuntime;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
return "";
|
|
742
|
+
}
|
|
743
|
+
const words = normalizedCommandWords(commandArgs);
|
|
744
|
+
const directRuntime = commandWordsRuntimeEntrypoint(words);
|
|
745
|
+
if (directRuntime) {
|
|
746
|
+
return directRuntime;
|
|
747
|
+
}
|
|
748
|
+
return "";
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function commandWordsRuntimeEntrypoint(wordsInput) {
|
|
752
|
+
let words = wordsInput;
|
|
753
|
+
words = normalizeExecutableWords(words);
|
|
754
|
+
const first = (words[0] ?? "").split("/").pop();
|
|
755
|
+
if (jsRuntimeEntrypoints.has(first)) {
|
|
756
|
+
return first;
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
const inlineCommand = shellInlineCommand(words);
|
|
760
|
+
if (!inlineCommand) {
|
|
761
|
+
return "";
|
|
762
|
+
}
|
|
763
|
+
for (const candidateWords of shellCommandWordCandidates(inlineCommand)) {
|
|
764
|
+
const shellRuntime = commandWordsRuntimeEntrypoint(candidateWords);
|
|
765
|
+
if (shellRuntime) {
|
|
766
|
+
return shellRuntime;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
return "";
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function commandNeedsAwsMacosPackageManager(commandArgs) {
|
|
773
|
+
if (isChangedGateCommand(commandArgs)) {
|
|
774
|
+
return true;
|
|
775
|
+
}
|
|
776
|
+
if (commandArgs.length === 1) {
|
|
777
|
+
return shellCommandWordCandidates(commandArgs[0]).some(commandWordsNeedAwsMacosPackageManager);
|
|
778
|
+
}
|
|
779
|
+
return commandWordsNeedAwsMacosPackageManager(normalizedCommandWords(commandArgs));
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
function commandWordsNeedAwsMacosPackageManager(wordsInput) {
|
|
783
|
+
let words = wordsInput;
|
|
784
|
+
words = normalizeExecutableWords(words);
|
|
785
|
+
const first = (words[0] ?? "").split("/").pop();
|
|
786
|
+
if (awsMacosCorepackEntrypoints.has(first)) {
|
|
787
|
+
return true;
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
const inlineCommand = shellInlineCommand(words);
|
|
791
|
+
if (!inlineCommand) {
|
|
792
|
+
return false;
|
|
793
|
+
}
|
|
794
|
+
return shellCommandWordCandidates(inlineCommand).some(commandWordsNeedAwsMacosPackageManager);
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
function isChangedGateCommand(commandArgs) {
|
|
798
|
+
if (commandArgs.length === 1) {
|
|
799
|
+
return shellCommandWordCandidates(commandArgs[0]).some(isChangedGateCommandWords);
|
|
800
|
+
}
|
|
801
|
+
const words = normalizedCommandWords(commandArgs);
|
|
802
|
+
return isChangedGateCommandWords(words);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
function isChangedGateCommandWords(wordsInput) {
|
|
806
|
+
let words = wordsInput;
|
|
807
|
+
words = normalizeExecutableWords(words);
|
|
808
|
+
if (isChangedGateWords(words)) {
|
|
809
|
+
return true;
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
const inlineCommand = shellInlineCommand(words);
|
|
813
|
+
return inlineCommand
|
|
814
|
+
? shellCommandWordCandidates(inlineCommand).some(isChangedGateCommandWords)
|
|
815
|
+
: false;
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
function isChangedGateWords(wordsInput) {
|
|
819
|
+
let words = wordsInput;
|
|
820
|
+
words = normalizeExecutableWords(words);
|
|
821
|
+
if (words[0] === "corepack") {
|
|
822
|
+
words.shift();
|
|
823
|
+
}
|
|
824
|
+
return (
|
|
825
|
+
(words[0] === "pnpm" && words[1] === "check:changed") ||
|
|
826
|
+
(words[0] === "pnpm" && words[1] === "run" && words[2] === "check:changed") ||
|
|
827
|
+
(words[0] === "node" && (words[1] ?? "").endsWith("scripts/check-changed.mjs"))
|
|
828
|
+
);
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
function shellInlineCommand(words) {
|
|
832
|
+
const command = shellWordBasename(words[0]);
|
|
833
|
+
if (!shellInlineCommandInterpreters.has(command)) {
|
|
834
|
+
return "";
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
for (let index = 1; index < words.length; index += 1) {
|
|
838
|
+
const word = words[index];
|
|
839
|
+
if (word === "--") {
|
|
840
|
+
return "";
|
|
841
|
+
}
|
|
842
|
+
if (!word.startsWith("-") && !word.startsWith("+")) {
|
|
843
|
+
return "";
|
|
844
|
+
}
|
|
845
|
+
if (word === "-c" || /^-[^-]*c/u.test(word)) {
|
|
846
|
+
return words[index + 1] ?? "";
|
|
847
|
+
}
|
|
848
|
+
if (shellInlineCommandOptionConsumesNextValue(word)) {
|
|
849
|
+
index += 1;
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
return "";
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
function shellInlineCommandOptionConsumesNextValue(word) {
|
|
856
|
+
return shellInlineCommandOptionsWithNextValue.has(word) || /^[+-][^-+]*[oO]$/u.test(word);
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function shellCommandWordCandidates(command) {
|
|
860
|
+
return shellCommandSegments(stripHeredocBodies(command.replace(/\\\r?\n/gu, " ")));
|
|
861
|
+
}
|
|
862
|
+
|
|
863
|
+
function pushShellCandidate(candidates, segment) {
|
|
864
|
+
const words = normalizedShellSegmentWords(segment);
|
|
865
|
+
if (words.length > 0) {
|
|
866
|
+
candidates.push(words);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
function normalizedShellSegmentWords(segment) {
|
|
871
|
+
const trimmed = segment.trim().replace(/^[({]\s*/u, "");
|
|
872
|
+
if (!trimmed || trimmed.startsWith("#")) {
|
|
873
|
+
return [];
|
|
874
|
+
}
|
|
875
|
+
const words = normalizedCommandWords(splitShellWords(trimmed));
|
|
876
|
+
while (shellControlCommandPrefixes.has(words[0])) {
|
|
877
|
+
words.shift();
|
|
878
|
+
}
|
|
879
|
+
const normalizedWords = normalizedCommandWords(words);
|
|
880
|
+
return normalizedCommandWords(stripShellExecutionPrefixes(normalizedWords));
|
|
881
|
+
}
|
|
882
|
+
|
|
883
|
+
function normalizeExecutableWords(words) {
|
|
884
|
+
return normalizedCommandWords(stripShellExecutionPrefixes(words));
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
function stripShellExecutionPrefixes(wordsInput) {
|
|
888
|
+
let words = wordsInput;
|
|
889
|
+
words = [...words];
|
|
890
|
+
for (;;) {
|
|
891
|
+
const first = shellWordBasename(words[0]);
|
|
892
|
+
if (shellCommandExecutionPrefixes.has(first)) {
|
|
893
|
+
words.shift();
|
|
894
|
+
continue;
|
|
895
|
+
}
|
|
896
|
+
if (first === "command") {
|
|
897
|
+
words.shift();
|
|
898
|
+
if (!stripCommandBuiltinOptions(words)) {
|
|
899
|
+
return words;
|
|
900
|
+
}
|
|
901
|
+
continue;
|
|
902
|
+
}
|
|
903
|
+
if (first === "env") {
|
|
904
|
+
if (!stripEnvCommandOptions(words, { canShimIgnoreEnvironment: false })) {
|
|
905
|
+
return words;
|
|
906
|
+
}
|
|
907
|
+
continue;
|
|
908
|
+
}
|
|
909
|
+
if (first === "time") {
|
|
910
|
+
words.shift();
|
|
911
|
+
stripTimeOptions(words);
|
|
912
|
+
continue;
|
|
913
|
+
}
|
|
914
|
+
if (first === "timeout") {
|
|
915
|
+
stripTimeoutOptions(words);
|
|
916
|
+
continue;
|
|
917
|
+
}
|
|
918
|
+
return words;
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
function stripEnvCommandOptions(words, { canShimIgnoreEnvironment = true } = {}) {
|
|
923
|
+
const originalWords = [...words];
|
|
924
|
+
const envCommand = words.shift() ?? "";
|
|
925
|
+
let ignoresEnvironment = false;
|
|
926
|
+
for (;;) {
|
|
927
|
+
const word = words[0] ?? "";
|
|
928
|
+
if (!word) {
|
|
929
|
+
words.splice(0, words.length, ...originalWords);
|
|
930
|
+
return false;
|
|
931
|
+
}
|
|
932
|
+
if (word === "--") {
|
|
933
|
+
words.shift();
|
|
934
|
+
return true;
|
|
935
|
+
}
|
|
936
|
+
if (/^[A-Za-z_][A-Za-z0-9_]*=/.test(word)) {
|
|
937
|
+
words.shift();
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
if (word === "-S" || word === "--split-string") {
|
|
941
|
+
if (ignoresEnvironment) {
|
|
942
|
+
words.splice(0, words.length, ...originalWords);
|
|
943
|
+
return false;
|
|
944
|
+
}
|
|
945
|
+
words.shift();
|
|
946
|
+
const split = splitShellWords(words.shift() ?? "");
|
|
947
|
+
words.unshift(...split);
|
|
948
|
+
return words.length > 0;
|
|
949
|
+
}
|
|
950
|
+
if (word.startsWith("-S") && word !== "-S") {
|
|
951
|
+
if (ignoresEnvironment) {
|
|
952
|
+
words.splice(0, words.length, ...originalWords);
|
|
953
|
+
return false;
|
|
954
|
+
}
|
|
955
|
+
words.shift();
|
|
956
|
+
words.unshift(...splitShellWords(word.slice(2)));
|
|
957
|
+
return words.length > 0;
|
|
958
|
+
}
|
|
959
|
+
if (word.startsWith("--split-string=")) {
|
|
960
|
+
if (ignoresEnvironment) {
|
|
961
|
+
words.splice(0, words.length, ...originalWords);
|
|
962
|
+
return false;
|
|
963
|
+
}
|
|
964
|
+
words.shift();
|
|
965
|
+
words.unshift(...splitShellWords(word.slice("--split-string=".length)));
|
|
966
|
+
return words.length > 0;
|
|
967
|
+
}
|
|
968
|
+
if (word === "-i" || word === "--ignore-environment") {
|
|
969
|
+
if (!canShimIgnoreEnvironment || envCommand.includes("/")) {
|
|
970
|
+
words.splice(0, words.length, ...originalWords);
|
|
971
|
+
return false;
|
|
972
|
+
}
|
|
973
|
+
ignoresEnvironment = true;
|
|
974
|
+
words.shift();
|
|
975
|
+
continue;
|
|
976
|
+
}
|
|
977
|
+
if (word === "-u" || word === "--unset" || word === "-C" || word === "--chdir") {
|
|
978
|
+
words.shift();
|
|
979
|
+
if (words[0]) {
|
|
980
|
+
words.shift();
|
|
981
|
+
}
|
|
982
|
+
continue;
|
|
983
|
+
}
|
|
984
|
+
if (word.startsWith("--unset=") || word.startsWith("--chdir=")) {
|
|
985
|
+
words.shift();
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
988
|
+
if (word.startsWith("-") && word !== "-") {
|
|
989
|
+
if (word.includes("i")) {
|
|
990
|
+
if (!canShimIgnoreEnvironment || envCommand.includes("/")) {
|
|
991
|
+
words.splice(0, words.length, ...originalWords);
|
|
992
|
+
return false;
|
|
993
|
+
}
|
|
994
|
+
ignoresEnvironment = true;
|
|
995
|
+
}
|
|
996
|
+
words.shift();
|
|
997
|
+
continue;
|
|
998
|
+
}
|
|
999
|
+
if (ignoresEnvironment && (!canShimIgnoreEnvironment || envCommand.includes("/"))) {
|
|
1000
|
+
words.splice(0, words.length, ...originalWords);
|
|
1001
|
+
return false;
|
|
1002
|
+
}
|
|
1003
|
+
return true;
|
|
1004
|
+
}
|
|
1005
|
+
}
|
|
1006
|
+
|
|
1007
|
+
function shellWordBasename(word) {
|
|
1008
|
+
return (word ?? "").split("/").pop() ?? "";
|
|
1009
|
+
}
|
|
1010
|
+
|
|
1011
|
+
function stripCommandBuiltinOptions(words) {
|
|
1012
|
+
for (;;) {
|
|
1013
|
+
if (words[0] === "--") {
|
|
1014
|
+
words.shift();
|
|
1015
|
+
return true;
|
|
1016
|
+
}
|
|
1017
|
+
if (words[0] === "-p") {
|
|
1018
|
+
words.shift();
|
|
1019
|
+
continue;
|
|
1020
|
+
}
|
|
1021
|
+
return words[0] !== "-v" && words[0] !== "-V";
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
function stripTimeOptions(words) {
|
|
1026
|
+
while ((words[0] ?? "").startsWith("-")) {
|
|
1027
|
+
if (words[0] === "--") {
|
|
1028
|
+
words.shift();
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
1031
|
+
words.shift();
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
|
|
1035
|
+
function stripTimeoutOptions(words) {
|
|
1036
|
+
words.shift();
|
|
1037
|
+
for (;;) {
|
|
1038
|
+
const word = words[0] ?? "";
|
|
1039
|
+
if (!word) {
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
if (word === "--") {
|
|
1043
|
+
words.shift();
|
|
1044
|
+
break;
|
|
1045
|
+
}
|
|
1046
|
+
if (word === "-k" || word === "--kill-after" || word === "-s" || word === "--signal") {
|
|
1047
|
+
words.shift();
|
|
1048
|
+
if (words[0]) {
|
|
1049
|
+
words.shift();
|
|
1050
|
+
}
|
|
1051
|
+
continue;
|
|
1052
|
+
}
|
|
1053
|
+
if (word.startsWith("--kill-after=") || word.startsWith("--signal=")) {
|
|
1054
|
+
words.shift();
|
|
1055
|
+
continue;
|
|
1056
|
+
}
|
|
1057
|
+
if (word.startsWith("-") && word !== "-") {
|
|
1058
|
+
words.shift();
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
break;
|
|
1062
|
+
}
|
|
1063
|
+
if (words[0]) {
|
|
1064
|
+
words.shift();
|
|
1065
|
+
}
|
|
1066
|
+
}
|
|
1067
|
+
|
|
1068
|
+
function splitShellWords(value) {
|
|
1069
|
+
const words = [];
|
|
1070
|
+
let word = "";
|
|
1071
|
+
let quote = "";
|
|
1072
|
+
let escaped = false;
|
|
1073
|
+
for (const char of value) {
|
|
1074
|
+
if (escaped) {
|
|
1075
|
+
word += char;
|
|
1076
|
+
escaped = false;
|
|
1077
|
+
continue;
|
|
1078
|
+
}
|
|
1079
|
+
if (char === "\\" && quote !== "'") {
|
|
1080
|
+
escaped = true;
|
|
1081
|
+
continue;
|
|
1082
|
+
}
|
|
1083
|
+
if (quote) {
|
|
1084
|
+
if (char === quote) {
|
|
1085
|
+
quote = "";
|
|
1086
|
+
} else {
|
|
1087
|
+
word += char;
|
|
1088
|
+
}
|
|
1089
|
+
continue;
|
|
1090
|
+
}
|
|
1091
|
+
if (char === "'" || char === '"') {
|
|
1092
|
+
quote = char;
|
|
1093
|
+
continue;
|
|
1094
|
+
}
|
|
1095
|
+
if (/\s/u.test(char)) {
|
|
1096
|
+
if (word) {
|
|
1097
|
+
words.push(word);
|
|
1098
|
+
word = "";
|
|
1099
|
+
}
|
|
1100
|
+
continue;
|
|
1101
|
+
}
|
|
1102
|
+
word += char;
|
|
1103
|
+
}
|
|
1104
|
+
if (word) {
|
|
1105
|
+
words.push(word);
|
|
1106
|
+
}
|
|
1107
|
+
return words;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
function stripHeredocBodies(command) {
|
|
1111
|
+
const lines = command.split("\n");
|
|
1112
|
+
const kept = [];
|
|
1113
|
+
const pendingDelimiters = [];
|
|
1114
|
+
for (const line of lines) {
|
|
1115
|
+
if (pendingDelimiters.length > 0) {
|
|
1116
|
+
const current = pendingDelimiters[0];
|
|
1117
|
+
const candidate = current.stripTabs ? line.replace(/^\t+/u, "") : line;
|
|
1118
|
+
if (candidate === current.delimiter) {
|
|
1119
|
+
pendingDelimiters.shift();
|
|
1120
|
+
} else if (current.expand) {
|
|
1121
|
+
kept.push(...extractCommandSubstitutionBodies(line));
|
|
1122
|
+
}
|
|
1123
|
+
continue;
|
|
1124
|
+
}
|
|
1125
|
+
kept.push(line);
|
|
1126
|
+
pendingDelimiters.push(...lineHeredocDelimiters(line));
|
|
1127
|
+
}
|
|
1128
|
+
return kept.join("\n");
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
function lineHeredocDelimiters(line) {
|
|
1132
|
+
const delimiters = [];
|
|
1133
|
+
let quote = "";
|
|
1134
|
+
let escaped = false;
|
|
1135
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
1136
|
+
const char = line[index];
|
|
1137
|
+
const next = line[index + 1] ?? "";
|
|
1138
|
+
if (escaped) {
|
|
1139
|
+
escaped = false;
|
|
1140
|
+
continue;
|
|
1141
|
+
}
|
|
1142
|
+
if (char === "\\" && quote !== "'") {
|
|
1143
|
+
escaped = true;
|
|
1144
|
+
continue;
|
|
1145
|
+
}
|
|
1146
|
+
if (quote) {
|
|
1147
|
+
if (char === quote) {
|
|
1148
|
+
quote = "";
|
|
1149
|
+
}
|
|
1150
|
+
continue;
|
|
1151
|
+
}
|
|
1152
|
+
if (char === "'" || char === '"') {
|
|
1153
|
+
quote = char;
|
|
1154
|
+
continue;
|
|
1155
|
+
}
|
|
1156
|
+
if (char !== "<" || next !== "<" || line[index + 2] === "<") {
|
|
1157
|
+
continue;
|
|
1158
|
+
}
|
|
1159
|
+
let delimiterStart = index + 2;
|
|
1160
|
+
const stripTabs = line[delimiterStart] === "-";
|
|
1161
|
+
if (stripTabs) {
|
|
1162
|
+
delimiterStart += 1;
|
|
1163
|
+
}
|
|
1164
|
+
while (/\s/u.test(line[delimiterStart] ?? "")) {
|
|
1165
|
+
delimiterStart += 1;
|
|
1166
|
+
}
|
|
1167
|
+
const parsed = readHeredocDelimiter(line, delimiterStart);
|
|
1168
|
+
if (parsed.delimiter) {
|
|
1169
|
+
delimiters.push({ delimiter: parsed.delimiter, stripTabs, expand: !parsed.quoted });
|
|
1170
|
+
index = parsed.endIndex;
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
return delimiters;
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
function readHeredocDelimiter(line, startIndex) {
|
|
1177
|
+
let delimiterResult = "";
|
|
1178
|
+
let quote = "";
|
|
1179
|
+
let escaped = false;
|
|
1180
|
+
let quoted = false;
|
|
1181
|
+
let index = startIndex;
|
|
1182
|
+
for (; index < line.length; index += 1) {
|
|
1183
|
+
const char = line[index];
|
|
1184
|
+
if (escaped) {
|
|
1185
|
+
delimiterResult += char;
|
|
1186
|
+
escaped = false;
|
|
1187
|
+
continue;
|
|
1188
|
+
}
|
|
1189
|
+
if (char === "\\" && quote !== "'") {
|
|
1190
|
+
quoted = true;
|
|
1191
|
+
escaped = true;
|
|
1192
|
+
continue;
|
|
1193
|
+
}
|
|
1194
|
+
if (quote) {
|
|
1195
|
+
if (char === quote) {
|
|
1196
|
+
quote = "";
|
|
1197
|
+
} else {
|
|
1198
|
+
delimiterResult += char;
|
|
1199
|
+
}
|
|
1200
|
+
continue;
|
|
1201
|
+
}
|
|
1202
|
+
if (char === "'" || char === '"') {
|
|
1203
|
+
quoted = true;
|
|
1204
|
+
quote = char;
|
|
1205
|
+
continue;
|
|
1206
|
+
}
|
|
1207
|
+
if (/\s/u.test(char) || /[;&|()<>]/u.test(char)) {
|
|
1208
|
+
break;
|
|
1209
|
+
}
|
|
1210
|
+
delimiterResult += char;
|
|
1211
|
+
}
|
|
1212
|
+
return { delimiter: delimiterResult, endIndex: Math.max(startIndex, index), quoted };
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
function extractCommandSubstitutionBodies(line) {
|
|
1216
|
+
const substitutions = [];
|
|
1217
|
+
let escaped = false;
|
|
1218
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
1219
|
+
const char = line[index];
|
|
1220
|
+
const next = line[index + 1] ?? "";
|
|
1221
|
+
if (escaped) {
|
|
1222
|
+
escaped = false;
|
|
1223
|
+
continue;
|
|
1224
|
+
}
|
|
1225
|
+
if (char === "\\") {
|
|
1226
|
+
escaped = true;
|
|
1227
|
+
continue;
|
|
1228
|
+
}
|
|
1229
|
+
if (char === "$" && next === "(" && line[index + 2] !== "(") {
|
|
1230
|
+
const substitution = readCommandSubstitution(line, index + 2);
|
|
1231
|
+
substitutions.push(substitution.content);
|
|
1232
|
+
index = substitution.endIndex;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
return substitutions;
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
function shellCommandSegments(command) {
|
|
1239
|
+
const segments = [];
|
|
1240
|
+
let segment = "";
|
|
1241
|
+
let quote = "";
|
|
1242
|
+
let escaped = false;
|
|
1243
|
+
let inCase = false;
|
|
1244
|
+
let readingCasePattern = false;
|
|
1245
|
+
for (let index = 0; index < command.length; index += 1) {
|
|
1246
|
+
const char = command[index];
|
|
1247
|
+
const next = command[index + 1] ?? "";
|
|
1248
|
+
if (escaped) {
|
|
1249
|
+
segment += char;
|
|
1250
|
+
escaped = false;
|
|
1251
|
+
continue;
|
|
1252
|
+
}
|
|
1253
|
+
if (char === "\\" && quote !== "'") {
|
|
1254
|
+
segment += char;
|
|
1255
|
+
escaped = true;
|
|
1256
|
+
continue;
|
|
1257
|
+
}
|
|
1258
|
+
if (quote) {
|
|
1259
|
+
if (quote === '"' && char === "$" && next === "(" && command[index + 2] !== "(") {
|
|
1260
|
+
const substitution = readCommandSubstitution(command, index + 2);
|
|
1261
|
+
segments.push(...shellCommandWordCandidates(substitution.content));
|
|
1262
|
+
index = substitution.endIndex;
|
|
1263
|
+
segment += "$()";
|
|
1264
|
+
continue;
|
|
1265
|
+
}
|
|
1266
|
+
if (char === quote) {
|
|
1267
|
+
quote = "";
|
|
1268
|
+
}
|
|
1269
|
+
segment += char;
|
|
1270
|
+
continue;
|
|
1271
|
+
}
|
|
1272
|
+
if (char === "'" || char === '"') {
|
|
1273
|
+
quote = char;
|
|
1274
|
+
segment += char;
|
|
1275
|
+
continue;
|
|
1276
|
+
}
|
|
1277
|
+
if (char === "#" && (segment.trim() === "" || /\s$/u.test(segment))) {
|
|
1278
|
+
index = skipUntilNewline(command, index);
|
|
1279
|
+
pushShellCandidate(segments, segment);
|
|
1280
|
+
segment = "";
|
|
1281
|
+
continue;
|
|
1282
|
+
}
|
|
1283
|
+
if (char === "$" && next === "(" && command[index + 2] !== "(") {
|
|
1284
|
+
const substitution = readCommandSubstitution(command, index + 2);
|
|
1285
|
+
segments.push(...shellCommandWordCandidates(substitution.content));
|
|
1286
|
+
index = substitution.endIndex;
|
|
1287
|
+
segment += "$()";
|
|
1288
|
+
continue;
|
|
1289
|
+
}
|
|
1290
|
+
if (segment.trim() === "" && startsShellReservedWord(command, index, "case")) {
|
|
1291
|
+
pushShellCandidate(segments, segment);
|
|
1292
|
+
segment = "";
|
|
1293
|
+
inCase = true;
|
|
1294
|
+
readingCasePattern = true;
|
|
1295
|
+
index += "case".length - 1;
|
|
1296
|
+
continue;
|
|
1297
|
+
}
|
|
1298
|
+
if (inCase && segment.trim() === "" && startsShellReservedWord(command, index, "esac")) {
|
|
1299
|
+
pushShellCandidate(segments, segment);
|
|
1300
|
+
segment = "";
|
|
1301
|
+
inCase = false;
|
|
1302
|
+
readingCasePattern = false;
|
|
1303
|
+
index += "esac".length - 1;
|
|
1304
|
+
continue;
|
|
1305
|
+
}
|
|
1306
|
+
if (inCase && readingCasePattern) {
|
|
1307
|
+
if (char === ")") {
|
|
1308
|
+
segment = "";
|
|
1309
|
+
readingCasePattern = false;
|
|
1310
|
+
continue;
|
|
1311
|
+
}
|
|
1312
|
+
segment += char;
|
|
1313
|
+
continue;
|
|
1314
|
+
}
|
|
1315
|
+
if (inCase && char === ";" && next === ";") {
|
|
1316
|
+
pushShellCandidate(segments, segment);
|
|
1317
|
+
segment = "";
|
|
1318
|
+
readingCasePattern = true;
|
|
1319
|
+
index += 1;
|
|
1320
|
+
continue;
|
|
1321
|
+
}
|
|
1322
|
+
if (char === "\n" || char === ";" || char === ")") {
|
|
1323
|
+
pushShellCandidate(segments, segment);
|
|
1324
|
+
segment = "";
|
|
1325
|
+
continue;
|
|
1326
|
+
}
|
|
1327
|
+
if ((char === "&" && next === "&") || (char === "|" && next === "|")) {
|
|
1328
|
+
pushShellCandidate(segments, segment);
|
|
1329
|
+
segment = "";
|
|
1330
|
+
index += 1;
|
|
1331
|
+
continue;
|
|
1332
|
+
}
|
|
1333
|
+
if (char === "&" && next !== ">" && command[index - 1] !== ">") {
|
|
1334
|
+
pushShellCandidate(segments, segment);
|
|
1335
|
+
segment = "";
|
|
1336
|
+
continue;
|
|
1337
|
+
}
|
|
1338
|
+
if (char === "|") {
|
|
1339
|
+
pushShellCandidate(segments, segment);
|
|
1340
|
+
segment = "";
|
|
1341
|
+
if (next === "&") {
|
|
1342
|
+
index += 1;
|
|
1343
|
+
}
|
|
1344
|
+
continue;
|
|
1345
|
+
}
|
|
1346
|
+
segment += char;
|
|
1347
|
+
}
|
|
1348
|
+
pushShellCandidate(segments, segment);
|
|
1349
|
+
return segments;
|
|
1350
|
+
}
|
|
1351
|
+
|
|
1352
|
+
function readCommandSubstitution(command, startIndex) {
|
|
1353
|
+
let depth = 1;
|
|
1354
|
+
let quote = "";
|
|
1355
|
+
let escaped = false;
|
|
1356
|
+
let inCase = false;
|
|
1357
|
+
let readingCasePattern = false;
|
|
1358
|
+
let content = "";
|
|
1359
|
+
for (let index = startIndex; index < command.length; index += 1) {
|
|
1360
|
+
const char = command[index];
|
|
1361
|
+
const next = command[index + 1] ?? "";
|
|
1362
|
+
if (escaped) {
|
|
1363
|
+
content += char;
|
|
1364
|
+
escaped = false;
|
|
1365
|
+
continue;
|
|
1366
|
+
}
|
|
1367
|
+
if (char === "\\" && quote !== "'") {
|
|
1368
|
+
content += char;
|
|
1369
|
+
escaped = true;
|
|
1370
|
+
continue;
|
|
1371
|
+
}
|
|
1372
|
+
if (quote) {
|
|
1373
|
+
if (char === quote) {
|
|
1374
|
+
quote = "";
|
|
1375
|
+
}
|
|
1376
|
+
content += char;
|
|
1377
|
+
continue;
|
|
1378
|
+
}
|
|
1379
|
+
if (char === "'" || char === '"') {
|
|
1380
|
+
quote = char;
|
|
1381
|
+
content += char;
|
|
1382
|
+
continue;
|
|
1383
|
+
}
|
|
1384
|
+
if (!inCase && startsShellToken(command, index, "case")) {
|
|
1385
|
+
inCase = true;
|
|
1386
|
+
readingCasePattern = true;
|
|
1387
|
+
} else if (inCase && startsShellToken(command, index, "esac")) {
|
|
1388
|
+
inCase = false;
|
|
1389
|
+
readingCasePattern = false;
|
|
1390
|
+
}
|
|
1391
|
+
if (char === "$" && next === "(") {
|
|
1392
|
+
depth += 1;
|
|
1393
|
+
content += "$(";
|
|
1394
|
+
index += 1;
|
|
1395
|
+
continue;
|
|
1396
|
+
}
|
|
1397
|
+
if (char === "(") {
|
|
1398
|
+
depth += 1;
|
|
1399
|
+
content += char;
|
|
1400
|
+
continue;
|
|
1401
|
+
}
|
|
1402
|
+
if (inCase && char === ";" && next === ";") {
|
|
1403
|
+
readingCasePattern = true;
|
|
1404
|
+
content += ";;";
|
|
1405
|
+
index += 1;
|
|
1406
|
+
continue;
|
|
1407
|
+
}
|
|
1408
|
+
if (inCase && readingCasePattern && depth === 1 && char === ")") {
|
|
1409
|
+
readingCasePattern = false;
|
|
1410
|
+
content += char;
|
|
1411
|
+
continue;
|
|
1412
|
+
}
|
|
1413
|
+
if (char === ")") {
|
|
1414
|
+
depth -= 1;
|
|
1415
|
+
if (depth === 0) {
|
|
1416
|
+
return { content, endIndex: index };
|
|
1417
|
+
}
|
|
1418
|
+
}
|
|
1419
|
+
content += char;
|
|
1420
|
+
}
|
|
1421
|
+
return { content, endIndex: command.length - 1 };
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
function startsShellReservedWord(command, index, word) {
|
|
1425
|
+
if (!command.startsWith(word, index)) {
|
|
1426
|
+
return false;
|
|
1427
|
+
}
|
|
1428
|
+
const after = command[index + word.length] ?? "";
|
|
1429
|
+
return !after || /\s|[;&|()<>]/u.test(after);
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
function startsShellToken(command, index, word) {
|
|
1433
|
+
if (!command.startsWith(word, index)) {
|
|
1434
|
+
return false;
|
|
1435
|
+
}
|
|
1436
|
+
const before = command[index - 1] ?? "";
|
|
1437
|
+
const after = command[index + word.length] ?? "";
|
|
1438
|
+
return (!before || /\s|[;&|()<>]/u.test(before)) && (!after || /\s|[;&|()<>]/u.test(after));
|
|
1439
|
+
}
|
|
1440
|
+
|
|
1441
|
+
function skipUntilNewline(command, index) {
|
|
1442
|
+
const newlineIndex = command.indexOf("\n", index);
|
|
1443
|
+
return newlineIndex < 0 ? command.length - 1 : newlineIndex;
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
function mergeBaseForChangedGate() {
|
|
1447
|
+
const base = gitOutput(["merge-base", "origin/main", "HEAD"]);
|
|
1448
|
+
return base.status === 0 && base.stdout ? base.stdout : "origin/main";
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
function remoteGitBootstrapForChangedGate(changedGateBase) {
|
|
1452
|
+
const quotedBase = shellQuote(changedGateBase);
|
|
1453
|
+
return [
|
|
1454
|
+
"if ! git status --short >/dev/null 2>&1; then",
|
|
1455
|
+
"rm -rf .git;",
|
|
1456
|
+
"git init -q;",
|
|
1457
|
+
"git remote add origin https://github.com/openclaw/openclaw.git 2>/dev/null || git remote set-url origin https://github.com/openclaw/openclaw.git;",
|
|
1458
|
+
`git fetch -q --depth=1 origin ${quotedBase}:refs/remotes/origin/main;`,
|
|
1459
|
+
"git reset --mixed --quiet refs/remotes/origin/main;",
|
|
1460
|
+
"git add -A;",
|
|
1461
|
+
"if ! git diff --cached --quiet; then git -c user.name=OpenClaw -c user.email=ci@openclaw.local commit -q --no-gpg-sign -m remote-changed-gate-tree; fi;",
|
|
1462
|
+
"fi",
|
|
1463
|
+
].join(" ");
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
function injectRemoteChangedGateEnvironment(commandArgs) {
|
|
1467
|
+
if (commandArgs[0] !== "run" || isWindowsRemoteTarget(commandArgs)) {
|
|
1468
|
+
return commandArgs;
|
|
1469
|
+
}
|
|
1470
|
+
|
|
1471
|
+
const { start } = runCommandBounds(commandArgs);
|
|
1472
|
+
if (start < 0) {
|
|
1473
|
+
return commandArgs;
|
|
1474
|
+
}
|
|
1475
|
+
|
|
1476
|
+
const remoteCommand = commandArgs.slice(start);
|
|
1477
|
+
if (!isChangedGateCommand(remoteCommand)) {
|
|
1478
|
+
return commandArgs;
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
const normalizedArgs = [...commandArgs];
|
|
1482
|
+
const markedRemoteCommand =
|
|
1483
|
+
hasOption(normalizedArgs, "--shell") && remoteCommand.length === 1
|
|
1484
|
+
? [markShellChangedGateAsRemoteChild(remoteCommand[0])]
|
|
1485
|
+
: markDirectChangedGateAsRemoteChild(remoteCommand);
|
|
1486
|
+
normalizedArgs.splice(start, normalizedArgs.length - start, ...markedRemoteCommand);
|
|
1487
|
+
return normalizedArgs;
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
function markShellChangedGateAsRemoteChild(command) {
|
|
1491
|
+
const missingEnv = remoteChangedGateEnv.filter((assignment) => !command.includes(assignment));
|
|
1492
|
+
if (missingEnv.length === 0) {
|
|
1493
|
+
return command;
|
|
1494
|
+
}
|
|
1495
|
+
return `export ${missingEnv.join(" ")}; ${command}`;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
function markDirectChangedGateAsRemoteChild(commandArgs) {
|
|
1499
|
+
const missingEnv = remoteChangedGateEnv.filter((assignment) => !commandArgs.includes(assignment));
|
|
1500
|
+
if (missingEnv.length === 0) {
|
|
1501
|
+
return commandArgs;
|
|
1502
|
+
}
|
|
1503
|
+
|
|
1504
|
+
const markedCommandArgs = [...commandArgs];
|
|
1505
|
+
if (shellWordBasename(markedCommandArgs[0]) !== "env") {
|
|
1506
|
+
return ["env", ...missingEnv, ...markedCommandArgs];
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
markedCommandArgs.splice(envAssignmentInsertIndex(markedCommandArgs), 0, ...missingEnv);
|
|
1510
|
+
return markedCommandArgs;
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
function envAssignmentInsertIndex(words) {
|
|
1514
|
+
let index = 1;
|
|
1515
|
+
for (;;) {
|
|
1516
|
+
const word = words[index] ?? "";
|
|
1517
|
+
if (!word) {
|
|
1518
|
+
return 1;
|
|
1519
|
+
}
|
|
1520
|
+
if (word === "--") {
|
|
1521
|
+
return index + 1;
|
|
1522
|
+
}
|
|
1523
|
+
if (word === "-S" || word === "--split-string" || (word.startsWith("-S") && word !== "-S")) {
|
|
1524
|
+
return index;
|
|
1525
|
+
}
|
|
1526
|
+
if (word === "-u" || word === "--unset" || word === "-C" || word === "--chdir") {
|
|
1527
|
+
index += 2;
|
|
1528
|
+
continue;
|
|
1529
|
+
}
|
|
1530
|
+
if (word.startsWith("--unset=") || word.startsWith("--chdir=")) {
|
|
1531
|
+
index += 1;
|
|
1532
|
+
continue;
|
|
1533
|
+
}
|
|
1534
|
+
if (word.startsWith("-") && word !== "-") {
|
|
1535
|
+
index += 1;
|
|
1536
|
+
continue;
|
|
1537
|
+
}
|
|
1538
|
+
return index;
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
|
|
1542
|
+
function isWindowsRemoteTarget(commandArgs) {
|
|
1543
|
+
return (
|
|
1544
|
+
optionValue(commandArgs, "--target") === "windows" || hasOption(commandArgs, "--windows-mode")
|
|
1545
|
+
);
|
|
1546
|
+
}
|
|
1547
|
+
|
|
1548
|
+
function isNativeWindowsRemoteTarget(commandArgs) {
|
|
1549
|
+
return (
|
|
1550
|
+
isWindowsRemoteTarget(commandArgs) && optionValue(commandArgs, "--windows-mode") !== "wsl2"
|
|
1551
|
+
);
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
function isAwsMacosRemoteTarget(commandArgs, providerName) {
|
|
1555
|
+
return (
|
|
1556
|
+
commandArgs[0] === "run" &&
|
|
1557
|
+
providerName === "aws" &&
|
|
1558
|
+
optionValue(commandArgs, "--target") === "macos"
|
|
1559
|
+
);
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
function remoteWindowsHydratedNodeModulesBootstrap() {
|
|
1563
|
+
return [
|
|
1564
|
+
"$openclawModulesDir = $env:PNPM_CONFIG_MODULES_DIR",
|
|
1565
|
+
"if ($openclawModulesDir) {",
|
|
1566
|
+
'if (-not (Test-Path $openclawModulesDir)) { throw "PNPM_CONFIG_MODULES_DIR does not exist: $openclawModulesDir" }',
|
|
1567
|
+
'$openclawWorkspaceModules = Join-Path (Get-Location).Path "node_modules"',
|
|
1568
|
+
'$openclawSelfModules = Join-Path $openclawModulesDir "node_modules"',
|
|
1569
|
+
'if (-not (Test-Path $openclawSelfModules)) { cmd /c mklink /J "$openclawSelfModules" "$openclawModulesDir" | Out-Host; if ($LASTEXITCODE -ne 0) { throw "failed to link hydrated pnpm node_modules" } }',
|
|
1570
|
+
'if (-not (Test-Path $openclawWorkspaceModules)) { cmd /c mklink /J "$openclawWorkspaceModules" "$openclawModulesDir" | Out-Host; if ($LASTEXITCODE -ne 0) { throw "failed to link workspace node_modules" } }',
|
|
1571
|
+
"}",
|
|
1572
|
+
].join("; ");
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
function injectRemoteWindowsHydratedNodeModulesBootstrap(commandArgs, providerName) {
|
|
1576
|
+
const runtimeEntrypoint = commandRuntimeEntrypoint(runCommandArgs(commandArgs));
|
|
1577
|
+
if (
|
|
1578
|
+
commandArgs[0] !== "run" ||
|
|
1579
|
+
providerName !== "aws" ||
|
|
1580
|
+
!isNativeWindowsRemoteTarget(commandArgs) ||
|
|
1581
|
+
!hasOption(commandArgs, "--id") ||
|
|
1582
|
+
!runtimeEntrypoint
|
|
1583
|
+
) {
|
|
1584
|
+
return commandArgs;
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
const { start, optionEnd } = runCommandBounds(commandArgs);
|
|
1588
|
+
if (start < 0) {
|
|
1589
|
+
return commandArgs;
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
const normalizedArgs = [...commandArgs];
|
|
1593
|
+
const remoteCommand = normalizedArgs.slice(start);
|
|
1594
|
+
const originalShellCommand =
|
|
1595
|
+
hasOption(normalizedArgs, "--shell") && remoteCommand.length === 1
|
|
1596
|
+
? remoteCommand[0]
|
|
1597
|
+
: powershellJoin(remoteCommand);
|
|
1598
|
+
const shellCommand = `${remoteWindowsHydratedNodeModulesBootstrap()}; ${originalShellCommand}`;
|
|
1599
|
+
|
|
1600
|
+
if (!hasOption(normalizedArgs, "--shell")) {
|
|
1601
|
+
normalizedArgs.splice(optionEnd, 0, "--shell");
|
|
1602
|
+
}
|
|
1603
|
+
|
|
1604
|
+
const updatedBounds = runCommandBounds(normalizedArgs);
|
|
1605
|
+
normalizedArgs.splice(
|
|
1606
|
+
updatedBounds.start,
|
|
1607
|
+
normalizedArgs.length - updatedBounds.start,
|
|
1608
|
+
shellCommand,
|
|
1609
|
+
);
|
|
1610
|
+
return normalizedArgs;
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
function injectRemoteChangedGateGitBootstrap(commandArgs, changedGateBase) {
|
|
1614
|
+
if (!changedGateBase || commandArgs[0] !== "run" || isWindowsRemoteTarget(commandArgs)) {
|
|
1615
|
+
return commandArgs;
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
const { start, optionEnd } = runCommandBounds(commandArgs);
|
|
1619
|
+
if (start < 0) {
|
|
1620
|
+
return commandArgs;
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
const normalizedArgs = [...commandArgs];
|
|
1624
|
+
const remoteCommand = normalizedArgs.slice(start);
|
|
1625
|
+
const originalShellCommand =
|
|
1626
|
+
hasOption(normalizedArgs, "--shell") && remoteCommand.length === 1
|
|
1627
|
+
? remoteCommand[0]
|
|
1628
|
+
: shellJoin(remoteCommand);
|
|
1629
|
+
const shellCommand = `${remoteGitBootstrapForChangedGate(changedGateBase)} && ${originalShellCommand}`;
|
|
1630
|
+
|
|
1631
|
+
if (!hasOption(normalizedArgs, "--shell")) {
|
|
1632
|
+
normalizedArgs.splice(optionEnd, 0, "--shell");
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
const updatedBounds = runCommandBounds(normalizedArgs);
|
|
1636
|
+
normalizedArgs.splice(
|
|
1637
|
+
updatedBounds.start,
|
|
1638
|
+
normalizedArgs.length - updatedBounds.start,
|
|
1639
|
+
shellCommand,
|
|
1640
|
+
);
|
|
1641
|
+
return normalizedArgs;
|
|
1642
|
+
}
|
|
1643
|
+
|
|
1644
|
+
function remoteAwsMacosJsBootstrap({ packageManager = false } = {}) {
|
|
1645
|
+
const nodeVersion = process.env.OPENCLAW_CRABBOX_MACOS_NODE_VERSION?.trim() || "24.15.0";
|
|
1646
|
+
const bootstrap = [
|
|
1647
|
+
"openclaw_crabbox_bootstrap_macos_js() {",
|
|
1648
|
+
'tool_root="${OPENCLAW_CRABBOX_MACOS_TOOLCHAIN_DIR:-$HOME/.openclaw-crabbox-toolchain}";',
|
|
1649
|
+
`node_version=${shellQuote(nodeVersion)};`,
|
|
1650
|
+
'arch="$(uname -m)";',
|
|
1651
|
+
'case "$arch" in arm64) node_arch=arm64 ;; x86_64) node_arch=x64 ;; *) echo "unsupported macOS arch: $arch" >&2; return 2 ;; esac;',
|
|
1652
|
+
'macos_locale="${OPENCLAW_CRABBOX_MACOS_LOCALE:-en_US.UTF-8}";',
|
|
1653
|
+
'case "${LANG:-}" in C.UTF-8|C.utf8|c.UTF-8|c.utf8) export LANG="$macos_locale" ;; esac;',
|
|
1654
|
+
'case "${LC_ALL:-}" in C.UTF-8|C.utf8|c.UTF-8|c.utf8) export LC_ALL="$macos_locale" ;; esac;',
|
|
1655
|
+
'case "${LC_CTYPE:-}" in C.UTF-8|C.utf8|c.UTF-8|c.utf8) export LC_CTYPE="$macos_locale" ;; esac;',
|
|
1656
|
+
'if [ -z "${TMPDIR:-}" ]; then export TMPDIR="/tmp"; fi;',
|
|
1657
|
+
'if [ ! -d "$TMPDIR" ]; then mkdir -p "$TMPDIR" 2>/dev/null || export TMPDIR="/tmp"; fi;',
|
|
1658
|
+
'if [ ! -d "$TMPDIR" ]; then echo "usable TMPDIR not found: $TMPDIR" >&2; return 1; fi;',
|
|
1659
|
+
'node_dir="$tool_root/node-v${node_version}-darwin-${node_arch}";',
|
|
1660
|
+
'ready_marker="$node_dir/.openclaw-crabbox-node-ready";',
|
|
1661
|
+
'export PATH="$node_dir/bin:$PATH";',
|
|
1662
|
+
'if [ ! -x "$node_dir/bin/node" ] || [ ! -f "$ready_marker" ]; then',
|
|
1663
|
+
'mkdir -p "$tool_root" || { status=$?; return "$status"; };',
|
|
1664
|
+
'install_lock="$tool_root/.node-${node_version}-${node_arch}.lock";',
|
|
1665
|
+
"lock_acquired=0;",
|
|
1666
|
+
"lock_deadline=$((SECONDS + 300));",
|
|
1667
|
+
"while true; do",
|
|
1668
|
+
'if mkdir "$install_lock" 2>/dev/null; then lock_acquired=1; printf "%s\\n" "$$" >"$install_lock/pid" || { status=$?; rm -rf "$install_lock"; return "$status"; }; break; fi;',
|
|
1669
|
+
'if [ -x "$node_dir/bin/node" ] && [ -f "$ready_marker" ]; then break; fi;',
|
|
1670
|
+
'if [ "$SECONDS" -ge "$lock_deadline" ]; then',
|
|
1671
|
+
'lock_pid="$(cat "$install_lock/pid" 2>/dev/null || true)";',
|
|
1672
|
+
'if [ -n "$lock_pid" ] && kill -0 "$lock_pid" 2>/dev/null; then echo "timed out waiting for active macOS Node toolchain install lock: $install_lock pid=$lock_pid" >&2; return 1; fi;',
|
|
1673
|
+
'echo "reclaiming stale macOS Node toolchain install lock: $install_lock" >&2;',
|
|
1674
|
+
'rm -rf "$install_lock" || return 1;',
|
|
1675
|
+
"lock_deadline=$((SECONDS + 300));",
|
|
1676
|
+
"fi;",
|
|
1677
|
+
"sleep 1;",
|
|
1678
|
+
"done;",
|
|
1679
|
+
'release_install_lock() { if [ "$lock_acquired" = "1" ]; then rm -rf "$install_lock" 2>/dev/null || true; fi; };',
|
|
1680
|
+
'if [ ! -x "$node_dir/bin/node" ] || [ ! -f "$ready_marker" ]; then',
|
|
1681
|
+
'tmp_dir="$(mktemp -d)" || { release_install_lock; return 1; };',
|
|
1682
|
+
'pkg="node-v${node_version}-darwin-${node_arch}.tar.gz";',
|
|
1683
|
+
'base_url="https://nodejs.org/dist/v${node_version}";',
|
|
1684
|
+
'curl -fsSLo "$tmp_dir/$pkg" "$base_url/$pkg" || { status=$?; release_install_lock; rm -rf "$tmp_dir"; return "$status"; };',
|
|
1685
|
+
'curl -fsSLo "$tmp_dir/SHASUMS256.txt" "$base_url/SHASUMS256.txt" || { status=$?; release_install_lock; rm -rf "$tmp_dir"; return "$status"; };',
|
|
1686
|
+
'(cd "$tmp_dir" && grep " $pkg$" SHASUMS256.txt | shasum -a 256 -c -) || { status=$?; release_install_lock; rm -rf "$tmp_dir"; return "$status"; };',
|
|
1687
|
+
'rm -rf "$node_dir" || { status=$?; release_install_lock; rm -rf "$tmp_dir"; return "$status"; };',
|
|
1688
|
+
'tar -xzf "$tmp_dir/$pkg" -C "$tool_root" || { status=$?; release_install_lock; rm -rf "$tmp_dir"; return "$status"; };',
|
|
1689
|
+
'touch "$ready_marker" || { status=$?; release_install_lock; rm -rf "$tmp_dir"; return "$status"; };',
|
|
1690
|
+
'rm -rf "$tmp_dir";',
|
|
1691
|
+
"fi;",
|
|
1692
|
+
"release_install_lock;",
|
|
1693
|
+
"fi;",
|
|
1694
|
+
"node --version >&2 || return 1;",
|
|
1695
|
+
"openclaw_crabbox_env() {",
|
|
1696
|
+
"openclaw_env_args=();",
|
|
1697
|
+
"openclaw_env_ignore=0;",
|
|
1698
|
+
"openclaw_env_path_seen=0;",
|
|
1699
|
+
'while [ "$#" -gt 0 ]; do',
|
|
1700
|
+
'case "$1" in',
|
|
1701
|
+
'-i|--ignore-environment) openclaw_env_ignore=1; openclaw_env_args+=("$1"); shift ;;',
|
|
1702
|
+
'-S|--split-string|-S*|--split-string=*) command env "${openclaw_env_args[@]}" "$@"; return ;;',
|
|
1703
|
+
'-[!-]*i*) openclaw_env_ignore=1; openclaw_env_args+=("$1"); shift ;;',
|
|
1704
|
+
'-u|--unset|-C|--chdir) openclaw_env_args+=("$1"); shift; if [ "$#" -gt 0 ]; then openclaw_env_args+=("$1"); shift; fi ;;',
|
|
1705
|
+
'--unset=*|--chdir=*) openclaw_env_args+=("$1"); shift ;;',
|
|
1706
|
+
'PATH=*) if [ "$openclaw_env_ignore" = "1" ]; then openclaw_env_args+=("PATH=$PATH:${1#PATH=}"); else openclaw_env_args+=("$1"); fi; openclaw_env_path_seen=1; shift ;;',
|
|
1707
|
+
'[A-Za-z_]*=*) openclaw_env_args+=("$1"); shift ;;',
|
|
1708
|
+
'--) openclaw_env_args+=("--"); shift; break ;;',
|
|
1709
|
+
"*) break ;;",
|
|
1710
|
+
"esac;",
|
|
1711
|
+
"done;",
|
|
1712
|
+
'if [ "$openclaw_env_ignore" = "1" ] && [ "$openclaw_env_path_seen" = "0" ]; then openclaw_env_args+=("PATH=$PATH"); fi;',
|
|
1713
|
+
'command env "${openclaw_env_args[@]}" "$@";',
|
|
1714
|
+
"};",
|
|
1715
|
+
];
|
|
1716
|
+
if (packageManager) {
|
|
1717
|
+
bootstrap.push(
|
|
1718
|
+
'export COREPACK_HOME="${COREPACK_HOME:-$tool_root/corepack}";',
|
|
1719
|
+
'export PNPM_HOME="${PNPM_HOME:-$tool_root/pnpm-home}";',
|
|
1720
|
+
'mkdir -p "$COREPACK_HOME" "$PNPM_HOME" || return 1;',
|
|
1721
|
+
'export PATH="$PNPM_HOME:$PATH";',
|
|
1722
|
+
'corepack enable --install-directory "$PNPM_HOME" || return 1;',
|
|
1723
|
+
"pnpm --version >&2;",
|
|
1724
|
+
);
|
|
1725
|
+
}
|
|
1726
|
+
bootstrap.push("};", "openclaw_crabbox_bootstrap_macos_js");
|
|
1727
|
+
return bootstrap.join(" ");
|
|
1728
|
+
}
|
|
1729
|
+
|
|
1730
|
+
function scopedAwsMacosEnvCommand(commandArgs) {
|
|
1731
|
+
if (
|
|
1732
|
+
commandArgs.length <= 1 ||
|
|
1733
|
+
shellWordBasename(commandArgs[0]) !== "env" ||
|
|
1734
|
+
commandArgs[0].includes("/")
|
|
1735
|
+
) {
|
|
1736
|
+
return null;
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
const targetWords = [...commandArgs];
|
|
1740
|
+
if (!stripEnvCommandOptions(targetWords, { canShimIgnoreEnvironment: true })) {
|
|
1741
|
+
return null;
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
const targetEntrypoint = shellWordBasename(targetWords[0]);
|
|
1745
|
+
if (
|
|
1746
|
+
!jsRuntimeEntrypoints.has(targetEntrypoint) &&
|
|
1747
|
+
!awsMacosCorepackEntrypoints.has(targetEntrypoint)
|
|
1748
|
+
) {
|
|
1749
|
+
return null;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
return {
|
|
1753
|
+
runtimeEntrypoint: targetEntrypoint,
|
|
1754
|
+
packageManager: awsMacosCorepackEntrypoints.has(targetEntrypoint),
|
|
1755
|
+
shellCommand: `openclaw_crabbox_env ${shellJoin(commandArgs.slice(1))}`,
|
|
1756
|
+
};
|
|
1757
|
+
}
|
|
1758
|
+
|
|
1759
|
+
function injectRemoteAwsMacosJsBootstrap(commandArgs, providerName) {
|
|
1760
|
+
const runArgs = runCommandArgs(commandArgs);
|
|
1761
|
+
const directScopedEnvCommand = hasOption(commandArgs, "--shell")
|
|
1762
|
+
? null
|
|
1763
|
+
: scopedAwsMacosEnvCommand(runArgs);
|
|
1764
|
+
const runtimeEntrypoint =
|
|
1765
|
+
directScopedEnvCommand?.runtimeEntrypoint || commandRuntimeEntrypoint(runArgs);
|
|
1766
|
+
if (!isAwsMacosRemoteTarget(commandArgs, providerName) || !runtimeEntrypoint) {
|
|
1767
|
+
return commandArgs;
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
const { start, optionEnd } = runCommandBounds(commandArgs);
|
|
1771
|
+
if (start < 0) {
|
|
1772
|
+
return commandArgs;
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
const normalizedArgs = [...commandArgs];
|
|
1776
|
+
const remoteCommand = normalizedArgs.slice(start);
|
|
1777
|
+
const originalShellCommand =
|
|
1778
|
+
directScopedEnvCommand?.shellCommand ??
|
|
1779
|
+
(hasOption(normalizedArgs, "--shell") && remoteCommand.length === 1
|
|
1780
|
+
? remoteCommand[0]
|
|
1781
|
+
: shellJoin(remoteCommand));
|
|
1782
|
+
const shellCommand = `${remoteAwsMacosJsBootstrap({
|
|
1783
|
+
packageManager:
|
|
1784
|
+
directScopedEnvCommand?.packageManager || commandNeedsAwsMacosPackageManager(runArgs),
|
|
1785
|
+
})} && { ${originalShellCommand}\n}`;
|
|
1786
|
+
|
|
1787
|
+
if (!hasOption(normalizedArgs, "--shell")) {
|
|
1788
|
+
normalizedArgs.splice(optionEnd, 0, "--shell");
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1791
|
+
const updatedBounds = runCommandBounds(normalizedArgs);
|
|
1792
|
+
normalizedArgs.splice(
|
|
1793
|
+
updatedBounds.start,
|
|
1794
|
+
normalizedArgs.length - updatedBounds.start,
|
|
1795
|
+
shellCommand,
|
|
1796
|
+
);
|
|
1797
|
+
return normalizedArgs;
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1800
|
+
function hasRunOption(commandArgs, name) {
|
|
1801
|
+
if (commandArgs[0] !== "run") {
|
|
1802
|
+
return false;
|
|
1803
|
+
}
|
|
1804
|
+
const { optionEnd } = runCommandBounds(commandArgs);
|
|
1805
|
+
const normalizedName = name.replace(/^-+/u, "");
|
|
1806
|
+
for (let index = 1; index < optionEnd; index += 1) {
|
|
1807
|
+
const arg = commandArgs[index];
|
|
1808
|
+
if (arg.startsWith("-") && runOptionName(arg) === normalizedName) {
|
|
1809
|
+
return true;
|
|
1810
|
+
}
|
|
1811
|
+
if (!arg.includes("=") && currentRunValueOptions().has(runOptionName(arg))) {
|
|
1812
|
+
index += 1;
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
return false;
|
|
1816
|
+
}
|
|
1817
|
+
|
|
1818
|
+
function replaceRunFlagWithScript(commandArgs, flagName, scriptPath) {
|
|
1819
|
+
const { optionEnd } = runCommandBounds(commandArgs);
|
|
1820
|
+
const normalizedName = flagName.replace(/^-+/u, "");
|
|
1821
|
+
const normalizedArgs = [...commandArgs];
|
|
1822
|
+
for (let index = 1; index < optionEnd; index += 1) {
|
|
1823
|
+
const arg = normalizedArgs[index];
|
|
1824
|
+
if (arg.startsWith("-") && runOptionName(arg) === normalizedName) {
|
|
1825
|
+
normalizedArgs.splice(index, 1, "--script", scriptPath);
|
|
1826
|
+
return normalizedArgs;
|
|
1827
|
+
}
|
|
1828
|
+
if (!arg.includes("=") && currentRunValueOptions().has(runOptionName(arg))) {
|
|
1829
|
+
index += 1;
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
return normalizedArgs;
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
function prepareAwsMacosScriptStdinBootstrap(commandArgs, providerName) {
|
|
1836
|
+
if (
|
|
1837
|
+
!isAwsMacosRemoteTarget(commandArgs, providerName) ||
|
|
1838
|
+
!hasRunOption(commandArgs, "--script-stdin")
|
|
1839
|
+
) {
|
|
1840
|
+
return { args: commandArgs, cleanup: () => {}, prepared: false };
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
const scriptRoot = mkdtempSync(resolve(tmpdir(), "openclaw-crabbox-macos-script-"));
|
|
1844
|
+
const scriptPath = resolve(scriptRoot, "script.sh");
|
|
1845
|
+
const script = readFileSync(0, "utf8");
|
|
1846
|
+
writeFileSync(scriptPath, createAwsMacosScriptStdinWrapper(script), "utf8");
|
|
1847
|
+
chmodSync(scriptPath, 0o700);
|
|
1848
|
+
return {
|
|
1849
|
+
args: replaceRunFlagWithScript(commandArgs, "--script-stdin", scriptPath),
|
|
1850
|
+
cleanup: () => rmSync(scriptRoot, { recursive: true, force: true }),
|
|
1851
|
+
prepared: true,
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
|
|
1855
|
+
function createAwsMacosScriptStdinWrapper(script) {
|
|
1856
|
+
const packageManager = scriptNeedsAwsMacosPackageManager(script);
|
|
1857
|
+
if (!script.startsWith("#!")) {
|
|
1858
|
+
return `${remoteAwsMacosJsBootstrap({ packageManager })} || exit $?\n${script}`;
|
|
1859
|
+
}
|
|
1860
|
+
const delimiterValue = uniqueHereDocDelimiter(script);
|
|
1861
|
+
return [
|
|
1862
|
+
`${remoteAwsMacosJsBootstrap({ packageManager })} || exit $?`,
|
|
1863
|
+
'tmp_script="$(mktemp "${TMPDIR:-/tmp}/openclaw-crabbox-script.XXXXXX")" || exit $?',
|
|
1864
|
+
'cleanup_openclaw_crabbox_script() { rm -f "$tmp_script"; }',
|
|
1865
|
+
"trap cleanup_openclaw_crabbox_script EXIT",
|
|
1866
|
+
`cat >"$tmp_script" <<'${delimiterValue}'`,
|
|
1867
|
+
script.endsWith("\n") ? script.slice(0, -1) : script,
|
|
1868
|
+
delimiterValue,
|
|
1869
|
+
'chmod 700 "$tmp_script" || exit $?',
|
|
1870
|
+
'"$tmp_script" "$@"',
|
|
1871
|
+
"",
|
|
1872
|
+
].join("\n");
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
function scriptNeedsAwsMacosPackageManager(script) {
|
|
1876
|
+
const firstLine = script.match(/^[^\r\n]*/u)?.[0] ?? "";
|
|
1877
|
+
if (firstLine.startsWith("#!")) {
|
|
1878
|
+
let words = firstLine.slice(2).trim().split(/\s+/u).filter(Boolean);
|
|
1879
|
+
if ((words[0] ?? "").split("/").pop() === "env") {
|
|
1880
|
+
words = words.slice(1);
|
|
1881
|
+
while ((words[0] ?? "").startsWith("-")) {
|
|
1882
|
+
words = words.slice(1);
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
if (commandWordsNeedAwsMacosPackageManager(words)) {
|
|
1886
|
+
return true;
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
return commandNeedsAwsMacosPackageManager([script]);
|
|
1890
|
+
}
|
|
1891
|
+
|
|
1892
|
+
function uniqueHereDocDelimiter(script) {
|
|
1893
|
+
let index = 0;
|
|
1894
|
+
for (;;) {
|
|
1895
|
+
const delimiterLocal = `OPENCLAW_CRABBOX_SCRIPT_${index}`;
|
|
1896
|
+
if (!new RegExp(`^${delimiterLocal}$`, "mu").test(script)) {
|
|
1897
|
+
return delimiterLocal;
|
|
1898
|
+
}
|
|
1899
|
+
index += 1;
|
|
1900
|
+
}
|
|
1901
|
+
}
|
|
1902
|
+
|
|
1903
|
+
function isSparseCheckout() {
|
|
1904
|
+
const config = gitOutput(["config", "--bool", "core.sparseCheckout"]);
|
|
1905
|
+
if (config.status === 0 && config.stdout === "true") {
|
|
1906
|
+
return true;
|
|
1907
|
+
}
|
|
1908
|
+
const patterns = gitOutput(["sparse-checkout", "list"]);
|
|
1909
|
+
return patterns.status === 0 && patterns.stdout.length > 0;
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
function isWorktreeClean() {
|
|
1913
|
+
return gitOutput(["status", "--porcelain=v1"]).stdout === "";
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
function shouldUseFullCheckoutForCleanRemoteSync(commandArgs, _providerName) {
|
|
1917
|
+
if (commandArgs[0] !== "run") {
|
|
1918
|
+
return false;
|
|
1919
|
+
}
|
|
1920
|
+
if (hasOption(commandArgs, "--no-sync")) {
|
|
1921
|
+
return false;
|
|
1922
|
+
}
|
|
1923
|
+
if (!isWorktreeClean()) {
|
|
1924
|
+
return false;
|
|
1925
|
+
}
|
|
1926
|
+
|
|
1927
|
+
return isSparseCheckout() || isChangedGateCommand(runCommandArgs(commandArgs));
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
function defaultFullCheckoutSyncRoot() {
|
|
1931
|
+
const home = homedir();
|
|
1932
|
+
if (home) {
|
|
1933
|
+
return resolve(home, ".cache", "openclaw", "crabbox-sync");
|
|
1934
|
+
}
|
|
1935
|
+
return resolve(tmpdir(), "openclaw-crabbox-sync");
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
function fullCheckoutSyncRoot() {
|
|
1939
|
+
const configured = process.env.OPENCLAW_CRABBOX_SYNC_TMPDIR?.trim();
|
|
1940
|
+
const root = configured ? resolve(configured) : defaultFullCheckoutSyncRoot();
|
|
1941
|
+
mkdirSync(root, { recursive: true });
|
|
1942
|
+
return root;
|
|
1943
|
+
}
|
|
1944
|
+
|
|
1945
|
+
function parseNonNegativeIntegerEnv(name, fallback, unit) {
|
|
1946
|
+
const raw = process.env[name]?.trim();
|
|
1947
|
+
if (!raw) {
|
|
1948
|
+
return fallback;
|
|
1949
|
+
}
|
|
1950
|
+
if (!/^\d+$/u.test(raw)) {
|
|
1951
|
+
throw new Error(`${name} must be a non-negative integer ${unit}, got ${JSON.stringify(raw)}`);
|
|
1952
|
+
}
|
|
1953
|
+
const parsed = Number(raw);
|
|
1954
|
+
if (!Number.isSafeInteger(parsed)) {
|
|
1955
|
+
throw new Error(
|
|
1956
|
+
`${name} must be a safe non-negative integer ${unit}, got ${JSON.stringify(raw)}`,
|
|
1957
|
+
);
|
|
1958
|
+
}
|
|
1959
|
+
return parsed;
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
function formatByteCount(bytes) {
|
|
1963
|
+
if (bytes < 1024) {
|
|
1964
|
+
return `${bytes} B`;
|
|
1965
|
+
}
|
|
1966
|
+
const units = ["KiB", "MiB", "GiB", "TiB"];
|
|
1967
|
+
let value = bytes / 1024;
|
|
1968
|
+
let unitIndex = 0;
|
|
1969
|
+
while (value >= 1024 && unitIndex < units.length - 1) {
|
|
1970
|
+
value /= 1024;
|
|
1971
|
+
unitIndex += 1;
|
|
1972
|
+
}
|
|
1973
|
+
return `${value.toFixed(value >= 10 ? 1 : 2)} ${units[unitIndex]}`;
|
|
1974
|
+
}
|
|
1975
|
+
|
|
1976
|
+
function assertFullCheckoutSyncDisk(root) {
|
|
1977
|
+
const requiredBytes = parseNonNegativeIntegerEnv(
|
|
1978
|
+
"OPENCLAW_CRABBOX_SYNC_MIN_FREE_BYTES",
|
|
1979
|
+
1024 * 1024 * 1024,
|
|
1980
|
+
"byte count",
|
|
1981
|
+
);
|
|
1982
|
+
if (requiredBytes === 0) {
|
|
1983
|
+
return;
|
|
1984
|
+
}
|
|
1985
|
+
const stats = statfsSync(root);
|
|
1986
|
+
const freeBytes = stats.bavail * stats.bsize;
|
|
1987
|
+
if (freeBytes >= requiredBytes) {
|
|
1988
|
+
return;
|
|
1989
|
+
}
|
|
1990
|
+
throw new Error(
|
|
1991
|
+
[
|
|
1992
|
+
"insufficient free disk for Crabbox sparse-sync full checkout",
|
|
1993
|
+
`root=${root}`,
|
|
1994
|
+
`free=${formatByteCount(freeBytes)}`,
|
|
1995
|
+
`required=${formatByteCount(requiredBytes)}`,
|
|
1996
|
+
"set OPENCLAW_CRABBOX_SYNC_TMPDIR to a roomier filesystem or lower OPENCLAW_CRABBOX_SYNC_MIN_FREE_BYTES if you know this checkout fits",
|
|
1997
|
+
].join("; "),
|
|
1998
|
+
);
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
function prepareFullCheckoutForSync(options = {}) {
|
|
2002
|
+
const syncRoot = fullCheckoutSyncRoot();
|
|
2003
|
+
assertFullCheckoutSyncDisk(syncRoot);
|
|
2004
|
+
const dir = mkdtempSync(resolve(syncRoot, "openclaw-crabbox-sync-"));
|
|
2005
|
+
let active = false;
|
|
2006
|
+
|
|
2007
|
+
function create() {
|
|
2008
|
+
const add = gitOutput(["worktree", "add", "--detach", dir, "HEAD"]);
|
|
2009
|
+
if (add.status !== 0) {
|
|
2010
|
+
rmSync(dir, { recursive: true, force: true });
|
|
2011
|
+
throw new Error(`git worktree add failed: ${add.text}`);
|
|
2012
|
+
}
|
|
2013
|
+
active = true;
|
|
2014
|
+
|
|
2015
|
+
const disableSparse = gitOutput(["-C", dir, "sparse-checkout", "disable"]);
|
|
2016
|
+
if (disableSparse.status !== 0) {
|
|
2017
|
+
cleanupFullCheckout(dir, active);
|
|
2018
|
+
active = false;
|
|
2019
|
+
throw new Error(`git sparse-checkout disable failed: ${disableSparse.text}`);
|
|
2020
|
+
}
|
|
2021
|
+
|
|
2022
|
+
if (options.changedGateBase) {
|
|
2023
|
+
const reset = gitOutput(["-C", dir, "reset", "--mixed", "--quiet", options.changedGateBase]);
|
|
2024
|
+
if (reset.status !== 0) {
|
|
2025
|
+
cleanupFullCheckout(dir, active);
|
|
2026
|
+
active = false;
|
|
2027
|
+
throw new Error(`git reset for changed-gate sync failed: ${reset.text}`);
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
create();
|
|
2033
|
+
|
|
2034
|
+
return {
|
|
2035
|
+
dir,
|
|
2036
|
+
changedGateBase: options.changedGateBase ?? "",
|
|
2037
|
+
restoreIfMissing() {
|
|
2038
|
+
try {
|
|
2039
|
+
if (statSync(dir).isDirectory()) {
|
|
2040
|
+
return false;
|
|
2041
|
+
}
|
|
2042
|
+
} catch {
|
|
2043
|
+
// Recreate below.
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
console.error(`[crabbox] temporary full checkout disappeared; recreating ${dir}`);
|
|
2047
|
+
if (active) {
|
|
2048
|
+
const remove = gitOutput(["worktree", "remove", "--force", dir]);
|
|
2049
|
+
if (remove.status !== 0) {
|
|
2050
|
+
console.error(`[crabbox] warning: git worktree remove failed for ${dir}: ${remove.text}`);
|
|
2051
|
+
}
|
|
2052
|
+
active = false;
|
|
2053
|
+
}
|
|
2054
|
+
rmSync(dir, { recursive: true, force: true });
|
|
2055
|
+
create();
|
|
2056
|
+
return true;
|
|
2057
|
+
},
|
|
2058
|
+
exists() {
|
|
2059
|
+
try {
|
|
2060
|
+
return statSync(dir).isDirectory();
|
|
2061
|
+
} catch {
|
|
2062
|
+
return false;
|
|
2063
|
+
}
|
|
2064
|
+
},
|
|
2065
|
+
cleanup() {
|
|
2066
|
+
cleanupFullCheckout(dir, active);
|
|
2067
|
+
active = false;
|
|
2068
|
+
},
|
|
2069
|
+
};
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
function startFullCheckoutKeepalive(checkout, options = {}) {
|
|
2073
|
+
let missingReported = false;
|
|
2074
|
+
const intervalMs = options.intervalMs ?? fullCheckoutKeepaliveIntervalMs();
|
|
2075
|
+
const refresh = () => {
|
|
2076
|
+
try {
|
|
2077
|
+
if (!checkout.exists()) {
|
|
2078
|
+
if (options.onMissing) {
|
|
2079
|
+
if (!missingReported) {
|
|
2080
|
+
missingReported = true;
|
|
2081
|
+
console.error(
|
|
2082
|
+
`[crabbox] temporary full checkout disappeared while Crabbox was running; terminating because the child cwd cannot be repaired: ${checkout.dir}`,
|
|
2083
|
+
);
|
|
2084
|
+
options.onMissing();
|
|
2085
|
+
}
|
|
2086
|
+
return;
|
|
2087
|
+
}
|
|
2088
|
+
checkout.restoreIfMissing();
|
|
2089
|
+
}
|
|
2090
|
+
const now = new Date();
|
|
2091
|
+
utimesSync(checkout.dir, now, now);
|
|
2092
|
+
} catch (error) {
|
|
2093
|
+
console.error(
|
|
2094
|
+
`[crabbox] warning: failed to refresh temporary full checkout ${checkout.dir}: ${error instanceof Error ? error.message : String(error)}`,
|
|
2095
|
+
);
|
|
2096
|
+
}
|
|
2097
|
+
};
|
|
2098
|
+
|
|
2099
|
+
refresh();
|
|
2100
|
+
if (intervalMs <= 0) {
|
|
2101
|
+
return () => {};
|
|
2102
|
+
}
|
|
2103
|
+
|
|
2104
|
+
const interval = setInterval(refresh, intervalMs);
|
|
2105
|
+
interval.unref?.();
|
|
2106
|
+
return () => clearInterval(interval);
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
function fullCheckoutKeepaliveIntervalMs() {
|
|
2110
|
+
return parseNonNegativeIntegerEnv(
|
|
2111
|
+
"OPENCLAW_CRABBOX_SYNC_KEEPALIVE_MS",
|
|
2112
|
+
5000,
|
|
2113
|
+
"millisecond interval",
|
|
2114
|
+
);
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
function cleanupFullCheckout(dir, active) {
|
|
2118
|
+
if (active) {
|
|
2119
|
+
const remove = gitOutput(["worktree", "remove", "--force", dir]);
|
|
2120
|
+
if (remove.status === 0) {
|
|
2121
|
+
return;
|
|
2122
|
+
}
|
|
2123
|
+
console.error(`[crabbox] warning: git worktree remove failed for ${dir}: ${remove.text}`);
|
|
2124
|
+
}
|
|
2125
|
+
rmSync(dir, { recursive: true, force: true });
|
|
2126
|
+
}
|
|
2127
|
+
|
|
2128
|
+
function assertFullCheckoutAvailableBeforeExit(dir) {
|
|
2129
|
+
try {
|
|
2130
|
+
if (statSync(dir).isDirectory()) {
|
|
2131
|
+
return true;
|
|
2132
|
+
}
|
|
2133
|
+
} catch {
|
|
2134
|
+
// Report below.
|
|
2135
|
+
}
|
|
2136
|
+
|
|
2137
|
+
console.error(
|
|
2138
|
+
`[crabbox] temporary full checkout vanished before Crabbox finished syncing: ${dir}`,
|
|
2139
|
+
);
|
|
2140
|
+
return false;
|
|
2141
|
+
}
|
|
2142
|
+
|
|
2143
|
+
const version = checkedOutput(binary, ["--version"]);
|
|
2144
|
+
const help = checkedOutput(binary, ["run", "--help"]);
|
|
2145
|
+
const providerAliases = new Map([
|
|
2146
|
+
["blacksmith", "blacksmith-testbox"],
|
|
2147
|
+
["cf", "cloudflare"],
|
|
2148
|
+
["container", "local-container"],
|
|
2149
|
+
["docker", "local-container"],
|
|
2150
|
+
["exe", "exe-dev"],
|
|
2151
|
+
["exedev", "exe-dev"],
|
|
2152
|
+
["google", "gcp"],
|
|
2153
|
+
["google-cloud", "gcp"],
|
|
2154
|
+
["local-docker", "local-container"],
|
|
2155
|
+
["namespace", "namespace-devbox"],
|
|
2156
|
+
["namespace-devboxes", "namespace-devbox"],
|
|
2157
|
+
["rail", "railway"],
|
|
2158
|
+
["railwayapp", "railway"],
|
|
2159
|
+
["run-pod", "runpod"],
|
|
2160
|
+
["runpodio", "runpod"],
|
|
2161
|
+
["sem", "semaphore"],
|
|
2162
|
+
["static", "ssh"],
|
|
2163
|
+
["static-ssh", "ssh"],
|
|
2164
|
+
["tensorlake-sbx", "tensorlake"],
|
|
2165
|
+
["tl", "tensorlake"],
|
|
2166
|
+
]);
|
|
2167
|
+
// Crabbox providerHelpAll can omit Tensorlake even when the binary accepts it.
|
|
2168
|
+
const providerHelpOmissions = new Set(["tensorlake"]);
|
|
2169
|
+
|
|
2170
|
+
function addProviderNames(names, text) {
|
|
2171
|
+
for (const name of text
|
|
2172
|
+
.replace(/\s+\(default\b.*$/u, "")
|
|
2173
|
+
.split(/\s*(?:,|\||\bor\b)\s*/u)
|
|
2174
|
+
.map((s) => s.trim())
|
|
2175
|
+
.filter(Boolean)) {
|
|
2176
|
+
if (/^[a-z0-9][a-z0-9-]*$/u.test(name)) {
|
|
2177
|
+
names.add(name);
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
}
|
|
2181
|
+
|
|
2182
|
+
function providerListContinuation(line, previousText) {
|
|
2183
|
+
const match = line.match(
|
|
2184
|
+
/^\s*((?:or\s+)?[a-z0-9][a-z0-9-]*(?:\s*(?:,|\||\bor\b)\s*(?:or\s+)?[a-z0-9][a-z0-9-]*)*\s*(?:,|\|)?)(?:\s+\(default\b.*)?\s*$/u,
|
|
2185
|
+
);
|
|
2186
|
+
if (!match) {
|
|
2187
|
+
return "";
|
|
2188
|
+
}
|
|
2189
|
+
if (/[,|]\s*$/u.test(previousText) || /[,|]|\bor\b|\(default\b/u.test(line)) {
|
|
2190
|
+
return match[1];
|
|
2191
|
+
}
|
|
2192
|
+
return "";
|
|
2193
|
+
}
|
|
2194
|
+
|
|
2195
|
+
function parseProvidersFromHelp(text) {
|
|
2196
|
+
const names = new Set();
|
|
2197
|
+
const lines = text.split(/\r?\n/u);
|
|
2198
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
2199
|
+
const line = lines[index];
|
|
2200
|
+
const providerMatch = line.match(/provider:\s*([a-z0-9][a-z0-9, -]*)(?:\s*\(default\b|$)/u);
|
|
2201
|
+
if (providerMatch) {
|
|
2202
|
+
let providerText = providerMatch[1];
|
|
2203
|
+
while (!/\(default\b/u.test(lines[index]) && index + 1 < lines.length) {
|
|
2204
|
+
const continuation = providerListContinuation(lines[index + 1], providerText);
|
|
2205
|
+
if (!continuation) {
|
|
2206
|
+
break;
|
|
2207
|
+
}
|
|
2208
|
+
index += 1;
|
|
2209
|
+
providerText = `${providerText} ${continuation}`;
|
|
2210
|
+
}
|
|
2211
|
+
addProviderNames(names, providerText);
|
|
2212
|
+
continue;
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
const flagMatch = line.match(
|
|
2216
|
+
/^\s+-{1,2}provider(?:[=\s]+)([a-z0-9][a-z0-9|, -]*)(?:\s{2,}|\s+\(|$)/u,
|
|
2217
|
+
);
|
|
2218
|
+
if (flagMatch && /[,|]|\bor\b/u.test(flagMatch[1])) {
|
|
2219
|
+
addProviderNames(names, flagMatch[1]);
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
return [...names];
|
|
2223
|
+
}
|
|
2224
|
+
|
|
2225
|
+
function isProviderAdvertised(provider, advertisedProviders) {
|
|
2226
|
+
const canonicalProvider = providerAliases.get(provider) ?? provider;
|
|
2227
|
+
return (
|
|
2228
|
+
advertisedProviders.includes(provider) ||
|
|
2229
|
+
advertisedProviders.includes(canonicalProvider) ||
|
|
2230
|
+
providerHelpOmissions.has(canonicalProvider)
|
|
2231
|
+
);
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
const providers = parseProvidersFromHelp(help.text);
|
|
2235
|
+
const displayBinary = binary === "crabbox" ? "crabbox" : relative(repoRoot, binary);
|
|
2236
|
+
const provider = selectedProvider(args, providers);
|
|
2237
|
+
const canonicalProvider = providerAliases.get(provider) ?? provider;
|
|
2238
|
+
const commandProviderValue = commandProvider(args);
|
|
2239
|
+
let normalizedArgs = ensureAwsMacOnDemandMarket(
|
|
2240
|
+
ensureAzureWindowsProvider(args, provider, providers),
|
|
2241
|
+
provider,
|
|
2242
|
+
);
|
|
2243
|
+
|
|
2244
|
+
console.error(
|
|
2245
|
+
`[crabbox] bin=${displayBinary} version=${version.text || "unknown"} provider=${provider || "unknown"} providers=${providers.join(",") || "unknown"}`,
|
|
2246
|
+
);
|
|
2247
|
+
|
|
2248
|
+
if (version.status !== 0 || help.status !== 0) {
|
|
2249
|
+
console.error("[crabbox] selected binary failed basic --version/--help sanity checks");
|
|
2250
|
+
process.exit(2);
|
|
2251
|
+
}
|
|
2252
|
+
|
|
2253
|
+
if (provider && !isProviderAdvertised(provider, providers)) {
|
|
2254
|
+
if (providers.length === 0) {
|
|
2255
|
+
console.error(
|
|
2256
|
+
"[crabbox] could not parse provider list from --help; refusing to run with --provider without validation",
|
|
2257
|
+
);
|
|
2258
|
+
process.exit(2);
|
|
2259
|
+
}
|
|
2260
|
+
console.error(
|
|
2261
|
+
`[crabbox] selected binary does not advertise provider ${provider}; update Crabbox or choose a supported provider`,
|
|
2262
|
+
);
|
|
2263
|
+
process.exit(2);
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
if (canonicalProvider === "blacksmith-testbox") {
|
|
2267
|
+
if (!satisfiesMinimumCrabboxVersion(version.text, minimumBlacksmithCrabboxVersion)) {
|
|
2268
|
+
console.error(
|
|
2269
|
+
[
|
|
2270
|
+
`[crabbox] provider=blacksmith-testbox requires Crabbox >= ${formatVersionTuple(minimumBlacksmithCrabboxVersion)} for current Testbox sync, queue, and cleanup behavior.`,
|
|
2271
|
+
`[crabbox] selected binary reported version=${version.text || "unknown"}.`,
|
|
2272
|
+
"[crabbox] if using ../crabbox, rebuild it: version=$(git -C ../crabbox describe --tags --always --dirty | sed 's/^v//') && go build -C ../crabbox -trimpath -ldflags \"-s -w -X github.com/openclaw/crabbox/internal/cli.version=${version}\" -o bin/crabbox ./cmd/crabbox",
|
|
2273
|
+
].join("\n"),
|
|
2274
|
+
);
|
|
2275
|
+
process.exit(2);
|
|
2276
|
+
}
|
|
2277
|
+
}
|
|
2278
|
+
|
|
2279
|
+
enforceBrokeredAws(normalizedArgs, provider);
|
|
2280
|
+
|
|
2281
|
+
if (canonicalProvider === "blacksmith-testbox") {
|
|
2282
|
+
const envProviderLocal = process.env.CRABBOX_PROVIDER?.trim();
|
|
2283
|
+
const source = commandProviderValue
|
|
2284
|
+
? "explicit"
|
|
2285
|
+
: envProviderLocal
|
|
2286
|
+
? "from CRABBOX_PROVIDER"
|
|
2287
|
+
: "from config";
|
|
2288
|
+
const fallback = commandProviderValue
|
|
2289
|
+
? "rerun without --provider to use .crabbox.yaml"
|
|
2290
|
+
: envProviderLocal
|
|
2291
|
+
? "unset CRABBOX_PROVIDER to use .crabbox.yaml"
|
|
2292
|
+
: "pass another --provider to override it";
|
|
2293
|
+
console.error(
|
|
2294
|
+
`[crabbox] provider=blacksmith-testbox ${source}; if Testbox is queued or down, ${fallback}`,
|
|
2295
|
+
);
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
let childCwd = repoRoot;
|
|
2299
|
+
let cleanupChildCwd = () => {};
|
|
2300
|
+
let fullCheckout = null;
|
|
2301
|
+
let stopFullCheckoutKeepalive = () => {};
|
|
2302
|
+
let cleanupDone = false;
|
|
2303
|
+
let remoteChangedGateBase = "";
|
|
2304
|
+
const scriptBootstrap = prepareAwsMacosScriptStdinBootstrap(normalizedArgs, provider);
|
|
2305
|
+
normalizedArgs = scriptBootstrap.args;
|
|
2306
|
+
const scriptStdinPrepared = scriptBootstrap.prepared;
|
|
2307
|
+
try {
|
|
2308
|
+
if (shouldUseFullCheckoutForCleanRemoteSync(normalizedArgs, provider)) {
|
|
2309
|
+
const runWords = runCommandArgs(normalizedArgs);
|
|
2310
|
+
const changedGateBase = isChangedGateCommand(runWords) ? mergeBaseForChangedGate() : "";
|
|
2311
|
+
const checkout = prepareFullCheckoutForSync({ changedGateBase });
|
|
2312
|
+
fullCheckout = checkout;
|
|
2313
|
+
childCwd = checkout.dir;
|
|
2314
|
+
cleanupChildCwd = () => checkout.cleanup();
|
|
2315
|
+
remoteChangedGateBase = checkout.changedGateBase;
|
|
2316
|
+
console.error(
|
|
2317
|
+
`[crabbox] sparse clean checkout detected; syncing from temporary full checkout ${checkout.dir}`,
|
|
2318
|
+
);
|
|
2319
|
+
if (checkout.changedGateBase) {
|
|
2320
|
+
console.error(
|
|
2321
|
+
`[crabbox] remote changed gate detected; overlaying local HEAD as worktree changes from ${checkout.changedGateBase}`,
|
|
2322
|
+
);
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
} catch (error) {
|
|
2326
|
+
scriptBootstrap.cleanup();
|
|
2327
|
+
throw error;
|
|
2328
|
+
}
|
|
2329
|
+
|
|
2330
|
+
function cleanupOnce() {
|
|
2331
|
+
if (cleanupDone) {
|
|
2332
|
+
return;
|
|
2333
|
+
}
|
|
2334
|
+
cleanupDone = true;
|
|
2335
|
+
stopFullCheckoutKeepalive();
|
|
2336
|
+
scriptBootstrap.cleanup();
|
|
2337
|
+
preserveTemporaryCrabboxRuns();
|
|
2338
|
+
cleanupChildCwd();
|
|
2339
|
+
}
|
|
2340
|
+
|
|
2341
|
+
const runtimeEntrypoint = commandRuntimeEntrypoint(runCommandArgs(normalizedArgs));
|
|
2342
|
+
if (
|
|
2343
|
+
normalizedArgs[0] === "run" &&
|
|
2344
|
+
provider === "aws" &&
|
|
2345
|
+
(runtimeEntrypoint || scriptStdinPrepared)
|
|
2346
|
+
) {
|
|
2347
|
+
if (isAwsMacosRemoteTarget(normalizedArgs, provider)) {
|
|
2348
|
+
console.error(
|
|
2349
|
+
`[crabbox] provider=aws macOS raw boxes may lack Node/Corepack/pnpm for ${runtimeEntrypoint || "--script-stdin"}; bootstrapping a pinned user-local Node toolchain before the command`,
|
|
2350
|
+
);
|
|
2351
|
+
} else {
|
|
2352
|
+
const id = optionValue(normalizedArgs, "--id");
|
|
2353
|
+
const hydrate = id
|
|
2354
|
+
? `pnpm crabbox:hydrate -- --id ${id}`
|
|
2355
|
+
: "pnpm crabbox:warmup, then pnpm crabbox:hydrate -- --id <id>";
|
|
2356
|
+
console.error(
|
|
2357
|
+
`[crabbox] warning: provider=aws raw boxes may lack Node/Corepack/pnpm for ${runtimeEntrypoint}; hydrate first (${hydrate}) or pass --provider blacksmith-testbox for OpenClaw CI-like proof; not switching providers automatically`,
|
|
2358
|
+
);
|
|
2359
|
+
}
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
const childEnv = { ...process.env };
|
|
2363
|
+
if (
|
|
2364
|
+
isLocalContainerProvider(provider) &&
|
|
2365
|
+
!childEnv.CRABBOX_LOCAL_CONTAINER_DOCKER_SOCKET &&
|
|
2366
|
+
!hasOption(normalizedArgs, "--local-container-docker-socket")
|
|
2367
|
+
) {
|
|
2368
|
+
childEnv.CRABBOX_LOCAL_CONTAINER_DOCKER_SOCKET = "1";
|
|
2369
|
+
console.error(
|
|
2370
|
+
"[crabbox] provider=docker enabling host Docker socket pass-through for OpenClaw Docker tests",
|
|
2371
|
+
);
|
|
2372
|
+
}
|
|
2373
|
+
if (
|
|
2374
|
+
isLocalContainerProvider(provider) &&
|
|
2375
|
+
process.platform === "linux" &&
|
|
2376
|
+
!childEnv.CRABBOX_LOCAL_CONTAINER_WORK_ROOT &&
|
|
2377
|
+
!hasOption(normalizedArgs, "--local-container-work-root")
|
|
2378
|
+
) {
|
|
2379
|
+
childEnv.CRABBOX_LOCAL_CONTAINER_WORK_ROOT = "/tmp/openclaw-crabbox-docker-work";
|
|
2380
|
+
console.error(
|
|
2381
|
+
"[crabbox] provider=docker using short host-visible work root for OpenClaw Docker tests",
|
|
2382
|
+
);
|
|
2383
|
+
}
|
|
2384
|
+
|
|
2385
|
+
const remoteMarkedArgs = injectRemoteChangedGateEnvironment(normalizedArgs);
|
|
2386
|
+
const childArgs =
|
|
2387
|
+
childCwd === repoRoot
|
|
2388
|
+
? injectRemoteWindowsHydratedNodeModulesBootstrap(
|
|
2389
|
+
injectRemoteAwsMacosJsBootstrap(remoteMarkedArgs, provider),
|
|
2390
|
+
provider,
|
|
2391
|
+
)
|
|
2392
|
+
: injectRemoteChangedGateGitBootstrap(
|
|
2393
|
+
injectRemoteWindowsHydratedNodeModulesBootstrap(
|
|
2394
|
+
injectRemoteAwsMacosJsBootstrap(absolutizeLocalRunPaths(remoteMarkedArgs), provider),
|
|
2395
|
+
provider,
|
|
2396
|
+
),
|
|
2397
|
+
remoteChangedGateBase,
|
|
2398
|
+
);
|
|
2399
|
+
let fullCheckoutKeepaliveIntervalMsValue = 0;
|
|
2400
|
+
if (fullCheckout) {
|
|
2401
|
+
try {
|
|
2402
|
+
fullCheckoutKeepaliveIntervalMsValue = fullCheckoutKeepaliveIntervalMs();
|
|
2403
|
+
} catch (error) {
|
|
2404
|
+
cleanupOnce();
|
|
2405
|
+
throw error;
|
|
2406
|
+
}
|
|
2407
|
+
}
|
|
2408
|
+
const childInvocation = spawnInvocation(binary, childArgs, childEnv, process.platform);
|
|
2409
|
+
const child = spawn(childInvocation.command, childInvocation.args, {
|
|
2410
|
+
cwd: childCwd,
|
|
2411
|
+
stdio: "inherit",
|
|
2412
|
+
env: childEnv,
|
|
2413
|
+
windowsVerbatimArguments: childInvocation.windowsVerbatimArguments,
|
|
2414
|
+
});
|
|
2415
|
+
if (fullCheckout) {
|
|
2416
|
+
try {
|
|
2417
|
+
stopFullCheckoutKeepalive = startFullCheckoutKeepalive(fullCheckout, {
|
|
2418
|
+
intervalMs: fullCheckoutKeepaliveIntervalMsValue,
|
|
2419
|
+
onMissing: () => {
|
|
2420
|
+
if (!child.killed) {
|
|
2421
|
+
child.kill("SIGTERM");
|
|
2422
|
+
}
|
|
2423
|
+
},
|
|
2424
|
+
});
|
|
2425
|
+
} catch (error) {
|
|
2426
|
+
if (!child.killed) {
|
|
2427
|
+
child.kill("SIGTERM");
|
|
2428
|
+
}
|
|
2429
|
+
cleanupOnce();
|
|
2430
|
+
throw error;
|
|
2431
|
+
}
|
|
2432
|
+
}
|
|
2433
|
+
|
|
2434
|
+
const signalExitCodes = new Map([
|
|
2435
|
+
["SIGHUP", 129],
|
|
2436
|
+
["SIGINT", 130],
|
|
2437
|
+
["SIGTERM", 143],
|
|
2438
|
+
]);
|
|
2439
|
+
for (const signal of signalExitCodes.keys()) {
|
|
2440
|
+
process.once(signal, () => {
|
|
2441
|
+
if (!child.killed) {
|
|
2442
|
+
child.kill(signal);
|
|
2443
|
+
}
|
|
2444
|
+
cleanupOnce();
|
|
2445
|
+
process.exit(signalExitCodes.get(signal) ?? 1);
|
|
2446
|
+
});
|
|
2447
|
+
}
|
|
2448
|
+
process.once("exit", cleanupOnce);
|
|
2449
|
+
|
|
2450
|
+
child.on("exit", (code, signal) => {
|
|
2451
|
+
let fullCheckoutAvailable = true;
|
|
2452
|
+
if (fullCheckout) {
|
|
2453
|
+
fullCheckoutAvailable = assertFullCheckoutAvailableBeforeExit(fullCheckout.dir);
|
|
2454
|
+
}
|
|
2455
|
+
cleanupOnce();
|
|
2456
|
+
if (signal) {
|
|
2457
|
+
process.exit(signalExitCodes.get(signal) ?? 1);
|
|
2458
|
+
return;
|
|
2459
|
+
}
|
|
2460
|
+
process.exit(fullCheckoutAvailable ? (code ?? 1) : 1);
|
|
2461
|
+
});
|
|
2462
|
+
|
|
2463
|
+
child.on("error", (error) => {
|
|
2464
|
+
if (fullCheckout) {
|
|
2465
|
+
assertFullCheckoutAvailableBeforeExit(fullCheckout.dir);
|
|
2466
|
+
}
|
|
2467
|
+
cleanupOnce();
|
|
2468
|
+
console.error(`[crabbox] failed to execute ${displayBinary}: ${error.message}`);
|
|
2469
|
+
process.exit(2);
|
|
2470
|
+
});
|