@4fu/pi-pwsh
Route pi's shell, directory listing, and search tasks through PowerShell 7 on Windows
Package details
Install @4fu/pi-pwsh from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@4fu/pi-pwsh- Package
@4fu/pi-pwsh- Version
0.6.2- Published
- Jul 23, 2026
- Downloads
- 1,498/mo · 1,498/wk
- Author
- 4fu
- License
- MIT
- Types
- extension
- Size
- 110.2 KB
- Dependencies
- 2 dependencies · 2 peers
Pi manifest JSON
{
"extensions": [
"./src/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-pwsh
Route pi's shell, directory listing, and search tasks through PowerShell 7 (pwsh) on Windows.
Why
- Windows has no reliable bash implementation (Git Bash hangs on background processes, path translation is flaky).
- A tool named
bashprimes the model to emit POSIX syntax (&&,grep,$VAR), which fails under PowerShell.
This extension disables bash, ls, find, and grep, then registers a pwsh tool instead.
What it does
- Registers a
pwshtool that reuses pi's built-in bash tool definition — tail truncation (last 2000 lines / 50KB), full output saved to a temp file, non-zero exit codes surfaced as tool errors, streaming preview, and the built-in renderer all come for free. Only the spawn layer is replaced. - Disables pi's built-in
ls,find, andgreptools so filesystem discovery and search are routed throughpwsh; prompt guidance prefers available cross-platform tools such asrgandfdand bounds native recursive cmdlets. - Spawns
pwsh -NoLogo -NoProfile -NonInteractive -ExecutionPolicy Bypass -Command <cmd>. - Lazily loads the background-job, PTY, and user-request PowerShell helpers only when their command names are referenced. Ordinary commands avoid parsing and initializing the helper scripts, substantially reducing per-call startup overhead.
- Provides persistent ConPTY sessions through PowerShell functions (
Start-Pty,Get-PtyScreen,Send-PtyInput, etc.) without adding another model tool; runGet-PtyHelpfor the progressive reference. - Provides TUI-backed user request functions for input, confirmation, selection, masked secrets, and direct secret injection into a PTY; run
Get-PiRequestHelpfor the progressive reference. - Forces plain UTF-8 output without a BOM (non-ASCII output is not mangled, PowerShell formatting does not leak ANSI escapes, and piped native-command input is not prefixed with
EF BB BF). - Defaults Python subprocesses to
PYTHONIOENCODING=utf-8,PYTHONUTF8=1, andPYTHONUNBUFFERED=1so stdio and implicit text-file reads use UTF-8 and long-running logs stream promptly. Existing environment values are respected, and the defaults are scoped to processes launched by the tool. - Preserves real native exit codes:
pwsh -Commandwould otherwise flatten them to 0/1, which breaksrg(1 = no match vs 2 = error) andgit diff --quiet-style semantics. An exit-code epilogue restores them — identically for foreground commands and background jobs. - Auto-retries with
cmd /cwhen a command fails with "not a valid Win32 application" (npm/yarn/pnpm are.cmdbatch files on Windows). Skipped for commands rewritten to background jobs, where cmd's&semantics would be wrong. - Kills the whole process tree (
taskkill /T /F) on timeout or abort — no orphanednpm run devprocesses. - Does not hang when a launched/detached descendant inherits stdout or stderr: after the shell exits, a short output-idle grace captures trailing data without waiting forever for inherited handles to close.
- No default timeout; the model can pass
timeout(seconds) per call. - Probes Windows Sudo once per session; when available in inline mode, the tool description tells the model it can prefix a command with
sudoto run it as administrator (each call still requires the user to approve the UAC prompt).
Background jobs
Every pwsh tool call spawns a fresh pwsh process, so native PowerShell jobs (Start-Job, the & background operator) die when the tool call ends. This extension fixes that from inside the shell — no extra tools:
- Job-related commands lazily load
src/jobs.ps1, which overrides the job cmdlets (Start-Job,Get-Job,Receive-Job,Stop-Job,Remove-Job,Wait-Job) with implementations backed by real detached OS processes. Pipeline forms work:Get-Job | Stop-Job,Start-Job { npm run build } -Name build | Wait-Job | Receive-Job. - Jobs are launched detached via double-spawn: a short-lived launcher process starts the job and exits immediately, breaking the parent chain that
taskkill /Twalks — aborting or timing out the tool call that started a job does not kill it. Jobs inherit the calling session's full environment (PATH, proxies,VIRTUAL_ENV, ...) through the launcher;Start-Job -Environment @{ NAME = 'value' }overrides or adds variables (stored in the job's wrap script under%TEMP%\pi-pwsh-jobsuntilRemove-Job). - A trailing
&(npm run dev &) is rewritten toStart-Jobbefore execution, using PowerShell's own parser (src/background.ts) — strings, comments, the& { }call operator and&&are never mistaken for it. Only single-pipeline commands are rewritten; anything else runs as-is. - Job state lives in
%TEMP%\pi-pwsh-jobs\(one.meta.json+ log/exit/script files per job), so it survives/reloadand pi restarts and is inspectable by hand. Receive-Jobconsumes output like the native cmdlet (each read returns what arrived since the last one;-Keepre-reads,-Tail Npeeks). Exit codes are captured (Get-JobshowsCompleted/Failed+ExitCode);Stop-Jobkills the whole process tree (taskkill /T /F).Suspend-Job/Resume-Job/Debug-Jobthrow actionable guidance.Get-JobHelpprints the full reference in the shell.
Interactive PTY sessions
PTY sessions are owned by the running extension and backed by node-pty/Windows ConPTY. They remain alive across independent pwsh calls, while the model continues to use the same single pwsh tool.
Get-PtyHelp # short overview and topic list
Get-PtyHelp input # fetch one detailed topic
Start-Pty -Command 'python' -Name py
Get-PtyScreen -Name py
Send-PtyInput -Name py -Text 'print(6 * 7)' -Enter
Wait-Pty -Name py | Receive-Pty
Stop-Pty -Name py | Remove-Pty
The PTY function group is Start-Pty, Get-Pty, Receive-Pty, Get-PtyScreen, Send-PtyInput, Wait-Pty, Resize-Pty, Stop-Pty, Remove-Pty, and Get-PtyHelp. Receive-Pty provides cleaned incremental output; Get-PtyScreen provides the current emulated viewport for applications that move the cursor or redraw existing lines.
Unlike detached background jobs, PTYs are session-scoped: /reload, session replacement, and pi shutdown close them. Aborting or timing out an ordinary pwsh call does not stop an already-created PTY.
User requests
User-request functions can block the current pwsh call while pi asks the user for information:
Get-PiRequestHelp
$name = Request-PiInput -Title 'Setup' -Prompt 'Display name'
$ok = Request-PiConfirmation -Title 'Deploy' -Message 'Continue?'
$region = Request-PiSelection -Title 'Region' -Options @('cn', 'us', 'eu')
For terminal logins, input can go directly from the TUI to a PTY without being returned to PowerShell or the model:
Request-PiPtyInput -Name login -Prompt 'Password' -Secret -Enter
Request-PiInput -Secret uses a masked editor but returns plaintext to the current PowerShell process, so assign it immediately and never print it. Request-PiPtyInput -Secret is safer for PTY logins because only a submission acknowledgement is returned. Secret entry requires TUI mode; normal input, confirmation, and selection also work through pi's RPC UI.
The helpers communicate with the extension over an authenticated, random, session-scoped named pipe. Pipe credentials are supplied only to helper calls and removed from the environment before the requested command runs.
Requirements
- PowerShell 7+ (
pwshonPATH). There is no fallback: ifpwshis not found, the extension shows an error notification and leaves the built-inbashtool active. - Windows 10 version 1809 or newer for ConPTY-backed interactive sessions.
node-ptyis installed as a runtime dependency; package managers that gate dependency install scripts must allow its trusted native install script.
Installation
With pi:
pi install npm:@4fu/pi-pwsh
Or try it for a single run without installing:
pi -e npm:@4fu/pi-pwsh
From source
Symlink or copy into pi's extensions directory:
New-Item -ItemType Junction -Path "$HOME\.pi\agent\extensions\pi-pwsh" -Target "<this-repo>"
Or add the path to ~/.pi/agent/settings.json:
{
"extensions": ["C:/path/to/pi-pwsh"]
}
Run npm install once in this directory, then /reload in pi.
Development
npm install
npm run typecheck
Tests:
# Runtime defaults (UTF-8 without BOM, plain errors, Python environment)
node scripts/smoke-runtime.mjs
# Job prelude end-to-end (each case is a fresh pwsh process, like a real tool call)
pwsh -NoProfile -ExecutionPolicy Bypass -File scripts/smoke-jobs.ps1
# Trailing-& interception (PowerShell parser based)
node scripts/smoke-background.mjs
# Foreground exit-code fidelity (native codes survive the pwsh -Command flattening)
node scripts/smoke-exitcode.mjs
# PTY + named-pipe helpers (cross-call lifecycle, screen, input, TUI requests)
npm run test:pty
# Timeout/abort settlement and inherited-stdio descendants
npm run test:timeout
License
MIT