From ea0d12dcb39de7b1bdb844ebd42d8ddb92d8907a Mon Sep 17 00:00:00 2001 From: "inference-gateway-maintainer[bot]" <246577062+inference-gateway-maintainer[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 14:46:47 +0000 Subject: [PATCH 1/5] feat: add Windows support --- internal/agent/agent_utils.go | 3 +++ internal/agent/tools/registry.go | 25 +++++++++++++++---------- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/internal/agent/agent_utils.go b/internal/agent/agent_utils.go index c2a28d6a..6ca1eebc 100644 --- a/internal/agent/agent_utils.go +++ b/internal/agent/agent_utils.go @@ -681,6 +681,9 @@ func (s *AgentServiceImpl) buildOSInfo() string { case "linux": osInfo += "\n- Keyboard shortcuts use the 'ctrl' modifier (e.g. 'ctrl+c')." osInfo += "\n- Open apps and files with 'xdg-open' or the command name." + case "windows": + osInfo += "\n- Keyboard shortcuts use the 'ctrl' modifier (e.g. 'ctrl+c')." + osInfo += "\n- Open apps and files with 'start' (e.g. 'start notepad.exe', 'start file.txt')." } return osInfo diff --git a/internal/agent/tools/registry.go b/internal/agent/tools/registry.go index da677cbb..9a6e8dd8 100644 --- a/internal/agent/tools/registry.go +++ b/internal/agent/tools/registry.go @@ -3,6 +3,7 @@ package tools import ( "fmt" "path/filepath" + "runtime" "strings" "sync" "time" @@ -164,17 +165,21 @@ func (r *Registry) registerTools() { } if cfg.ComputerUse.Enabled { - displayProvider, err := display.DetectDisplay() - if err != nil { - logger.Warn("no compatible display platform detected, computer use tools will be disabled", "error", err) + if runtime.GOOS == "windows" { + logger.Warn("computer use is not supported on Windows - mouse, keyboard, and screenshot tools will be disabled") } else { - rateLimiter := utils.NewRateLimiter(cfg.ComputerUse.RateLimit) - r.tools["MouseMove"] = NewMouseMoveTool(cfg, rateLimiter, displayProvider, r.stateManager) - r.tools["MouseClick"] = NewMouseClickTool(cfg, rateLimiter, displayProvider, r.stateManager) - r.tools["MouseScroll"] = NewMouseScrollTool(cfg, rateLimiter, displayProvider) - r.tools["KeyboardType"] = NewKeyboardTypeTool(cfg, rateLimiter, displayProvider) - r.tools["GetFocusedApp"] = NewGetFocusedAppTool(r.config) - r.tools["ActivateApp"] = NewActivateAppTool(r.config) + displayProvider, err := display.DetectDisplay() + if err != nil { + logger.Warn("no compatible display platform detected, computer use tools will be disabled", "error", err) + } else { + rateLimiter := utils.NewRateLimiter(cfg.ComputerUse.RateLimit) + r.tools["MouseMove"] = NewMouseMoveTool(cfg, rateLimiter, displayProvider, r.stateManager) + r.tools["MouseClick"] = NewMouseClickTool(cfg, rateLimiter, displayProvider, r.stateManager) + r.tools["MouseScroll"] = NewMouseScrollTool(cfg, rateLimiter, displayProvider) + r.tools["KeyboardType"] = NewKeyboardTypeTool(cfg, rateLimiter, displayProvider) + r.tools["GetFocusedApp"] = NewGetFocusedAppTool(r.config) + r.tools["ActivateApp"] = NewActivateAppTool(r.config) + } } } From 215bd148c4a0f6c860d97dea0bc6af380122c23a Mon Sep 17 00:00:00 2001 From: "inference-gateway-maintainer[bot]" <246577062+inference-gateway-maintainer[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 15:11:37 +0000 Subject: [PATCH 2/5] feat: add Windows support - build constraints, release pipeline, and installer --- .github/workflows/release.yml | 19 ++++ Taskfile.yml | 10 ++ install.ps1 | 150 +++++++++++++++++++++++++ internal/display/wayland/client.go | 2 + internal/display/wayland/controller.go | 2 + internal/display/wayland/stub.go | 3 + internal/display/x11/client.go | 2 + internal/display/x11/controller.go | 2 + internal/display/x11/stub.go | 3 + 9 files changed, 193 insertions(+) create mode 100644 install.ps1 create mode 100644 internal/display/wayland/stub.go create mode 100644 internal/display/x11/stub.go diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bfc64b7f..c7ef37f0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -125,6 +125,14 @@ jobs: goos: darwin goarch: arm64 cgo: "1" + - os: windows-2025 + goos: windows + goarch: amd64 + cgo: "0" + - os: windows-2025 + goos: windows + goarch: arm64 + cgo: "0" steps: - name: Checkout uses: actions/checkout@v7.0.0 @@ -162,6 +170,15 @@ jobs: run: | go build -ldflags "-w -s -X github.com/inference-gateway/cli/cmd.version=v${{ needs.version.outputs.new_release_version }}" -o infer-${{ matrix.goos }}-${{ matrix.goarch }} . + - name: Build binary (Windows with CGO disabled - pure Go) + if: matrix.goos == 'windows' + env: + CGO_ENABLED: ${{ matrix.cgo }} + GOOS: windows + GOARCH: ${{ matrix.goarch }} + run: | + go build -ldflags "-w -s -X github.com/inference-gateway/cli/cmd.version=v${{ needs.version.outputs.new_release_version }}" -o infer-${{ matrix.goos }}-${{ matrix.goarch }} . + - name: Upload artifact uses: actions/upload-artifact@v7.0.1 with: @@ -280,6 +297,8 @@ jobs: infer-linux-arm64 \ infer-darwin-amd64 \ infer-darwin-arm64 \ + infer-windows-amd64 \ + infer-windows-arm64 \ checksums.txt \ checksums.txt.sigstore.json; do if [ ! -f "$f" ]; then diff --git a/Taskfile.yml b/Taskfile.yml index 5a6ee9a9..60f113be 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -164,6 +164,16 @@ tasks: -o dist/{{.BINARY_NAME}}-linux-{{.GOARCH}} .' echo "✓ Built portable dist/{{.BINARY_NAME}}-linux-{{.GOARCH}} (pure Go, all storage backends supported)" + release:build:windows: + desc: Build Windows binary (cross-compiled, pure Go) + vars: + GOARCH: + sh: go env GOARCH + cmds: + - mkdir -p dist + - CGO_ENABLED=0 GOOS=windows GOARCH={{.GOARCH}} go build -ldflags "-w -s -X github.com/inference-gateway/cli/cmd.version={{.VERSION}}" -o dist/{{.BINARY_NAME}}-windows-{{.GOARCH}} {{.MAIN_PACKAGE}} + - echo "✓ Built dist/{{.BINARY_NAME}}-windows-{{.GOARCH}}" + container:build: desc: Build container image locally for testing cmds: diff --git a/install.ps1 b/install.ps1 new file mode 100644 index 00000000..ef528646 --- /dev/null +++ b/install.ps1 @@ -0,0 +1,150 @@ +#!/usr/bin/env pwsh +# Install script for Inference Gateway CLI (Windows) +# +# Usage: +# .\install.ps1 # Install latest version +# .\install.ps1 -Version v0.1.0 # Install specific version +# $env:INSTALL_DIR = "C:\tools"; .\install.ps1 # Custom install directory + +param( + [string]$Version = "", + [string]$InstallDir = "" +) + +$GithubRepo = "inference-gateway/cli" +$BinaryName = "infer" + +if ($InstallDir -eq "") { + $InstallDir = $env:INSTALL_DIR + if ($InstallDir -eq "") { + $InstallDir = "$env:LOCALAPPDATA\Programs\infer" + } +} + +function Write-Status { + param([string]$Message) + Write-Host "[INFO] $Message" -ForegroundColor Blue +} + +function Write-Success { + param([string]$Message) + Write-Host "[SUCCESS] $Message" -ForegroundColor Green +} + +function Write-Warning { + param([string]$Message) + Write-Host "[WARNING] $Message" -ForegroundColor Yellow +} + +function Write-Error { + param([string]$Message) + Write-Host "[ERROR] $Message" -ForegroundColor Red +} + +function Get-Architecture { + $arch = (Get-CimInstance Win32_Processor | Select-Object -First 1).AddressWidth + if ($env:PROCESSOR_ARCHITECTURE -eq "ARM64") { + return "arm64" + } + return "amd64" +} + +function Get-LatestVersion { + $apiUrl = "https://api.github.com/repos/$GithubRepo/releases/latest" + try { + $response = Invoke-RestMethod -Uri $apiUrl -Method Get + return $response.tag_name + } catch { + Write-Error "Failed to fetch latest version: $_" + exit 1 + } +} + +function Install-CLI { + Write-Status "Installing $BinaryName CLI for Windows..." + + $arch = Get-Architecture + Write-Status "Detected architecture: $arch" + + if ($Version -eq "") { + $Version = Get-LatestVersion + } + + Write-Status "Installing version: $Version" + + $filename = "${BinaryName}-windows-${arch}" + $downloadUrl = "https://github.com/$GithubRepo/releases/download/$Version/$filename" + + Write-Status "Download URL: $downloadUrl" + + $tempDir = Join-Path $env:TEMP "infer-install" + New-Item -ItemType Directory -Force -Path $tempDir | Out-Null + + $tempFile = Join-Path $tempDir $filename + + try { + Write-Status "Downloading $filename..." + Invoke-WebRequest -Uri $downloadUrl -OutFile $tempFile -UseBasicParsing + } catch { + Write-Error "Failed to download $filename`: $_" + Remove-Item -Recurse -Force $tempDir -ErrorAction SilentlyContinue + exit 1 + } + + if (-not (Test-Path $InstallDir)) { + Write-Status "Creating installation directory: $InstallDir" + New-Item -ItemType Directory -Force -Path $InstallDir | Out-Null + } + + $targetPath = Join-Path $InstallDir "$BinaryName.exe" + Write-Status "Installing binary to $targetPath..." + + try { + Move-Item -Force $tempFile $targetPath + } catch { + Write-Error "Failed to install binary to $targetPath`: $_" + Write-Warning "Try running as Administrator or set InstallDir to a writable directory" + Remove-Item -Recurse -Force $tempDir -ErrorAction SilentlyContinue + exit 1 + } + + Remove-Item -Recurse -Force $tempDir -ErrorAction SilentlyContinue + + if (Test-Path $targetPath) { + Write-Success "$BinaryName CLI installed successfully!" + Write-Status "Installed to: $targetPath" + + $userPath = [Environment]::GetEnvironmentVariable("Path", "User") + if ($userPath -notlike "*$InstallDir*") { + Write-Warning "$InstallDir is not in your PATH" + Write-Status "Add the following line to your PowerShell profile:" + Write-Host " `$env:Path += `";$InstallDir`"" + Write-Status "" + Write-Status "Or run this command to add it temporarily:" + Write-Host " `$env:Path += `";$InstallDir`"" + } else { + Write-Status "You can now run: $BinaryName --help" + } + } else { + Write-Error "Installation verification failed" + exit 1 + } +} + +# Banner +Write-Host @" + ___ __ +|_ _|_ __ / _| ___ _ __ ___ _ __ ___ ___ + | || '_ \| |_ / _ \ '__/ _ \ '_ \ / __/ _ \ + | || | | | _| __/ | | __/ | | | (_| __/ +|___|_| |_|_| \___|_| \___|_| |_|\___\___| + + ____ _ + / ___| __ _| |_ _____ ____ _ _ _ + | | _ / _` | __/ _ \ \ /\ / / _` | | | | + | |_| | (_| | || __/\ V V / (_| | |_| | + \____|\__,_|\__\___| \_/\_/ \__,_|\__, | + |___/ +"@ -ForegroundColor Blue + +Install-CLI diff --git a/internal/display/wayland/client.go b/internal/display/wayland/client.go index 6353327b..38c5f47f 100644 --- a/internal/display/wayland/client.go +++ b/internal/display/wayland/client.go @@ -1,3 +1,5 @@ +//go:build linux + package wayland import ( diff --git a/internal/display/wayland/controller.go b/internal/display/wayland/controller.go index 74533775..c52ec5e8 100644 --- a/internal/display/wayland/controller.go +++ b/internal/display/wayland/controller.go @@ -1,3 +1,5 @@ +//go:build linux + package wayland import ( diff --git a/internal/display/wayland/stub.go b/internal/display/wayland/stub.go new file mode 100644 index 00000000..c05eea7c --- /dev/null +++ b/internal/display/wayland/stub.go @@ -0,0 +1,3 @@ +//go:build !linux + +package wayland diff --git a/internal/display/x11/client.go b/internal/display/x11/client.go index 7d169417..d617093f 100644 --- a/internal/display/x11/client.go +++ b/internal/display/x11/client.go @@ -1,3 +1,5 @@ +//go:build linux + package x11 import ( diff --git a/internal/display/x11/controller.go b/internal/display/x11/controller.go index 3593b344..35f917af 100644 --- a/internal/display/x11/controller.go +++ b/internal/display/x11/controller.go @@ -1,3 +1,5 @@ +//go:build linux + package x11 import ( diff --git a/internal/display/x11/stub.go b/internal/display/x11/stub.go new file mode 100644 index 00000000..e6549359 --- /dev/null +++ b/internal/display/x11/stub.go @@ -0,0 +1,3 @@ +//go:build !linux + +package x11 From c09149d82e3ada6009c1d6301903fcb24ae48d50 Mon Sep 17 00:00:00 2001 From: "inference-gateway-maintainer[bot]" <246577062+inference-gateway-maintainer[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 15:31:11 +0000 Subject: [PATCH 3/5] fix: refactor computer use tool registration to fix nestif lint error (#765) --- internal/agent/tools/registry.go | 42 ++++++++++++++++++++------------ 1 file changed, 26 insertions(+), 16 deletions(-) diff --git a/internal/agent/tools/registry.go b/internal/agent/tools/registry.go index 9a6e8dd8..16354eb3 100644 --- a/internal/agent/tools/registry.go +++ b/internal/agent/tools/registry.go @@ -165,22 +165,7 @@ func (r *Registry) registerTools() { } if cfg.ComputerUse.Enabled { - if runtime.GOOS == "windows" { - logger.Warn("computer use is not supported on Windows - mouse, keyboard, and screenshot tools will be disabled") - } else { - displayProvider, err := display.DetectDisplay() - if err != nil { - logger.Warn("no compatible display platform detected, computer use tools will be disabled", "error", err) - } else { - rateLimiter := utils.NewRateLimiter(cfg.ComputerUse.RateLimit) - r.tools["MouseMove"] = NewMouseMoveTool(cfg, rateLimiter, displayProvider, r.stateManager) - r.tools["MouseClick"] = NewMouseClickTool(cfg, rateLimiter, displayProvider, r.stateManager) - r.tools["MouseScroll"] = NewMouseScrollTool(cfg, rateLimiter, displayProvider) - r.tools["KeyboardType"] = NewKeyboardTypeTool(cfg, rateLimiter, displayProvider) - r.tools["GetFocusedApp"] = NewGetFocusedAppTool(r.config) - r.tools["ActivateApp"] = NewActivateAppTool(r.config) - } - } + r.registerComputerUseTools() } if cfg.Memory.Enabled { @@ -188,6 +173,31 @@ func (r *Registry) registerTools() { } } +// registerComputerUseTools registers computer use tools (mouse, keyboard, +// screenshot). On Windows these tools are not supported and a warning is +// logged. On Linux/macOS the display platform is auto-detected. +func (r *Registry) registerComputerUseTools() { + if runtime.GOOS == "windows" { + logger.Warn("computer use is not supported on Windows - mouse, keyboard, and screenshot tools will be disabled") + return + } + + cfg := r.config + displayProvider, err := display.DetectDisplay() + if err != nil { + logger.Warn("no compatible display platform detected, computer use tools will be disabled", "error", err) + return + } + + rateLimiter := utils.NewRateLimiter(cfg.ComputerUse.RateLimit) + r.tools["MouseMove"] = NewMouseMoveTool(cfg, rateLimiter, displayProvider, r.stateManager) + r.tools["MouseClick"] = NewMouseClickTool(cfg, rateLimiter, displayProvider, r.stateManager) + r.tools["MouseScroll"] = NewMouseScrollTool(cfg, rateLimiter, displayProvider) + r.tools["KeyboardType"] = NewKeyboardTypeTool(cfg, rateLimiter, displayProvider) + r.tools["GetFocusedApp"] = NewGetFocusedAppTool(r.config) + r.tools["ActivateApp"] = NewActivateAppTool(r.config) +} + // SetMemoryBackend wires the memory sync backend into the Memory tool so a // write/delete pushes to the remote. The container calls this after // constructing the shared backend; it re-registers the Memory tool so the From 1fbfc670aec71a55f761cd29c68db9a435c25df0 Mon Sep 17 00:00:00 2001 From: "inference-gateway-maintainer[bot]" <246577062+inference-gateway-maintainer[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 15:44:02 +0000 Subject: [PATCH 4/5] docs: add Windows installation and platform documentation --- README.md | 50 ++++++++++++++++++++++++++++++++++++++++++++++++-- install.sh | 4 ++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7a5c37b4..5f61c635 100644 --- a/README.md +++ b/README.md @@ -125,8 +125,8 @@ infer --help > **Not recommended for production.** For production or CI, prefer the > [install script](#using-install-script), [container image](#using-container-image), or -> [building from source](#build-from-source). Prebuilt binaries cover Linux and macOS on -> amd64/arm64 - on Windows, use WSL. +> [building from source](#build-from-source). Prebuilt binaries cover Linux, macOS, and Windows on +> amd64/arm64. ### Using Go Install @@ -162,6 +162,8 @@ docker run -it --rm --network inference-gateway ghcr.io/inference-gateway/cli:la ### Using Install Script +**Linux/macOS:** + ```bash # Latest version curl -fsSL https://raw.githubusercontent.com/inference-gateway/cli/main/install.sh | bash @@ -173,10 +175,41 @@ curl -fsSL https://raw.githubusercontent.com/inference-gateway/cli/main/install. curl -fsSL https://raw.githubusercontent.com/inference-gateway/cli/main/install.sh | bash -s -- --install-dir $HOME/.local/bin ``` +**Windows (PowerShell 5.1+ / pwsh):** + +```powershell +# Latest version +.\install.ps1 + +# Specific version +.\install.ps1 -Version v0.1.0 + +# Custom installation directory +$env:INSTALL_DIR = "C:\tools"; .\install.ps1 +``` + +Or run directly from GitHub: + +```powershell +# Download and run +iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/inference-gateway/cli/main/install.ps1')) +``` + ### Manual Download Download the latest release binary for your platform from the [releases page](https://github.com/inference-gateway/cli/releases). +Available binaries: + +| Platform | Binary | +| -------- | ------ | +| Linux amd64 | `infer-linux-amd64` | +| Linux arm64 | `infer-linux-arm64` | +| macOS amd64 (Intel) | `infer-darwin-amd64` | +| macOS arm64 (Apple Silicon) | `infer-darwin-arm64` | +| Windows amd64 | `infer-windows-amd64.exe` | +| Windows arm64 | `infer-windows-arm64.exe` | + **Verify the binary** (recommended for security): ```bash @@ -206,6 +239,15 @@ go build -o infer cmd/infer/main.go sudo mv infer /usr/local/bin/ ``` +On Windows, build with: + +```powershell +git clone https://github.com/inference-gateway/cli.git +cd cli +go build -o infer.exe cmd/infer/main.go +# The binary is at .\infer.exe +``` + ## Quick Start 1. **Initialize your project**: @@ -1360,6 +1402,10 @@ governed by `computer_use.enabled` plus the configured rate limits. On macOS an progress window** can mirror what the agent is doing. For a sandboxed desktop to drive, see [examples/computer-use](examples/computer-use/). +> **⚠️ Windows note:** Computer use (mouse, keyboard, screenshot tools) is **not supported on Windows**. +> The agent will log a warning and disable these tools when running on Windows. All other features +> work normally. + ## Persistent Memory The agent keeps a durable, cross-session memory: individual Markdown **fact-files** under a global diff --git a/install.sh b/install.sh index ca4a0121..d57aa0e8 100755 --- a/install.sh +++ b/install.sh @@ -81,6 +81,10 @@ detect_platform() { ;; *) print_error "Unsupported operating system: $os" + print_error "If you are on Windows, use the PowerShell installer instead:" + print_error " https://raw.githubusercontent.com/inference-gateway/cli/main/install.ps1" + print_error "" + print_error "Or run: iex ((New-Object System.Net.WebClient).DownloadString('https://raw.githubusercontent.com/inference-gateway/cli/main/install.ps1'))" exit 1 ;; esac From 95f9e62f3433db4c074cbb416004984318ff6cdb Mon Sep 17 00:00:00 2001 From: "inference-gateway-maintainer[bot]" <246577062+inference-gateway-maintainer[bot]@users.noreply.github.com> Date: Mon, 6 Jul 2026 23:01:46 +0000 Subject: [PATCH 5/5] feat(npm): add Windows support to npm/npx package --- npm/bin/install.js | 4 ++-- npm/package.json | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/npm/bin/install.js b/npm/bin/install.js index 95a79d69..b80a2d70 100755 --- a/npm/bin/install.js +++ b/npm/bin/install.js @@ -20,7 +20,7 @@ const BASE_URL = `https://github.com/${REPO}/releases/download/v${version}`; // Maps Node's process.platform / process.arch onto the release asset names. -const PLATFORMS = { linux: "linux", darwin: "darwin" }; +const PLATFORMS = { linux: "linux", darwin: "darwin", win32: "windows" }; const ARCHES = { x64: "amd64", arm64: "arm64" }; const binName = process.platform === "win32" ? "infer.exe" : "infer"; @@ -37,7 +37,7 @@ function resolveAsset() { if (!goos || !goarch) { fail( `unsupported platform ${process.platform}/${process.arch}. ` + - `Prebuilt binaries are published for linux and darwin on amd64/arm64 only. ` + + `Prebuilt binaries are published for linux, darwin, and win32 on amd64/arm64 only. ` + `Install from source instead: https://github.com/${REPO}#installation` ); } diff --git a/npm/package.json b/npm/package.json index 6930ee2a..cc2dda27 100644 --- a/npm/package.json +++ b/npm/package.json @@ -40,7 +40,8 @@ }, "os": [ "linux", - "darwin" + "darwin", + "win32" ], "cpu": [ "x64",