diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 506ed86..ea55bf2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -58,6 +58,39 @@ jobs: working-directory: editor run: npm run build + windows-bootstrap: + name: Windows clean bootstrap + runs-on: windows-latest + env: + BLACKNODE_BOOTSTRAP_ONLY: "1" + BLACKNODE_PACKAGE_AUTO_UPDATE: "0" + + steps: + - name: Check out repository + uses: actions/checkout@v6 + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: "3.13" + cache: pip + cache-dependency-path: pyproject.toml + + - name: Set up Node + uses: actions/setup-node@v5 + with: + node-version: "20.19.0" + cache: npm + cache-dependency-path: editor/package-lock.json + + - name: Bootstrap through the public launcher + shell: powershell + run: .\start.ps1 + + - name: Run the installed CLI demo + shell: powershell + run: .\.venv\Scripts\blacknode.exe demo + rust: name: Rust check runs-on: ubuntu-latest diff --git a/README.md b/README.md index 36ceb30..c5342ba 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,31 @@ process, workstation, Jetson, robot, simulator, or cloud runtime. ## Start Here -**New users should begin with the [Beginner Walkthrough](docs/walkthrough.md).** +Clone Blacknode and run its launcher. The first run creates an isolated Python +environment, installs the backend and editor dependencies, starts both services, +and opens the browser. Later starts reuse the installed dependencies. + +Windows: + +```powershell +git clone https://github.com/temiroff/Blacknode.git +cd Blacknode +start.bat +``` + +macOS/Linux: + +```bash +git clone https://github.com/temiroff/Blacknode.git +cd Blacknode +chmod +x start.sh +./start.sh +``` + +Python 3.11+ and Node.js 20.19+ or 22.12+ are required. A warm start normally +takes less than a minute; first-run time depends on network and package caches. + +Continue with the [Beginner Walkthrough](docs/walkthrough.md). It shows the exact commands to run, buttons to press, templates to open, results to expect, NVIDIA NIM paths, MCP setup, framework export, Docker Compose, diff --git a/docs/walkthrough.md b/docs/walkthrough.md index 43e7286..393a1c1 100644 --- a/docs/walkthrough.md +++ b/docs/walkthrough.md @@ -30,54 +30,65 @@ macOS/Linux: cd path/to/Blacknode ``` -## 2. Install Blacknode +## 2. Start Blacknode -Run this once. It installs the Python package, the `blacknode` command, and MCP -support. +The launcher performs first-run setup automatically. It creates a project-local +`.venv`, installs Blacknode and the editor dependencies, starts the backend and +visual editor, and opens the browser. Later starts reuse the installed files. Windows: -```powershell -pip install -e ".[mcp]" -cd editor-server -pip install -r requirements.txt -cd ..\editor -npm install -cd .. +```bat +start.bat ``` macOS/Linux: ```bash -pip install -e ".[mcp]" -cd editor-server -pip install -r requirements.txt -cd ../editor -npm install -cd .. +chmod +x start.sh +./start.sh ``` What this does: -- `pip install -e ".[mcp]"` creates the `blacknode` terminal command from - `pyproject.toml`. -- `editor-server` dependencies run the local backend on port `7777`. -- `editor` dependencies run the browser UI on port `3000`. +- Creates `.venv` and installs the Python runtime, CLI, backend, and MCP support. +- Installs the visual editor dependencies under `editor/node_modules`. +- Starts the backend on port `7777` and the browser UI on port `3000`. +- Opens `http://localhost:3000` when possible. + +First-run time depends on network speed and whether pip/npm packages are already +cached. Later starts normally complete in less than one minute. ## 3. Check the Local Setup -Run: +Keep Blacknode running. In a second terminal, activate its environment once; +the remaining `blacknode` commands in this guide will then work directly. + +Windows: ```powershell +.\.venv\Scripts\Activate.ps1 +blacknode doctor +``` + +macOS/Linux: + +```bash +source .venv/bin/activate blacknode doctor ``` +If PowerShell blocks activation, call the executable directly: + +```powershell +.\.venv\Scripts\blacknode.exe doctor +``` + Expected result: - Green `[OK]` means that part is ready. - Red `[NOT OK]` means install or fix that required part. -- Yellow `[WARN] Editor server: not running` is normal before starting the - editor. +- The editor-server check should be green because the launcher is still running. - The final line should say `Required checks passed.` ## 4. Run the First Workflow Without the Browser @@ -124,20 +135,10 @@ Expected result: - `import-python` recreates workflow JSON from the exported Python file. - `python workflow.py` prints `Hello World`. -## 5. Start the Visual Editor +## 5. Open the Visual Editor -Windows: - -```bat -start.bat -``` - -macOS/Linux: - -```bash -chmod +x start.sh -./start.sh -``` +The launcher from step 2 keeps the backend and visual editor running. Return to +the browser window it opened. Expected result: @@ -147,6 +148,8 @@ Expected result: - If an old Blacknode editor is already on port `3000`, the launcher restarts it and keeps the same URL. +If you stopped the launcher, run `start.bat` or `./start.sh` again. + If the browser does not open, open this manually: ```text @@ -560,14 +563,20 @@ Expected result: ### `blacknode` command not found -Run: +Activate the project environment: ```powershell -pip install -e ".[mcp]" +.\.venv\Scripts\Activate.ps1 +``` + +or run the executable directly: + +```powershell +.\.venv\Scripts\blacknode.exe doctor ``` -Then close and reopen the terminal. The command is created by the package entry -point: +On macOS/Linux, run `source .venv/bin/activate`. The command is created by the +package entry point: ```text blacknode = "blacknode.cli:main" diff --git a/start.ps1 b/start.ps1 index 9a74d48..b67d931 100644 --- a/start.ps1 +++ b/start.ps1 @@ -6,6 +6,7 @@ $ServerOut = Join-Path $LogDir "server.out.log" $ServerErr = Join-Path $LogDir "server.err.log" $EditorOut = Join-Path $LogDir "editor.out.log" $EditorErr = Join-Path $LogDir "editor.err.log" +$VenvDir = if ($env:BLACKNODE_VENV) { $env:BLACKNODE_VENV } else { Join-Path $Root ".venv" } $BackendProcess = $null $FrontendProcess = $null @@ -38,6 +39,32 @@ function Resolve-RequiredCommand { throw $ErrorMessage } +function Assert-PythonVersion { + param([string] $Python) + + if (-not (Invoke-NativeProbe -Command $Python -Arguments @("-c", "import sys; raise SystemExit(0 if sys.version_info >= (3, 11) else 1)"))) { + $Version = & $Python --version 2>&1 + throw "Python 3.11+ is required. Found: $Version" + } +} + +function Resolve-ProjectPython { + $SystemPython = Resolve-RequiredCommand -Names @("python", "py") -ErrorMessage "Python 3.11+ is required." + Assert-PythonVersion -Python $SystemPython + + $VenvPython = Join-Path $VenvDir "Scripts\python.exe" + if (-not (Test-Path -LiteralPath $VenvPython)) { + Write-Step "Creating Python virtual environment (.venv)..." + & $SystemPython -m venv $VenvDir + if ($LASTEXITCODE -ne 0 -or -not (Test-Path -LiteralPath $VenvPython)) { + throw "Could not create the Python virtual environment at $VenvDir." + } + } + + Assert-PythonVersion -Python $VenvPython + return $VenvPython +} + function Stop-ProcessTree { param([int] $TargetProcessId) @@ -387,7 +414,7 @@ try { New-Item -ItemType Directory -Path $LogDir -Force | Out-Null - $Python = Resolve-RequiredCommand -Names @("python", "py") -ErrorMessage "Python 3.11+ is required." + $Python = Resolve-ProjectPython $Npm = Resolve-RequiredCommand -Names @("npm.cmd", "npm") -ErrorMessage "npm is required. Install Node.js 20.19+ or 22.12+." Write-Step "Checking Python dependencies..." @@ -396,7 +423,8 @@ try { throw "Python dependency install failed." } - if (-not (Test-PythonDistribution -Python $Python -Name "blacknode")) { + if ((-not (Test-PythonDistribution -Python $Python -Name "blacknode")) -or + (-not (Test-PythonModule -Python $Python -Name "blacknode"))) { Write-Step "Installing blacknode package for the CLI..." & $Python -m pip install -e $Root -q --disable-pip-version-check if ($LASTEXITCODE -ne 0) { @@ -404,18 +432,6 @@ try { } } - # Optional: install CuPy for the GPU/CUDA nodes when an NVIDIA GPU is present. - # Non-fatal: a failure here never blocks the editor. - if (Get-Command nvidia-smi -ErrorAction SilentlyContinue) { - if (-not (Test-PythonModule -Python $Python -Name "cupy")) { - Write-Step "NVIDIA GPU detected - installing CuPy for CUDA nodes (one-time, large download)..." - & $Python -m pip install cupy-cuda12x -q --disable-pip-version-check - if ($LASTEXITCODE -ne 0) { - Write-Host " CuPy install failed; GPU/CUDA nodes stay unavailable but the editor will run." -ForegroundColor Yellow - } - } - } - Invoke-PackageHealthCheck $NodeModules = Join-Path $Root "editor\node_modules" @@ -428,6 +444,11 @@ try { Write-Step "Done." Write-Host "" + if ($env:BLACKNODE_BOOTSTRAP_ONLY -eq "1") { + Write-Step "Bootstrap complete (BLACKNODE_BOOTSTRAP_ONLY=1)." + return + } + Stop-PortListener -Port $BackendPort Write-Step "[1/2] Starting Python server (http://127.0.0.1:$BackendPort)" diff --git a/start.sh b/start.sh index f135a02..cba08f5 100755 --- a/start.sh +++ b/start.sh @@ -475,29 +475,23 @@ ensure_python_environment echo " Checking Python dependencies..." pip_install -r "$ROOT_DIR/editor-server/requirements.txt" -q --disable-pip-version-check -if ! "$PYTHON_BIN" -c "import importlib.metadata; importlib.metadata.version('blacknode')" >/dev/null 2>&1; then +if ! "$PYTHON_BIN" -c "import blacknode, importlib.metadata; importlib.metadata.version('blacknode')" >/dev/null 2>&1; then echo " Installing blacknode package for the CLI..." pip_install -e "$ROOT_DIR" -q --disable-pip-version-check fi ensure_blacknode_command -# Optional: install CuPy for the GPU/CUDA nodes when an NVIDIA GPU is present. -# macOS has no NVIDIA CUDA GPUs, so nvidia-smi is absent and this is skipped. -# Non-fatal: a failure here never blocks the editor. -if command -v nvidia-smi >/dev/null 2>&1; then - if ! "$PYTHON_BIN" -c "import cupy" >/dev/null 2>&1; then - echo " NVIDIA GPU detected - installing CuPy for CUDA nodes (one-time, large download)..." - pip_install cupy-cuda12x -q --disable-pip-version-check \ - || echo " CuPy install failed; GPU/CUDA nodes stay unavailable but the editor will run." - fi -fi - check_package_dependencies ensure_frontend_dependencies echo " Done." echo +if [[ "${BLACKNODE_BOOTSTRAP_ONLY:-0}" == "1" ]]; then + echo " Bootstrap complete (BLACKNODE_BOOTSTRAP_ONLY=1)." + exit 0 +fi + stop_port_listener "$BACKEND_PORT" wait_port_free "$BACKEND_PORT" || { print_port_busy_error "$BACKEND_PORT" diff --git a/tests/test_launchers.py b/tests/test_launchers.py new file mode 100644 index 0000000..a633f91 --- /dev/null +++ b/tests/test_launchers.py @@ -0,0 +1,25 @@ +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] + + +def test_launchers_create_isolated_environment_and_support_bootstrap_only(): + powershell = (ROOT / "start.ps1").read_text(encoding="utf-8") + shell = (ROOT / "start.sh").read_text(encoding="utf-8") + + assert 'Join-Path $Root ".venv"' in powershell + assert " -m venv " in powershell + assert "BLACKNODE_BOOTSTRAP_ONLY" in powershell + assert "Test-PythonModule -Python $Python -Name \"blacknode\"" in powershell + assert 'VENV_DIR="${BLACKNODE_VENV:-$ROOT_DIR/.venv}"' in shell + assert "BLACKNODE_BOOTSTRAP_ONLY" in shell + assert "import blacknode, importlib.metadata" in shell + + +def test_core_launchers_do_not_install_optional_cuda_dependencies(): + powershell = (ROOT / "start.ps1").read_text(encoding="utf-8") + shell = (ROOT / "start.sh").read_text(encoding="utf-8") + + assert "pip install cupy" not in powershell.lower() + assert "pip_install cupy" not in shell.lower()