Windows (Native) Guide
Nedura runs natively on Windows 10 and Windows 11 — no WSL, no Cygwin, no Docker. This page is the deep dive: what works natively, what's WSL-only, what the installer actually does, and the Windows-specific knobs you might need to touch.
If you just want to install, the one-liner on the landing page or Installation page is all you need. Come back here when something surprises you.
If you prefer a POSIX environment for fork semantics or Linux-style file watchers, see the Windows (WSL2) Guide. Both coexist cleanly: native data lives under %LOCALAPPDATA%\nedura, WSL data lives under ~/.nedura.
Quick install
Nedura is installed and upgraded by your administrator with nedura-deploy (Linux, Windows Server, containers/Helm, Terraform). See Installation, or contact support@nedura.ai.
No admin rights required. The installer goes to %LOCALAPPDATA%\nedura\ and adds nedura to your User PATH — open a new terminal after it finishes.
| Parameter | Purpose |
|---|---|
-Channel stable|main | stable (default): the latest published release. main: every commit, saved like nedura update --set-channel main. $env:NEDURA_INSTALL_CHANNEL sets it for a plain irm | iex. |
-Branch NAME | Select the source branch; default main. Another branch is installed at its tip. |
-Commit SHA | Select a commit after the branch checkout. |
-HermesHome PATH | Select the data directory. |
-InstallDir PATH | Select the source checkout directory. |
-NonInteractive | Skip setup and gateway stages that need input. |
-ShowResolvedPaths | Print resolved paths as JSON without installing. |
-Verbose | Stream every child command's output instead of one status line per step. |
-Manifest / -ProtocolVersion | Inspect the stage protocol used by the bootstrap GUI. |
-Stage NAME -Json | Run one stage and emit its result frame. |
The current script does not accept -NoVenv or -Tag. -SkipSetup is still accepted as a deprecated alias for -NonInteractive so older install wrappers keep binding.
To diagnose an unexpected short Windows path, use -ShowResolvedPaths first.
MSIX / App Installer and Microsoft Store
The bundled desktop is separate from the source script. Its MSIX package requires Windows 11 22H2 or later. Windows 10 source-script support does not mean the MSIX package supports Windows 10.
Open the downloaded .appinstaller file with Windows App Installer. It installs
a signed universal bundle and records the update source. The package includes
Python, Node, supported dependencies, and prebuilt interfaces. It does not clone
a checkout or build the base runtime on first launch.
The MSIX execution aliases expose nedura, hermes-agent, and hermes-acp.
If another installation shadows an alias, inspect Get-Command nedura -All.
Windows Settings → Apps → Advanced app settings → App execution aliases
controls the aliases.
Sideload updates use the app's Update control and Windows App Installer.
Nedura downloads a local descriptor before teardown and registers automatic
relaunch. It does not require the ms-appinstaller: URL protocol. An unknown
update-check result is not a claim that the package is current.
The Microsoft Store variant uses its Partner Center package identity and Store
updates. It does not use the sideload feed. nedura update inside either
bundled runtime does not run Git against package files.
It provisions a source checkout through the scripts. Do not confuse it with the self-contained MSIX package. See Updating & Uninstalling.
Dependency bootstrap
PM owns managed tools. Feature code asks PM for the package it needs (pm.ensure("<package>"), e.g. cua-driver for Computer Use) instead of re-running the installer. Already installed tools are reused from PM's recorded state; a missing optional tool is fetched on demand only when security.allow_lazy_installs permits it.
nedura pm doctor
nedura pm install
Feature matrix
Windows support is feature- and architecture-specific. The base interfaces run natively, but some optional SDKs are excluded from particular targets.
| Feature | Native Windows | WSL2 |
|---|---|---|
CLI (nedura chat, nedura setup, nedura gateway, …) | ✓ | ✓ |
Interactive TUI (nedura --tui) | ✓ | ✓ |
| Messaging gateway (Telegram, Discord, Slack, WhatsApp, 15+ platforms) | ✓ | ✓ |
| Cron scheduler | ✓ | ✓ |
| Browser tool (Chromium via Node) | ✓ | ✓ |
| MCP servers (stdio and HTTP) | ✓ | ✓ |
| Local Ollama / LM Studio / llama-server | ✓ | ✓ (via WSL networking) |
| Web dashboard (sessions, jobs, metrics, config) | ✓ | ✓ |
Dashboard /chat embedded terminal pane | ConPTY through pywinpty | POSIX PTY |
| Auto-start at login | ✓ (schtasks) | ✓ (systemd) |
The dashboard uses its pywinpty/ConPTY bridge on Windows and ptyprocess
on POSIX. A missing or broken native dependency can make the terminal
unavailable; WSL is an alternative, not a requirement of the current design.
Optional dependency limits
- Matrix's native encrypted adapter is Linux-only; use a supported proxy route or a Linux backend on Windows.
- Native Windows ARM64 excludes the
google-chatSDK extra and the openWakeWord engine. Sherpa supports native Windows ARM64 and is the automatic wake-word default on that target. - Local Faster-Whisper STT is excluded on native Windows ARM64. Use a cloud or command-based STT provider. Porcupine remains a wake-engine alternative.
The platform markers in pyproject.toml define the packaged dependency set.
A general gateway or voice feature claim does not override those markers.
How Nedura runs shell commands on Windows
Nedura's terminal tool runs commands through Git Bash, same strategy Claude Code uses. This sidesteps the POSIX-vs-Windows gap without rewriting every tool.
pm.shell() owns Bash resolution. It first checks the Git package recorded in
PM facts, then the provisioned PATH. If a PATH candidate belongs to a WindowsApps
package, the resolver prefers a conventional Git for Windows installation when available.
Packaged tools are not general-purpose host installations. An external Python
process can fail to start a WindowsApps payload executable with WinError 5.
Use the package's own launcher, or use conventional tools for a source checkout.
Do not disable Windows security controls to work around that boundary.
The current installer does not set NEDURA_GIT_BASH_PATH. MinGit is not a
replacement for Git for Windows with Bash.
UTF-8 console on Windows
Python's default stdio on Windows uses the console's active code page (usually cp1252 or cp437). Nedura's banner, slash-command list, tool feed, Rich panels, and skill descriptions all contain Unicode. Without intervention, any of that crashes with UnicodeEncodeError: 'charmap' codec can't encode character….
The fix is in hermes_cli/stdio.py::configure_windows_stdio(), called early in every entry point (cli.py::main, hermes_cli/main.py::main, gateway/run.py::main). It:
- Flips the console code page to CP_UTF8 (65001) via
kernel32.SetConsoleCP/SetConsoleOutputCP. - Reconfigures
sys.stdout/sys.stderr/sys.stdinto UTF-8 witherrors='replace'. - Sets
PYTHONIOENCODING=utf-8andPYTHONUTF8=1(viasetdefault, so explicit user values win) so child Python subprocesses inherit UTF-8. - Sets
EDITOR=notepadif neitherEDITORnorVISUALis set (see the Editor section below).
Idempotent. No-op on non-Windows.
Opt out: NEDURA_DISABLE_WINDOWS_UTF8=1 in the environment falls back to the legacy cp1252 stdio path. Useful for bisecting an encoding bug; unlikely to be the right setting in normal operation.
The editor (Ctrl-X Ctrl-E, /edit)
Pre-#21561, pressing Ctrl-X Ctrl-E or typing /edit silently did nothing on Windows. prompt_toolkit has a hardcoded POSIX-absolute fallback list (/usr/bin/nano, /usr/bin/pico, /usr/bin/vi, …) that never resolves on Windows — even with full Git for Windows installed.
Nedura's Windows stdio shim now sets EDITOR=notepad as a default. Notepad ships with every Windows install and works as a blocking editor — subprocess.call(["notepad", file]) blocks until the window closes.
User overrides still win (they're checked before the setdefault):
| Editor | PowerShell command |
|---|---|
| VS Code | $env:EDITOR = "code --wait" |
| Notepad++ | $env:EDITOR = "'C:\Program Files\Notepad++\notepad++.exe' -multiInst -nosession" |
| Neovim | $env:EDITOR = "nvim" |
| Helix | $env:EDITOR = "hx" |
The --wait flag on VS Code is critical — without it the editor returns immediately and Nedura gets a blank buffer back.
Set it permanently in your PowerShell profile:
# In $PROFILE
$env:EDITOR = "code --wait"
Or as a User environment variable in System Settings so every new shell picks it up.
Ctrl+Enter for newline in the CLI
Windows Terminal passes Ctrl+Enter through as a dedicated key sequence. Nedura binds it to "insert newline" so you can compose multi-line prompts in the CLI without falling back to Esc-then-Enter. Works in Windows Terminal, VS Code integrated terminal, and any modern Windows console host that honors VT escape sequences.
On legacy cmd.exe consoles Ctrl+Enter collapses to plain Enter — use Esc Enter instead, or upgrade to Windows Terminal (it's free and installed by default on Windows 11).
Running the gateway at Windows login
nedura gateway install on Windows uses Scheduled Tasks with a Startup-folder fallback — no admin required.
Install
nedura gateway install
What happens under the hood:
schtasks /Create /SC ONLOGON /RL LIMITED /TN Hermes_Gateway— registers a task that runs at your login with standard (non-elevated) permissions. No UAC prompt.- If schtasks is blocked by group policy, falls back to writing a small
Hermes_Gateway.vbslauncher (run hidden viawscript.exe) into%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup. Same effect, slightly cruder. A VBScript is used rather than acmd.exeshortcut because a console allocated at logon can receive a close event that kills the gateway before it finishes starting. Only one of the two is ever kept: a successful task install removes any Startup-folder entry (including a legacyHermes_Gateway.cmd), the fallback is skipped while a task is still registered, andnedura update/nedura doctor --fixclean up older installs that have both, since both would launch the gateway at logon. - Spawns the gateway detached via
pythonw.exe— notpython.exe.pythonw.exehas no console attached, which immunizes it againstCTRL_C_EVENTbroadcasts from sibling processes (a real issue that used to kill the gateway when you Ctrl+C'd anything in the same process group).
Flags used when spawning: DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP | CREATE_NO_WINDOW | CREATE_BREAKAWAY_FROM_JOB.
Manage
nedura gateway status # Merged view: schtasks + Startup folder + running PID
nedura gateway start # Starts the gateway in the background (asks about login auto-start only on a TTY when nothing is installed)
nedura gateway stop # Writes the planned-stop marker, waits for the gateway to drain (≤ agent.restart_drain_timeout, capped at 30 s), then force-kills only if it is still alive
nedura gateway restart # Same drain-first stop, then a fresh start
nedura gateway uninstall # Removes schtasks entry, Startup shortcut, pid file
nedura gateway status is idempotent — call it a thousand times in a row and it will never accidentally kill the gateway. (Pre-PR #21561 it silently did, via os.kill(pid, 0) colliding with CTRL_C_EVENT at the C level — see "process management internals" below if you care about the story.)
Login auto-start is only ever installed on an explicit answer: nedura gateway install, a Y on a real terminal, or NEDURA_GATEWAY_INSTALL_START_ON_LOGIN=1. A scripted or piped nedura gateway start (no TTY, or NEDURA_NONINTERACTIVE=1) starts the gateway without touching the Scheduled Task or the Startup folder; set NEDURA_GATEWAY_INSTALL_START_ON_LOGIN=0 to skip the question on a terminal too.
Why not a Windows Service?
Services require admin rights to install and tie the gateway's lifecycle to machine boot, not user login. The typical Nedura user wants: log in → gateway available, log out → gateway gone. Scheduled Tasks do exactly that without elevation. If you genuinely want a service, use nssm or sc create manually — but you probably don't. If you do, name it Nedura* or point its binary path inside the Nedura install (venv\Scripts\hermes.exe, the checkout, or gateway-service\): nedura update stops and restarts only services it can positively identify as Nedura-owned through the Service Control Manager, and pauses a Scheduled-Task-launched gateway by PID (Task Scheduler itself is never touched).
Data layout
| Path | Contents |
|---|---|
%LOCALAPPDATA%\nedura\hermes-agent\ | Source checkout for the script installation; absent from an MSIX-only install. |
%LOCALAPPDATA%\nedura\tools\ | Writable managed-tool store. MSIX base tools remain inside the package. |
%LOCALAPPDATA%\nedura\installs\ | Per-install runtime selection, journals, and Python generations. |
%LOCALAPPDATA%\nedura\bin\ | Source-install CLI launchers. MSIX instead provides execution aliases. |
%LOCALAPPDATA%\nedura\ | User configuration, credentials, sessions, plugins, skills, and logs. |
These are default paths. NEDURA_HOME and installer path arguments can change
them. A full deletion of %LOCALAPPDATA%\nedura also deletes user data and
can affect other installations that share it. Use the uninstall command or
Windows package removal instead of deleting that root to repair an app.
Browser tool
The browser tool (browser_exec) is part of Nedura's Python environment.
For local browsing, Nedura launches the pinned Chromium that PM installs
(nedura pm install chromium if it is missing); set browser.executable_path
to use another Chromium-family binary. Cloud browsers are selected in
nedura tools. A self-contained MSIX includes the browser tools in its payload.
On Windows ARM64, the pinned Chromium is the x64 build and runs under Windows' x64 emulation. This differs from the native ARM64 Python runtime. See Browser automation for browser sources.
Running Nedura on Windows — practical notes
PATH after install
The installer adds %LOCALAPPDATA%\nedura\bin to your User PATH via [Environment]::SetEnvironmentVariable. Existing terminals don't pick this up — open a new PowerShell window (or Windows Terminal tab) after installation. Close-and-reopen, don't $env:PATH += … by hand unless you know what you're doing.
Verify:
Get-Command nedura # should print C:\Users\<you>\AppData\Local\nedura\bin\hermes.exe
nedura --version
Environment variables
Nedura honors both $env:X (process-scope) and User environment variables (permanent, set in System Properties → Environment Variables). Setting API keys in %LOCALAPPDATA%\nedura\.env (your NEDURA_HOME) is the normal path — same as Linux:
OPENROUTER_API_KEY=sk-or-...
TELEGRAM_BOT_TOKEN=...
Don't put secrets in User environment variables unless you specifically want every Windows process to see them (it isn't what you want).
Windows-specific env vars
These only affect native Windows installs:
| Variable | Effect |
|---|---|
NEDURA_DISABLE_WINDOWS_UTF8 | Set to 1 to disable the UTF-8 stdio shim and fall back to the locale code page. Useful for bisecting an encoding bug. |
EDITOR / VISUAL | Your editor for /edit and Ctrl-X Ctrl-E. Nedura defaults to notepad if both are unset. |
Uninstall
From PowerShell:
nedura uninstall
For source installs, the uninstaller removes owned launchers, service entries,
and application files. Review nedura uninstall --dry-run before removal.
--full also removes data; --data removes data without removing packaged code.
For MSIX or Store installations, remove the app through Windows Settings →
Apps → Installed apps. The CLI refuses to delete package-owned code.
Before deleting data, stop every Nedura process that uses the selected NEDURA_HOME and make a backup.
Review nedura uninstall --dry-run before choosing a data-removal mode.
Do not recursively delete the default data root to repair one application or profile.
A custom NEDURA_HOME can be elsewhere, and package removal does not remove that data.
The nedura uninstall CLI subcommand also handles the case where the schtasks entry was registered under a different task name (older installs) — it searches by install path rather than by hardcoded task name.
Process management internals
This is background material — skip unless you're debugging an "it's killing itself" weirdness.
On Linux and macOS, the POSIX idiom os.kill(pid, 0) is a no-op permission check: "is this PID alive and can I signal it?" On Windows, Python's os.kill maps sig=0 to CTRL_C_EVENT — they collide at integer value 0 — and routes it through GenerateConsoleCtrlEvent(0, pid), which broadcasts Ctrl+C to the entire console process group containing the target PID. That's bpo-14484, open since 2012. It won't be fixed because changing it would break scripts that depend on the current behavior.
Consequence: any codepath that said "check if this PID is alive" via os.kill(pid, 0) on Windows was silently killing the target. Nedura migrated every such site (14 across 11 files) to gateway.status._pid_exists(), which uses psutil.pid_exists() (which in turn uses OpenProcess + GetExitCodeProcess on Windows — no signals). If you're writing a plugin or patch, use psutil.pid_exists() directly or gateway.status._pid_exists() — never os.kill(pid, 0).
scripts/check-windows-footguns.py enforces this in CI: any new os.kill(pid, 0) call fails the Windows footguns (blocking) check unless the line carries a # windows-footgun: ok — <reason> marker.
Common pitfalls
nedura: command not found right after install.
Open a new PowerShell window. The installer added %LOCALAPPDATA%\nedura\bin to User PATH, but existing shells need to be restarted to pick it up. In the meantime you can run & "$env:LOCALAPPDATA\nedura\bin\hermes.exe".
WinError 193: %1 is not a valid Win32 application when running a tool.
You hit a shebang-script invocation that bypassed the .cmd shim. Nedura resolves commands through shutil.which(cmd, path=local_bin) so PATHEXT picks up .CMD — if you're invoking the tool via a hardcoded path instead, switch to the .cmd variant (e.g., npx.cmd, not npx).
The irm | iex form strips BOMs automatically; [scriptblock]::Create((irm ...)) does not. Re-run with the simple irm | iex form, or download the script manually and save it without a BOM via [IO.File]::WriteAllText($path, $text, (New-Object Text.UTF8Encoding $false)).
Gateway won't stay running after restart.
Check nedura gateway status — it merges the schtasks entry, the Startup-folder shortcut (if used), and the live PID. If schtasks is registered but not running, group policy may be blocking ONLOGON triggers. Run schtasks /Query /TN Hermes_Gateway /V /FO LIST (Hermes_Gateway_<profile> for a named profile) to see the task's failure reason. The Startup-folder fallback engages automatically only when schtasks itself fails to register the task; there is no environment variable or flag to force it.
/edit still does nothing after setting $env:EDITOR.
You set it in the current process only; close and reopen the shell, or set it at User scope in System Properties → Environment Variables. Verify with echo $env:EDITOR in a new PowerShell window.
Browser tool launches but tools time out.
Run nedura doctor and nedura pm doctor. Use nedura tools to inspect the
selected browser provider. A slow first Chromium start can be given more time
with browser.command_timeout (the launch timeout floor, in seconds). Do not
install an unrelated Playwright revision into a signed app payload.
Chinese / Japanese / Arabic characters show as ? in the CLI.
The UTF-8 stdio shim didn't activate. Check that NEDURA_DISABLE_WINDOWS_UTF8 is NOT set (Get-ChildItem env:NEDURA_DISABLE_WINDOWS_UTF8). If it's empty and you still see ?, the console host (very old cmd.exe) may not support UTF-8 at all — switch to Windows Terminal.
Gateway can't send Telegram photos — "BadRequest: payload contains invalid characters".
This is unrelated to Windows but sometimes surfaces first there. Usually it means your file path contains unescaped backslashes in a JSON body. Telegram should be receiving paths Nedura normalizes, not raw Windows paths — if you're seeing this inside a custom plugin, make sure you're passing the Nedura-provided path, not str(Path(...)) from user input.
"Works on my other machine" encoding weirdness after git pull.
If you edited Nedura config or a skill on Windows using a non-UTF-8 editor (Notepad on older Windows versions, some Chinese IMEs), the file may have been saved with a BOM. Nedura tolerates utf-8-sig on most config reads, but a BOM inside a folded YAML scalar (description: >) silently breaks YAML parsing. Re-save the file as plain UTF-8 without BOM.
Where to go next
- Installation — the full install page, including Linux/macOS/WSL2.
- Windows (WSL2) Guide — if you want POSIX semantics or the dashboard terminal pane.
- CLI Reference — every
nedurasubcommand. - FAQ — common non-Windows-specific questions.
- Messaging Gateway — running Telegram/Discord/Slack on Windows.