feat: put ncp on PATH on both platforms; rename WSL reqs to Windows
Ports the ncp PATH work from upstream. Registering ncp as a shell-profile function failed three ways on Windows: the default Restricted execution policy blocks the profile itself, profiles don't exist outside PowerShell (cmd, Win+R, Task Scheduler), and the self-elevating installer writes the admin's profile. Windows now ships management/ncp.cmd and the installer appends management\ to the Machine PATH via [Environment]::SetEnvironmentVariable -- never setx, which truncates PATH at 1024 chars. A .cmd is exempt from the execution policy. Linux symlinks /usr/local/bin/ncp -> management/nexus-cli.sh, falling back to the old .bashrc function when sudo is unavailable. Also renames requirements-wsl.txt to requirements-windows.txt and purges stale WSL references, including vite.config.js's dev-server comment and controlpanel.py's "Check WSLg." error string. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -16,7 +16,7 @@ start --ai`), not on backend startup.
|
||||
**Windows (recommended):**
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\install-windows.ps1 # one-time native install
|
||||
.\launch_nexus.ps1 # memory :8001 + backend :8000
|
||||
ncp web # memory :8001 + backend :8000 + UI
|
||||
```
|
||||
|
||||
**Linux full stack (dev):**
|
||||
@@ -80,7 +80,7 @@ cd interface/web && npm run build
|
||||
## Architecture
|
||||
|
||||
### Python venv
|
||||
All Python code runs inside `Promethean/` (a local venv). Always activate it before running backend commands: `source Promethean/bin/activate`. Dependencies are layered: `requirements-base.txt` holds the GPU-agnostic core, and a thin overlay pins the right PyTorch build for the target — `requirements-amd.txt` (ROCm), `requirements-nvidia.txt` (CUDA, generated by `bin/gen-nvidia-reqs.py`), or `requirements-wsl.txt` (CPU-only). `bin/install.sh` selects NVIDIA, AMD, or CPU/WSL requirements from the host.
|
||||
All Python code runs inside `Promethean/` (a local venv). Always activate it before running backend commands: `source Promethean/bin/activate`. Dependencies are layered: `requirements-base.txt` holds the GPU-agnostic core, and a thin overlay pins the right PyTorch build for the target — `requirements-amd.txt` (ROCm), `requirements-nvidia.txt` (CUDA, generated by `bin/gen-nvidia-reqs.py`), or `requirements-windows.txt` (CPU-only). `bin/sync.py` (`requirements()`) selects NVIDIA, AMD, or CPU/Windows requirements from the host.
|
||||
|
||||
### Synapse Backend (`synapse/`)
|
||||
FastAPI app at `synapse/main.py`. Key responsibilities:
|
||||
@@ -125,4 +125,4 @@ Most data lands in `synapse/memory/memory.db` (SQLite, WAL mode). Tables: memory
|
||||
| Python dependencies (base) | `requirements-base.txt` |
|
||||
| Python dependencies (AMD/ROCm) | `requirements-amd.txt` |
|
||||
| Python dependencies (NVIDIA/CUDA) | `requirements-nvidia.txt` (generated by `bin/gen-nvidia-reqs.py`) |
|
||||
| Python dependencies (WSL/CPU) | `requirements-wsl.txt` |
|
||||
| Python dependencies (Windows/CPU) | `requirements-windows.txt` |
|
||||
|
||||
@@ -17,8 +17,8 @@ memory service, and a React frontend. No external AI provider is called.
|
||||
NexusOS ("Nexus") is a self-hosted assistant you actually own. All inference
|
||||
runs through a **locally bundled Ollama** on `localhost`; conversations, facts,
|
||||
and settings live in local SQLite. It ships with desktop branding (XFCE theme,
|
||||
icons, boot splash) so it can be run as a full assistant environment, not just
|
||||
a web app.
|
||||
icons, boot splash) so it can be run as a full assistant environment on Linux,
|
||||
not just a web app.
|
||||
|
||||
- **Chat** — streaming responses from local Ollama models (SSE).
|
||||
- **Persistent memory** — a dedicated service auto-extracts durable facts from
|
||||
@@ -47,26 +47,21 @@ cd nexus-core
|
||||
powershell -ExecutionPolicy Bypass -File .\install-windows.ps1
|
||||
```
|
||||
|
||||
> **Execution policy:** Windows blocks unsigned `.ps1` scripts by default, so
|
||||
> run them with `-ExecutionPolicy Bypass` as shown (a one-run override — nothing
|
||||
> permanent). Double-clicking `install-windows.ps1` or running `.\install-windows.ps1`
|
||||
> bare will fail with *"running scripts is disabled on this system"*.
|
||||
> The installer self-elevates (a UAC prompt will appear).
|
||||
>
|
||||
> To allow scripts persistently instead (then you can run `.\...ps1` directly):
|
||||
> ```powershell
|
||||
> Set-ExecutionPolicy -Scope CurrentUser -ExecutionPolicy RemoteSigned
|
||||
> ```
|
||||
|
||||
Then double-click the **NexusOS** desktop icon (the shortcut already passes the
|
||||
bypass), or launch from a shell with:
|
||||
Then double-click the **NexusOS** desktop icon, or launch from a **new** shell
|
||||
(PATH is read at process start, so already-open windows won't have `ncp` yet):
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File .\launch_nexus.ps1
|
||||
ncp web
|
||||
```
|
||||
|
||||
> The installer needs `-ExecutionPolicy Bypass` because Windows blocks unsigned
|
||||
> `.ps1` by default; it's a one-run override, nothing permanent. It self-elevates,
|
||||
> so a UAC prompt appears. `ncp` itself is `management\ncp.cmd` on the machine
|
||||
> PATH — a `.cmd`, so no execution-policy change is ever needed, and it works
|
||||
> from cmd and Task Scheduler as well as PowerShell.
|
||||
|
||||
The app opens at `:8000`; click **Start AI** to launch Ollama. The installer
|
||||
uses `requirements-wsl.txt` (CPU-only, pure-Python — no ML stack, since Ollama
|
||||
uses `requirements-windows.txt` (CPU-only, pure-Python — no ML stack, since Ollama
|
||||
does all inference over HTTP).
|
||||
|
||||
### Linux
|
||||
@@ -77,12 +72,13 @@ does all inference over HTTP).
|
||||
./install.sh
|
||||
|
||||
# 2. Launch (memory :8001, backend :8000 — backend also serves the built UI)
|
||||
./launch_nexus.sh
|
||||
# The install symlinks ncp into /usr/local/bin (sudo); open a new shell first.
|
||||
ncp web
|
||||
```
|
||||
|
||||
Python deps are layered: `requirements-base.txt` (GPU-agnostic core) plus one
|
||||
GPU overlay — `requirements-amd.txt` (ROCm) or `requirements-nvidia.txt` (CUDA).
|
||||
`requirements-wsl.txt` is the standalone CPU-only runtime (no base overlay).
|
||||
`requirements-windows.txt` is the standalone CPU-only runtime (no base overlay).
|
||||
`bin/sync.py` picks the right one for the host.
|
||||
|
||||
`./install.sh` is also the update path — re-run it any time to pull the latest
|
||||
@@ -115,12 +111,14 @@ ncp chat "<message>" # stream a reply
|
||||
ncp memory list | add <text> | rm <id>
|
||||
ncp playbook list | show <id> # first playbook (*) = active system prompt
|
||||
ncp history [query] # recent conversations
|
||||
ncp doctor # diagnostics: venv, Node, imports, Ollama, status
|
||||
```
|
||||
|
||||
## Architecture
|
||||
|
||||
| Component | Location | Role |
|
||||
|---|---|---|
|
||||
| **Promethean** (venv) | `Promethean/` | The Python venv all backend code runs in — `source Promethean/bin/activate` (Linux) / `Promethean\Scripts\python.exe` (Windows). Keeps deps out of the system Python. |
|
||||
| **Synapse** (backend) | `synapse/` | FastAPI app. `/chat/stream`, `/playbooks`, `/memory`, `/models`, `/conversations`, `/settings`, `/ollama`, `/icons`. Assembles the system prompt: active playbook → reference playbooks → memory facts → relevant past snippets. |
|
||||
| **Memory service** | `synapse/memory/` | Separate FastAPI app (:8001). `/memories/extract` uses an Ollama prompt to decide what to persist. Shares the SQLite DB with the backend. |
|
||||
| **Playbooks** | `synapse/playbooks/` + `data/playbooks/` | Ordered `{id}.yaml` records managed by `PlaybookManager`. |
|
||||
@@ -142,6 +140,7 @@ management/ nexus-cli.sh, ncp API client, control panel, desktop theme
|
||||
bin/ install, backup/restore, panel + provisioning scripts
|
||||
assets/ branding: icons, boot splash, XFCE/GTK theme
|
||||
data/playbooks/ active playbook YAML
|
||||
Promethean/ Python venv (gitignored, built by the installer)
|
||||
```
|
||||
|
||||
## Configuration
|
||||
@@ -151,7 +150,7 @@ data/playbooks/ active playbook YAML
|
||||
| Ollama host | `OLLAMA_HOST` env (default `http://127.0.0.1:11434`) |
|
||||
| Filesystem paths | `synapse/nexus_config.py` |
|
||||
| Frontend API base URL | `interface/web/src/config.js` |
|
||||
| Python deps | `requirements-base.txt` + amd/nvidia GPU overlay; `requirements-wsl.txt` = standalone CPU runtime |
|
||||
| Python deps | `requirements-base.txt` + amd/nvidia GPU overlay; `requirements-windows.txt` = standalone CPU runtime |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ cp -f "$NEXUS/management/panel/genmon-16.rc" "$PANEL_CFG/genmon-16.rc"
|
||||
# ── Register the genmon applets into the XFCE panel ────────────────────────
|
||||
# The network applet's genmon (plugin-13) was added by hand once; the Nexus
|
||||
# (15) and Bluetooth (16) applets are wired up programmatically via xfconf so a
|
||||
# fresh install picks them up. No-op on machines without XFCE (e.g. the WSL box).
|
||||
# fresh install picks them up. No-op on machines without XFCE (e.g. the Windows box).
|
||||
# place = before|after — where to insert relative to the network applet (13).
|
||||
register_genmon() {
|
||||
local id=$1 place=$2 anchor=13 panel=panel-1
|
||||
|
||||
+15
-6
@@ -52,11 +52,20 @@ if [ "$stage" = "runtime" ]; then
|
||||
echo "Warning: bin/fetch-ollama.sh not found — skipping Ollama fetch."
|
||||
fi
|
||||
|
||||
# Shell wiring: the `ncp` function and the Promethean venv alias. This is the
|
||||
# only place they get registered now that bin/install.sh is gone. Guarded by a
|
||||
# grep so an in-place restore is a no-op and a scratch clone can't re-point an
|
||||
# already-wired ~/.bashrc at itself.
|
||||
if ! grep -q "management/nexus-cli.sh" "$HOME/.bashrc" 2>/dev/null; then
|
||||
# `ncp` on PATH, not a shell function: /usr/local/bin is visible to sh, cron,
|
||||
# systemd units and .desktop Exec lines, none of which read ~/.bashrc. The
|
||||
# Windows installer does the matching thing with management\ncp.cmd on PATH.
|
||||
# Falls back to the old ~/.bashrc function when there's no sudo (a shell
|
||||
# function shadows the symlink anyway, so having both is harmless).
|
||||
ncp_link="/usr/local/bin/ncp"
|
||||
ncp_target="$NEXUS_ROOT/management/nexus-cli.sh"
|
||||
if [ "$(readlink -f "$ncp_link" 2>/dev/null)" = "$ncp_target" ]; then
|
||||
echo "ncp already on PATH at $ncp_link."
|
||||
elif sudo ln -sfn "$ncp_target" "$ncp_link"; then
|
||||
echo "Registered ncp at $ncp_link."
|
||||
elif ! grep -q "management/nexus-cli.sh" "$HOME/.bashrc" 2>/dev/null; then
|
||||
# Guarded by a grep so an in-place restore is a no-op and a scratch clone
|
||||
# can't re-point an already-wired ~/.bashrc at itself.
|
||||
cat >> "$HOME/.bashrc" <<EOF
|
||||
|
||||
# Nexus
|
||||
@@ -64,7 +73,7 @@ ncp() {
|
||||
$NEXUS_ROOT/management/nexus-cli.sh "\$@"
|
||||
}
|
||||
EOF
|
||||
echo "Registered ncp in ~/.bashrc."
|
||||
echo "No sudo for $ncp_link — registered ncp in ~/.bashrc instead."
|
||||
fi
|
||||
|
||||
if ! grep -qF "alias promethean=" "$HOME/.bashrc" 2>/dev/null; then
|
||||
|
||||
+1
-1
@@ -69,7 +69,7 @@ def venv_python() -> Path:
|
||||
def requirements() -> str:
|
||||
"""Pick the PyTorch overlay for this host."""
|
||||
if os.name == "nt":
|
||||
return "requirements-wsl.txt" # CPU / pure-Python, right for native Windows
|
||||
return "requirements-windows.txt" # CPU / pure-Python, right for native Windows
|
||||
if shutil.which("nvidia-smi"):
|
||||
return "requirements-nvidia.txt"
|
||||
lspci = shutil.which("lspci")
|
||||
|
||||
+33
-47
@@ -1,7 +1,7 @@
|
||||
#Requires -Version 5.1
|
||||
<#
|
||||
.SYNOPSIS
|
||||
NexusOS installer for Windows (native - no WSL).
|
||||
NexusOS installer for Windows (native).
|
||||
.DESCRIPTION
|
||||
Installs Python, Node.js, and Ollama via winget, builds the Promethean
|
||||
virtualenv and the web UI, and drops a desktop shortcut. NexusOS then runs
|
||||
@@ -11,17 +11,8 @@
|
||||
Run from the nexus-core directory:
|
||||
Right-click install-windows.ps1 -> "Run with PowerShell"
|
||||
|
||||
Requires Windows 10/11 with winget (App Installer). No WSL, no reboot.
|
||||
.PARAMETER InvokerProfile
|
||||
Internal. The PowerShell profile path of the user who launched the
|
||||
installer, captured before self-elevation and passed to the elevated run.
|
||||
If a standard account elevates with a DIFFERENT admin's credentials, the
|
||||
elevated process sees that admin's $PROFILE - so ncp would be registered
|
||||
for the wrong user. Not meant to be passed by hand.
|
||||
Requires Windows 10/11 with winget (App Installer). No reboot needed.
|
||||
#>
|
||||
param(
|
||||
[string]$InvokerProfile = ""
|
||||
)
|
||||
|
||||
Set-StrictMode -Version Latest
|
||||
$ErrorActionPreference = "Stop"
|
||||
@@ -57,11 +48,7 @@ $IsAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIden
|
||||
[Security.Principal.WindowsBuiltInRole]::Administrator)
|
||||
if (-not $IsAdmin) {
|
||||
Write-Host "Requesting administrator privileges..." -ForegroundColor Yellow
|
||||
# Capture THIS user's profile path before elevating and hand it to the
|
||||
# elevated run: if UAC prompts for another admin's credentials, that process
|
||||
# would otherwise register ncp in the wrong user's profile.
|
||||
$Invoker = $PROFILE.CurrentUserAllHosts
|
||||
Start-Process powershell -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`" -InvokerProfile `"$Invoker`"" -Verb RunAs
|
||||
Start-Process powershell -ArgumentList "-NoProfile -ExecutionPolicy Bypass -File `"$PSCommandPath`"" -Verb RunAs
|
||||
exit
|
||||
}
|
||||
|
||||
@@ -100,7 +87,7 @@ if (-not (Test-Path $VenvPy)) {
|
||||
if (-not (Test-Path $VenvPy)) { Write-Fail "venv creation failed at $Venv" }
|
||||
|
||||
& $VenvPy -m pip install --upgrade pip -q
|
||||
$Req = Join-Path $RepoRoot "requirements-wsl.txt" # CPU / pure-Python overlay - right for native Windows too
|
||||
$Req = Join-Path $RepoRoot "requirements-windows.txt" # CPU / pure-Python overlay - right for native Windows too
|
||||
if (-not (Test-Path $Req)) { Write-Fail "requirements file not found: $Req" }
|
||||
& $VenvPy -m pip install -r $Req
|
||||
if ($LASTEXITCODE -ne 0) { Write-Fail "pip install failed (exit $LASTEXITCODE) - see output above" }
|
||||
@@ -178,41 +165,40 @@ try {
|
||||
}
|
||||
|
||||
# -- ncp command ---------------------------------------------------------------
|
||||
# The Linux side registers ncp in ~/.bashrc from restore-linux.sh; this is the
|
||||
# same idea for PowerShell. Guarded by a marker so re-running the installer is a
|
||||
# no-op instead of stacking duplicate functions.
|
||||
# management\ncp.cmd goes on the machine PATH rather than a `function ncp` in the
|
||||
# PowerShell profile. Three reasons the profile route kept biting:
|
||||
# 1. Execution policy defaults to Restricted, which blocks the profile itself -
|
||||
# so the function was never defined, and ncp.ps1 could not have run anyway.
|
||||
# 2. Profiles are a PowerShell thing. cmd, Win+R, Task Scheduler and .lnk
|
||||
# targets all reported "ncp is not recognized".
|
||||
# 3. We are elevated here, so $PROFILE is the ADMIN's profile - the whole
|
||||
# -InvokerProfile dance existed to work around that. Machine PATH has no
|
||||
# such split.
|
||||
# The Linux side does the matching thing: a symlink in /usr/local/bin.
|
||||
Write-Step "Registering the ncp command"
|
||||
try {
|
||||
# CurrentUserAllHosts (profile.ps1), not $PROFILE: $PROFILE is host-specific,
|
||||
# so ncp would exist in the console but not in the VS Code terminal or ISE.
|
||||
# Prefer the invoking user's path when we were elevated - see -InvokerProfile.
|
||||
$ProfilePath = if ($InvokerProfile) { $InvokerProfile } else { $PROFILE.CurrentUserAllHosts }
|
||||
$ProfileDir = Split-Path -Parent $ProfilePath
|
||||
if (-not (Test-Path $ProfileDir)) {
|
||||
New-Item -ItemType Directory -Path $ProfileDir -Force | Out-Null
|
||||
}
|
||||
$Marker = "# NexusOS ncp"
|
||||
$Existing = ""
|
||||
if (Test-Path $ProfilePath) {
|
||||
$Existing = Get-Content -Path $ProfilePath -Raw -ErrorAction SilentlyContinue
|
||||
}
|
||||
if ($Existing -and $Existing.Contains($Marker)) {
|
||||
Write-OK "ncp already registered in $ProfilePath"
|
||||
$NcpCmd = Join-Path $RepoRoot "management\ncp.cmd"
|
||||
if (-not (Test-Path $NcpCmd)) { throw "ncp.cmd not found at '$NcpCmd'" }
|
||||
$BinDir = Split-Path -Parent $NcpCmd
|
||||
|
||||
# Machine scope needs admin, which the self-elevation above already gave us.
|
||||
# Read-modify-write through .NET, NEVER setx: setx silently truncates PATH at
|
||||
# 1024 characters and has permanently broken machines.
|
||||
$Current = [Environment]::GetEnvironmentVariable("Path", "Machine")
|
||||
$Entries = @($Current -split ';' | Where-Object { $_ -ne "" })
|
||||
if ($Entries -contains $BinDir) {
|
||||
Write-OK "ncp already on the machine PATH ($BinDir)"
|
||||
} else {
|
||||
$NcpPs1 = Join-Path $RepoRoot "management\ncp.ps1"
|
||||
# Without this, a Join-Path that fails writes `function ncp { & "" }` -
|
||||
# a broken profile with no error. Fail into the catch below instead.
|
||||
if (-not (Test-Path $NcpPs1)) { throw "ncp.ps1 not found at '$NcpPs1'" }
|
||||
# ASCII encoding: the content is ASCII anyway, and -Encoding UTF8 on
|
||||
# PowerShell 5.1 can plant a BOM in the middle of an existing profile.
|
||||
Add-Content -Path $ProfilePath -Encoding ASCII -Value ""
|
||||
Add-Content -Path $ProfilePath -Encoding ASCII -Value $Marker
|
||||
Add-Content -Path $ProfilePath -Encoding ASCII -Value "function ncp { & `"$NcpPs1`" @args }"
|
||||
Write-OK "ncp registered in $ProfilePath"
|
||||
$New = (@($Entries) + $BinDir) -join ';'
|
||||
[Environment]::SetEnvironmentVariable("Path", $New, "Machine")
|
||||
Write-OK "ncp registered on the machine PATH ($BinDir)"
|
||||
}
|
||||
# PATH is read at process start, so this shell and any already-open one still
|
||||
# do not have it. Patch ours so the rest of the installer can call ncp.
|
||||
$env:Path = "$env:Path;$BinDir"
|
||||
} catch {
|
||||
Write-Warn "Could not register ncp - add this line to your PowerShell profile:"
|
||||
Write-Warn " function ncp { & '$RepoRoot\management\ncp.ps1' @args }"
|
||||
Write-Warn "Could not put ncp on PATH - add this directory to PATH by hand:"
|
||||
Write-Warn " $RepoRoot\management"
|
||||
}
|
||||
|
||||
# -- Desktop shortcut ----------------------------------------------------------
|
||||
|
||||
@@ -4,7 +4,8 @@ import react from '@vitejs/plugin-react'
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
// host: true binds 0.0.0.0 so the Windows browser can reach the dev server
|
||||
// through WSL2 (loopback-only binding isn't reliably forwarded out of WSL).
|
||||
// host: true binds 0.0.0.0 so the dev server is reachable from another
|
||||
// machine on the LAN, not just localhost. Dev-only - production is the built
|
||||
// dist/ served by the backend on :8000.
|
||||
server: { host: true },
|
||||
})
|
||||
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
# NexusOS launcher for Windows (native, no WSL, no Vite).
|
||||
# NexusOS launcher for Windows (native, no Vite).
|
||||
#
|
||||
# Single-process app: the backend on :8000 serves the built web UI itself, so
|
||||
# this starts only the memory service + backend, then opens the UI as an Edge
|
||||
|
||||
@@ -80,7 +80,7 @@ class NexusControlPanel:
|
||||
self.root = root
|
||||
|
||||
if os.name != 'nt' and not os.environ.get('DISPLAY'):
|
||||
print("ERROR: No DISPLAY variable. Check WSLg.")
|
||||
print("ERROR: No DISPLAY variable. Is an X session running?")
|
||||
sys.exit(1)
|
||||
|
||||
self.root.title("Nexus Control Panel")
|
||||
@@ -91,7 +91,7 @@ class NexusControlPanel:
|
||||
print(f"Icon load failed: {e}")
|
||||
|
||||
if not sys.platform.startswith("linux"):
|
||||
# Keep the custom borderless titlebar on Windows/WSL,
|
||||
# Keep the custom borderless titlebar on Windows,
|
||||
# but allow normal stacking on Linux Mint.
|
||||
self.root.overrideredirect(True)
|
||||
self.root.configure(background="#1e1e1e")
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
@echo off
|
||||
REM ncp - PATH entry point on Windows. A .cmd, not a .ps1, on purpose: PowerShell's
|
||||
REM execution policy governs .ps1 only, so this keeps working under the default
|
||||
REM Restricted policy and from cmd, Win+R, Task Scheduler and .lnk targets - none
|
||||
REM of which load a PowerShell profile. install-windows.ps1 puts this directory on
|
||||
REM the machine PATH. ncp.ps1 stays for pwsh-on-Linux, where there is no PATH shim.
|
||||
REM
|
||||
REM ASCII only, same rule as the .ps1 files - a test in tests/test_smoke.py enforces it.
|
||||
setlocal
|
||||
set "ROOT=%~dp0.."
|
||||
REM System Python fallback so backup/restore work before the venv exists; those
|
||||
REM delegate to the stdlib-only bin/sync.py. Mirrors the same fallback in ncp.ps1.
|
||||
set "PY=%ROOT%\Promethean\Scripts\python.exe"
|
||||
if not exist "%PY%" set "PY=python"
|
||||
"%PY%" "%ROOT%\management\ncp.py" %*
|
||||
exit /b %ERRORLEVEL%
|
||||
+4
-2
@@ -2,8 +2,10 @@
|
||||
# file the Linux shell wrapper runs; this only picks an interpreter and forwards
|
||||
# the arguments.
|
||||
#
|
||||
# Windows: install-windows.ps1 registers this automatically. To do it by hand,
|
||||
# or to get ncp inside PowerShell on Linux, add to $PROFILE.CurrentUserAllHosts:
|
||||
# On Windows, ncp comes from management/ncp.cmd on the machine PATH instead (a
|
||||
# .cmd is not subject to the execution policy and works outside PowerShell too) -
|
||||
# install-windows.ps1 sets that up. This file is what gives ncp to pwsh on Linux,
|
||||
# where restore-linux.sh registers it in $PROFILE.CurrentUserAllHosts as:
|
||||
#
|
||||
# function ncp { & "<repo>/management/ncp.ps1" @args }
|
||||
#
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ bin/restore-linux.sh, controlpanel.py, nexus-popup.py and nexus-app.sh all keep
|
||||
calling what they always called.
|
||||
|
||||
psutil does the process work (cmdlines, pattern sweeps, port owners). It is
|
||||
declared in requirements-base.txt AND requirements-wsl.txt, so both boxes have
|
||||
declared in requirements-base.txt AND requirements-windows.txt, so both boxes have
|
||||
it - but it is imported lazily so `ncp backup`/`restore` still run before the
|
||||
venv exists, exactly as they did when they shelled out to sync.py.
|
||||
"""
|
||||
|
||||
Regular → Executable
+25
-5
@@ -54,19 +54,39 @@ def test_default_models_have_one_source_of_truth():
|
||||
assert hardcoded not in ps1, f"install-windows.ps1 hardcodes {hardcoded!r}"
|
||||
|
||||
|
||||
def test_powershell_files_stay_ascii():
|
||||
def test_windows_scripts_stay_ascii():
|
||||
# PowerShell 5.1 decodes BOM-less files as ANSI: one stray Unicode dash
|
||||
# eats a quote and the whole script dies at parse time. Globbed rather than
|
||||
# listed by name so a newly added .ps1 is covered without editing this test.
|
||||
# eats a quote and the whole script dies at parse time. cmd.exe is worse
|
||||
# still - it decodes by the console codepage. Globbed rather than listed by
|
||||
# name so a newly added script is covered without editing this test.
|
||||
skip = {"Promethean", "node_modules", ".git", "dist"}
|
||||
ps1s = [p for p in REPO_ROOT.rglob("*.ps1") if not skip & set(p.parts)]
|
||||
assert ps1s, "no .ps1 files found - did the Windows path move?"
|
||||
ps1s = [p for pat in ("*.ps1", "*.cmd") for p in REPO_ROOT.rglob(pat)
|
||||
if not skip & set(p.parts)]
|
||||
assert ps1s, "no .ps1/.cmd files found - did the Windows path move?"
|
||||
for path in ps1s:
|
||||
raw = path.read_bytes()
|
||||
bad = [(i, b) for i, b in enumerate(raw) if b > 0x7F]
|
||||
assert not bad, f"{path.relative_to(REPO_ROOT)} has non-ASCII bytes at {bad[:3]}"
|
||||
|
||||
|
||||
def test_ncp_is_registered_on_path_not_in_a_shell_profile():
|
||||
"""A `function ncp` in a shell profile is invisible to cron, .desktop Exec
|
||||
lines, cmd.exe and Task Scheduler - and on Windows the default Restricted
|
||||
execution policy blocks the profile outright. Both installers must put ncp
|
||||
on PATH; the profile wiring only survives as a no-sudo fallback."""
|
||||
linux = (REPO_ROOT / "bin" / "restore-linux.sh").read_text(encoding="utf-8")
|
||||
runtime = linux.split('if [ "$stage" = "runtime" ]; then')[1].split("\n exit 0\nfi")[0]
|
||||
assert "/usr/local/bin/ncp" in runtime
|
||||
|
||||
ps1 = (REPO_ROOT / "install-windows.ps1").read_text(encoding="utf-8")
|
||||
assert "ncp.cmd" in ps1, "installer must register the .cmd shim, not a profile function"
|
||||
# setx truncates PATH at 1024 characters and has permanently broken machines.
|
||||
# Comments stripped so the code that explains the ban does not trip it.
|
||||
code = "\n".join(ln for ln in ps1.splitlines() if not ln.strip().startswith("#"))
|
||||
assert "setx" not in code.lower()
|
||||
assert 'SetEnvironmentVariable("Path"' in code
|
||||
|
||||
|
||||
def test_first_playbook_is_the_system_prompt(tmp_path, monkeypatch):
|
||||
store = PlaybookFileStore(tmp_path)
|
||||
store.add_playbook(PlaybookItem(id="ctx", title="Reference", goal="ref goal",
|
||||
|
||||
Reference in New Issue
Block a user