Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 25 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
89 changes: 49 additions & 40 deletions docs/walkthrough.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:

Expand All @@ -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
Expand Down Expand Up @@ -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"
Expand Down
49 changes: 35 additions & 14 deletions start.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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..."
Expand All @@ -396,26 +423,15 @@ 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) {
throw "Blacknode package install failed."
}
}

# 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"
Expand All @@ -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)"
Expand Down
18 changes: 6 additions & 12 deletions start.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
25 changes: 25 additions & 0 deletions tests/test_launchers.py
Original file line number Diff line number Diff line change
@@ -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()