feat(windows): Phase-1 native install + scheduled-task service (#438)

Closes Phase-1 of #298 (Native Windows Support RFC).

Reverses the long-standing "native Windows is not supported" stance. PowerShell installer at `deploy/windows/install.ps1` (published to https://open-jarvis.github.io/OpenJarvis/install.ps1) plus a `jarvis-service.ps1` scheduled-task helper that mirrors `deploy/systemd/openjarvis.service` and `deploy/launchd/com.openjarvis.plist`. Loopback default (127.0.0.1, no API key) — same as launchd.

One-liner install:
    irm https://open-jarvis.github.io/OpenJarvis/install.ps1 | iex

Adversarial review caught and fixed two real bugs pre-commit:
1. `irm | iex` drops `param()` flags — added env-var fallbacks (OPENJARVIS_SKIP_SERVICE / OPENJARVIS_SERVICE / OPENJARVIS_FORCE).
2. Scheduled tasks don't inherit the registering session's env — the LAN-exposed `OPENJARVIS_API_KEY` path now persists the key to User scope so the task's logon environment can read it.

Supersedes #434 (the guidance-only "use WSL2" install.ps1).

Massive thanks to @SeCuReDmE-main-dev for the careful RFC #298 — the three-phase decomposition (install / service / shared-memory bridge) is exactly the right framing. This PR ships Phase-1 and Phase-2 of the RFC fused into one release; Phase-3 (shared memory bridge) remains future work.

Thanks also to @KadenBordeaux for raising #334 ("'bash' is not recognized as the name of a cmdlet"). That report is what made this whole Windows-support cluster a priority — without your bug report the unsupported stance would still be in the README. The friction you hit motivated #432 (numpy/python cap), #433 (CLI startup resilience), #436 (python discovery helpers), #437 (desktop launcher fix), and this PR.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Jon Saad-Falcon
2026-05-29 16:44:10 -07:00
committed by GitHub
co-authored by Claude Opus 4.7
parent b6a8280d68
commit a79cd6f5e9
8 changed files with 746 additions and 12 deletions
+7 -2
View File
@@ -39,13 +39,18 @@ curl -fsSL https://open-jarvis.github.io/OpenJarvis/install.sh | bash
The installer handles everything for you — including [uv](https://docs.astral.sh/uv/), the Python venv, Ollama, and a small starter model. You don't need to install anything first.
**Windows:** the installer is a `bash` script and won't run in PowerShell or `cmd`. Pick one of:
**Windows:** the bash installer won't run in PowerShell or `cmd`. Pick one of:
- **WSL2 (recommended for the CLI / Python SDK)** — one-time setup in an admin PowerShell, then run the same `curl … | bash` inside Ubuntu:
```powershell
wsl --install -d Ubuntu-24.04
```
Open the Ubuntu shell that gets installed, then follow [WSL2 install instructions](https://open-jarvis.github.io/OpenJarvis/getting-started/wsl2/).
- **Native PowerShell (advanced)** — Phase-1 of the [native Windows RFC (#298)](https://github.com/open-jarvis/OpenJarvis/issues/298). Same `uv sync --extra server`, no WSL2 / no Docker:
```powershell
irm https://open-jarvis.github.io/OpenJarvis/install.ps1 | iex
```
See [native Windows install guide](https://open-jarvis.github.io/OpenJarvis/getting-started/windows-native/).
- **Desktop app** — download the [Windows installer (`.exe`)](https://github.com/open-jarvis/OpenJarvis/releases/download/desktop-v1.0.2/OpenJarvis_1.0.1_x64-setup.exe) from the latest [desktop release](https://github.com/open-jarvis/OpenJarvis/releases/tag/desktop-v1.0.2) (macOS `.dmg` and Linux `.deb`/`.rpm`/`.AppImage` are there too) for the GUI experience, no terminal required. **Prerequisite:** the desktop app expects [uv](https://docs.astral.sh/uv/) to be installed already — if it isn't, install it first in PowerShell, then launch the app:
```powershell
powershell -ExecutionPolicy Bypass -c "irm https://astral.sh/uv/install.ps1 | iex"
@@ -59,7 +64,7 @@ jarvis
The Rust extension and bigger models continue downloading in the background while you chat. Run `jarvis doctor` to see status.
**Platforms:** macOS (Intel + Apple Silicon), Linux, WSL2 on Windows. Native Windows is not supporteduse WSL2 or the desktop binary.
**Platforms:** macOS (Intel + Apple Silicon), Linux, WSL2 on Windows, and Phase-1 native Windows (PowerShell install + scheduled-task service — see [#298](https://github.com/open-jarvis/OpenJarvis/issues/298)).
**Manual install / contributors:** see [docs/getting-started/install.md](docs/getting-started/install.md).
+126
View File
@@ -0,0 +1,126 @@
# OpenJarvis on native Windows
Phase-1 of the native-Windows-support RFC (#298). Mirrors the Linux
(`deploy/systemd/`) and macOS (`deploy/launchd/`) deployments — but for
PowerShell, without WSL2 or Docker.
## One-liner install
In an elevated-or-regular PowerShell:
```powershell
irm https://open-jarvis.github.io/OpenJarvis/install.ps1 | iex
```
What it does:
1. Refuses non-Windows hosts and Windows < 10 1809.
2. Checks Python 3.10 3.13 (3.14 has no numpy wheels yet — see #432).
3. Checks `git` on PATH.
4. Installs `uv` (https://astral.sh/uv) if absent.
5. Clones the OpenJarvis repository to `%LOCALAPPDATA%\OpenJarvis`
(override with `$env:OPENJARVIS_HOME`).
6. Runs `uv sync --extra server` so the FastAPI server entry point is
importable.
7. Optionally prompts to register a scheduled task that auto-starts the
server at logon.
Flags (when invoked directly rather than via `irm | iex`):
| Flag | Effect |
|------|--------|
| `-Service` | Register the scheduled task without prompting |
| `-SkipService` | Don't prompt; don't register |
| `-Force` | Re-run all steps even if already done |
`irm | iex` can't pass `param()` args into a piped script string, so
the same knobs are honored via env vars when the corresponding flag is
absent:
```powershell
$env:OPENJARVIS_SKIP_SERVICE = '1'
irm https://open-jarvis.github.io/OpenJarvis/install.ps1 | iex
```
The available env vars: `OPENJARVIS_SKIP_SERVICE`, `OPENJARVIS_SERVICE`,
`OPENJARVIS_FORCE`. If you need richer control, save the script first
(`irm ... -OutFile install.ps1; .\install.ps1 -Force`).
## Manual scheduled-task setup
If you skipped the prompt during install, you can register / inspect /
remove the task with `jarvis-service.ps1`:
```powershell
$srv = "$env:LOCALAPPDATA\OpenJarvis\src\deploy\windows\jarvis-service.ps1"
# install (idempotent — replaces existing)
powershell -ExecutionPolicy Bypass -File $srv install
# status
powershell -ExecutionPolicy Bypass -File $srv status
# remove
powershell -ExecutionPolicy Bypass -File $srv uninstall
```
The task runs as the current user with `LogonType=Interactive` and
`RunLevel=Limited`. It restarts up to 3 times on failure (1-minute
gap), has no execution-time limit, and starts when available (catches
up if missed).
## Loopback vs LAN-exposed
By default the scheduled task binds `127.0.0.1` — reachable only from
this machine, no API key required. This matches launchd parity (see
`deploy/launchd/com.openjarvis.plist`).
To expose on your LAN:
```powershell
# 1. Generate an API key. The server REFUSES to bind 0.0.0.0 without one.
$env:OPENJARVIS_API_KEY = (uv run jarvis auth generate-key)
# 2. Re-register the task with -ListenHost 0.0.0.0.
powershell -ExecutionPolicy Bypass -File $srv install -ListenHost 0.0.0.0
```
`jarvis-service.ps1 install` refuses `-ListenHost 0.0.0.0` if
`$env:OPENJARVIS_API_KEY` is unset — same guard as the systemd unit's
`EnvironmentFile=/etc/openjarvis/env`.
## Parity table
| Concern | systemd | launchd | Windows |
|---------|---------|---------|---------|
| Service definition | `deploy/systemd/openjarvis.service` | `deploy/launchd/com.openjarvis.plist` | `deploy/windows/jarvis-service.ps1` (cmdlet-driven) |
| Default bind | `0.0.0.0` (with API key) | `127.0.0.1` (no API key) | `127.0.0.1` (no API key) |
| Restart on failure | `Restart=on-failure RestartSec=5` | `KeepAlive=true` | `RestartCount=3 RestartInterval=PT1M` |
| Auto-start | `multi-user.target` | `RunAtLoad=true` | `AtLogOn` trigger |
## Updating
To pull the latest:
```powershell
cd "$env:LOCALAPPDATA\OpenJarvis\src"
git pull --ff-only
uv sync --extra server
```
Or re-run the installer with `-Force`:
```powershell
irm https://open-jarvis.github.io/OpenJarvis/install.ps1 | iex
# (then re-run with the file directly, passing -Force)
```
## Uninstall
```powershell
powershell -ExecutionPolicy Bypass -File "$env:LOCALAPPDATA\OpenJarvis\src\deploy\windows\jarvis-service.ps1" uninstall
Remove-Item -Recurse -Force "$env:LOCALAPPDATA\OpenJarvis"
```
Uninstalling does NOT remove `uv` (it's a separate tool — you may have
other Python projects using it).
+299
View File
@@ -0,0 +1,299 @@
<#
.SYNOPSIS
OpenJarvis native Windows installer.
.DESCRIPTION
Phase-1 of the native-Windows-support RFC (#298). Mirrors the
behavior of scripts/install/install.sh (the curl-pipe-bash installer
for Linux/WSL2/macOS) but for native Windows PowerShell — no WSL,
no Docker, no MSYS2.
Steps:
1. Refuse non-Windows / Windows < 10.
2. Check Python 3.10 — 3.13 on PATH (3.14 has no numpy wheels yet,
see #432).
3. Check git on PATH.
4. Install uv (https://astral.sh/uv) if absent.
5. Clone the OpenJarvis repository to $env:LOCALAPPDATA\OpenJarvis
(override with $env:OPENJARVIS_HOME).
6. Run `uv sync --extra server` so the FastAPI server entry point
is importable.
7. Optionally register the scheduled-task service (see
deploy/windows/jarvis-service.ps1).
Usage (one-liner):
irm https://open-jarvis.github.io/OpenJarvis/install.ps1 | iex
Usage (file invocation, supports flags):
irm https://open-jarvis.github.io/OpenJarvis/install.ps1 -OutFile install.ps1
.\install.ps1 -SkipService
Flags (when running the file directly):
-SkipService Don't prompt for / install the scheduled task.
-Service Install the scheduled task without prompting.
-Force Re-run all steps even if already done.
Under `irm | iex` the param block is unreachable (Invoke-Expression
can't pass named args into a piped script string), so the same knobs
are honored via env vars when the corresponding flag is absent:
$env:OPENJARVIS_SKIP_SERVICE = '1'
$env:OPENJARVIS_SERVICE = '1'
$env:OPENJARVIS_FORCE = '1'
.NOTES
Loopback default: the scheduled-task service binds 127.0.0.1, so no
API key is needed. To expose on the LAN, edit the registered task to
pass `--host 0.0.0.0` AND set $env:OPENJARVIS_API_KEY (an
unauthenticated 0.0.0.0 server refuses to start). See
deploy/windows/README.md.
#>
[CmdletBinding()]
param(
[switch] $SkipService,
[switch] $Service,
[switch] $Force
)
$ErrorActionPreference = 'Stop'
# Env-var fallback for the `irm | iex` path, where the param block is
# unreachable (see header comment). Any explicit -switch wins; env vars
# only fill in the gaps.
if (-not $SkipService -and $env:OPENJARVIS_SKIP_SERVICE) { $SkipService = $true }
if (-not $Service -and $env:OPENJARVIS_SERVICE) { $Service = $true }
if (-not $Force -and $env:OPENJARVIS_FORCE) { $Force = $true }
# ---------------------------------------------------------------------------
# Output helpers — coloured but plain enough for Constrained Language Mode.
# ---------------------------------------------------------------------------
function Write-Info ($msg) { Write-Host "[info] $msg" -ForegroundColor Cyan }
function Write-Ok ($msg) { Write-Host "[ok] $msg" -ForegroundColor Green }
function Write-Warn2 ($msg) { Write-Host "[warn] $msg" -ForegroundColor Yellow }
function Write-Fail ($msg) {
Write-Host "[fail] $msg" -ForegroundColor Red
exit 1
}
# ---------------------------------------------------------------------------
# 1. OS check
# ---------------------------------------------------------------------------
Write-Info "Checking OS..."
if ($PSVersionTable.Platform -and $PSVersionTable.Platform -ne 'Win32NT') {
Write-Fail "install.ps1 is for native Windows. On Linux/macOS use install.sh."
}
# Build number 17763 = Windows 10 1809 (the oldest LTS we test against).
$build = [System.Environment]::OSVersion.Version.Build
if ($build -lt 17763) {
Write-Fail "Windows 10 1809 (build 17763) or newer is required. Detected build $build."
}
Write-Ok "Windows build $build"
# ---------------------------------------------------------------------------
# 2. Python check
# ---------------------------------------------------------------------------
function Get-PythonCommand {
# Prefer `python3` (matches our cross-platform helper convention),
# fall back to `python` (the Windows store / python.org default).
foreach ($name in @('python3', 'python')) {
$cmd = Get-Command $name -ErrorAction SilentlyContinue
if ($cmd) { return $cmd.Source }
}
return $null
}
Write-Info "Checking Python (3.10 - 3.13)..."
$pythonExe = Get-PythonCommand
if (-not $pythonExe) {
Write-Fail @"
Python 3.10 - 3.13 not found on PATH.
Install from https://python.org (check 'Add python.exe to PATH' during
install) or via winget:
winget install Python.Python.3.13
Then re-run this installer.
"@
}
$verRaw = & $pythonExe --version 2>&1
$verMatch = [regex]::Match($verRaw, '(\d+)\.(\d+)\.(\d+)')
if (-not $verMatch.Success) {
Write-Fail "Could not parse Python version from: $verRaw"
}
$pyMajor = [int]$verMatch.Groups[1].Value
$pyMinor = [int]$verMatch.Groups[2].Value
if ($pyMajor -ne 3 -or $pyMinor -lt 10 -or $pyMinor -gt 13) {
Write-Fail @"
Found Python $pyMajor.$pyMinor at $pythonExe, but OpenJarvis requires
3.10 - 3.13. Python 3.14 has no numpy Windows wheels yet (#432, will
re-open once numpy ships cp314).
"@
}
Write-Ok "Python $pyMajor.$pyMinor ($pythonExe)"
# ---------------------------------------------------------------------------
# 3. git check
# ---------------------------------------------------------------------------
Write-Info "Checking git..."
$gitExe = (Get-Command git -ErrorAction SilentlyContinue).Source
if (-not $gitExe) {
Write-Fail @"
git not found on PATH.
Install via winget:
winget install Git.Git
or download from https://git-scm.com, then re-run this installer.
"@
}
Write-Ok "git ($gitExe)"
# ---------------------------------------------------------------------------
# 4. uv check / install
# ---------------------------------------------------------------------------
Write-Info "Checking uv..."
$uvExe = (Get-Command uv -ErrorAction SilentlyContinue).Source
if (-not $uvExe) {
Write-Info "Installing uv via astral.sh/uv (official PowerShell installer)..."
try {
Invoke-RestMethod -Uri 'https://astral.sh/uv/install.ps1' -UseBasicParsing | Invoke-Expression
} catch {
Write-Fail "uv install failed: $($_.Exception.Message)"
}
# The astral installer puts uv at %USERPROFILE%\.local\bin\uv.exe and
# adds that dir to the User PATH. The current process's PATH isn't
# refreshed automatically — prepend the install dir so the rest of
# this script picks it up.
$uvDir = Join-Path $env:USERPROFILE '.local\bin'
if (Test-Path (Join-Path $uvDir 'uv.exe')) {
$env:Path = "$uvDir;$env:Path"
}
$uvExe = (Get-Command uv -ErrorAction SilentlyContinue).Source
if (-not $uvExe) {
Write-Fail "uv installed but isn't on PATH. Re-open a fresh PowerShell and re-run."
}
}
Write-Ok "uv ($uvExe)"
# ---------------------------------------------------------------------------
# 5. Clone the repo
# ---------------------------------------------------------------------------
$installRoot = if ($env:OPENJARVIS_HOME) {
$env:OPENJARVIS_HOME
} else {
Join-Path $env:LOCALAPPDATA 'OpenJarvis'
}
$srcDir = Join-Path $installRoot 'src'
Write-Info "Install root: $installRoot"
if (-not (Test-Path $installRoot)) {
New-Item -ItemType Directory -Path $installRoot | Out-Null
}
$repoUrl = if ($env:OPENJARVIS_REPO_URL) {
$env:OPENJARVIS_REPO_URL
} else {
'https://github.com/open-jarvis/OpenJarvis.git'
}
if (Test-Path (Join-Path $srcDir '.git')) {
if ($Force) {
Write-Info "Force: pulling latest from $repoUrl..."
& $gitExe -C $srcDir pull --ff-only
if ($LASTEXITCODE -ne 0) { Write-Fail "git pull failed" }
} else {
Write-Ok "Repository already cloned (use -Force to update)"
}
} else {
Write-Info "Cloning $repoUrl..."
& $gitExe clone --depth 1 $repoUrl $srcDir
if ($LASTEXITCODE -ne 0) { Write-Fail "git clone failed" }
Write-Ok "Cloned to $srcDir"
}
# ---------------------------------------------------------------------------
# 6. uv sync --extra server
# ---------------------------------------------------------------------------
Write-Info "Running 'uv sync --extra server' in $srcDir (this can take a few minutes)..."
Push-Location $srcDir
try {
& $uvExe sync --extra server
if ($LASTEXITCODE -ne 0) {
Write-Fail "uv sync failed with exit code $LASTEXITCODE. Check the output above."
}
} finally {
Pop-Location
}
Write-Ok "Dependencies installed"
# ---------------------------------------------------------------------------
# 7. Optional: register the scheduled-task service
# ---------------------------------------------------------------------------
$serviceScript = Join-Path $srcDir 'deploy\windows\jarvis-service.ps1'
$shouldInstallService = $false
if ($Service) {
$shouldInstallService = $true
} elseif ($SkipService) {
$shouldInstallService = $false
} else {
# Interactive prompt only when there's a real user at the keyboard
# AND stdin isn't piped. [Environment]::UserInteractive is the
# canonical PowerShell idiom for "is this a user session" (false for
# services, scheduled tasks, etc); we additionally guard against the
# `irm | iex` case where stdin is redirected.
$isInteractive = [Environment]::UserInteractive `
-and -not [System.Console]::IsInputRedirected
if ($isInteractive) {
$reply = Read-Host "Register OpenJarvis as a Windows scheduled task (auto-start at logon, loopback only)? [y/N]"
$shouldInstallService = ($reply -match '^[yY]')
} else {
Write-Warn2 "Non-interactive install — skipping scheduled-task setup."
Write-Warn2 "To register the service later, run:"
Write-Warn2 " powershell -ExecutionPolicy Bypass -File `"$serviceScript`" install"
}
}
if ($shouldInstallService) {
if (-not (Test-Path $serviceScript)) {
Write-Fail "Service script not found at $serviceScript (the clone may be missing files; try -Force)."
}
Write-Info "Installing scheduled task..."
& powershell -ExecutionPolicy Bypass -File $serviceScript install -InstallRoot $installRoot
if ($LASTEXITCODE -ne 0) {
Write-Fail "Scheduled task setup failed."
}
Write-Ok "Scheduled task 'OpenJarvis' registered (loopback default)."
}
# ---------------------------------------------------------------------------
# 8. Final message
# ---------------------------------------------------------------------------
Write-Host ""
Write-Host " ┌──────────────────────────────────┐" -ForegroundColor Green
Write-Host " │ OpenJarvis install complete │" -ForegroundColor Green
Write-Host " └──────────────────────────────────┘" -ForegroundColor Green
Write-Host ""
Write-Host " Repo: $srcDir"
Write-Host " Run it: cd `"$srcDir`"; uv run jarvis serve"
if ($shouldInstallService) {
Write-Host " Service: schtasks /Query /TN OpenJarvis (status)"
Write-Host " powershell -File `"$serviceScript`" uninstall (remove)"
}
Write-Host ""
Write-Host " Docs: https://open-jarvis.github.io/OpenJarvis/"
Write-Host ""
+208
View File
@@ -0,0 +1,208 @@
<#
.SYNOPSIS
Register / unregister the OpenJarvis Windows scheduled task.
.DESCRIPTION
The Windows equivalent of deploy/systemd/openjarvis.service and
deploy/launchd/com.openjarvis.plist.
Registers a per-user scheduled task named "OpenJarvis" that starts
`jarvis serve` at logon and restarts on failure. Loopback default
(127.0.0.1) so no API key is required — matches launchd parity.
Subcommands:
install — create or replace the task
uninstall — remove the task
status — show task state
Arguments (install only):
-InstallRoot <path> default: %LOCALAPPDATA%\OpenJarvis (matches
install.ps1's default)
-ListenHost <addr> default: 127.0.0.1 (loopback). Set to 0.0.0.0
ONLY if you also set $env:OPENJARVIS_API_KEY
— the server refuses to start unauthenticated
on a non-loopback bind.
-ListenPort <int> default: 8000
Usage:
powershell -ExecutionPolicy Bypass -File jarvis-service.ps1 install
powershell -ExecutionPolicy Bypass -File jarvis-service.ps1 uninstall
powershell -ExecutionPolicy Bypass -File jarvis-service.ps1 status
#>
[CmdletBinding()]
param(
[Parameter(Position = 0)]
[ValidateSet('install', 'uninstall', 'status')]
[string] $Command = 'status',
[string] $InstallRoot,
[string] $ListenHost = '127.0.0.1',
[int] $ListenPort = 8000
)
$ErrorActionPreference = 'Stop'
$TaskName = 'OpenJarvis'
function Write-Info ($msg) { Write-Host "[info] $msg" -ForegroundColor Cyan }
function Write-Ok ($msg) { Write-Host "[ok] $msg" -ForegroundColor Green }
function Write-Warn2 ($msg) { Write-Host "[warn] $msg" -ForegroundColor Yellow }
function Write-Fail ($msg) {
Write-Host "[fail] $msg" -ForegroundColor Red
exit 1
}
function Get-DefaultInstallRoot {
# Use $script: prefix so this is robust to being called from any
# function scope (PowerShell's default dynamic lookup would also
# work today, but $script: is the explicit contract).
if ($script:InstallRoot) { return $script:InstallRoot }
if ($env:OPENJARVIS_HOME) { return $env:OPENJARVIS_HOME }
return (Join-Path $env:LOCALAPPDATA 'OpenJarvis')
}
# ---------------------------------------------------------------------------
# install
# ---------------------------------------------------------------------------
function Install-Task {
$root = Get-DefaultInstallRoot
$srcDir = Join-Path $root 'src'
if (-not (Test-Path $srcDir)) {
Write-Fail "OpenJarvis source not found at $srcDir. Run install.ps1 first."
}
$uvCmd = Get-Command uv -ErrorAction SilentlyContinue
if (-not $uvCmd) {
$uvFallback = Join-Path $env:USERPROFILE '.local\bin\uv.exe'
if (Test-Path $uvFallback) {
$uvPath = $uvFallback
} else {
Write-Fail "uv.exe not found on PATH or at $uvFallback. Re-run install.ps1."
}
} else {
$uvPath = $uvCmd.Source
}
# Safety: refuse to register a non-loopback bind without an API key.
# Mirrors deploy/systemd/openjarvis.service's EnvironmentFile guard.
$isLoopback = ($ListenHost -eq '127.0.0.1' -or $ListenHost -eq 'localhost')
if (-not $isLoopback -and -not $env:OPENJARVIS_API_KEY) {
Write-Fail @"
ListenHost is $ListenHost (non-loopback) but `$env:OPENJARVIS_API_KEY is
not set. An unauthenticated non-loopback bind is refused by jarvis serve
and would also create a security hole. Set the env var first:
`$env:OPENJARVIS_API_KEY = (uv run jarvis auth generate-key)
then re-run with -ListenHost 0.0.0.0.
"@
}
# CRITICAL: scheduled tasks do NOT inherit the registering session's
# environment. If we registered the task now and stopped here, the
# task would launch at logon with a clean env, find no API key, and
# `jarvis serve` would refuse to bind 0.0.0.0 — failing silently every
# logon. Persist the key to the User env scope so the task's logon
# session picks it up. (Loopback path doesn't need the key, so this
# only runs for the explicit LAN-exposed case.)
if (-not $isLoopback) {
Write-Info "Persisting OPENJARVIS_API_KEY to User environment so the scheduled task can read it at logon."
[System.Environment]::SetEnvironmentVariable(
'OPENJARVIS_API_KEY',
$env:OPENJARVIS_API_KEY,
'User'
)
}
Write-Info "Registering scheduled task '$TaskName'..."
Write-Info " Working dir : $srcDir"
Write-Info " Listen : $ListenHost`:$ListenPort"
Write-Info " User : $env:USERNAME"
# If a previous task exists, remove it first (idempotent install).
$existing = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
if ($existing) {
Write-Info "Existing task found — replacing."
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false
}
$action = New-ScheduledTaskAction `
-Execute $uvPath `
-Argument "run jarvis serve --host $ListenHost --port $ListenPort" `
-WorkingDirectory $srcDir
$trigger = New-ScheduledTaskTrigger -AtLogOn -User $env:USERNAME
$settings = New-ScheduledTaskSettingsSet `
-AllowStartIfOnBatteries `
-DontStopIfGoingOnBatteries `
-StartWhenAvailable `
-RestartCount 3 `
-RestartInterval (New-TimeSpan -Minutes 1) `
-ExecutionTimeLimit (New-TimeSpan -Seconds 0)
$principal = New-ScheduledTaskPrincipal `
-UserId $env:USERNAME `
-LogonType Interactive `
-RunLevel Limited
Register-ScheduledTask `
-TaskName $TaskName `
-Action $action `
-Trigger $trigger `
-Settings $settings `
-Principal $principal `
-Description 'OpenJarvis API server (loopback default — see deploy/windows/README.md)' | Out-Null
Write-Ok "Task '$TaskName' registered."
Write-Info "It will start automatically at next logon."
Write-Info "To start it now: Start-ScheduledTask -TaskName $TaskName"
}
# ---------------------------------------------------------------------------
# uninstall
# ---------------------------------------------------------------------------
function Uninstall-Task {
$existing = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
if (-not $existing) {
Write-Warn2 "Task '$TaskName' is not registered — nothing to remove."
return
}
Write-Info "Stopping '$TaskName' (if running)..."
Stop-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
Write-Info "Unregistering '$TaskName'..."
Unregister-ScheduledTask -TaskName $TaskName -Confirm:$false
Write-Ok "Task '$TaskName' removed."
}
# ---------------------------------------------------------------------------
# status
# ---------------------------------------------------------------------------
function Show-Status {
$task = Get-ScheduledTask -TaskName $TaskName -ErrorAction SilentlyContinue
if (-not $task) {
Write-Host "Task '$TaskName' is not registered."
Write-Host "Install it with:"
Write-Host " powershell -ExecutionPolicy Bypass -File `"$PSCommandPath`" install"
return
}
$info = Get-ScheduledTaskInfo -TaskName $TaskName
Write-Host "Task : $TaskName"
Write-Host "State : $($task.State)"
Write-Host "LastRun : $($info.LastRunTime)"
Write-Host "LastRes : 0x$('{0:X8}' -f $info.LastTaskResult)"
Write-Host "NextRun : $($info.NextRunTime)"
}
# ---------------------------------------------------------------------------
# dispatch
# ---------------------------------------------------------------------------
switch ($Command) {
'install' { Install-Task }
'uninstall' { Uninstall-Task }
'status' { Show-Status }
}
+18 -9
View File
@@ -1,21 +1,30 @@
"""Publish the canonical install.sh into the docs site.
"""Publish the canonical install scripts into the docs site.
Serves the installers at::
https://open-jarvis.github.io/OpenJarvis/install.sh (Linux / macOS / WSL2)
https://open-jarvis.github.io/OpenJarvis/install.ps1 (native Windows)
Serves the installer at ``https://open-jarvis.github.io/OpenJarvis/install.sh``
so users have an HTTPS-valid, project-controlled install URL that does not
depend on the externally-hosted ``openjarvis.ai`` domain — whose TLS config
broke and which the project does not control (issue #337).
Single source of truth: the script lives at ``scripts/install/install.sh``
(also bundled into the wheel as ``_install_scripts/``). This copies it
verbatim into the built site at ``install.sh`` on every ``mkdocs build``,
so the published copy can never drift from the canonical one.
Single source of truth: the scripts live under ``scripts/install/`` and
``deploy/windows/`` (also bundled into the wheel as ``_install_scripts/``).
This copies them verbatim into the built site on every ``mkdocs build``,
so the published copies can never drift from the canonical ones.
"""
from pathlib import Path
import mkdocs_gen_files
_SRC = Path("scripts/install/install.sh")
# (source path, published URL path)
_SCRIPTS = [
(Path("scripts/install/install.sh"), "install.sh"),
(Path("deploy/windows/install.ps1"), "install.ps1"),
]
with mkdocs_gen_files.open("install.sh", "wb") as dst:
dst.write(_SRC.read_bytes())
for src, dest in _SCRIPTS:
with mkdocs_gen_files.open(dest, "wb") as out:
out.write(src.read_bytes())
+83
View File
@@ -0,0 +1,83 @@
# Native Windows (advanced)
Phase-1 of the native-Windows-support RFC (#298). Mirrors the Linux
(systemd) and macOS (launchd) deployments — but for PowerShell, without
WSL2 or Docker. Choose this over [WSL2](wsl2.md) only if you want to
avoid a Linux VM; WSL2 remains the smoother experience for most users.
## What you get
- A PowerShell installer that probes prerequisites, installs `uv`,
clones the repo, and runs `uv sync --extra server`.
- An optional Windows scheduled-task service equivalent to the systemd
unit and launchd plist.
- Loopback default — the service binds `127.0.0.1` so no API key is
required.
## What you need
- Windows 10 1809+ or Windows 11.
- Python 3.10 3.13 (Python 3.14 has no numpy Windows wheels yet —
see [#432](https://github.com/open-jarvis/OpenJarvis/issues/432)).
- `git` on PATH.
- ~5 GB free disk on `%LOCALAPPDATA%`.
## Install
In any PowerShell:
```powershell
irm https://open-jarvis.github.io/OpenJarvis/install.ps1 | iex
```
The installer will:
1. Refuse non-Windows hosts and old Windows builds.
2. Confirm Python 3.10 3.13.
3. Confirm `git`.
4. Install `uv` if absent (via the official `astral.sh/uv` PowerShell
installer).
5. Clone the repo to `%LOCALAPPDATA%\OpenJarvis\src`.
6. Run `uv sync --extra server`.
7. Prompt to register the scheduled-task service (skip with
`-SkipService`).
## Run it
```powershell
cd "$env:LOCALAPPDATA\OpenJarvis\src"
uv run jarvis serve
```
Open `http://127.0.0.1:8000/health` to verify.
## Scheduled-task service
If you skipped the prompt during install, register the auto-start task
manually:
```powershell
$srv = "$env:LOCALAPPDATA\OpenJarvis\src\deploy\windows\jarvis-service.ps1"
powershell -ExecutionPolicy Bypass -File $srv install
```
State:
```powershell
powershell -ExecutionPolicy Bypass -File $srv status
```
Remove:
```powershell
powershell -ExecutionPolicy Bypass -File $srv uninstall
```
See [`deploy/windows/README.md`](https://github.com/open-jarvis/OpenJarvis/blob/main/deploy/windows/README.md)
for the LAN-exposed configuration and the parity table against
systemd / launchd.
## See also
- [WSL2 install](wsl2.md) — the recommended Windows path.
- [Full installer reference](install.md).
+4 -1
View File
@@ -1,6 +1,9 @@
# WSL2 Install
OpenJarvis runs in WSL2 on Windows. Native Windows is not supported.
OpenJarvis on Windows installs two ways: **WSL2** (this page — the
recommended path; identical to native Linux) or **[native Windows
(advanced)](windows-native.md)** (Phase-1; PowerShell installer, no
WSL2 / no Docker). Pick WSL2 for the smoothest experience.
## One-time WSL setup
+1
View File
@@ -160,6 +160,7 @@ packages = ["src/openjarvis"]
"src/openjarvis/agents/claude_code_runner" = "_node_modules/claude_code_runner"
"src/openjarvis/channels/whatsapp_baileys_bridge" = "_node_modules/whatsapp_baileys_bridge"
"scripts/install" = "_install_scripts"
"deploy/windows" = "_deploy/windows"
[tool.pytest.ini_options]
testpaths = ["tests"]