fix: delete management/ncp.ps1 - it shadowed the ncp.cmd PATH shim

PowerShell resolves ExternalScript (.ps1) ahead of Application (.cmd), and both
lived in the directory the installer puts on PATH -- so in PowerShell `ncp` ran
the .ps1 and was execution-policy-bound again, the exact thing the .cmd exists
to avoid. Its other justification (giving ncp to pwsh on Linux) stopped being
true once /usr/local/bin/ncp existed: pwsh runs a PATH symlink to a shell script
as an Application. A test now prevents the file coming back.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
jon
2026-07-22 14:59:56 -05:00
co-authored by Claude Opus 4.8
parent b5564c4720
commit 786552b790
6 changed files with 22 additions and 61 deletions
-14
View File
@@ -82,20 +82,6 @@ EOF
echo "Registered promethean in ~/.bashrc." echo "Registered promethean in ~/.bashrc."
fi fi
# Same ncp inside PowerShell, if pwsh is installed here. bash functions are
# invisible to pwsh, so without this `ncp` is "not recognized" there even
# though it works in bash. install-windows.ps1 does this on the Windows side;
# this profile lives outside the repo, so a restore has to re-add it.
if command -v pwsh >/dev/null; then
ps_profile="$HOME/.config/powershell/profile.ps1"
if ! grep -q "NexusOS ncp" "$ps_profile" 2>/dev/null; then
mkdir -p "$(dirname "$ps_profile")"
printf '\n# NexusOS ncp\nfunction ncp { & "%s/management/ncp.ps1" @args }\n' \
"$NEXUS_ROOT" >> "$ps_profile"
echo "Registered ncp in $ps_profile."
fi
fi
# The AMD box runs CPU-only (Vega 20, 4GB VRAM thrashes). An NVIDIA box should not. # The AMD box runs CPU-only (Vega 20, 4GB VRAM thrashes). An NVIDIA box should not.
if command -v nvidia-smi >/dev/null && command -v sqlite3 >/dev/null && \ if command -v nvidia-smi >/dev/null && command -v sqlite3 >/dev/null && \
[ "$(sqlite3 "$NEXUS_ROOT/synapse/memory/memory.db" \ [ "$(sqlite3 "$NEXUS_ROOT/synapse/memory/memory.db" \
+2 -1
View File
@@ -127,7 +127,8 @@ Write-OK "Web UI built (interface\web\dist)"
# management\ncp.cmd goes on the machine PATH rather than a `function ncp` in the # management\ncp.cmd goes on the machine PATH rather than a `function ncp` in the
# PowerShell profile. Three reasons the profile route kept biting: # PowerShell profile. Three reasons the profile route kept biting:
# 1. Execution policy defaults to Restricted, which blocks the profile itself - # 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. # so the function was never defined, and the .ps1 it called could not
# have run anyway.
# 2. Profiles are a PowerShell thing. cmd, Win+R, Task Scheduler and .lnk # 2. Profiles are a PowerShell thing. cmd, Win+R, Task Scheduler and .lnk
# targets all reported "ncp is not recognized". # targets all reported "ncp is not recognized".
# 3. We are elevated here, so $PROFILE is the ADMIN's profile - the whole # 3. We are elevated here, so $PROFILE is the ADMIN's profile - the whole
+6 -1
View File
@@ -3,7 +3,12 @@ REM ncp - PATH entry point on Windows. A .cmd, not a .ps1, on purpose: PowerShel
REM execution policy governs .ps1 only, so this keeps working under the default 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 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 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 the machine PATH.
REM
REM This file must NOT be accompanied by an ncp.ps1 in the same directory:
REM PowerShell resolves ExternalScript (.ps1) ahead of Application (.cmd), so a
REM sibling ncp.ps1 wins in PowerShell and drags the execution policy back in -
REM exactly what this file exists to avoid.
REM REM
REM ASCII only, same rule as the .ps1 files - a test in tests/test_smoke.py enforces it. REM ASCII only, same rule as the .ps1 files - a test in tests/test_smoke.py enforces it.
setlocal setlocal
-43
View File
@@ -1,43 +0,0 @@
# ncp - PowerShell entry point. The CLI itself is management/ncp.py, the same
# file the Linux shell wrapper runs; this only picks an interpreter and forwards
# the arguments.
#
# 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 }
#
# ASCII only, no exceptions: PowerShell 5.1 decodes BOM-less files as ANSI, so a
# single Unicode dash eats a quote and the script dies at parse time. A test in
# tests/test_smoke.py fails if any .ps1 in this repo gains a non-ASCII byte.
$Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path)
# Forward slashes and per-platform venv layout: backslash is a literal character
# on Linux, not a separator, so "Promethean\Scripts\python.exe" would resolve to
# a single nonsense filename under pwsh on Linux. Windows accepts "/" happily.
# $env:OS is the 5.1-safe check; the automatic $IsWindows only exists on PS 6+.
$OnWindows = ($env:OS -eq "Windows_NT")
if ($OnWindows) {
$Py = Join-Path $Root "Promethean/Scripts/python.exe"
} else {
$Py = Join-Path $Root "Promethean/bin/python3"
}
# Fall back to a system Python so backup/restore still work before the venv is
# built - those delegate to the stdlib-only bin/sync.py.
if (-not (Test-Path $Py)) {
foreach ($candidate in @("python3", "python")) {
$found = Get-Command $candidate -ErrorAction SilentlyContinue
if ($found) { $Py = $found.Source; break }
}
}
if (-not (Test-Path $Py)) {
Write-Error "No Python found. Run install.sh (Linux) or install-windows.ps1 first."
exit 1
}
& $Py (Join-Path $Root "management/ncp.py") @args
exit $LASTEXITCODE
+1 -1
View File
@@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# ncp - Linux entry point. The CLI itself is management/ncp.py, which runs # ncp - Linux entry point. The CLI itself is management/ncp.py, which runs
# unchanged on Windows too (see management/ncp.ps1); this stays a shell script # unchanged on Windows too (see management/ncp.cmd); this stays a shell script
# because ~/.bashrc, launch_nexus.sh, bin/restore-linux.sh, controlpanel.py, # because ~/.bashrc, launch_nexus.sh, bin/restore-linux.sh, controlpanel.py,
# bin/panel/nexus-popup.py and management/nexus-app.sh all invoke this path. # bin/panel/nexus-popup.py and management/nexus-app.sh all invoke this path.
# #
+13 -1
View File
@@ -96,6 +96,18 @@ def test_ncp_is_registered_on_path_not_in_a_shell_profile():
"model pull must come after the desktop shortcut" "model pull must come after the desktop shortcut"
def test_no_ps1_shadows_the_ncp_path_shim():
"""PowerShell resolves ExternalScript (.ps1) ahead of Application (.cmd), so
an ncp.ps1 sitting next to ncp.cmd wins in PowerShell and drags the execution
policy back in - the exact thing the .cmd exists to avoid. Observed on the
Windows VM: `Get-Command ncp -All` listed ncp.ps1 first, from the same
directory the installer had just put on PATH."""
shim = REPO_ROOT / "management" / "ncp.cmd"
assert shim.exists(), "the Windows PATH shim is missing"
twin = shim.with_suffix(".ps1")
assert not twin.exists(), f"{twin.name} shadows {shim.name} in PowerShell"
def test_first_playbook_is_the_system_prompt(tmp_path, monkeypatch): def test_first_playbook_is_the_system_prompt(tmp_path, monkeypatch):
store = PlaybookFileStore(tmp_path) store = PlaybookFileStore(tmp_path)
@@ -268,7 +280,7 @@ def test_desktop_stage_is_the_only_one_touching_home():
# and in the PowerShell profile is how you launch NexusOS at all, and each is # and in the PowerShell profile is how you launch NexusOS at all, and each is
# a grep-guarded no-op on re-run. Desktop config (xfconf, plank, themes, # a grep-guarded no-op on re-run. Desktop config (xfconf, plank, themes,
# os-release) must stay in the desktop stage so --no-desktop really is safe. # os-release) must stay in the desktop stage so --no-desktop really is safe.
shell_wiring = (".bashrc", "powershell/profile.ps1") shell_wiring = (".bashrc",)
home_writes = [ln for ln in runtime.splitlines() home_writes = [ln for ln in runtime.splitlines()
if "$HOME" in ln and not any(w in ln for w in shell_wiring)] if "$HOME" in ln and not any(w in ln for w in shell_wiring)]
assert not home_writes, f"runtime stage writes to $HOME: {home_writes}" assert not home_writes, f"runtime stage writes to $HOME: {home_writes}"