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."
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.
if command -v nvidia-smi >/dev/null && command -v sqlite3 >/dev/null && \
[ "$(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
# 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.
# 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
# targets all reported "ncp is not recognized".
# 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 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 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 ASCII only, same rule as the .ps1 files - a test in tests/test_smoke.py enforces it.
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
# 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,
# 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"
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):
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
# 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.
shell_wiring = (".bashrc", "powershell/profile.ps1")
shell_wiring = (".bashrc",)
home_writes = [ln for ln in runtime.splitlines()
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}"