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
19 changes: 19 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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
Expand Down
50 changes: 48 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,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

Expand Down Expand Up @@ -164,6 +164,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
Expand All @@ -175,10 +177,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
Expand Down Expand Up @@ -208,6 +241,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**:
Expand Down Expand Up @@ -1374,6 +1416,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
Expand Down
10 changes: 10 additions & 0 deletions Taskfile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
150 changes: 150 additions & 0 deletions install.ps1
Original file line number Diff line number Diff line change
@@ -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
4 changes: 4 additions & 0 deletions install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions internal/agent/agent_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -719,6 +719,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
Expand Down
39 changes: 27 additions & 12 deletions internal/agent/tools/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package tools
import (
"fmt"
"path/filepath"
"runtime"
"strings"
"sync"
"time"
Expand Down Expand Up @@ -164,25 +165,39 @@ 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)
} 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 {
r.tools["Memory"] = NewMemoryTool(cfg, r.memoryBackend, project.Detect())
}
}

// 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
Expand Down
2 changes: 2 additions & 0 deletions internal/display/wayland/client.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build linux

package wayland

import (
Expand Down
2 changes: 2 additions & 0 deletions internal/display/wayland/controller.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build linux

package wayland

import (
Expand Down
3 changes: 3 additions & 0 deletions internal/display/wayland/stub.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
//go:build !linux

package wayland
2 changes: 2 additions & 0 deletions internal/display/x11/client.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
//go:build linux

package x11

import (
Expand Down
Loading