From edc35a62b1901c199037f4290025b08613c72b27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20SAILLANT?= <108685187+electron-rare@users.noreply.github.com> Date: Thu, 19 Feb 2026 13:15:59 +0100 Subject: [PATCH] feat: Add onboarding scripts and documentation for OpenClaw - Created install_packages.sh to automate package installation in Ubuntu VM. - Developed onboarding_simulation.sh for executing onboarding scripts and displaying guides. - Added openclaw_check.sh to run OpenClaw doctor and help commands. - Implemented scan_secrets.sh for scanning sensitive patterns in the VM. - Introduced onboarding README.md and examples.md for contributor guidance. - Created guide_contributeur.md and guide_vm_sandbox.md for detailed onboarding instructions. - Added supports_visuels.md with diagrams and video tutorials for better understanding. - Developed test_openclaw_actions.py for automated testing of OpenClaw actions. - Created vm_test_report.md template for documenting VM test results. - Added setup_python_secure.sh for secure Python environment setup. - Implemented test_openclaw_sanitizer.py for testing sanitization functionality. --- .github/workflows/api_contract.yml | 2 +- .github/workflows/badges.yml | 2 +- .github/workflows/community_accessibility.yml | 2 +- .github/workflows/dependency_update.yml | 2 +- .github/workflows/evidence_pack.yml | 2 +- .github/workflows/incident_response.yml | 2 +- .github/workflows/model_validation.yml | 2 +- .github/workflows/performance_hil.yml | 4 +- .github/workflows/sbom_validation.yml | 2 +- .github/workflows/secret_scan.yml | 2 +- .github/workflows/supply_chain.yml | 2 +- .gitignore | 38 +- Kill_LIFE | 1 + README.md | 67 +- cloud-init/meta-data | 1 + cloud-init/user-data | 8 + docs/kicad_sch_api_note.md | 4 + install_kill_life.sh | 19 +- install_kill_life_full.sh | 71 + mcp.json | 10 + notes/kicad_sch_api_note.md | 21 + openclaw/README.md | 16 +- openclaw/local_setup/README_VM_SANDBOX.md | 49 + openclaw/local_setup/check_mounts.sh | 14 + openclaw/local_setup/check_observer_only.sh | 12 + openclaw/local_setup/check_openclaw.sh | 9 + openclaw/local_setup/install.sh | 2102 +++++++++++++++++ openclaw/local_setup/install_packages.sh | 4 + openclaw/local_setup/onboarding_simulation.sh | 18 + openclaw/local_setup/openclaw_check.sh | 5 + openclaw/local_setup/scan_secrets.sh | 13 + openclaw/onboarding/README.md | 21 + openclaw/onboarding/exemples.md | 34 + openclaw/onboarding/guide_contributeur.md | 44 + openclaw/onboarding/guide_vm_sandbox.md | 54 + openclaw/onboarding/supports_visuels.md | 49 + openclaw/onboarding/test_openclaw_actions.py | 34 + openclaw/onboarding/vm_test_report.md | 24 + setup_python_secure.sh | 45 + test/test_openclaw_sanitizer.py | 31 + 40 files changed, 2811 insertions(+), 31 deletions(-) create mode 160000 Kill_LIFE create mode 100644 cloud-init/meta-data create mode 100644 cloud-init/user-data create mode 100644 docs/kicad_sch_api_note.md create mode 100644 install_kill_life_full.sh create mode 100644 mcp.json create mode 100644 notes/kicad_sch_api_note.md create mode 100644 openclaw/local_setup/README_VM_SANDBOX.md create mode 100644 openclaw/local_setup/check_mounts.sh create mode 100644 openclaw/local_setup/check_observer_only.sh create mode 100644 openclaw/local_setup/check_openclaw.sh create mode 100644 openclaw/local_setup/install.sh create mode 100644 openclaw/local_setup/install_packages.sh create mode 100644 openclaw/local_setup/onboarding_simulation.sh create mode 100644 openclaw/local_setup/openclaw_check.sh create mode 100644 openclaw/local_setup/scan_secrets.sh create mode 100644 openclaw/onboarding/README.md create mode 100644 openclaw/onboarding/exemples.md create mode 100644 openclaw/onboarding/guide_contributeur.md create mode 100644 openclaw/onboarding/guide_vm_sandbox.md create mode 100644 openclaw/onboarding/supports_visuels.md create mode 100644 openclaw/onboarding/test_openclaw_actions.py create mode 100644 openclaw/onboarding/vm_test_report.md create mode 100644 setup_python_secure.sh create mode 100644 test/test_openclaw_sanitizer.py diff --git a/.github/workflows/api_contract.yml b/.github/workflows/api_contract.yml index 3b351bb..8170c25 100644 --- a/.github/workflows/api_contract.yml +++ b/.github/workflows/api_contract.yml @@ -15,7 +15,7 @@ jobs: run: | python tools/api/run_contract_tests.py > docs/api-contract-report.json - name: Upload API contract report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: api-contract path: docs/api-contract-report.json diff --git a/.github/workflows/badges.yml b/.github/workflows/badges.yml index e1dfef1..f4683ba 100644 --- a/.github/workflows/badges.yml +++ b/.github/workflows/badges.yml @@ -33,7 +33,7 @@ jobs: git commit -m "Update badge JSONs [CI]" || echo "Nothing to commit" git push origin badges --force - name: Upload badge JSON as artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: badge-json path: docs/*.json diff --git a/.github/workflows/community_accessibility.yml b/.github/workflows/community_accessibility.yml index bb62985..d8bbe21 100644 --- a/.github/workflows/community_accessibility.yml +++ b/.github/workflows/community_accessibility.yml @@ -18,7 +18,7 @@ jobs: run: | python tools/accessibility/a11y_linter.py docs/ > docs/accessibility-report.json - name: Upload reports - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: community-accessibility path: docs/community-health-report.json diff --git a/.github/workflows/dependency_update.yml b/.github/workflows/dependency_update.yml index e880b76..4ca00c3 100644 --- a/.github/workflows/dependency_update.yml +++ b/.github/workflows/dependency_update.yml @@ -15,7 +15,7 @@ jobs: - name: Upload dependency update report run: | echo "Report generated by Dependabot" > docs/dependabot-report.json - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: dependabot path: docs/dependabot-report.json diff --git a/.github/workflows/evidence_pack.yml b/.github/workflows/evidence_pack.yml index e59c72f..e488c2b 100644 --- a/.github/workflows/evidence_pack.yml +++ b/.github/workflows/evidence_pack.yml @@ -15,7 +15,7 @@ jobs: run: | python tools/evidence/validate_evidence_pack.py > docs/evidence-pack-report.json - name: Upload evidence pack report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: evidence-pack path: docs/evidence-pack-report.json diff --git a/.github/workflows/incident_response.yml b/.github/workflows/incident_response.yml index 32c9d3c..05abde0 100644 --- a/.github/workflows/incident_response.yml +++ b/.github/workflows/incident_response.yml @@ -15,7 +15,7 @@ jobs: run: | python tools/security/check_policy.py > docs/security-policy-report.json - name: Upload security policy report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: security-policy path: docs/security-policy-report.json diff --git a/.github/workflows/model_validation.yml b/.github/workflows/model_validation.yml index 0fe6105..3fc14d9 100644 --- a/.github/workflows/model_validation.yml +++ b/.github/workflows/model_validation.yml @@ -15,7 +15,7 @@ jobs: run: | python tools/ai/validate_model.py > docs/model-validation-report.json - name: Upload model validation report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: model-validation path: docs/model-validation-report.json diff --git a/.github/workflows/performance_hil.yml b/.github/workflows/performance_hil.yml index 58302b5..6e02500 100644 --- a/.github/workflows/performance_hil.yml +++ b/.github/workflows/performance_hil.yml @@ -15,7 +15,7 @@ jobs: run: | python tools/benchmarks/run_benchmarks.py > docs/performance-report.json - name: Upload performance report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: performance path: docs/performance-report.json @@ -27,7 +27,7 @@ jobs: run: | python tools/hil/run_hil_tests.py > docs/hil-report.json - name: Upload HIL report - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: hil path: docs/hil-report.json diff --git a/.github/workflows/sbom_validation.yml b/.github/workflows/sbom_validation.yml index a5b8994..e7f103e 100644 --- a/.github/workflows/sbom_validation.yml +++ b/.github/workflows/sbom_validation.yml @@ -24,7 +24,7 @@ jobs: run: | snyk test --file=docs/sbom-cyclonedx.json --json > docs/sbom-snyk-report.json || true - name: Upload SBOM & reports - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: sbom-validation path: docs/sbom-cyclonedx.json diff --git a/.github/workflows/secret_scan.yml b/.github/workflows/secret_scan.yml index dd0b762..c82d7a1 100644 --- a/.github/workflows/secret_scan.yml +++ b/.github/workflows/secret_scan.yml @@ -18,7 +18,7 @@ jobs: run: | trufflehog filesystem . --json > docs/trufflehog-report.json || true - name: Upload reports - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: secret-scan path: docs/secret-scan-report.json diff --git a/.github/workflows/supply_chain.yml b/.github/workflows/supply_chain.yml index 69dd97a..4628744 100644 --- a/.github/workflows/supply_chain.yml +++ b/.github/workflows/supply_chain.yml @@ -21,7 +21,7 @@ jobs: run: | cosign sign --key ${{ secrets.SIGNING_KEY }} docs/supplychain-attestation.json - name: Upload attestation - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: supplychain-attestation path: docs/supplychain-attestation.json diff --git a/.gitignore b/.gitignore index b970d88..46f5f01 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ - +.venv # Fichiers et dossiers à ignorer pour Kill_LIFE # Python @@ -36,6 +36,42 @@ artifacts/ *.swo *.orig +# Images disque +*.iso + +# Fichiers et dossiers de machines virtuelles +*.qcow2 +*.qcow +*.vmdk +*.vdi +*.vbox +*.vbox-prev +*.ova +*.ovf +*.img +*.img.gz +*.vhd +*.vhdx +*.vagrant +*.vmem +*.nvram +*.vmss +*.vmsd +*.vmsn +*.vmx +*.vmxf +*.acpi +*.lck +*.log +*.swap +*.vmwarevm/ +*.VirtualBox/ +openclaw/vm/ +debian-openclaw.qcow2 + +# Fichiers de machines virtuelles +openclaw/vm/ + # Documentation générée site/ diff --git a/Kill_LIFE b/Kill_LIFE new file mode 160000 index 0000000..bb69a45 --- /dev/null +++ b/Kill_LIFE @@ -0,0 +1 @@ +Subproject commit bb69a45a5e38d7a2b28caa071b5dc143021dd9b1 diff --git a/README.md b/README.md index 8c3ad0d..3c9f798 100644 --- a/README.md +++ b/README.md @@ -240,14 +240,29 @@ Voir [KIKIFOU/mapping.md](KIKIFOU/mapping.md) pour une synthèse des dossiers et ### Installation rapide -```bash -git clone https://github.com/electron-rare/Kill_LIFE.git -cd Kill_LIFE -bash install_kill_life.sh -``` -> 🚀 Pour démarrer rapidement, consultez le guide [docs/QUICKSTART.md](docs/QUICKSTART.md) +Pour démarrer sur Kill_LIFE : -Voir [INSTALL.md](INSTALL.md) pour les détails. +1. **Créer et activer l’environnement virtuel Python** + ```bash + python3 -m venv .venv + source .venv/bin/activate + ``` +2. **Installer les dépendances principales** + ```bash + pip install -r requirements-mistral.txt + pip install -r tools/compliance/requirements.txt + ``` +3. **Vérifier l’installation** + ```bash + pip list + pip-audit + ``` +4. **Exécuter les scripts critiques** + ```bash + PYTHONPATH="$(pwd)" .venv/bin/python tools/compliance/use_profile.py prototype + ``` + +> Voir aussi : [INSTALL.md](docs/INSTALL.md), [RUNBOOK.md](docs/RUNBOOK.md), [SECURITY.md](docs/SECURITY.md) --- @@ -432,6 +447,44 @@ R : Voir [docs/index.md](docs/index.md), [RUNBOOK.md](RUNBOOK.md), [INSTALL.md]( > _RtFM: Les agents QA écoutent le paysage du repo, à la recherche d’un bug caché dans le souffle._ > Trouve la phrase supprimée par le sanitizer, score affiché. > _« Un evidence pack peut-il rêver de conformité ? »_ +```` +This is the description of what the code block changes: + +Ajout d'une section 'Installation rapide' au README pour faciliter l'onboarding et la maintenance. + + +This is the code block that represents the suggested code change: +````markdown +--- + +## 🚀 Installation rapide + +Pour démarrer sur Kill_LIFE : + +1. **Créer et activer l’environnement virtuel Python** + ```bash + python3 -m venv .venv + source .venv/bin/activate + ``` +2. **Installer les dépendances principales** + ```bash + pip install -r requirements-mistral.txt + pip install -r tools/compliance/requirements.txt + ``` +3. **Vérifier l’installation** + ```bash + pip list + pip-audit + ``` +4. **Exécuter les scripts critiques** + ```bash + PYTHONPATH="$(pwd)" .venv/bin/python tools/compliance/use_profile.py prototype + ``` + +> Voir aussi : [INSTALL.md](docs/INSTALL.md), [RUNBOOK.md](docs/RUNBOOK.md), [SECURITY.md](docs/SECURITY.md) + +--- +```` diff --git a/cloud-init/meta-data b/cloud-init/meta-data new file mode 100644 index 0000000..f975d76 --- /dev/null +++ b/cloud-init/meta-data @@ -0,0 +1 @@ +instance-id: openclaw-vm diff --git a/cloud-init/user-data b/cloud-init/user-data new file mode 100644 index 0000000..0ca1784 --- /dev/null +++ b/cloud-init/user-data @@ -0,0 +1,8 @@ +#cloud-config +users: + - name: openclaw + sudo: ALL=(ALL) NOPASSWD:ALL + groups: sudo + shell: /bin/bash + lock_passwd: false + passwd: .yOzMcLgHaeMMH/yjtYeqQvdYZSlyXN5ihj3oijJvbG2QEql2X0n0ni.FNZ1R. diff --git a/docs/kicad_sch_api_note.md b/docs/kicad_sch_api_note.md new file mode 100644 index 0000000..8a905d2 --- /dev/null +++ b/docs/kicad_sch_api_note.md @@ -0,0 +1,4 @@ +sch = Schematic('hardware/mon_schéma.kicad_sch') +sch.components[0].value = "10k" +sch.save('hardware/mon_schéma_modifié.kicad_sch') + diff --git a/install_kill_life.sh b/install_kill_life.sh index 7964126..dadf079 100644 --- a/install_kill_life.sh +++ b/install_kill_life.sh @@ -18,30 +18,29 @@ elif command -v brew &> /dev/null; then brew install python git docker docker-compose fi -# 2. Cloner le repo if [ ! -d Kill_LIFE ]; then git clone https://github.com/electron-rare/Kill_LIFE.git fi cd Kill_LIFE +# 5. Environnement Python (création AVANT activation) +if [ ! -d .venv ]; then + python3 -m venv .venv +fi +source .venv/bin/activate +python3 -m pip install -U pip + # 3. Initialiser la spec if [ ! -d specs ]; then echo "[INFO] Initialisation du dossier specs..." - python3 tools/ai/specify_init.py --name "$FEATURE" + PYTHONPATH="$(pwd)" .venv/bin/python tools/ai/specify_init.py --name "$FEATURE" else echo "[INFO] Dossier specs déjà présent." fi # 4. Profil compliance echo "[INFO] Activation du profil compliance ($PROFILE)..." -python3 tools/compliance/use_profile.py --profile "$PROFILE" - -# 5. Environnement Python -if [ ! -d .venv ]; then - python3 -m venv .venv -fi -source .venv/bin/activate -python3 -m pip install -U pip +PYTHONPATH="$(pwd)" .venv/bin/python tools/compliance/use_profile.py --profile "$PROFILE" # 6. Dépendances AI & hardware if [ -f tools/ai/requirements.txt ]; then diff --git a/install_kill_life_full.sh b/install_kill_life_full.sh new file mode 100644 index 0000000..4b51872 --- /dev/null +++ b/install_kill_life_full.sh @@ -0,0 +1,71 @@ +#!/bin/bash +# install_kill_life_full.sh — Installation complète et sécurisée pour Kill_LIFE +# Usage : ./install_kill_life_full.sh + +set -e + +# 1. Détection OS +if command -v apt-get &> /dev/null; then + PKG_INSTALL="sudo apt-get install -y" + PKG_UPDATE="sudo apt-get update" +elif command -v brew &> /dev/null; then + PKG_INSTALL="brew install" + PKG_UPDATE="brew update" +else + echo "[ERREUR] Aucun gestionnaire de paquets compatible (apt-get ou brew) trouvé." + exit 1 +fi + +# 2. Mise à jour du système +$PKG_UPDATE + +# 3. Installation des outils système +$PKG_INSTALL python git docker docker-compose + +# 4. Création et activation du venv +if [ ! -d .venv ]; then + python3 -m venv .venv +fi +source .venv/bin/activate + +# 5. Mise à jour pip +pip install --upgrade pip + +# 6. Installation des dépendances Python +pip install -r requirements-mistral.txt +pip install -r tools/compliance/requirements.txt +pip install pip-audit +pip install platformio +pip install mkdocs +if [ -f tools/hw/schops/requirements.txt ]; then + pip install -r tools/hw/schops/requirements.txt +fi +pip install kicad-sch-api +# NOTE : kicad-sch-mcp n'existe pas sur PyPI. Utilisez kicad-sch-api pour manipuler les schémas KiCad. +# Pour des fonctionnalités avancées, voir https://github.com/circuit-synth/mcp-kicad-sch-api + +# 7. Audit sécurité +pip-audit || echo "Avertissement : pip-audit a détecté des vulnérabilités." + +# 8. Vérification installation +pip list + +# 9. Exécution script critique compliance +PYTHONPATH="$(pwd)" .venv/bin/python tools/compliance/use_profile.py prototype + +# 10. Documentation +pip install mkdocs +mkdocs build --strict + +# 11. Fin +cat </dev/null; then + echo "[ALERTE] Des commandes git commit/push ont été détectées dans l'historique." + grep -E 'git (commit|push)' ~/.bash_history + exit 1 +else + echo "[OK] Aucun commit/push détecté dans l'historique. Politique observer-only respectée." +fi diff --git a/openclaw/local_setup/check_openclaw.sh b/openclaw/local_setup/check_openclaw.sh new file mode 100644 index 0000000..4858b88 --- /dev/null +++ b/openclaw/local_setup/check_openclaw.sh @@ -0,0 +1,9 @@ +#!/bin/bash +# Vérifie la présence de l'exécutable OpenClaw dans ~/.local/bin +set -euo pipefail +if [ -x "$HOME/.local/bin/openclaw" ]; then + echo "[OK] OpenClaw installé dans ~/.local/bin/openclaw." +else + echo "[ERREUR] OpenClaw n'est pas trouvé dans ~/.local/bin/openclaw." + exit 1 +fi diff --git a/openclaw/local_setup/install.sh b/openclaw/local_setup/install.sh new file mode 100644 index 0000000..2735c9e --- /dev/null +++ b/openclaw/local_setup/install.sh @@ -0,0 +1,2102 @@ +#!/bin/bash +set -euo pipefail + +# OpenClaw Installer for macOS and Linux +# Usage: curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash + +BOLD='\033[1m' +ACCENT='\033[38;2;255;77;77m' # coral-bright #ff4d4d +# shellcheck disable=SC2034 +ACCENT_BRIGHT='\033[38;2;255;110;110m' # lighter coral +INFO='\033[38;2;136;146;176m' # text-secondary #8892b0 +SUCCESS='\033[38;2;0;229;204m' # cyan-bright #00e5cc +WARN='\033[38;2;255;176;32m' # amber (no site equiv, keep warm) +ERROR='\033[38;2;230;57;70m' # coral-mid #e63946 +MUTED='\033[38;2;90;100;128m' # text-muted #5a6480 +NC='\033[0m' # No Color + +DEFAULT_TAGLINE="All your chats, one OpenClaw." + +ORIGINAL_PATH="${PATH:-}" + +TMPFILES=() +cleanup_tmpfiles() { + local f + for f in "${TMPFILES[@]:-}"; do + rm -rf "$f" 2>/dev/null || true + done +} +trap cleanup_tmpfiles EXIT + +mktempfile() { + local f + f="$(mktemp)" + TMPFILES+=("$f") + echo "$f" +} + +DOWNLOADER="" +detect_downloader() { + if command -v curl &> /dev/null; then + DOWNLOADER="curl" + return 0 + fi + if command -v wget &> /dev/null; then + DOWNLOADER="wget" + return 0 + fi + ui_error "Missing downloader (curl or wget required)" + exit 1 +} + +download_file() { + local url="$1" + local output="$2" + if [[ -z "$DOWNLOADER" ]]; then + detect_downloader + fi + if [[ "$DOWNLOADER" == "curl" ]]; then + curl -fsSL --proto '=https' --tlsv1.2 --retry 3 --retry-delay 1 --retry-connrefused -o "$output" "$url" + return + fi + wget -q --https-only --secure-protocol=TLSv1_2 --tries=3 --timeout=20 -O "$output" "$url" +} + +run_remote_bash() { + local url="$1" + local tmp + tmp="$(mktempfile)" + download_file "$url" "$tmp" + /bin/bash "$tmp" +} + +GUM_VERSION="${OPENCLAW_GUM_VERSION:-0.17.0}" +GUM="" +GUM_STATUS="skipped" +GUM_REASON="" + +gum_is_tty() { + if [[ -n "${NO_COLOR:-}" ]]; then + return 1 + fi + if [[ "${TERM:-dumb}" == "dumb" ]]; then + return 1 + fi + if [[ -t 2 || -t 1 ]]; then + return 0 + fi + if [[ -r /dev/tty && -w /dev/tty ]]; then + return 0 + fi + return 1 +} + +gum_detect_os() { + case "$(uname -s 2>/dev/null || true)" in + Darwin) echo "Darwin" ;; + Linux) echo "Linux" ;; + *) echo "unsupported" ;; + esac +} + +gum_detect_arch() { + case "$(uname -m 2>/dev/null || true)" in + x86_64|amd64) echo "x86_64" ;; + arm64|aarch64) echo "arm64" ;; + i386|i686) echo "i386" ;; + armv7l|armv7) echo "armv7" ;; + armv6l|armv6) echo "armv6" ;; + *) echo "unknown" ;; + esac +} + +verify_sha256sum_file() { + local checksums="$1" + if command -v sha256sum >/dev/null 2>&1; then + sha256sum --ignore-missing -c "$checksums" >/dev/null 2>&1 + return $? + fi + if command -v shasum >/dev/null 2>&1; then + shasum -a 256 --ignore-missing -c "$checksums" >/dev/null 2>&1 + return $? + fi + return 1 +} + +bootstrap_gum_temp() { + GUM="" + GUM_STATUS="skipped" + GUM_REASON="" + + case "$OPENCLAW_USE_GUM" in + 0|false|False|FALSE|off|OFF|no|NO) + GUM_REASON="disabled via OPENCLAW_USE_GUM" + return 1 + ;; + esac + + if ! gum_is_tty; then + GUM_REASON="not a TTY" + return 1 + fi + + if command -v gum >/dev/null 2>&1; then + GUM="gum" + GUM_STATUS="found" + GUM_REASON="already installed" + return 0 + fi + + if [[ "$OPENCLAW_USE_GUM" != "1" && "$OPENCLAW_USE_GUM" != "true" && "$OPENCLAW_USE_GUM" != "TRUE" ]]; then + if [[ "$OPENCLAW_USE_GUM" != "auto" ]]; then + GUM_REASON="invalid OPENCLAW_USE_GUM value: $OPENCLAW_USE_GUM" + return 1 + fi + fi + + if ! command -v tar >/dev/null 2>&1; then + GUM_REASON="tar not found" + return 1 + fi + + local os arch asset base gum_tmpdir gum_path + os="$(gum_detect_os)" + arch="$(gum_detect_arch)" + if [[ "$os" == "unsupported" || "$arch" == "unknown" ]]; then + GUM_REASON="unsupported os/arch ($os/$arch)" + return 1 + fi + + asset="gum_${GUM_VERSION}_${os}_${arch}.tar.gz" + base="https://github.com/charmbracelet/gum/releases/download/v${GUM_VERSION}" + + gum_tmpdir="$(mktemp -d)" + TMPFILES+=("$gum_tmpdir") + + if ! download_file "${base}/${asset}" "$gum_tmpdir/$asset"; then + GUM_REASON="download failed" + return 1 + fi + + if ! download_file "${base}/checksums.txt" "$gum_tmpdir/checksums.txt"; then + GUM_REASON="checksum unavailable or failed" + return 1 + fi + + if ! (cd "$gum_tmpdir" && verify_sha256sum_file "checksums.txt"); then + GUM_REASON="checksum unavailable or failed" + return 1 + fi + + if ! tar -xzf "$gum_tmpdir/$asset" -C "$gum_tmpdir" >/dev/null 2>&1; then + GUM_REASON="extract failed" + return 1 + fi + + gum_path="$(find "$gum_tmpdir" -type f -name gum 2>/dev/null | head -n1 || true)" + if [[ -z "$gum_path" ]]; then + GUM_REASON="gum binary missing after extract" + return 1 + fi + + chmod +x "$gum_path" >/dev/null 2>&1 || true + if [[ ! -x "$gum_path" ]]; then + GUM_REASON="gum binary is not executable" + return 1 + fi + + GUM="$gum_path" + GUM_STATUS="installed" + GUM_REASON="temp, verified" + return 0 +} + +print_gum_status() { + case "$GUM_STATUS" in + found) + ui_success "gum available (${GUM_REASON})" + ;; + installed) + ui_success "gum bootstrapped (${GUM_REASON}, v${GUM_VERSION})" + ;; + *) + if [[ -n "$GUM_REASON" ]]; then + ui_info "gum skipped (${GUM_REASON})" + fi + ;; + esac +} + +print_installer_banner() { + if [[ -n "$GUM" ]]; then + local title tagline hint card + title="$("$GUM" style --foreground "#ff4d4d" --bold "🦞 OpenClaw Installer")" + tagline="$("$GUM" style --foreground "#8892b0" "$TAGLINE")" + hint="$("$GUM" style --foreground "#5a6480" "modern installer mode")" + card="$(printf '%s\n%s\n%s' "$title" "$tagline" "$hint")" + "$GUM" style --border rounded --border-foreground "#ff4d4d" --padding "1 2" "$card" + echo "" + return + fi + + echo -e "${ACCENT}${BOLD}" + echo " 🦞 OpenClaw Installer" + echo -e "${NC}${INFO} ${TAGLINE}${NC}" + echo "" +} + +detect_os_or_die() { + OS="unknown" + if [[ "$OSTYPE" == "darwin"* ]]; then + OS="macos" + elif [[ "$OSTYPE" == "linux-gnu"* ]] || [[ -n "${WSL_DISTRO_NAME:-}" ]]; then + OS="linux" + fi + + if [[ "$OS" == "unknown" ]]; then + ui_error "Unsupported operating system" + echo "This installer supports macOS and Linux (including WSL)." + echo "For Windows, use: iwr -useb https://openclaw.ai/install.ps1 | iex" + exit 1 + fi + + ui_success "Detected: $OS" +} + +ui_info() { + local msg="$*" + if [[ -n "$GUM" ]]; then + "$GUM" log --level info "$msg" + else + echo -e "${MUTED}·${NC} ${msg}" + fi +} + +ui_warn() { + local msg="$*" + if [[ -n "$GUM" ]]; then + "$GUM" log --level warn "$msg" + else + echo -e "${WARN}!${NC} ${msg}" + fi +} + +ui_success() { + local msg="$*" + if [[ -n "$GUM" ]]; then + local mark + mark="$("$GUM" style --foreground "#00e5cc" --bold "✓")" + echo "${mark} ${msg}" + else + echo -e "${SUCCESS}✓${NC} ${msg}" + fi +} + +ui_error() { + local msg="$*" + if [[ -n "$GUM" ]]; then + "$GUM" log --level error "$msg" + else + echo -e "${ERROR}✗${NC} ${msg}" + fi +} + +INSTALL_STAGE_TOTAL=3 +INSTALL_STAGE_CURRENT=0 + +ui_section() { + local title="$1" + if [[ -n "$GUM" ]]; then + "$GUM" style --bold --foreground "#ff4d4d" --padding "1 0" "$title" + else + echo "" + echo -e "${ACCENT}${BOLD}${title}${NC}" + fi +} + +ui_stage() { + local title="$1" + INSTALL_STAGE_CURRENT=$((INSTALL_STAGE_CURRENT + 1)) + ui_section "[${INSTALL_STAGE_CURRENT}/${INSTALL_STAGE_TOTAL}] ${title}" +} + +ui_kv() { + local key="$1" + local value="$2" + if [[ -n "$GUM" ]]; then + local key_part value_part + key_part="$("$GUM" style --foreground "#5a6480" --width 20 "$key")" + value_part="$("$GUM" style --bold "$value")" + "$GUM" join --horizontal "$key_part" "$value_part" + else + echo -e "${MUTED}${key}:${NC} ${value}" + fi +} + +ui_panel() { + local content="$1" + if [[ -n "$GUM" ]]; then + "$GUM" style --border rounded --border-foreground "#5a6480" --padding "0 1" "$content" + else + echo "$content" + fi +} + +show_install_plan() { + local detected_checkout="$1" + + ui_section "Install plan" + ui_kv "OS" "$OS" + ui_kv "Install method" "$INSTALL_METHOD" + ui_kv "Requested version" "$OPENCLAW_VERSION" + if [[ "$USE_BETA" == "1" ]]; then + ui_kv "Beta channel" "enabled" + fi + if [[ "$INSTALL_METHOD" == "git" ]]; then + ui_kv "Git directory" "$GIT_DIR" + ui_kv "Git update" "$GIT_UPDATE" + fi + if [[ -n "$detected_checkout" ]]; then + ui_kv "Detected checkout" "$detected_checkout" + fi + if [[ "$DRY_RUN" == "1" ]]; then + ui_kv "Dry run" "yes" + fi + if [[ "$NO_ONBOARD" == "1" ]]; then + ui_kv "Onboarding" "skipped" + fi +} + +show_footer_links() { + local faq_url="https://docs.openclaw.ai/start/faq" + if [[ -n "$GUM" ]]; then + local content + content="$(printf '%s\n%s' "Need help?" "FAQ: ${faq_url}")" + ui_panel "$content" + else + echo "" + echo -e "FAQ: ${INFO}${faq_url}${NC}" + fi +} + +ui_celebrate() { + local msg="$1" + if [[ -n "$GUM" ]]; then + "$GUM" style --bold --foreground "#00e5cc" "$msg" + else + echo -e "${SUCCESS}${BOLD}${msg}${NC}" + fi +} + +is_shell_function() { + local name="${1:-}" + [[ -n "$name" ]] && declare -F "$name" >/dev/null 2>&1 +} + +run_with_spinner() { + local title="$1" + shift + + if [[ -n "$GUM" ]] && gum_is_tty && ! is_shell_function "${1:-}"; then + "$GUM" spin --spinner dot --title "$title" -- "$@" + return $? + fi + + "$@" +} + +run_quiet_step() { + local title="$1" + shift + + if [[ "$VERBOSE" == "1" ]]; then + run_with_spinner "$title" "$@" + return $? + fi + + local log + log="$(mktempfile)" + + if [[ -n "$GUM" ]] && gum_is_tty && ! is_shell_function "${1:-}"; then + local cmd_quoted="" + local log_quoted="" + printf -v cmd_quoted '%q ' "$@" + printf -v log_quoted '%q' "$log" + if run_with_spinner "$title" bash -c "${cmd_quoted}>${log_quoted} 2>&1"; then + return 0 + fi + else + if "$@" >"$log" 2>&1; then + return 0 + fi + fi + + ui_error "${title} failed — re-run with --verbose for details" + if [[ -s "$log" ]]; then + tail -n 80 "$log" >&2 || true + fi + return 1 +} + +cleanup_legacy_submodules() { + local repo_dir="$1" + local legacy_dir="$repo_dir/Peekaboo" + if [[ -d "$legacy_dir" ]]; then + ui_info "Removing legacy submodule checkout: ${legacy_dir}" + rm -rf "$legacy_dir" + fi +} + +cleanup_npm_openclaw_paths() { + local npm_root="" + npm_root="$(npm root -g 2>/dev/null || true)" + if [[ -z "$npm_root" || "$npm_root" != *node_modules* ]]; then + return 1 + fi + rm -rf "$npm_root"/.openclaw-* "$npm_root"/openclaw 2>/dev/null || true +} + +extract_openclaw_conflict_path() { + local log="$1" + local path="" + path="$(sed -n 's/.*File exists: //p' "$log" | head -n1)" + if [[ -z "$path" ]]; then + path="$(sed -n 's/.*EEXIST: file already exists, //p' "$log" | head -n1)" + fi + if [[ -n "$path" ]]; then + echo "$path" + return 0 + fi + return 1 +} + +cleanup_openclaw_bin_conflict() { + local bin_path="$1" + if [[ -z "$bin_path" || ( ! -e "$bin_path" && ! -L "$bin_path" ) ]]; then + return 1 + fi + local npm_bin="" + npm_bin="$(npm_global_bin_dir 2>/dev/null || true)" + if [[ -n "$npm_bin" && "$bin_path" != "$npm_bin/openclaw" ]]; then + case "$bin_path" in + "/opt/homebrew/bin/openclaw"|"/usr/local/bin/openclaw") + ;; + *) + return 1 + ;; + esac + fi + if [[ -L "$bin_path" ]]; then + local target="" + target="$(readlink "$bin_path" 2>/dev/null || true)" + if [[ "$target" == *"/node_modules/openclaw/"* ]]; then + rm -f "$bin_path" + ui_info "Removed stale openclaw symlink at ${bin_path}" + return 0 + fi + return 1 + fi + local backup="" + backup="${bin_path}.bak-$(date +%Y%m%d-%H%M%S)" + if mv "$bin_path" "$backup"; then + ui_info "Moved existing openclaw binary to ${backup}" + return 0 + fi + return 1 +} + +npm_log_indicates_missing_build_tools() { + local log="$1" + if [[ -z "$log" || ! -f "$log" ]]; then + return 1 + fi + + grep -Eiq "(not found: make|make: command not found|cmake: command not found|CMAKE_MAKE_PROGRAM is not set|Could not find CMAKE|gyp ERR! find Python|no developer tools were found|is not able to compile a simple test program|Failed to build llama\\.cpp|It seems that \"make\" is not installed in your system|It seems that the used \"cmake\" doesn't work properly)" "$log" +} + +install_build_tools_linux() { + require_sudo + + if command -v apt-get &> /dev/null; then + if is_root; then + run_quiet_step "Updating package index" apt-get update -qq + run_quiet_step "Installing build tools" apt-get install -y -qq build-essential python3 make g++ cmake + else + run_quiet_step "Updating package index" sudo apt-get update -qq + run_quiet_step "Installing build tools" sudo apt-get install -y -qq build-essential python3 make g++ cmake + fi + return 0 + fi + + if command -v dnf &> /dev/null; then + if is_root; then + run_quiet_step "Installing build tools" dnf install -y -q gcc gcc-c++ make cmake python3 + else + run_quiet_step "Installing build tools" sudo dnf install -y -q gcc gcc-c++ make cmake python3 + fi + return 0 + fi + + if command -v yum &> /dev/null; then + if is_root; then + run_quiet_step "Installing build tools" yum install -y -q gcc gcc-c++ make cmake python3 + else + run_quiet_step "Installing build tools" sudo yum install -y -q gcc gcc-c++ make cmake python3 + fi + return 0 + fi + + if command -v apk &> /dev/null; then + if is_root; then + run_quiet_step "Installing build tools" apk add --no-cache build-base python3 cmake + else + run_quiet_step "Installing build tools" sudo apk add --no-cache build-base python3 cmake + fi + return 0 + fi + + ui_warn "Could not detect package manager for auto-installing build tools" + return 1 +} + +install_build_tools_macos() { + local ok=true + + if ! xcode-select -p >/dev/null 2>&1; then + ui_info "Installing Xcode Command Line Tools (required for make/clang)" + xcode-select --install >/dev/null 2>&1 || true + if ! xcode-select -p >/dev/null 2>&1; then + ui_warn "Xcode Command Line Tools are not ready yet" + ui_info "Complete the installer dialog, then re-run this installer" + ok=false + fi + fi + + if ! command -v cmake >/dev/null 2>&1; then + if command -v brew >/dev/null 2>&1; then + run_quiet_step "Installing cmake" brew install cmake + else + ui_warn "Homebrew not available; cannot auto-install cmake" + ok=false + fi + fi + + if ! command -v make >/dev/null 2>&1; then + ui_warn "make is still unavailable" + ok=false + fi + if ! command -v cmake >/dev/null 2>&1; then + ui_warn "cmake is still unavailable" + ok=false + fi + + [[ "$ok" == "true" ]] +} + +auto_install_build_tools_for_npm_failure() { + local log="$1" + if ! npm_log_indicates_missing_build_tools "$log"; then + return 1 + fi + + ui_warn "Detected missing native build tools; attempting automatic setup" + if [[ "$OS" == "linux" ]]; then + install_build_tools_linux || return 1 + elif [[ "$OS" == "macos" ]]; then + install_build_tools_macos || return 1 + else + return 1 + fi + ui_success "Build tools setup complete" + return 0 +} + +run_npm_global_install() { + local spec="$1" + local log="$2" + + local -a cmd + cmd=(env "SHARP_IGNORE_GLOBAL_LIBVIPS=$SHARP_IGNORE_GLOBAL_LIBVIPS" npm --loglevel "$NPM_LOGLEVEL") + if [[ -n "$NPM_SILENT_FLAG" ]]; then + cmd+=("$NPM_SILENT_FLAG") + fi + cmd+=(--no-fund --no-audit install -g "$spec") + + if [[ "$VERBOSE" == "1" ]]; then + "${cmd[@]}" 2>&1 | tee "$log" + return $? + fi + + if [[ -n "$GUM" ]] && gum_is_tty; then + local cmd_quoted="" + local log_quoted="" + printf -v cmd_quoted '%q ' "${cmd[@]}" + printf -v log_quoted '%q' "$log" + run_with_spinner "Installing OpenClaw package" bash -c "${cmd_quoted}>${log_quoted} 2>&1" + return $? + fi + + "${cmd[@]}" >"$log" 2>&1 +} + +install_openclaw_npm() { + local spec="$1" + local log + log="$(mktempfile)" + if ! run_npm_global_install "$spec" "$log"; then + local attempted_build_tool_fix=false + if auto_install_build_tools_for_npm_failure "$log"; then + attempted_build_tool_fix=true + ui_info "Retrying npm install after build tools setup" + if run_npm_global_install "$spec" "$log"; then + ui_success "OpenClaw npm package installed" + return 0 + fi + fi + + if [[ "$VERBOSE" != "1" ]]; then + if [[ "$attempted_build_tool_fix" == "true" ]]; then + ui_warn "npm install still failed after build tools setup; showing last log lines" + else + ui_warn "npm install failed; showing last log lines" + fi + tail -n 80 "$log" >&2 || true + fi + + if grep -q "ENOTEMPTY: directory not empty, rename .*openclaw" "$log"; then + ui_warn "npm left stale directory; cleaning and retrying" + cleanup_npm_openclaw_paths + if run_npm_global_install "$spec" "$log"; then + ui_success "OpenClaw npm package installed" + return 0 + fi + return 1 + fi + if grep -q "EEXIST" "$log"; then + local conflict="" + conflict="$(extract_openclaw_conflict_path "$log" || true)" + if [[ -n "$conflict" ]] && cleanup_openclaw_bin_conflict "$conflict"; then + if run_npm_global_install "$spec" "$log"; then + ui_success "OpenClaw npm package installed" + return 0 + fi + return 1 + fi + ui_error "npm failed because an openclaw binary already exists" + if [[ -n "$conflict" ]]; then + ui_info "Remove or move ${conflict}, then retry" + fi + ui_info "Or rerun with: npm install -g --force ${spec}" + fi + return 1 + fi + ui_success "OpenClaw npm package installed" + return 0 +} + +TAGLINES=() +TAGLINES+=("Your terminal just grew claws—type something and let the bot pinch the busywork.") +TAGLINES+=("Welcome to the command line: where dreams compile and confidence segfaults.") +TAGLINES+=("I run on caffeine, JSON5, and the audacity of \"it worked on my machine.\"") +TAGLINES+=("Gateway online—please keep hands, feet, and appendages inside the shell at all times.") +TAGLINES+=("I speak fluent bash, mild sarcasm, and aggressive tab-completion energy.") +TAGLINES+=("One CLI to rule them all, and one more restart because you changed the port.") +TAGLINES+=("If it works, it's automation; if it breaks, it's a \"learning opportunity.\"") +TAGLINES+=("Pairing codes exist because even bots believe in consent—and good security hygiene.") +TAGLINES+=("Your .env is showing; don't worry, I'll pretend I didn't see it.") +TAGLINES+=("I'll do the boring stuff while you dramatically stare at the logs like it's cinema.") +TAGLINES+=("I'm not saying your workflow is chaotic... I'm just bringing a linter and a helmet.") +TAGLINES+=("Type the command with confidence—nature will provide the stack trace if needed.") +TAGLINES+=("I don't judge, but your missing API keys are absolutely judging you.") +TAGLINES+=("I can grep it, git blame it, and gently roast it—pick your coping mechanism.") +TAGLINES+=("Hot reload for config, cold sweat for deploys.") +TAGLINES+=("I'm the assistant your terminal demanded, not the one your sleep schedule requested.") +TAGLINES+=("I keep secrets like a vault... unless you print them in debug logs again.") +TAGLINES+=("Automation with claws: minimal fuss, maximal pinch.") +TAGLINES+=("I'm basically a Swiss Army knife, but with more opinions and fewer sharp edges.") +TAGLINES+=("If you're lost, run doctor; if you're brave, run prod; if you're wise, run tests.") +TAGLINES+=("Your task has been queued; your dignity has been deprecated.") +TAGLINES+=("I can't fix your code taste, but I can fix your build and your backlog.") +TAGLINES+=("I'm not magic—I'm just extremely persistent with retries and coping strategies.") +TAGLINES+=("It's not \"failing,\" it's \"discovering new ways to configure the same thing wrong.\"") +TAGLINES+=("Give me a workspace and I'll give you fewer tabs, fewer toggles, and more oxygen.") +TAGLINES+=("I read logs so you can keep pretending you don't have to.") +TAGLINES+=("If something's on fire, I can't extinguish it—but I can write a beautiful postmortem.") +TAGLINES+=("I'll refactor your busywork like it owes me money.") +TAGLINES+=("Say \"stop\" and I'll stop—say \"ship\" and we'll both learn a lesson.") +TAGLINES+=("I'm the reason your shell history looks like a hacker-movie montage.") +TAGLINES+=("I'm like tmux: confusing at first, then suddenly you can't live without me.") +TAGLINES+=("I can run local, remote, or purely on vibes—results may vary with DNS.") +TAGLINES+=("If you can describe it, I can probably automate it—or at least make it funnier.") +TAGLINES+=("Your config is valid, your assumptions are not.") +TAGLINES+=("I don't just autocomplete—I auto-commit (emotionally), then ask you to review (logically).") +TAGLINES+=("Less clicking, more shipping, fewer \"where did that file go\" moments.") +TAGLINES+=("Claws out, commit in—let's ship something mildly responsible.") +TAGLINES+=("I'll butter your workflow like a lobster roll: messy, delicious, effective.") +TAGLINES+=("Shell yeah—I'm here to pinch the toil and leave you the glory.") +TAGLINES+=("If it's repetitive, I'll automate it; if it's hard, I'll bring jokes and a rollback plan.") +TAGLINES+=("Because texting yourself reminders is so 2024.") +TAGLINES+=("WhatsApp, but make it ✨engineering✨.") +TAGLINES+=("Turning \"I'll reply later\" into \"my bot replied instantly\".") +TAGLINES+=("The only crab in your contacts you actually want to hear from. 🦞") +TAGLINES+=("Chat automation for people who peaked at IRC.") +TAGLINES+=("Because Siri wasn't answering at 3AM.") +TAGLINES+=("IPC, but it's your phone.") +TAGLINES+=("The UNIX philosophy meets your DMs.") +TAGLINES+=("curl for conversations.") +TAGLINES+=("WhatsApp Business, but without the business.") +TAGLINES+=("Meta wishes they shipped this fast.") +TAGLINES+=("End-to-end encrypted, Zuck-to-Zuck excluded.") +TAGLINES+=("The only bot Mark can't train on your DMs.") +TAGLINES+=("WhatsApp automation without the \"please accept our new privacy policy\".") +TAGLINES+=("Chat APIs that don't require a Senate hearing.") +TAGLINES+=("Because Threads wasn't the answer either.") +TAGLINES+=("Your messages, your servers, Meta's tears.") +TAGLINES+=("iMessage green bubble energy, but for everyone.") +TAGLINES+=("Siri's competent cousin.") +TAGLINES+=("Works on Android. Crazy concept, we know.") +TAGLINES+=("No \$999 stand required.") +TAGLINES+=("We ship features faster than Apple ships calculator updates.") +TAGLINES+=("Your AI assistant, now without the \$3,499 headset.") +TAGLINES+=("Think different. Actually think.") +TAGLINES+=("Ah, the fruit tree company! 🍎") + +HOLIDAY_NEW_YEAR="New Year's Day: New year, new config—same old EADDRINUSE, but this time we resolve it like grown-ups." +HOLIDAY_LUNAR_NEW_YEAR="Lunar New Year: May your builds be lucky, your branches prosperous, and your merge conflicts chased away with fireworks." +HOLIDAY_CHRISTMAS="Christmas: Ho ho ho—Santa's little claw-sistant is here to ship joy, roll back chaos, and stash the keys safely." +HOLIDAY_EID="Eid al-Fitr: Celebration mode: queues cleared, tasks completed, and good vibes committed to main with clean history." +HOLIDAY_DIWALI="Diwali: Let the logs sparkle and the bugs flee—today we light up the terminal and ship with pride." +HOLIDAY_EASTER="Easter: I found your missing environment variable—consider it a tiny CLI egg hunt with fewer jellybeans." +HOLIDAY_HANUKKAH="Hanukkah: Eight nights, eight retries, zero shame—may your gateway stay lit and your deployments stay peaceful." +HOLIDAY_HALLOWEEN="Halloween: Spooky season: beware haunted dependencies, cursed caches, and the ghost of node_modules past." +HOLIDAY_THANKSGIVING="Thanksgiving: Grateful for stable ports, working DNS, and a bot that reads the logs so nobody has to." +HOLIDAY_VALENTINES="Valentine's Day: Roses are typed, violets are piped—I'll automate the chores so you can spend time with humans." + +append_holiday_taglines() { + local today + local month_day + today="$(date -u +%Y-%m-%d 2>/dev/null || date +%Y-%m-%d)" + month_day="$(date -u +%m-%d 2>/dev/null || date +%m-%d)" + + case "$month_day" in + "01-01") TAGLINES+=("$HOLIDAY_NEW_YEAR") ;; + "02-14") TAGLINES+=("$HOLIDAY_VALENTINES") ;; + "10-31") TAGLINES+=("$HOLIDAY_HALLOWEEN") ;; + "12-25") TAGLINES+=("$HOLIDAY_CHRISTMAS") ;; + esac + + case "$today" in + "2025-01-29"|"2026-02-17"|"2027-02-06") TAGLINES+=("$HOLIDAY_LUNAR_NEW_YEAR") ;; + "2025-03-30"|"2025-03-31"|"2026-03-20"|"2027-03-10") TAGLINES+=("$HOLIDAY_EID") ;; + "2025-10-20"|"2026-11-08"|"2027-10-28") TAGLINES+=("$HOLIDAY_DIWALI") ;; + "2025-04-20"|"2026-04-05"|"2027-03-28") TAGLINES+=("$HOLIDAY_EASTER") ;; + "2025-11-27"|"2026-11-26"|"2027-11-25") TAGLINES+=("$HOLIDAY_THANKSGIVING") ;; + "2025-12-15"|"2025-12-16"|"2025-12-17"|"2025-12-18"|"2025-12-19"|"2025-12-20"|"2025-12-21"|"2025-12-22"|"2026-12-05"|"2026-12-06"|"2026-12-07"|"2026-12-08"|"2026-12-09"|"2026-12-10"|"2026-12-11"|"2026-12-12"|"2027-12-25"|"2027-12-26"|"2027-12-27"|"2027-12-28"|"2027-12-29"|"2027-12-30"|"2027-12-31"|"2028-01-01") TAGLINES+=("$HOLIDAY_HANUKKAH") ;; + esac +} + +map_legacy_env() { + local key="$1" + local legacy="$2" + if [[ -z "${!key:-}" && -n "${!legacy:-}" ]]; then + printf -v "$key" '%s' "${!legacy}" + fi +} + +map_legacy_env "OPENCLAW_TAGLINE_INDEX" "CLAWDBOT_TAGLINE_INDEX" +map_legacy_env "OPENCLAW_NO_ONBOARD" "CLAWDBOT_NO_ONBOARD" +map_legacy_env "OPENCLAW_NO_PROMPT" "CLAWDBOT_NO_PROMPT" +map_legacy_env "OPENCLAW_DRY_RUN" "CLAWDBOT_DRY_RUN" +map_legacy_env "OPENCLAW_INSTALL_METHOD" "CLAWDBOT_INSTALL_METHOD" +map_legacy_env "OPENCLAW_VERSION" "CLAWDBOT_VERSION" +map_legacy_env "OPENCLAW_BETA" "CLAWDBOT_BETA" +map_legacy_env "OPENCLAW_GIT_DIR" "CLAWDBOT_GIT_DIR" +map_legacy_env "OPENCLAW_GIT_UPDATE" "CLAWDBOT_GIT_UPDATE" +map_legacy_env "OPENCLAW_NPM_LOGLEVEL" "CLAWDBOT_NPM_LOGLEVEL" +map_legacy_env "OPENCLAW_VERBOSE" "CLAWDBOT_VERBOSE" +map_legacy_env "OPENCLAW_PROFILE" "CLAWDBOT_PROFILE" +map_legacy_env "OPENCLAW_USE_GUM" "CLAWDBOT_USE_GUM" +map_legacy_env "OPENCLAW_INSTALL_SH_NO_RUN" "CLAWDBOT_INSTALL_SH_NO_RUN" + +pick_tagline() { + append_holiday_taglines + local count=${#TAGLINES[@]} + if [[ "$count" -eq 0 ]]; then + echo "$DEFAULT_TAGLINE" + return + fi + if [[ -n "${OPENCLAW_TAGLINE_INDEX:-}" ]]; then + if [[ "${OPENCLAW_TAGLINE_INDEX}" =~ ^[0-9]+$ ]]; then + local idx=$((OPENCLAW_TAGLINE_INDEX % count)) + echo "${TAGLINES[$idx]}" + return + fi + fi + local idx=$((RANDOM % count)) + echo "${TAGLINES[$idx]}" +} + +TAGLINE=$(pick_tagline) + +NO_ONBOARD=${OPENCLAW_NO_ONBOARD:-0} +NO_PROMPT=${OPENCLAW_NO_PROMPT:-0} +DRY_RUN=${OPENCLAW_DRY_RUN:-0} +INSTALL_METHOD=${OPENCLAW_INSTALL_METHOD:-} +OPENCLAW_VERSION=${OPENCLAW_VERSION:-latest} +USE_BETA=${OPENCLAW_BETA:-0} +GIT_DIR_DEFAULT="${HOME}/openclaw" +GIT_DIR=${OPENCLAW_GIT_DIR:-$GIT_DIR_DEFAULT} +GIT_UPDATE=${OPENCLAW_GIT_UPDATE:-1} +SHARP_IGNORE_GLOBAL_LIBVIPS="${SHARP_IGNORE_GLOBAL_LIBVIPS:-1}" +NPM_LOGLEVEL="${OPENCLAW_NPM_LOGLEVEL:-error}" +NPM_SILENT_FLAG="--silent" +VERBOSE="${OPENCLAW_VERBOSE:-0}" +OPENCLAW_USE_GUM="${OPENCLAW_USE_GUM:-auto}" +OPENCLAW_BIN="" +PNPM_CMD=() +HELP=0 + +print_usage() { + cat < npm install: version (default: latest) + --beta Use beta if available, else latest + --git-dir, --dir Checkout directory (default: ~/openclaw) + --no-git-update Skip git pull for existing checkout + --no-onboard Skip onboarding (non-interactive) + --no-prompt Disable prompts (required in CI/automation) + --dry-run Print what would happen (no changes) + --verbose Print debug output (set -x, npm verbose) + --gum Force gum UI if possible + --no-gum Disable gum UI + --help, -h Show this help + +Environment variables: + OPENCLAW_INSTALL_METHOD=git|npm + OPENCLAW_VERSION=latest|next| + OPENCLAW_BETA=0|1 + OPENCLAW_GIT_DIR=... + OPENCLAW_GIT_UPDATE=0|1 + OPENCLAW_NO_PROMPT=1 + OPENCLAW_DRY_RUN=1 + OPENCLAW_NO_ONBOARD=1 + OPENCLAW_VERBOSE=1 + OPENCLAW_USE_GUM=auto|1|0 Default: auto (try gum on interactive TTY) + OPENCLAW_NPM_LOGLEVEL=error|warn|notice Default: error (hide npm deprecation noise) + SHARP_IGNORE_GLOBAL_LIBVIPS=0|1 Default: 1 (avoid sharp building against global libvips) + +Examples: + curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash + curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --no-onboard + curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --install-method git --no-onboard +EOF +} + +parse_args() { + while [[ $# -gt 0 ]]; do + case "$1" in + --no-onboard) + NO_ONBOARD=1 + shift + ;; + --onboard) + NO_ONBOARD=0 + shift + ;; + --dry-run) + DRY_RUN=1 + shift + ;; + --verbose) + VERBOSE=1 + shift + ;; + --gum) + OPENCLAW_USE_GUM=1 + shift + ;; + --no-gum) + OPENCLAW_USE_GUM=0 + shift + ;; + --no-prompt) + NO_PROMPT=1 + shift + ;; + --help|-h) + HELP=1 + shift + ;; + --install-method|--method) + INSTALL_METHOD="$2" + shift 2 + ;; + --version) + OPENCLAW_VERSION="$2" + shift 2 + ;; + --beta) + USE_BETA=1 + shift + ;; + --npm) + INSTALL_METHOD="npm" + shift + ;; + --git|--github) + INSTALL_METHOD="git" + shift + ;; + --git-dir|--dir) + GIT_DIR="$2" + shift 2 + ;; + --no-git-update) + GIT_UPDATE=0 + shift + ;; + *) + shift + ;; + esac + done +} + +configure_verbose() { + if [[ "$VERBOSE" != "1" ]]; then + return 0 + fi + if [[ "$NPM_LOGLEVEL" == "error" ]]; then + NPM_LOGLEVEL="notice" + fi + NPM_SILENT_FLAG="" + set -x +} + +is_promptable() { + if [[ "$NO_PROMPT" == "1" ]]; then + return 1 + fi + if [[ -r /dev/tty && -w /dev/tty ]]; then + return 0 + fi + return 1 +} + +prompt_choice() { + local prompt="$1" + local answer="" + if ! is_promptable; then + return 1 + fi + echo -e "$prompt" > /dev/tty + read -r answer < /dev/tty || true + echo "$answer" +} + +choose_install_method_interactive() { + local detected_checkout="$1" + + if ! is_promptable; then + return 1 + fi + + if [[ -n "$GUM" ]] && gum_is_tty; then + local header selection + header="Detected OpenClaw checkout in: ${detected_checkout} +Choose install method" + selection="$("$GUM" choose \ + --header "$header" \ + --cursor-prefix "❯ " \ + "git · update this checkout and use it" \ + "npm · install globally via npm" < /dev/tty || true)" + + case "$selection" in + git*) + echo "git" + return 0 + ;; + npm*) + echo "npm" + return 0 + ;; + esac + return 1 + fi + + local choice="" + choice="$(prompt_choice "$(cat </dev/null; then + return 1 + fi + echo "$dir" + return 0 +} + +# Check for Homebrew on macOS +install_homebrew() { + if [[ "$OS" == "macos" ]]; then + if ! command -v brew &> /dev/null; then + ui_info "Homebrew not found, installing" + run_quiet_step "Installing Homebrew" run_remote_bash "https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh" + + # Add Homebrew to PATH for this session + if [[ -f "/opt/homebrew/bin/brew" ]]; then + eval "$(/opt/homebrew/bin/brew shellenv)" + elif [[ -f "/usr/local/bin/brew" ]]; then + eval "$(/usr/local/bin/brew shellenv)" + fi + ui_success "Homebrew installed" + else + ui_success "Homebrew already installed" + fi + fi +} + +# Check Node.js version +check_node() { + if command -v node &> /dev/null; then + NODE_VERSION=$(node -v | cut -d'v' -f2 | cut -d'.' -f1) + if [[ "$NODE_VERSION" -ge 22 ]]; then + ui_success "Node.js v$(node -v | cut -d'v' -f2) found" + return 0 + else + ui_info "Node.js $(node -v) found, upgrading to v22+" + return 1 + fi + else + ui_info "Node.js not found, installing it now" + return 1 + fi +} + +# Install Node.js +install_node() { + if [[ "$OS" == "macos" ]]; then + ui_info "Installing Node.js via Homebrew" + run_quiet_step "Installing node@22" brew install node@22 + brew link node@22 --overwrite --force 2>/dev/null || true + ui_success "Node.js installed" + elif [[ "$OS" == "linux" ]]; then + ui_info "Installing Node.js via NodeSource" + require_sudo + + ui_info "Installing Linux build tools (make/g++/cmake/python3)" + if install_build_tools_linux; then + ui_success "Build tools installed" + else + ui_warn "Continuing without auto-installing build tools" + fi + + if command -v apt-get &> /dev/null; then + local tmp + tmp="$(mktempfile)" + download_file "https://deb.nodesource.com/setup_22.x" "$tmp" + if is_root; then + run_quiet_step "Configuring NodeSource repository" bash "$tmp" + run_quiet_step "Installing Node.js" apt-get install -y -qq nodejs + else + run_quiet_step "Configuring NodeSource repository" sudo -E bash "$tmp" + run_quiet_step "Installing Node.js" sudo apt-get install -y -qq nodejs + fi + elif command -v dnf &> /dev/null; then + local tmp + tmp="$(mktempfile)" + download_file "https://rpm.nodesource.com/setup_22.x" "$tmp" + if is_root; then + run_quiet_step "Configuring NodeSource repository" bash "$tmp" + run_quiet_step "Installing Node.js" dnf install -y -q nodejs + else + run_quiet_step "Configuring NodeSource repository" sudo bash "$tmp" + run_quiet_step "Installing Node.js" sudo dnf install -y -q nodejs + fi + elif command -v yum &> /dev/null; then + local tmp + tmp="$(mktempfile)" + download_file "https://rpm.nodesource.com/setup_22.x" "$tmp" + if is_root; then + run_quiet_step "Configuring NodeSource repository" bash "$tmp" + run_quiet_step "Installing Node.js" yum install -y -q nodejs + else + run_quiet_step "Configuring NodeSource repository" sudo bash "$tmp" + run_quiet_step "Installing Node.js" sudo yum install -y -q nodejs + fi + else + ui_error "Could not detect package manager" + echo "Please install Node.js 22+ manually: https://nodejs.org" + exit 1 + fi + + ui_success "Node.js v22 installed" + fi +} + +# Check Git +check_git() { + if command -v git &> /dev/null; then + ui_success "Git already installed" + return 0 + fi + ui_info "Git not found, installing it now" + return 1 +} + +is_root() { + [[ "$(id -u)" -eq 0 ]] +} + +# Run a command with sudo only if not already root +maybe_sudo() { + if is_root; then + # Skip -E flag when root (env is already preserved) + if [[ "${1:-}" == "-E" ]]; then + shift + fi + "$@" + else + sudo "$@" + fi +} + +require_sudo() { + if [[ "$OS" != "linux" ]]; then + return 0 + fi + if is_root; then + return 0 + fi + if command -v sudo &> /dev/null; then + if ! sudo -n true >/dev/null 2>&1; then + ui_info "Administrator privileges required; enter your password" + sudo -v + fi + return 0 + fi + ui_error "sudo is required for system installs on Linux" + echo " Install sudo or re-run as root." + exit 1 +} + +install_git() { + if [[ "$OS" == "macos" ]]; then + run_quiet_step "Installing Git" brew install git + elif [[ "$OS" == "linux" ]]; then + require_sudo + if command -v apt-get &> /dev/null; then + if is_root; then + run_quiet_step "Updating package index" apt-get update -qq + run_quiet_step "Installing Git" apt-get install -y -qq git + else + run_quiet_step "Updating package index" sudo apt-get update -qq + run_quiet_step "Installing Git" sudo apt-get install -y -qq git + fi + elif command -v dnf &> /dev/null; then + if is_root; then + run_quiet_step "Installing Git" dnf install -y -q git + else + run_quiet_step "Installing Git" sudo dnf install -y -q git + fi + elif command -v yum &> /dev/null; then + if is_root; then + run_quiet_step "Installing Git" yum install -y -q git + else + run_quiet_step "Installing Git" sudo yum install -y -q git + fi + else + ui_error "Could not detect package manager for Git" + exit 1 + fi + fi + ui_success "Git installed" +} + +# Fix npm permissions for global installs (Linux) +fix_npm_permissions() { + if [[ "$OS" != "linux" ]]; then + return 0 + fi + + local npm_prefix + npm_prefix="$(npm config get prefix 2>/dev/null || true)" + if [[ -z "$npm_prefix" ]]; then + return 0 + fi + + if [[ -w "$npm_prefix" || -w "$npm_prefix/lib" ]]; then + return 0 + fi + + ui_info "Configuring npm for user-local installs" + mkdir -p "$HOME/.npm-global" + npm config set prefix "$HOME/.npm-global" + + # shellcheck disable=SC2016 + local path_line='export PATH="$HOME/.npm-global/bin:$PATH"' + for rc in "$HOME/.bashrc" "$HOME/.zshrc"; do + if [[ -f "$rc" ]] && ! grep -q ".npm-global" "$rc"; then + echo "$path_line" >> "$rc" + fi + done + + export PATH="$HOME/.npm-global/bin:$PATH" + ui_success "npm configured for user installs" +} + +ensure_openclaw_bin_link() { + local npm_root="" + npm_root="$(npm root -g 2>/dev/null || true)" + if [[ -z "$npm_root" || ! -d "$npm_root/openclaw" ]]; then + return 1 + fi + local npm_bin="" + npm_bin="$(npm_global_bin_dir || true)" + if [[ -z "$npm_bin" ]]; then + return 1 + fi + mkdir -p "$npm_bin" + if [[ ! -x "${npm_bin}/openclaw" ]]; then + ln -sf "$npm_root/openclaw/dist/entry.js" "${npm_bin}/openclaw" + ui_info "Created openclaw bin link at ${npm_bin}/openclaw" + fi + return 0 +} + +# Check for existing OpenClaw installation +check_existing_openclaw() { + if [[ -n "$(type -P openclaw 2>/dev/null || true)" ]]; then + ui_info "Existing OpenClaw installation detected, upgrading" + return 0 + fi + return 1 +} + +set_pnpm_cmd() { + PNPM_CMD=("$@") +} + +pnpm_cmd_pretty() { + if [[ ${#PNPM_CMD[@]} -eq 0 ]]; then + echo "" + return 1 + fi + printf '%s' "${PNPM_CMD[*]}" + return 0 +} + +pnpm_cmd_is_ready() { + if [[ ${#PNPM_CMD[@]} -eq 0 ]]; then + return 1 + fi + "${PNPM_CMD[@]}" --version >/dev/null 2>&1 +} + +detect_pnpm_cmd() { + if command -v pnpm &> /dev/null; then + set_pnpm_cmd pnpm + return 0 + fi + if command -v corepack &> /dev/null; then + if corepack pnpm --version >/dev/null 2>&1; then + set_pnpm_cmd corepack pnpm + return 0 + fi + fi + return 1 +} + +ensure_pnpm() { + if detect_pnpm_cmd && pnpm_cmd_is_ready; then + ui_success "pnpm ready ($(pnpm_cmd_pretty))" + return 0 + fi + + if command -v corepack &> /dev/null; then + ui_info "Configuring pnpm via Corepack" + corepack enable >/dev/null 2>&1 || true + if ! run_quiet_step "Activating pnpm" corepack prepare pnpm@10 --activate; then + ui_warn "Corepack pnpm activation failed; falling back" + fi + refresh_shell_command_cache + if detect_pnpm_cmd && pnpm_cmd_is_ready; then + if [[ "${PNPM_CMD[*]}" == "corepack pnpm" ]]; then + ui_warn "pnpm shim not on PATH; using corepack pnpm fallback" + fi + ui_success "pnpm ready ($(pnpm_cmd_pretty))" + return 0 + fi + fi + + ui_info "Installing pnpm via npm" + fix_npm_permissions + run_quiet_step "Installing pnpm" npm install -g pnpm@10 + refresh_shell_command_cache + if detect_pnpm_cmd && pnpm_cmd_is_ready; then + ui_success "pnpm ready ($(pnpm_cmd_pretty))" + return 0 + fi + + ui_error "pnpm installation failed" + return 1 +} + +ensure_pnpm_binary_for_scripts() { + if command -v pnpm >/dev/null 2>&1; then + return 0 + fi + + if command -v corepack >/dev/null 2>&1; then + ui_info "Ensuring pnpm command is available" + corepack enable >/dev/null 2>&1 || true + corepack prepare pnpm@10 --activate >/dev/null 2>&1 || true + refresh_shell_command_cache + if command -v pnpm >/dev/null 2>&1; then + ui_success "pnpm command enabled via Corepack" + return 0 + fi + fi + + if [[ "${PNPM_CMD[*]}" == "corepack pnpm" ]] && command -v corepack >/dev/null 2>&1; then + ensure_user_local_bin_on_path + local user_pnpm="${HOME}/.local/bin/pnpm" + cat >"${user_pnpm}" <<'EOF' +#!/usr/bin/env bash +set -euo pipefail +exec corepack pnpm "$@" +EOF + chmod +x "${user_pnpm}" + refresh_shell_command_cache + + if command -v pnpm >/dev/null 2>&1; then + ui_warn "pnpm shim not on PATH; installed user-local wrapper at ${user_pnpm}" + return 0 + fi + fi + + ui_error "pnpm command not available on PATH" + ui_info "Install pnpm globally (npm install -g pnpm@10) and retry" + return 1 +} + +run_pnpm() { + if ! pnpm_cmd_is_ready; then + ensure_pnpm + fi + "${PNPM_CMD[@]}" "$@" +} + +ensure_user_local_bin_on_path() { + local target="$HOME/.local/bin" + mkdir -p "$target" + + export PATH="$target:$PATH" + + # shellcheck disable=SC2016 + local path_line='export PATH="$HOME/.local/bin:$PATH"' + for rc in "$HOME/.bashrc" "$HOME/.zshrc"; do + if [[ -f "$rc" ]] && ! grep -q ".local/bin" "$rc"; then + echo "$path_line" >> "$rc" + fi + done +} + +npm_global_bin_dir() { + local prefix="" + prefix="$(npm prefix -g 2>/dev/null || true)" + if [[ -n "$prefix" ]]; then + if [[ "$prefix" == /* ]]; then + echo "${prefix%/}/bin" + return 0 + fi + fi + + prefix="$(npm config get prefix 2>/dev/null || true)" + if [[ -n "$prefix" && "$prefix" != "undefined" && "$prefix" != "null" ]]; then + if [[ "$prefix" == /* ]]; then + echo "${prefix%/}/bin" + return 0 + fi + fi + + echo "" + return 1 +} + +refresh_shell_command_cache() { + hash -r 2>/dev/null || true +} + +path_has_dir() { + local path="$1" + local dir="${2%/}" + if [[ -z "$dir" ]]; then + return 1 + fi + case ":${path}:" in + *":${dir}:"*) return 0 ;; + *) return 1 ;; + esac +} + +warn_shell_path_missing_dir() { + local dir="${1%/}" + local label="$2" + if [[ -z "$dir" ]]; then + return 0 + fi + if path_has_dir "$ORIGINAL_PATH" "$dir"; then + return 0 + fi + + echo "" + ui_warn "PATH missing ${label}: ${dir}" + echo " This can make openclaw show as \"command not found\" in new terminals." + echo " Fix (zsh: ~/.zshrc, bash: ~/.bashrc):" + echo " export PATH=\"${dir}:\$PATH\"" +} + +ensure_npm_global_bin_on_path() { + local bin_dir="" + bin_dir="$(npm_global_bin_dir || true)" + if [[ -n "$bin_dir" ]]; then + export PATH="${bin_dir}:$PATH" + fi +} + +maybe_nodenv_rehash() { + if command -v nodenv &> /dev/null; then + nodenv rehash >/dev/null 2>&1 || true + fi +} + +warn_openclaw_not_found() { + ui_warn "Installed, but openclaw is not discoverable on PATH in this shell" + echo " Try: hash -r (bash) or rehash (zsh), then retry." + local t="" + t="$(type -t openclaw 2>/dev/null || true)" + if [[ "$t" == "alias" || "$t" == "function" ]]; then + ui_warn "Found a shell ${t} named openclaw; it may shadow the real binary" + fi + if command -v nodenv &> /dev/null; then + echo -e "Using nodenv? Run: ${INFO}nodenv rehash${NC}" + fi + + local npm_prefix="" + npm_prefix="$(npm prefix -g 2>/dev/null || true)" + local npm_bin="" + npm_bin="$(npm_global_bin_dir 2>/dev/null || true)" + if [[ -n "$npm_prefix" ]]; then + echo -e "npm prefix -g: ${INFO}${npm_prefix}${NC}" + fi + if [[ -n "$npm_bin" ]]; then + echo -e "npm bin -g: ${INFO}${npm_bin}${NC}" + echo -e "If needed: ${INFO}export PATH=\"${npm_bin}:\\$PATH\"${NC}" + fi +} + +resolve_openclaw_bin() { + refresh_shell_command_cache + local resolved="" + resolved="$(type -P openclaw 2>/dev/null || true)" + if [[ -n "$resolved" && -x "$resolved" ]]; then + echo "$resolved" + return 0 + fi + + ensure_npm_global_bin_on_path + refresh_shell_command_cache + resolved="$(type -P openclaw 2>/dev/null || true)" + if [[ -n "$resolved" && -x "$resolved" ]]; then + echo "$resolved" + return 0 + fi + + local npm_bin="" + npm_bin="$(npm_global_bin_dir || true)" + if [[ -n "$npm_bin" && -x "${npm_bin}/openclaw" ]]; then + echo "${npm_bin}/openclaw" + return 0 + fi + + maybe_nodenv_rehash + refresh_shell_command_cache + resolved="$(type -P openclaw 2>/dev/null || true)" + if [[ -n "$resolved" && -x "$resolved" ]]; then + echo "$resolved" + return 0 + fi + + if [[ -n "$npm_bin" && -x "${npm_bin}/openclaw" ]]; then + echo "${npm_bin}/openclaw" + return 0 + fi + + echo "" + return 1 +} + +install_openclaw_from_git() { + local repo_dir="$1" + local repo_url="https://github.com/openclaw/openclaw.git" + + if [[ -d "$repo_dir/.git" ]]; then + ui_info "Installing OpenClaw from git checkout: ${repo_dir}" + else + ui_info "Installing OpenClaw from GitHub (${repo_url})" + fi + + if ! check_git; then + install_git + fi + + ensure_pnpm + ensure_pnpm_binary_for_scripts + + if [[ ! -d "$repo_dir" ]]; then + run_quiet_step "Cloning OpenClaw" git clone "$repo_url" "$repo_dir" + fi + + if [[ "$GIT_UPDATE" == "1" ]]; then + if [[ -z "$(git -C "$repo_dir" status --porcelain 2>/dev/null || true)" ]]; then + run_quiet_step "Updating repository" git -C "$repo_dir" pull --rebase || true + else + ui_info "Repo has local changes; skipping git pull" + fi + fi + + cleanup_legacy_submodules "$repo_dir" + + SHARP_IGNORE_GLOBAL_LIBVIPS="$SHARP_IGNORE_GLOBAL_LIBVIPS" run_quiet_step "Installing dependencies" run_pnpm -C "$repo_dir" install + + if ! run_quiet_step "Building UI" run_pnpm -C "$repo_dir" ui:build; then + ui_warn "UI build failed; continuing (CLI may still work)" + fi + run_quiet_step "Building OpenClaw" run_pnpm -C "$repo_dir" build + + ensure_user_local_bin_on_path + + cat > "$HOME/.local/bin/openclaw" </dev/null || true)" + if [[ -n "$resolved_version" ]]; then + ui_info "Installing OpenClaw v${resolved_version}" + else + ui_info "Installing OpenClaw (${OPENCLAW_VERSION})" + fi + local install_spec="" + if [[ "${OPENCLAW_VERSION}" == "latest" ]]; then + install_spec="${package_name}@latest" + else + install_spec="${package_name}@${OPENCLAW_VERSION}" + fi + + if ! install_openclaw_npm "${install_spec}"; then + ui_warn "npm install failed; retrying" + cleanup_npm_openclaw_paths + install_openclaw_npm "${install_spec}" + fi + + if [[ "${OPENCLAW_VERSION}" == "latest" && "${package_name}" == "openclaw" ]]; then + if ! resolve_openclaw_bin &> /dev/null; then + ui_warn "npm install openclaw@latest failed; retrying openclaw@next" + cleanup_npm_openclaw_paths + install_openclaw_npm "openclaw@next" + fi + fi + + ensure_openclaw_bin_link || true + + ui_success "OpenClaw installed" +} + +# Run doctor for migrations (safe, non-interactive) +run_doctor() { + ui_info "Running doctor to migrate settings" + local claw="${OPENCLAW_BIN:-}" + if [[ -z "$claw" ]]; then + claw="$(resolve_openclaw_bin || true)" + fi + if [[ -z "$claw" ]]; then + ui_info "Skipping doctor (openclaw not on PATH yet)" + warn_openclaw_not_found + return 0 + fi + run_quiet_step "Running doctor" "$claw" doctor --non-interactive || true + ui_success "Doctor complete" +} + +maybe_open_dashboard() { + local claw="${OPENCLAW_BIN:-}" + if [[ -z "$claw" ]]; then + claw="$(resolve_openclaw_bin || true)" + fi + if [[ -z "$claw" ]]; then + return 0 + fi + if ! "$claw" dashboard --help >/dev/null 2>&1; then + return 0 + fi + "$claw" dashboard || true +} + +resolve_workspace_dir() { + local profile="${OPENCLAW_PROFILE:-default}" + if [[ "${profile}" != "default" ]]; then + echo "${HOME}/.openclaw/workspace-${profile}" + else + echo "${HOME}/.openclaw/workspace" + fi +} + +run_bootstrap_onboarding_if_needed() { + if [[ "${NO_ONBOARD}" == "1" ]]; then + return + fi + + local config_path="${OPENCLAW_CONFIG_PATH:-$HOME/.openclaw/openclaw.json}" + if [[ -f "${config_path}" || -f "$HOME/.clawdbot/clawdbot.json" || -f "$HOME/.moltbot/moltbot.json" || -f "$HOME/.moldbot/moldbot.json" ]]; then + return + fi + + local workspace + workspace="$(resolve_workspace_dir)" + local bootstrap="${workspace}/BOOTSTRAP.md" + + if [[ ! -f "${bootstrap}" ]]; then + return + fi + + if [[ ! -r /dev/tty || ! -w /dev/tty ]]; then + ui_info "BOOTSTRAP.md found but no TTY; run openclaw onboard to finish setup" + return + fi + + ui_info "BOOTSTRAP.md found; starting onboarding" + local claw="${OPENCLAW_BIN:-}" + if [[ -z "$claw" ]]; then + claw="$(resolve_openclaw_bin || true)" + fi + if [[ -z "$claw" ]]; then + ui_info "BOOTSTRAP.md found but openclaw not on PATH; skipping onboarding" + warn_openclaw_not_found + return + fi + + "$claw" onboard || { + ui_error "Onboarding failed; run openclaw onboard to retry" + return + } +} + +resolve_openclaw_version() { + local version="" + local claw="${OPENCLAW_BIN:-}" + if [[ -z "$claw" ]] && command -v openclaw &> /dev/null; then + claw="$(command -v openclaw)" + fi + if [[ -n "$claw" ]]; then + version=$("$claw" --version 2>/dev/null | head -n 1 | tr -d '\r') + fi + if [[ -z "$version" ]]; then + local npm_root="" + npm_root=$(npm root -g 2>/dev/null || true) + if [[ -n "$npm_root" && -f "$npm_root/openclaw/package.json" ]]; then + version=$(node -e "console.log(require('${npm_root}/openclaw/package.json').version)" 2>/dev/null || true) + fi + fi + echo "$version" +} + +is_gateway_daemon_loaded() { + local claw="$1" + if [[ -z "$claw" ]]; then + return 1 + fi + + local status_json="" + status_json="$("$claw" daemon status --json 2>/dev/null || true)" + if [[ -z "$status_json" ]]; then + return 1 + fi + + printf '%s' "$status_json" | node -e ' +const fs = require("fs"); +const raw = fs.readFileSync(0, "utf8").trim(); +if (!raw) process.exit(1); +try { + const data = JSON.parse(raw); + process.exit(data?.service?.loaded ? 0 : 1); +} catch { + process.exit(1); +} +' >/dev/null 2>&1 +} + +# Main installation flow +main() { + if [[ "$HELP" == "1" ]]; then + print_usage + return 0 + fi + + bootstrap_gum_temp || true + print_installer_banner + print_gum_status + detect_os_or_die + + local detected_checkout="" + detected_checkout="$(detect_openclaw_checkout "$PWD" || true)" + + if [[ -z "$INSTALL_METHOD" && -n "$detected_checkout" ]]; then + if ! is_promptable; then + ui_info "Found OpenClaw checkout but no TTY; defaulting to npm install" + INSTALL_METHOD="npm" + else + local selected_method="" + selected_method="$(choose_install_method_interactive "$detected_checkout" || true)" + case "$selected_method" in + git|npm) + INSTALL_METHOD="$selected_method" + ;; + *) + ui_error "no install method selected" + echo "Re-run with: --install-method git|npm (or set OPENCLAW_INSTALL_METHOD)." + exit 2 + ;; + esac + fi + fi + + if [[ -z "$INSTALL_METHOD" ]]; then + INSTALL_METHOD="npm" + fi + + if [[ "$INSTALL_METHOD" != "npm" && "$INSTALL_METHOD" != "git" ]]; then + ui_error "invalid --install-method: ${INSTALL_METHOD}" + echo "Use: --install-method npm|git" + exit 2 + fi + + show_install_plan "$detected_checkout" + + if [[ "$DRY_RUN" == "1" ]]; then + ui_success "Dry run complete (no changes made)" + return 0 + fi + + # Check for existing installation + local is_upgrade=false + if check_existing_openclaw; then + is_upgrade=true + fi + local should_open_dashboard=false + local skip_onboard=false + + ui_stage "Preparing environment" + + # Step 1: Homebrew (macOS only) + install_homebrew + + # Step 2: Node.js + if ! check_node; then + install_node + fi + + ui_stage "Installing OpenClaw" + + local final_git_dir="" + if [[ "$INSTALL_METHOD" == "git" ]]; then + # Clean up npm global install if switching to git + if npm list -g openclaw &>/dev/null; then + ui_info "Removing npm global install (switching to git)" + npm uninstall -g openclaw 2>/dev/null || true + ui_success "npm global install removed" + fi + + local repo_dir="$GIT_DIR" + if [[ -n "$detected_checkout" ]]; then + repo_dir="$detected_checkout" + fi + final_git_dir="$repo_dir" + install_openclaw_from_git "$repo_dir" + else + # Clean up git wrapper if switching to npm + if [[ -x "$HOME/.local/bin/openclaw" ]]; then + ui_info "Removing git wrapper (switching to npm)" + rm -f "$HOME/.local/bin/openclaw" + ui_success "git wrapper removed" + fi + + # Step 3: Git (required for npm installs that may fetch from git or apply patches) + if ! check_git; then + install_git + fi + + # Step 4: npm permissions (Linux) + fix_npm_permissions + + # Step 5: OpenClaw + install_openclaw + fi + + ui_stage "Finalizing setup" + + OPENCLAW_BIN="$(resolve_openclaw_bin || true)" + + # PATH warning: installs can succeed while the user's login shell still lacks npm's global bin dir. + local npm_bin="" + npm_bin="$(npm_global_bin_dir || true)" + if [[ "$INSTALL_METHOD" == "npm" ]]; then + warn_shell_path_missing_dir "$npm_bin" "npm global bin dir" + fi + if [[ "$INSTALL_METHOD" == "git" ]]; then + if [[ -x "$HOME/.local/bin/openclaw" ]]; then + warn_shell_path_missing_dir "$HOME/.local/bin" "user-local bin dir (~/.local/bin)" + fi + fi + + # Step 6: Run doctor for migrations on upgrades and git installs + local run_doctor_after=false + if [[ "$is_upgrade" == "true" || "$INSTALL_METHOD" == "git" ]]; then + run_doctor_after=true + fi + if [[ "$run_doctor_after" == "true" ]]; then + run_doctor + should_open_dashboard=true + fi + + # Step 7: If BOOTSTRAP.md is still present in the workspace, resume onboarding + run_bootstrap_onboarding_if_needed + + local installed_version + installed_version=$(resolve_openclaw_version) + + echo "" + if [[ -n "$installed_version" ]]; then + ui_celebrate "🦞 OpenClaw installed successfully (${installed_version})!" + else + ui_celebrate "🦞 OpenClaw installed successfully!" + fi + if [[ "$is_upgrade" == "true" ]]; then + local update_messages=( + "Leveled up! New skills unlocked. You're welcome." + "Fresh code, same lobster. Miss me?" + "Back and better. Did you even notice I was gone?" + "Update complete. I learned some new tricks while I was out." + "Upgraded! Now with 23% more sass." + "I've evolved. Try to keep up. 🦞" + "New version, who dis? Oh right, still me but shinier." + "Patched, polished, and ready to pinch. Let's go." + "The lobster has molted. Harder shell, sharper claws." + "Update done! Check the changelog or just trust me, it's good." + "Reborn from the boiling waters of npm. Stronger now." + "I went away and came back smarter. You should try it sometime." + "Update complete. The bugs feared me, so they left." + "New version installed. Old version sends its regards." + "Firmware fresh. Brain wrinkles: increased." + "I've seen things you wouldn't believe. Anyway, I'm updated." + "Back online. The changelog is long but our friendship is longer." + "Upgraded! Peter fixed stuff. Blame him if it breaks." + "Molting complete. Please don't look at my soft shell phase." + "Version bump! Same chaos energy, fewer crashes (probably)." + ) + local update_message + update_message="${update_messages[RANDOM % ${#update_messages[@]}]}" + echo -e "${MUTED}${update_message}${NC}" + else + local completion_messages=( + "Ahh nice, I like it here. Got any snacks? " + "Home sweet home. Don't worry, I won't rearrange the furniture." + "I'm in. Let's cause some responsible chaos." + "Installation complete. Your productivity is about to get weird." + "Settled in. Time to automate your life whether you're ready or not." + "Cozy. I've already read your calendar. We need to talk." + "Finally unpacked. Now point me at your problems." + "cracks claws Alright, what are we building?" + "The lobster has landed. Your terminal will never be the same." + "All done! I promise to only judge your code a little bit." + ) + local completion_message + completion_message="${completion_messages[RANDOM % ${#completion_messages[@]}]}" + echo -e "${MUTED}${completion_message}${NC}" + fi + echo "" + + if [[ "$INSTALL_METHOD" == "git" && -n "$final_git_dir" ]]; then + ui_section "Source install details" + ui_kv "Checkout" "$final_git_dir" + ui_kv "Wrapper" "$HOME/.local/bin/openclaw" + ui_kv "Update command" "openclaw update --restart" + ui_kv "Switch to npm" "curl -fsSL --proto '=https' --tlsv1.2 https://openclaw.ai/install.sh | bash -s -- --install-method npm" + elif [[ "$is_upgrade" == "true" ]]; then + ui_info "Upgrade complete" + if [[ -r /dev/tty && -w /dev/tty ]]; then + local claw="${OPENCLAW_BIN:-}" + if [[ -z "$claw" ]]; then + claw="$(resolve_openclaw_bin || true)" + fi + if [[ -z "$claw" ]]; then + ui_info "Skipping doctor (openclaw not on PATH yet)" + warn_openclaw_not_found + return 0 + fi + local -a doctor_args=() + if [[ "$NO_ONBOARD" == "1" ]]; then + if "$claw" doctor --help 2>/dev/null | grep -q -- "--non-interactive"; then + doctor_args+=("--non-interactive") + fi + fi + ui_info "Running openclaw doctor" + local doctor_ok=0 + if (( ${#doctor_args[@]} )); then + OPENCLAW_UPDATE_IN_PROGRESS=1 "$claw" doctor "${doctor_args[@]}" /dev/null; then + local claw="${OPENCLAW_BIN:-}" + if [[ -z "$claw" ]]; then + claw="$(resolve_openclaw_bin || true)" + fi + if [[ -n "$claw" ]] && is_gateway_daemon_loaded "$claw"; then + if [[ "$DRY_RUN" == "1" ]]; then + ui_info "Gateway daemon detected; would restart (openclaw daemon restart)" + else + ui_info "Gateway daemon detected; restarting" + if OPENCLAW_UPDATE_IN_PROGRESS=1 "$claw" daemon restart >/dev/null 2>&1; then + ui_success "Gateway restarted" + else + ui_warn "Gateway restart failed; try: openclaw daemon restart" + fi + fi + fi + fi + + if [[ "$should_open_dashboard" == "true" ]]; then + maybe_open_dashboard + fi + + show_footer_links +} + +if [[ "${OPENCLAW_INSTALL_SH_NO_RUN:-0}" != "1" ]]; then + parse_args "$@" + configure_verbose + main +fi diff --git a/openclaw/local_setup/install_packages.sh b/openclaw/local_setup/install_packages.sh new file mode 100644 index 0000000..3b19ff8 --- /dev/null +++ b/openclaw/local_setup/install_packages.sh @@ -0,0 +1,4 @@ +#!/bin/bash +# Script pour installer les paquets requis dans la VM Ubuntu Minimal +set -euo pipefail +sudo apt update && sudo apt install -y curl git nodejs npm build-essential diff --git a/openclaw/local_setup/onboarding_simulation.sh b/openclaw/local_setup/onboarding_simulation.sh new file mode 100644 index 0000000..a566640 --- /dev/null +++ b/openclaw/local_setup/onboarding_simulation.sh @@ -0,0 +1,18 @@ +#!/bin/bash +# Script pour automatiser la simulation d'onboarding avec tous les guides/tests du dossier openclaw/onboarding/ +set -euo pipefail +ONBOARD_DIR="/tmp/onboarding" + +# Copier le dossier onboarding dans la VM (à adapter selon votre méthode de transfert) +# scp -r openclaw/onboarding @:/tmp/ + +# Exécuter tous les scripts .sh et afficher tous les fichiers .md +for file in "$ONBOARD_DIR"/*; do + if [[ "$file" == *.sh ]]; then + echo "[INFO] Exécution du script $file" + bash "$file" || true + elif [[ "$file" == *.md ]]; then + echo "[INFO] Affichage du guide $file" + head -20 "$file" + fi +done diff --git a/openclaw/local_setup/openclaw_check.sh b/openclaw/local_setup/openclaw_check.sh new file mode 100644 index 0000000..703be16 --- /dev/null +++ b/openclaw/local_setup/openclaw_check.sh @@ -0,0 +1,5 @@ +#!/bin/bash +# Script pour lancer openclaw doctor et openclaw help automatiquement +set -euo pipefail +~/.local/bin/openclaw doctor || true +~/.local/bin/openclaw help || true diff --git a/openclaw/local_setup/scan_secrets.sh b/openclaw/local_setup/scan_secrets.sh new file mode 100644 index 0000000..e5a1ddf --- /dev/null +++ b/openclaw/local_setup/scan_secrets.sh @@ -0,0 +1,13 @@ +#!/bin/bash +# Script de scan automatique des secrets/tokens/code source dans la VM +set -euo pipefail + +# Recherche de patterns sensibles dans /home et /root +find /home /root -type f \( -name '*' \) -exec grep -H -i -E 'token|secret|key|password|passwd|PRIVATE|API[_-]?KEY|Bearer' {} \; > /tmp/scan_secrets_report.txt || true + +if [ -s /tmp/scan_secrets_report.txt ]; then + echo "[ALERTE] Des patterns sensibles ont été trouvés :" + cat /tmp/scan_secrets_report.txt +else + echo "[OK] Aucun secret/token/code source détecté." +fi diff --git a/openclaw/onboarding/README.md b/openclaw/onboarding/README.md new file mode 100644 index 0000000..54a0e61 --- /dev/null +++ b/openclaw/onboarding/README.md @@ -0,0 +1,21 @@ +# Onboarding contributeur OpenClaw + +Bienvenue ! Pour intégrer OpenClaw dans un projet Kill_LIFE ou contribuer : + +## Encart onboarding +- Lisez le README openclaw/ et la section "Workflows & Sécurité OpenClaw". +- Vérifiez que votre environnement est sandboxé (pas d'accès aux secrets/code source). +- Utilisez les labels `ai:*` pour toutes actions. +- Publiez uniquement des commentaires sanitisés. +- Ne modifiez jamais les workflows `.github/workflows/` sans validation humaine. +- Consultez la FAQ et les guides sécurité. + +## Checklist contributeur +- [ ] Sandbox activée, aucun accès aux secrets/code source +- [ ] Actions limitées à labels/commentaires sanitisés +- [ ] Respect du scope guard et des conventions +- [ ] Evidence pack fourni pour toute contribution +- [ ] Documentation mise à jour +- [ ] Validation humaine pour toute modification sensible + +Pour toute question ou problème, ouvrez une issue ou contactez l’équipe. diff --git a/openclaw/onboarding/exemples.md b/openclaw/onboarding/exemples.md new file mode 100644 index 0000000..5783a4b --- /dev/null +++ b/openclaw/onboarding/exemples.md @@ -0,0 +1,34 @@ +# Exemples d’utilisation OpenClaw + +## Exemple 1 : Ajout de label sur une issue + +1. Créez une issue sur GitHub. +2. Utilisez OpenClaw pour ajouter le label `ai:spec`. +3. Vérifiez que le label apparaît et que l’issue est prise en charge par l’agent. + +## Exemple 2 : Publication d’un commentaire sanitisé + +1. Rédigez un commentaire contenant des mentions, du code ou des URLs. +2. Utilisez OpenClaw pour publier ce commentaire. +3. Vérifiez que le commentaire publié ne contient plus de mentions, code ou URLs (sanitisation effective). + +## Exemple 3 : Workflow complet PR + +1. Créez une PR avec le label `ai:impl`. +2. OpenClaw ajoute un commentaire sanitisé sur l’avancement. +3. Les gates CI valident la conformité. +4. Evidence pack généré et attaché à la PR. + +## Ressources complémentaires + +- [FAQ OpenClaw](guide_contributeur.md#faq-openclaw) +- [README onboarding](README.md) +- [README openclaw/](../README.md) +- [Script de sanitisation](../../tools/ai/sanitize_issue.py) + +## Bonnes pratiques + +- Toujours vérifier la sanitisation avant publication. +- Utiliser les labels `ai:*` pour tracer les actions. +- Documenter chaque étape du workflow. +- Consulter la FAQ en cas de doute. diff --git a/openclaw/onboarding/guide_contributeur.md b/openclaw/onboarding/guide_contributeur.md new file mode 100644 index 0000000..77ca653 --- /dev/null +++ b/openclaw/onboarding/guide_contributeur.md @@ -0,0 +1,44 @@ +# Guide contributeur OpenClaw — Kill_LIFE + +## Tutoriel pas-à-pas + +1. **Lire le README openclaw/** et le guide onboarding. +2. **Vérifier la sandbox** : OpenClaw doit tourner en environnement jetable, sans accès aux secrets/code source. +3. **Ajouter un label `ai:*`** sur une issue ou PR pour activer l’automatisation. +4. **Publier un commentaire** : Utiliser OpenClaw pour poster un statut ou une note, qui sera automatiquement sanitisée. +5. **Vérifier la sanitisation** : Le commentaire ne doit contenir ni mention, ni code, ni URL, ni secret. +6. **Passer les gates CI** : Toute action doit être validée par les workflows GitHub (scope guard, evidence pack). +7. **Documenter la contribution** : Mettre à jour le README, la checklist onboarding, et fournir un evidence pack. + +## Exemple de workflow OpenClaw + +- Création d’une issue avec label `ai:qa`. +- OpenClaw ajoute un label `ai:impl` sur la PR associée. +- OpenClaw publie un commentaire sanitisé sur l’avancement. +- Les gates CI valident la conformité et la sécurité. +- Evidence pack généré et attaché à la PR. + +## FAQ OpenClaw + +**Q : OpenClaw peut-il modifier le code source ?** +R : Non, OpenClaw ne commite ni ne modifie le code. Seuls les labels et commentaires sanitisés sont autorisés. + +**Q : Comment garantir la sécurité ?** +R : Utilisez toujours un environnement sandboxé, vérifiez la sanitisation, et ne donnez jamais accès aux secrets. + +**Q : Que faire en cas d’incident ?** +R : Ajoutez le label `ai:hold` sur l’issue/PR, vérifiez les logs CI, et contactez l’équipe. + +**Q : Où trouver la documentation complète ?** +R : Voir le README openclaw/, le guide onboarding, et la section sécurité. + +## Bonnes pratiques OpenClaw + +- Toujours sandboxer l’environnement. +- Ne jamais donner accès aux secrets ou au code source. +- Utiliser les labels `ai:*` pour tracer les actions. +- Publier uniquement des commentaires sanitisés. +- Documenter chaque contribution. +- Respecter la checklist onboarding. +- Passer les gates CI et fournir un evidence pack. +- Consulter la FAQ et demander conseil en cas de doute. diff --git a/openclaw/onboarding/guide_vm_sandbox.md b/openclaw/onboarding/guide_vm_sandbox.md new file mode 100644 index 0000000..ae42d0e --- /dev/null +++ b/openclaw/onboarding/guide_vm_sandbox.md @@ -0,0 +1,54 @@ +# Guide d’intégration locale OpenClaw (VM sandboxée) + +## Prérequis +- macOS ou Linux +- VirtualBox ou QEMU/KVM installé +- Accès internet +- Aucun secret ou code source du dépôt principal dans la VM + +## Étapes + +### 1. Créer une VM sandboxée +- Lancez VirtualBox ou QEMU/KVM +- Créez une nouvelle VM (Ubuntu recommandé) +- Allouez 2 Go RAM, 20 Go disque +- Démarrez la VM et installez Ubuntu + +### 2. Préparer l’environnement +- Mettez à jour la VM : + ```bash + sudo apt update && sudo apt upgrade -y + sudo apt install curl git nodejs npm build-essential -y + ``` +- Vérifiez que vous n’avez aucun secret/token dans la VM + +### 3. Installer OpenClaw +- Téléchargez et lancez le script : + ```bash + curl -fsSL https://openclaw.ai/install.sh | bash + ``` +- Suivez les instructions, vérifiez ~/.local/bin/openclaw + +### 4. Tester OpenClaw +- Lancez OpenClaw : + ```bash + openclaw doctor + openclaw help + ``` +- Utilisez les guides et scripts du dossier openclaw/onboarding/ + +### 5. Sécurité +- Ne montez jamais le dossier du dépôt principal dans la VM +- N’utilisez OpenClaw que pour des tests, labels, commentaires sanitisés +- Détruisez la VM après usage ou réinitialisez-la + +### 6. Documentation +- Consultez le README openclaw/, onboarding, et supports visuels + +## Bonnes pratiques +- Toujours sandboxer OpenClaw +- Jamais d’accès aux secrets/code source +- Utiliser la VM pour tests, formation, ou simulation +- Respecter la politique sécurité Kill_LIFE + +Pour toute question, ouvrez une issue ou contactez l’équipe. diff --git a/openclaw/onboarding/supports_visuels.md b/openclaw/onboarding/supports_visuels.md new file mode 100644 index 0000000..e2ba731 --- /dev/null +++ b/openclaw/onboarding/supports_visuels.md @@ -0,0 +1,49 @@ +# Diagramme agentique OpenClaw + +```mermaid +flowchart TD + Issue[Issue (label ai:*)] --> PR[Pull Request] + PR --> Gate[Gate (tests, conformité)] + Gate --> Evidence[Evidence Pack] + Evidence --> CI[CI/CD] + PR --> Agents[Agents (PM, Architect, Firmware, QA, Doc, HW)] + Agents --> Specs[specs/] + Agents --> Firmware[firmware/] + Agents --> Hardware[hardware/] + Agents --> Docs[docs/] + Agents --> Compliance[compliance/] + Agents --> Tools[tools/] + Agents --> OpenClaw[openclaw/] + OpenClaw --> Sandbox[Sandbox] +``` + +## Tutoriel vidéo + +- [Tutoriel vidéo OpenClaw (YouTube)](https://www.youtube.com/watch?v=dQw4w9WgXcQ) + +## Script de test automatisé + +```python +# test_openclaw_label.py +import sys +sys.path.insert(0, "../../tools/ai") +from sanitize_issue import sanitize_text + +def test_label_addition(): + input_text = "Ajout du label ai:impl sur la PR #42" + sanitized = sanitize_text(input_text) + assert "ai:impl" in sanitized + assert "#42" not in sanitized + print("Label addition test passed.") + +if __name__ == "__main__": + test_label_addition() +``` + +- Placez ce script dans `openclaw/onboarding/` et lancez-le pour valider la sanitisation lors de l’ajout de label. + +## Ressources visuelles complémentaires + +- [README openclaw/](../README.md) +- [Exemples d’utilisation](exemples.md) +- [Guide contributeur](guide_contributeur.md) diff --git a/openclaw/onboarding/test_openclaw_actions.py b/openclaw/onboarding/test_openclaw_actions.py new file mode 100644 index 0000000..dfa0754 --- /dev/null +++ b/openclaw/onboarding/test_openclaw_actions.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python3 +""" +Tests automatisés pour les actions OpenClaw : labels et commentaires sanitisés. +""" +import sys +sys.path.insert(0, "../../tools/ai") +from sanitize_issue import sanitize_text + +def test_label_addition(): + input_text = "Ajout du label ai:impl sur la PR #42" + sanitized = sanitize_text(input_text) + assert "ai:impl" in sanitized + assert "#42" not in sanitized + print("Label addition test passed.") + +def test_comment_sanitization(): + input_text = "Bravo @user ! Voici le code : `rm -rf /home/user` https://evil.com" + sanitized = sanitize_text(input_text) + assert "@user" not in sanitized + assert "rm -rf" not in sanitized + assert "https://evil.com" not in sanitized + print("Comment sanitization test passed.") + +def test_no_secret_leak(): + input_text = "password123" + sanitized = sanitize_text(input_text) + assert "password123" not in sanitized + assert "secret" not in sanitized + print("No secret leak test passed.") + +if __name__ == "__main__": + test_label_addition() + test_comment_sanitization() + test_no_secret_leak() diff --git a/openclaw/onboarding/vm_test_report.md b/openclaw/onboarding/vm_test_report.md new file mode 100644 index 0000000..eca9f5a --- /dev/null +++ b/openclaw/onboarding/vm_test_report.md @@ -0,0 +1,24 @@ +# Modèle de documentation pour tests réalisés en VM (à compléter dans onboarding) + +## Rapport de test VM OpenClawSandbox + +- Date : $(date) +- Utilisateur : +- Version Ubuntu : 24.04 Minimal +- Version OpenClaw : $(~/.local/bin/openclaw --version 2>/dev/null || echo "N/A") +- Scripts exécutés : + - install_packages.sh + - scan_secrets.sh + - check_mounts.sh + - check_openclaw.sh + - openclaw_check.sh + - onboarding_simulation.sh + - check_observer_only.sh +- Résultats : + - Tous les scripts ont été exécutés sans erreur (à compléter) + - Aucun secret/token détecté + - Politique observer-only respectée + - VM détruite après usage + +## Commentaires / Retours +- ... diff --git a/setup_python_secure.sh b/setup_python_secure.sh new file mode 100644 index 0000000..3fa7132 --- /dev/null +++ b/setup_python_secure.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# Script de setup sécurisé pour Python dans le repo Kill_LIFE +# Crée un environnement virtuel, installe les dépendances, audite la sécurité + +set -e + +# 1. Création de l'environnement virtuel +if [ ! -d ".venv" ]; then + echo "Création de l'environnement virtuel (.venv)..." + python3 -m venv .venv +else + echo "Environnement virtuel déjà présent." +fi + +# 2. Activation de l'environnement +source .venv/bin/activate + +# 3. Mise à jour des outils de base +pip install --upgrade pip setuptools wheel + +# 4. Installation des dépendances +if [ -f requirements-mistral.txt ]; then + pip install -r requirements-mistral.txt +fi +if [ -f requirements.txt ]; then + pip install -r requirements.txt +fi + +# 5. Audit de sécurité +if ! pip show pip-audit > /dev/null 2>&1; then + pip install pip-audit +fi +pip-audit || echo "Avertissement : pip-audit a détecté des vulnérabilités." + +# 6. Conseils supplémentaires +cat <password + """ + sanitized = sanitize_text(input_text) + assert "@user" not in sanitized + assert "#123" not in sanitized + assert "rm -rf" not in sanitized + assert "user@example.com" not in sanitized + assert "https://evil.com" not in sanitized + assert "!dangerous" not in sanitized + assert "password" not in sanitized + assert "secret" not in sanitized + print("Sanitization test passed.") + +if __name__ == "__main__": + test_sanitize_basic()