898 字
4 分钟
Disabling npm in pnpm Environments with npma as a Real npm Forwarder

Background#

Mixing npm and pnpm in daily development easily causes chaos:

  • The same global package gets installed by both managers, command shims overwrite each other, and uninstalling one accidentally deletes the other’s entry;
  • You accidentally run npm when installing a new package, resulting in a dependency layout inconsistent with what pnpm manages.

So the idea is: standardize on pnpm and disable the npm command. But pnpm can’t uninstall or update packages that npm installed globally — if you completely disable npm, those packages become unmanageable.

The solution has two layers:

  1. ps1 script (interactive terminal): the npm command is intercepted (prompting to use pnpm), while npma is defined as a forwarder to the real npm, specifically for managing npm’s own global packages.
  2. AGENTS.md instructions (agent environment): opencode’s shell starts with -NoProfile and doesn’t load the profile, so the same rules need to be documented in AGENTS.md for the agent.

Part 1: The ps1 Script#

Add the following to your PowerShell profile ($PROFILE, i.e. Microsoft.PowerShell_profile.ps1):

Terminal window
# Unified UTF-8 encoding to avoid garbled Chinese characters (console codepage may be GBK 936)
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
[Console]::InputEncoding = [System.Text.Encoding]::UTF8
$OutputEncoding = [System.Text.Encoding]::UTF8
# Disable npm, standardize on pnpm (fall back to real npm when pnpm is unavailable to avoid blocking package management)
function npm {
if (Get-Command pnpm -ErrorAction SilentlyContinue) {
Write-Error "npm has been disabled. Please use pnpm (use npma to forward to real npm for managing npm's own global packages)."
throw "npm disabled: use pnpm instead"
}
$realNpm = @(Get-Command npm -All -ErrorAction SilentlyContinue |
Where-Object { $_.CommandType -ne 'Function' -and $_.CommandType -ne 'Alias' } |
Select-Object -First 1)
if ($realNpm) {
Write-Warning "pnpm not detected, calling real npm: $($realNpm.Source)"
& $realNpm.Source @args
return
}
Write-Error "npm has been disabled, and neither pnpm nor real npm was found"
throw "npm disabled and no fallback npm found"
}
function npma {
# npma = real npm forwarder: the npm name is intercepted by the function above,
# use npma when managing global packages installed by npm (visible via npm ls -g, not managed by pnpm).
$realNpm = @(Get-Command npm -All -ErrorAction SilentlyContinue |
Where-Object { $_.CommandType -ne 'Function' -and $_.CommandType -ne 'Alias' } |
Select-Object -First 1)
if ($realNpm) {
& $realNpm.Source @args
return
}
Write-Error "npma: real npm not found"
throw "npma: real npm not found"
}

How It Works#

  • npm function: blocks execution as long as pnpm is detected; only falls back to the real npm if pnpm is absent (to avoid completely locking out package management).
  • npma function: since the npm name is occupied by the function above, npma serves as the alias for the “real npm”. It uses Get-Command npm -All to find the actual npm on disk (filtering out Functions and Aliases), then forwards arguments as-is.
  • Key detail: Get-Command npm -All will include the npm function defined above — you must filter with Where-Object { $_.CommandType -ne 'Function' -and $_.CommandType -ne 'Alias' } to avoid an infinite loop.

Usage#

Terminal window
npm install -g foo
pnpm add -g foo
npma uninstall -g hexo

Gotcha: Command Name ≠ Package Name#

If uninstalling reports “package not found”, you’re probably using the command name instead of the package name:

Command NameActual Package Name (as shown in npm ls -g)
mimo@mimo-ai/cli
gemini@google/gemini-cli
hexohexo-cli
vercelvercel (also provides a vc command)

Always verify the package name via npm ls -g / pnpm ls -g, not the command name shown by Get-Command.

Part 2: AGENTS.md Instructions#

opencode (or other agents) launches commands with pwsh -NoProfile -NonInteractive, which does not load your PowerShell profile — so the npm intercept function and npma from above don’t exist in the agent’s shell: npm in the agent is the real npm, and npma doesn’t exist.

Therefore, the same rules need to be documented in the global AGENTS.md (~/.config/opencode/AGENTS.md):

## Package Manager Conventions
The user's PowerShell profile has disabled the `npm` command (intercepted when pnpm is detected) and defined `npma` = real npm forwarder (for managing npm's own global packages). **The agent's shell starts with `-NoProfile` and does not load that profile** — so in the agent environment, `npm` is the real npm and `npma` does not exist. Based on this:
- Package management operations **default to `pnpm`** (install / update / uninstall global and project dependencies).
- When managing **npm's own global packages** (listed by `npm ls -g`, not managed by pnpm), use the real npm directly: `npm uninstall/update/ls -g ...` (in the interactive terminal the corresponding command is `npma`).
- **A single global package must only be managed by one package manager**: never use pnpm to uninstall/update npm-installed packages, and never use npm to uninstall/update pnpm-installed packages (mixing managers on the same package causes shim overwrites / accidental deletions).
- Global directories are unified: npm prefix and pnpm global are both `Z:\Code\packages\npm` (pnpm store at `Z:\.pnpm-store\v11`, pnpm/yarn versions managed by corepack).

Why the Agent Needs Separate Rules#

EnvironmentWhat npm IsDoes npma Exist?
Interactive terminal (loads profile)Intercepted by function, errors outYes, forwards to real npm
Agent shell (-NoProfile)Real npm, no interceptionNo

Conclusion: running npm in the agent runs the real npm — which is exactly the behavior needed when managing npm’s own global packages. So AGENTS.md simply states “default to pnpm for package management, use npm directly when managing npm’s own packages”, matching the semantics of npma in the interactive terminal.

Appendix: Unified Global Directories#

This setup also unifies global directories, preventing packages from being scattered across the C: drive and tool drives:

  • npm prefix: Z:\Code\packages\npm
  • pnpm global (global-dir / global-bin-dir): also Z:\Code\packages\npm
  • pnpm store: Z:\.pnpm-store\v11
  • pnpm / yarn versions managed by corepack

pnpm’s global config lives in its own config.yaml (%LOCALAPPDATA%\pnpm\config\config.yaml), not in ~/.npmrc, to avoid npm emitting warnings.

Disabling npm in pnpm Environments with npma as a Real npm Forwarder
https://tski.uk/blog/en/pnpm-disable-npm-npma/
作者
Tokisaki Galaxy
发布于
2026-08-17
许可协议
CC BY