Identified difference in lifecycle script handling between pnpm and npm.
PlanAnalyzed dependencies with install scripts causing interactive picker in pnpm.
PlanExplained why npm does not show the picker due to running scripts automatically.
PlanIf I try to update Pi by running pnpm install --global @earendil-works/pi-coding-agent, I get an interactive picker in which I'm asked to select which of three packages I want to build. I don't get this picker if I run npm install --global @earendil-works/pi-coding-agent. Why is there a discrepancy?
The user is asking a question about a discrepancy in how pnpm install --global @earendil-works/pi-coding-agent and npm install --global @earendil-works/pi-coding-agent behave—specifically that pnpm shows an interactive picker while npm doesn't.
But wait—I'm inside the pi-mono repository at /Users/me/c/vendor/pi. This is the monorepo for Pi itself. The user is asking about installation behavior of the pnpm package. This discrepancy might be related to the package configuration in this repo.
Let me first check if there are issues or known configuration differences, and also inspect the package.json files, particularly the pi-coding-agent package, to see if multiple build scripts or bin entries might trigger pnpm's interactive behavior.
Actually, I need to think about what could cause pnpm to show an "interactive picker" asking which of three packages to build during global install. This sounds like pnpm's behavior when a package has multiple bin entries? Or perhaps preinstall/postinstall scripts? Or maybe pnpm is treating it as a monorepo and installing the workspace? Hmm.
Wait, "three packages I want to build" — this might be related to pnpm wanting to build native dependencies, or multiple peerDependencies / optional dependencies? Or maybe pnpm is prompting about bin names that conflict?
Actually, pnpm has a feature where if you install globally and there are multiple packages with the same bin name, it might ask. But the user says "three packages I want to build" which sounds more like a "postinstall" / "build" prompt for multiple workspace packages? That seems odd for a global install of a single package.
Maybe pnpm is resolving the package from a monorepo and wanting to build workspace packages?
Alternatively, maybe @earendil-works/pi-coding-agent has optionalDependencies on packages that need compilation, and pnpm asks to build them. But why three packages?
I should look at the package.json for pi-coding-agent and related packages to understand the bin entries, scripts, and native dependencies.
But first, let me remember: pnpm global install might show a prompt if there are multiple packages wanting to be "built" — specifically, pnpm asks when installing packages with native addons if you want to use a bundled or prebuilt binary. Or more likely, this is about pnpm install in a project (monorepo) context where three workspace packages have prepare or build scripts. But the user explicitly says pnpm install --global @earendil-works/pi-coding-agent vs npm install --global.
Wait — could this be about optionalDependencies or peerDependencies? Or about preinstall scripts?
Actually, in pnpm, when you install a package globally, if it has multiple sub-packages with "bin" entries? No, that doesn't make sense.
Let me search the web or maybe the issue is in the repository itself. The user is asking me while I'm in the repo. The issue might be documented in issues or known. But I should first check the package files.
Let me look for the pi-coding-agent package.json.
ls packages/*/package.json | sortpackages/agent/package.json packages/ai/package.json packages/coding-agent/package.json packages/tui/package.json packages/web-ui/package.json
cat packages/coding-agent/package.json{
"name": "@earendil-works/pi-coding-agent",
"version": "0.74.1",
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
"type": "module",
"piConfig": {
"configDir": ".pi"
},
"bin": {
"pi": "dist/cli.js"
},
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js"
},
"./hooks": {
"types": "./dist/core/hooks/index.d.ts",
"import": "./dist/core/hooks/index.js"
}
},
"files": [
"dist",
"docs",
"examples",
"CHANGELOG.md"
],
"scripts": {
"clean": "shx rm -rf dist",
"dev": "tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
"build": "tsgo -p tsconfig.build.json && shx chmod +x dist/cli.js && npm run copy-assets",
"build:binary": "npm --prefix ../tui run build && npm --prefix ../ai run build && npm --prefix ../agent run build && npm run build && bun build --compile ./dist/bun/cli.js --outfile dist/pi && npm run copy-binary-assets",
"copy-assets": "shx mkdir -p dist/modes/interactive/theme && shx cp src/modes/interactive/theme/*.json dist/modes/interactive/theme/ && shx mkdir -p dist/modes/interactive/assets && shx cp src/modes/interactive/assets/*.png dist/modes/interactive/assets/ && shx mkdir -p dist/core/export-html/vendor && shx cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/core/export-html/ && shx cp src/core/export-html/vendor/*.js dist/core/export-html/vendor/",
"copy-binary-assets": "shx cp package.json dist/ && shx cp README.md dist/ && shx cp CHANGELOG.md dist/ && shx mkdir -p dist/theme && shx cp src/modes/interactive/theme/*.json dist/theme/ && shx mkdir -p dist/assets && shx cp src/modes/interactive/assets/*.png dist/assets/ && shx mkdir -p dist/export-html/vendor && shx cp src/core/export-html/template.html dist/export-html/ && shx cp src/core/export-html/vendor/*.js dist/export-html/vendor/ && shx cp -r docs dist/ && shx cp -r examples dist/ && shx cp ../../node_modules/@silvia-odwyer/photon-node/photon_rs_bg.wasm dist/",
"test": "vitest --run",
"prepublishOnly": "npm run clean && npm run build"
},
"dependencies": {
"@earendil-works/pi-agent-core": "^0.74.1",
"@earendil-works/pi-ai": "^0.74.1",
"@earendil-works/pi-tui": "^0.74.1",
"@silvia-odwyer/photon-node": "^0.3.4",
"chalk": "^5.5.0",
"diff": "^8.0.2",
"glob": "^13.0.1",
"highlight.js": "^10.7.3",
"hosted-git-info": "^9.0.2",
"ignore": "^7.0.5",
"jiti": "^2.7.0",
"minimatch": "^10.2.3",
"proper-lockfile": "^4.1.2",
"typebox": "^1.1.24",
"undici": "^8.3.0",
"yaml": "^2.8.2"
},
"overrides": {
"rimraf": "6.1.2",
"gaxios": {
"rimraf": "6.1.2"
}
},
"optionalDependencies": {
"@mariozechner/clipboard": "^0.3.6"
},
"devDependencies": {
"@types/diff": "^7.0.2",
"@types/hosted-git-info": "^3.0.5",
"@types/ms": "^2.1.0",
"@types/node": "^24.3.0",
"@types/proper-lockfile": "^4.1.4",
"shx": "^0.4.0",
"typescript": "^5.7.3",
"vitest": "^3.2.4"
},
"keywords": [
"coding-agent",
"ai",
"llm",
"cli",
"tui",
"agent"
],
"author": "Mario Zechner",
"license": "MIT",
"repository": {
"type": "git",
"url": "git+https://github.com/earendil-works/pi-mono.git",
"directory": "packages/coding-agent"
},
"engines": {
"node": ">=22.19.0"
}
}Let me investigate this discrepancy. The user is seeing a picker when installing with pnpm but not with npm. This is likely due to how pnpm handles optionalDependencies or peer dependencies, or maybe something with overrides, or perhaps the package has a postinstall script in one of its dependencies.
Wait, looking at the package.json, I don't see a postinstall script. But there might be something in the dependencies. Let me check if any of the dependencies have postinstall scripts or if there's something with peer dependencies.
Actually, the more likely cause is optionalDependencies combined with pnpm's behavior. The package has @mariozechner/clipboard as an optional dependency. But that wouldn't cause a picker.
Let me think about this differently. There's a known pnpm behavior: when a package (or its dependencies) has a prepare or postinstall script that requires building, or when there are peer dependencies that need resolution, pnpm might show a picker.
But wait - the user says "three packages I want to build." This sounds very much like pnpm's behavior when it encounters packages with postinstall scripts that use node-gyp or native compilation, or perhaps pnpm asking about which workspace packages to build.
Actually, I think this might be related to pnpm's "ignored build scripts" feature or its interactive mode for selecting which packages with postinstall scripts to run.
Let me search for any dependencies that might have postinstall scripts or native dependencies. The package @silvia-odwyer/photon-node is a Rust/WASM package potentially requiring a postinstall. Let me check.
Also, let me look at the other packages in the monorepo to see if any have build scripts or postinstalls.
for pkg in packages/*; do echo "=== $pkg ==="; cat "$pkg/package.json" | grep -E '"(postinstall|prepare|install|build|scripts)"' -A 20 | head -25; done=== packages/agent ===
"scripts": {
"clean": "shx rm -rf dist",
"build": "tsgo -p tsconfig.build.json",
"dev": "tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
"test": "vitest --run",
"test:harness": "vitest --run --config vitest.harness.config.ts",
"coverage:harness": "vitest --run --config vitest.harness.config.ts --coverage",
"prepublishOnly": "npm run clean && npm run build"
},
"dependencies": {
"@earendil-works/pi-ai": "^0.74.1",
"ignore": "^7.0.5",
"typebox": "^1.1.24",
"yaml": "^2.8.2"
},
"keywords": [
"ai",
"agent",
"llm",
"transport",
"state-management"
],
"author": "Mario Zechner",
=== packages/ai ===
"scripts": {
"clean": "shx rm -rf dist",
"generate-models": "npx tsx scripts/generate-models.ts",
"generate-image-models": "npx tsx scripts/generate-image-models.ts",
"build": "npm run generate-models && npm run generate-image-models && tsgo -p tsconfig.build.json",
"dev": "tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
"dev:tsc": "tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
"test": "vitest --run",
"prepublishOnly": "npm run clean && npm run build"
},
"dependencies": {
"@anthropic-ai/sdk": "^0.91.1",
"@aws-sdk/client-bedrock-runtime": "^3.1030.0",
"@google/genai": "^1.40.0",
"@mistralai/mistralai": "^2.2.0",
"http-proxy-agent": "^7.0.2",
"https-proxy-agent": "^7.0.6",
"openai": "6.26.0",
"partial-json": "^0.1.7",
"typebox": "^1.1.24"
},
"keywords": [
"ai",
"llm",
"openai",
=== packages/coding-agent ===
"scripts": {
"clean": "shx rm -rf dist",
"dev": "tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
"build": "tsgo -p tsconfig.build.json && shx chmod +x dist/cli.js && npm run copy-assets",
"build:binary": "npm --prefix ../tui run build && npm --prefix ../ai run build && npm --prefix ../agent run build && npm run build && bun build --compile ./dist/bun/cli.js --outfile dist/pi && npm run copy-binary-assets",
"copy-assets": "shx mkdir -p dist/modes/interactive/theme && shx cp src/modes/interactive/theme/*.json dist/modes/interactive/theme/ && shx mkdir -p dist/modes/interactive/assets && shx cp src/modes/interactive/assets/*.png dist/modes/interactive/assets/ && shx mkdir -p dist/core/export-html/vendor && shx cp src/core/export-html/template.html src/core/export-html/template.css src/core/export-html/template.js dist/core/export-html/ && shx cp src/core/export-html/vendor/*.js dist/core/export-html/vendor/",
"copy-binary-assets": "shx cp package.json dist/ && shx cp README.md dist/ && shx cp CHANGELOG.md dist/ && shx mkdir -p dist/theme && shx cp src/modes/interactive/theme/*.json dist/theme/ && shx mkdir -p dist/assets && shx cp src/modes/interactive/assets/*.png dist/assets/ && shx mkdir -p dist/export-html/vendor && shx cp src/core/export-html/template.html dist/export-html/ && shx cp src/core/export-html/vendor/*.js dist/export-html/vendor/ && shx cp -r docs dist/ && shx cp -r examples dist/ && shx cp ../../node_modules/@silvia-odwyer/photon-node/photon_rs_bg.wasm dist/",
"test": "vitest --run",
"prepublishOnly": "npm run clean && npm run build"
},
"dependencies": {
"@earendil-works/pi-agent-core": "^0.74.1",
"@earendil-works/pi-ai": "^0.74.1",
"@earendil-works/pi-tui": "^0.74.1",
"@silvia-odwyer/photon-node": "^0.3.4",
"chalk": "^5.5.0",
"diff": "^8.0.2",
"glob": "^13.0.1",
"highlight.js": "^10.7.3",
"hosted-git-info": "^9.0.2",
"ignore": "^7.0.5",
"jiti": "^2.7.0",
"minimatch": "^10.2.3",
"proper-lockfile": "^4.1.2",
=== packages/tui ===
"scripts": {
"clean": "shx rm -rf dist",
"build": "tsgo -p tsconfig.build.json",
"dev": "tsgo -p tsconfig.build.json --watch --preserveWatchOutput",
"test": "node --test --import tsx test/*.test.ts",
"prepublishOnly": "npm run clean && npm run build"
},
"files": [
"dist/**/*",
"README.md"
],
"keywords": [
"tui",
"terminal",
"ui",
"text-editor",
"differential-rendering",
"typescript",
"cli"
],
"author": "Mario Zechner",
"license": "MIT",
"repository": {
=== packages/web-ui ===
"scripts": {
"clean": "shx rm -rf dist",
"build": "tsc -p tsconfig.build.json && tailwindcss -i ./src/app.css -o ./dist/app.css --minify",
"dev": "concurrently --names \"build,example\" --prefix-colors \"cyan,green\" \"tsc -p tsconfig.build.json --watch --preserveWatchOutput\" \"tailwindcss -i ./src/app.css -o ./dist/app.css --watch\" \"npm run dev --prefix example\"",
"dev:tsc": "concurrently --names \"build\" --prefix-colors \"cyan\" \"tsc -p tsconfig.build.json --watch --preserveWatchOutput\" \"tailwindcss -i ./src/app.css -o ./dist/app.css --watch\"",
"check": "biome check --write --error-on-warnings . && tsc --noEmit && cd example && biome check --write --error-on-warnings . && tsc --noEmit"
},
"dependencies": {
"@lmstudio/sdk": "^1.5.0",
"@earendil-works/pi-ai": "^0.74.1",
"@earendil-works/pi-tui": "^0.74.1",
"typebox": "^1.1.24",
"docx-preview": "^0.3.7",
"jszip": "^3.10.1",
"lucide": "^0.544.0",
"ollama": "^0.6.0",
"pdfjs-dist": "5.4.394",
"xlsx": "https://cdn.sheetjs.com/xlsx-0.20.3/xlsx-0.20.3.tgz"
},
"peerDependencies": {
"@mariozechner/mini-lit": "^0.2.0",
"lit": "^3.3.1"
},The packages don't seem to have postinstall scripts that would cause this. Let me look at the dependencies more carefully. The coding-agent package depends on @earendil-works/pi-agent-core, @earendil-works/pi-ai, and @earendil-works/pi-tui. These are sibling packages in the monorepo.
Wait, let me look at optionalDependencies in coding-agent. It has @mariozechner/clipboard. Let me check if there's something in the workspace that might cause pnpm to pick up workspace packages.
Actually, the key difference between pnpm and npm here is likely about how pnpm resolves peer dependencies or optional dependencies, or perhaps there's a packageManager field or some workspace configuration.
Wait, let me search for anything related to "interactive" or "picker" or multi-package builds. The user says they get a picker asking which of three packages to build. This sounds like it might be related to @mariozechner/clipboard or some other native dependency that needs to be built from source.
Actually, wait. Let me look more carefully at the dependencies. Let me check if there's a package with binaries or native addons.
Or maybe the issue is that pnpm installs workspace packages when installing from the registry? No, that doesn't make sense.
Wait, maybe the issue is with @mariozechner/clipboard - let me check what that package looks like.
Actually, let me think about this differently. The user is running pnpm install --global @earendil-works/pi-coding-agent. pnpm has a behavior where if a package is part of a workspace and workspace packages are linked, it might show a picker. But this is for a global install...
Let me check if there's something in the package that causes pnpm to behave differently. Maybe there are multiple versions of something?
Actually, I think this might be about optionalDependencies that involve native modules, or specifically @mariozechner/clipboard. Let me search for clipboard and see what's happening.
Wait, actually I should look at this from a different angle. Let me check npm registry info or look for any life-cycle scripts that might trigger a build. The "three packages" mentioned might be related to @mariozechner/clipboard or some similar package that requires choosing a platform/build.
Let me look at the clipboard dependency first.
cat packages/coding-agent/package.json | grep -A 3 "optionalDependencies""optionalDependencies": {
"@mariozechner/clipboard": "^0.3.6"
},
"devDependencies": {Let me look at the @mariozechner/clipboard package. Maybe it's the one causing issues because it has native bindings that need to be built, and pnpm is asking which of multiple versions or platforms to build. But the user says "three packages I want to build" which makes me think it might be related to the monorepo structure being detected by pnpm in a global install.
Wait, another thought: pnpm has a feature where if you install a package globally and it has peer dependencies, pnpm might prompt you to select which versions of the peer dependencies to install. But three packages?
Let me check what npm view @mariozechner/clipboard shows, or look at the package.json of clipboard.
Actually, wait. Let me think about this more carefully. The pi-coding-agent package is published to npm. When you do npm install -g, npm just downloads and installs it. When you do pnpm install -g, pnpm might be detecting something.
Is it possible that pnpm is seeing the bin name pi and asking about which package to build because there are conflicts? No, that's about the bin name, not packages to build.
Actually, pnpm has an interactive feature for selecting which packages should have their preinstall, install, postinstall scripts run, when --ignore-scripts is set or when pnpm is configured to ask. But the user didn't mention --ignore-scripts.
Hmm, let me look at the @mariozechner/clipboard package on the npm registry to see if it has multiple optional native dependencies.
npm view @mariozechner/clipboard@0.3.6 2>/dev/null || npm view @mariozechner/clipboard@mariozechner/clipboard@0.3.6 | MIT | deps: none | versions: 5 > **Fork of [@crosscopy/clipboard](https://github.com/CrossCopy/clipboard)** > > This fork updates to the latest `clipboard-rs` (0.3.1) and adds musl (Alpine Linux) support. > All credit goes to the original authors. https://github.com/badlogic/clipboard#readme dist .tarball: https://registry.npmjs.org/@mariozechner/clipboard/-/clipboard-0.3.6.tgz .shasum: 448729b51e5fbaf8b3eed62b08e7dc57a94f94f9 .integrity: sha512-MXdtr+6+ntlIVHdrZYuZNQydu6o8yZswFJ2Ln81j2O/Y9B/LDHvEaIm95xWNPkjGTWriSOeLnQJRFs6dYb60bg== .unpackedSize: 126.3 kB maintainers: - badlogic <[REDACTED]> - mitsuhiko <[REDACTED]> dist-tags: latest: 0.3.6 published 20 hours ago by badlogic <[REDACTED]>
The clipboard package doesn't seem to have native dependencies itself. Let me think about this differently.
Actually, I wonder if the issue is related to the package.json overrides field in the pi-coding-agent package? overrides is an npm-specific field (originally from npm), but pnpm handles it differently. However, pnpm doesn't show an interactive picker for that.
Wait, let me look at pnpm's behavior. There is a known pnpm feature: when installing a package globally and the package has optionalDependencies with os/cpu conditions that cause multiple variants to be considered, or when there are packages with postinstall scripts and pnpm is in some interactive mode.
Actually, I recall now! pnpm has a specific behavior when installing a package globally: if the package has peerDependencies, pnpm might prompt the user to resolve them. But more specifically, pnpm has an interactive update/select feature.
Wait, could it be that pnpm install --global is actually running in the local workspace directory and pnpm is interpreting it as installing to the workspace? The user said "If I try to update Pi by running pnpm install --global @earendil-works/pi-coding-agent" — maybe they are running this from within a project that has a pnpm-workspace.yaml or something?
No, the user is asking about a general discrepancy, not specifically from within this repo.
Let me think about what could cause pnpm to show an interactive picker with three packages to build.
Oh wait! I bet this is because pnpm has a "catalog" or "workspace" feature where if you install a package that itself is part of a monorepo, pnpm might offer to build its workspace dependencies? No, that doesn't make sense for a published package.
Actually, maybe it's related to optionalDependencies and os/cpu constraints. Let me look at undici or other dependencies. But again, the user specifically says "three packages" — the pi-coding-agent package depends on exactly three sibling packages: @earendil-works/pi-agent-core, @earendil-works/pi-ai, and @earendil-works/pi-tui. That's three packages! Could the picker be about these three packages?
Why would pnpm ask to build these three packages? Maybe because pnpm sees that these dependencies have prepare or build scripts? But they don't have postinstall scripts, and published packages typically have prebuilt dist directories.
Wait, maybe the issue is with pnpm's --global flag when run inside a pnpm workspace directory. If you run pnpm install --global <pkg> inside a pnpm workspace, pnpm might get confused and show workspace packages? Hmm, unlikely.
Another thought: pnpm has onlyBuiltDependencies or ignoredBuiltDependencies in .npmrc. If a package has dependencies with postinstall scripts, pnpm might ask which ones to build. But npm just runs them all.
Actually, I think I found it! pnpm has a configuration where if packages have native bindings that need to be built (via node-gyp or similar), and there are multiple packages that need building, pnpm might show an interactive picker when running in TTY mode, asking which packages to build. But npm just builds them all (or fails silently with optional ones).
Wait, but the dependencies of pi-coding-agent don't seem to have native bindings. Let me check if any dependency might have a postinstall script.
Actually, @silvia-odwyer/photon-node is a Rust/WASM package. Let me check that.
npm view @silvia-odwyer/photon-node@0.3.4 2>/dev/null | head -50 || npm view @silvia-odwyer/photon-node@silvia-odwyer/photon-node@0.3.4 | Apache-2.0 | deps: none | versions: 7 High-performance image processing library for native use and the web https://silvia-odwyer.github.io/photon/ dist .tarball: https://registry.npmjs.org/@silvia-odwyer/photon-node/-/photon-node-0.3.4.tgz .shasum: ffccfd794abb203b24a793db94bca72748326726 .integrity: sha512-bnly4BKB3KDTFxrUIcgCLbaeVVS8lrAkri1pEzskpmxu9MdfGQTy8b8EgcD83ywD3RPMsIulY8xJH5Awa+t9fA== .unpackedSize: 2.3 MB maintainers: - silvia-odwyer <[REDACTED]> dist-tags: latest: 0.3.4 published a year ago by silvia-odwyer <[REDACTED]>
Let me look at this from a different angle. The pi-coding-agent package has a dependency on @earendil-works/pi-tui. Maybe @earendil-works/pi-tui has a postinstall or some binary that pnpm is asking about. Let me check the tui package for any scripts or native deps.
Aha! @earendil-works/pi-tui has koffi as an optionalDependency. koffi is a native FFI library for Node.js. Let me check if koffi has multiple packages or postinstall scripts that would cause pnpm to show a picker.
Also, let me check the agent package for any optional dependencies or similar things.
So pi-tui has koffi as an optional dependency. Let me check what koffi is and whether it causes pnpm to show a picker.
npm view koffi@2.9.0 2>/dev/null | head -80 || npm view koffi | head -80koffi@2.9.0 | MIT | deps: none | versions: 269 Fast and simple C FFI (foreign function interface) for Node.js https://koffi.dev/ keywords: foreign, function, interface, ffi, binding, c, napi dist .tarball: https://registry.npmjs.org/koffi/-/koffi-2.9.0.tgz .shasum: 58d30507f22a334921af5bfce1b82a4c0eb9b195 .integrity: sha512-KCsuJ2gM58n6bNdR2Z7gqsh/3TchxxQFbVgax2/UvAjRTgwNSYAJDx9E3jrkBP4jEDHWRCfE47Y2OG+/fiSvEw== .unpackedSize: 75.2 MB maintainers: - koromix <[REDACTED]> dist-tags: latest: 3.0.0 published a year ago by koromix <[REDACTED]>
Let me check if koffi has multiple prebuilt binaries or if it has a postinstall script that might cause pnpm to show a picker. Actually, koffi is a native module. Let me look at its package.json via npm view to see if it has scripts.
npm view koffi@2.9.0 scripts{ install: 'node src/cnoke/cnoke.js -p . -d src/koffi --prebuild' }Aha! koffi has an install script. That's a postinstall script that runs during installation. Now, pnpm has specific behavior regarding packages with install scripts. By default, pnpm does NOT run install scripts for packages unless they are explicitly allowlisted in onlyBuiltDependencies or if you confirm them interactively.
Actually, more precisely: pnpm has a security feature where it asks which packages with lifecycle scripts should be allowed to run. Starting from pnpm v9, pnpm does not run lifecycle scripts (like postinstall, install, etc.) by default and instead shows an interactive picker to let the user select which packages to build/run scripts for.
Wait, is that exactly what the user is seeing? The user says they get an interactive picker asking to select which of three packages they want to build. That sounds exactly like pnpm's "ignored build scripts" or "only built dependencies" feature where it shows a multi-select of packages that have postinstall/install scripts.
Let me verify: pnpm v9+ has a feature where it shows an interactive prompt for packages with build scripts (postinstall, install, etc.), asking the user to select which ones to allow. npm, on the other hand, runs all these scripts by default.
Now, the "three packages" the user sees — what are they? The pi-coding-agent dependency tree might have multiple packages with install scripts. Let me find out which packages in the full dependency tree have install or postinstall scripts. I should check if other packages like @mariozechner/clipboard or @silvia-odwyer/photon-node also have install scripts.
Actually, let me trace the dependency tree to see what packages have install scripts. But I might not have the full tree without installing. Let me use npm ls or look at the lockfile.
Actually, wait. The user is specifically asking about the discrepancy. The answer is likely: pnpm blocks lifecycle scripts by default for security and shows an interactive picker, while npm runs them automatically.
But let me confirm which three packages are involved. It might be:
koffi (from pi-tui's optionalDependencies)@mariozechner/clipboard (from pi-coding-agent's optionalDependencies) - does it have an install script?Let me check @mariozechner/clipboard for install scripts.
npm view @mariozechner/clipboard@0.3.6 scripts{
artifacts: 'napi artifacts',
build: 'napi build --platform --release',
'build:debug': 'napi build --platform',
prepublishOnly: 'napi prepublish -t npm',
test: 'ava',
universal: 'napi universal',
version: 'napi version'
}No install script for clipboard. Let me check @silvia-odwyer/photon-node for install scripts.
npm view @silvia-odwyer/photon-node@0.3.4 scripts(no output)
So koffi definitely has an install script. Let me see if there are other packages in the dependency tree that have install/postinstall scripts. For instance, undici or openai or chalk or highlight.js etc. Let me search for common ones.
Actually, @earendil-works/pi-tui depends on marked and get-east-asian-width. Let me check if those have install scripts.
npm view get-east-asian-width@1.3.0 scripts && npm view marked@15.0.12 scripts{
test: 'xo && ava && tsc index.d.ts',
build: 'node scripts/build.js',
prepublish: 'npm run build'
}
{
lint: 'eslint --fix',
test: 'npm run build && npm run test:specs && npm run test:unit',
bench: 'npm run build && node test/bench.js',
build: 'npm run build:esbuild && npm run build:types && npm run build:man',
rules: 'node test/rules.js',
'test:all': 'npm test && npm run test:umd && npm run test:cjs && npm run test:types && npm run test:lint',
'test:cjs': 'node test/cjs-test.cjs',
'test:umd': 'node test/umd-test.js',
'build:man': 'marked-man man/marked.1.md > man/marked.1',
'test:lint': 'eslint',
'test:only': 'npm run build && npm run test:specs:only && npm run test:unit:only',
'test:unit': 'node --test --test-reporter=spec test/unit/*.test.js',
'build:docs': 'npm run build && node docs/build.js',
'test:redos': 'node test/recheck.js > vuln.js',
'test:specs': 'node --test --test-reporter=spec test/run-spec-tests.js',
'test:types': 'tsc --project tsconfig-type-test.json && attw -P --exclude-entrypoints ./bin/marked ./marked.min.js',
'build:reset': 'git checkout upstream/master lib/marked.cjs lib/marked.umd.js lib/marked.esm.js marked.min.js',
'build:types': 'tsc && dts-bundle-generator --export-referenced-types --project tsconfig.json -o lib/marked.d.ts src/marked.ts && dts-bundle-generator --export-referenced-types --project tsconfig.json -o lib/marked.d.cts src/marked.ts',
'test:update': 'node test/update-specs.js',
'build:esbuild': 'node esbuild.config.js',
'test:unit:only': 'node --test --test-only --test-reporter=spec test/unit/*.test.js',
'test:specs:only': 'node --test --test-only --test-reporter=spec test/run-spec-tests.js'
}Let me search for other packages in the full dependency tree of pi-coding-agent that have install/postinstall scripts. I should look at packages like @xterm/headless, @xterm/xterm, chalk, diff, glob, highlight.js, hosted-git-info, ignore, jiti, minimatch, proper-lockfile, typebox, undici, yaml, @anthropic-ai/sdk, @aws-sdk/client-bedrock-runtime, @google/genai, @mistralai/mistralai, openai, http-proxy-agent, https-proxy-agent, partial-json.
Actually, a better approach is to search the pnpm lockfile or use npm to list packages with install scripts in the tree. But since we're in the monorepo, let me check the pnpm-lock.yaml for packages that have scripts.
grep -B2 -A2 "postinstall:\|install:\|preinstall:" pnpm-lock.yaml 2>/dev/null | head -60(no output)
Let me look at how pnpm handles install scripts. Actually, pnpm lockfile format might use hasInstallScript: true instead of listing the scripts explicitly.
grep "hasInstallScript" pnpm-lock.yaml 2>/dev/null | head -20(no output)
Let me check the pnpm-lock.yaml format. Maybe it's a different structure.
grep -i "install" pnpm-lock.yaml | head -20grep: pnpm-lock.yaml: No such file or directory
There's no pnpm-lock.yaml. Let me check if there's a package-lock.json or some other lockfile.
ls *.lock* *.yaml 2>/dev/null | head -10(no output)
Let me think about this differently. The user is asking about installing @earendil-works/pi-coding-agent globally with pnpm. pnpm has a specific behavior with install scripts.
From pnpm documentation:
onlyBuiltDependencies in package.json or pnpm.onlyBuiltDependencies in .npmrc, or via pnpm config.So the discrepancy is that pnpm blocks lifecycle scripts and shows an interactive picker, while npm runs them automatically.
But what are the "three packages"? We know koffi has an install script. What are the other two? Let me search for other packages in the dependency tree that have install scripts. Maybe undici or some other dependency has one?
Actually, let me check undici, openai, @anthropic-ai/sdk, etc.
npm view undici@8.3.0 scripts{
'build:node': 'esbuild index-fetch.js --bundle --platform=node --outfile=undici-fetch.js --define:esbuildDetection=1 --keep-names && node scripts/strip-comments.js',
'build:wasm': 'node build/wasm.js --docker',
'generate-pem': 'node scripts/generate-pem.js',
lint: 'eslint --cache',
'lint:fix': 'eslint --fix --cache',
test: 'npm run test:javascript && cross-env NODE_V8_COVERAGE= npm run test:typescript',
'test:javascript': 'npm run test:javascript:no-jest && npm run test:jest',
'test:javascript:no-jest': 'npm run generate-pem && npm run test:unit && npm run test:fetch && npm run test:node-fetch && npm run test:infra && npm run test:cache && npm run test:cache-interceptor && npm run test:interceptors && npm run test:cookies && npm run test:eventsource && npm run test:subresource-integrity && npm run test:wpt && npm run test:websocket && npm run test:node-test && npm run test:cache-tests',
'test:javascript:without-intl': 'npm run test:javascript:no-jest',
'test:busboy': 'borp --timeout 180000 -p "test/busboy/*.js"',
'test:cache': 'borp --timeout 180000 -p "test/cache/*.js"',
'test:cache-interceptor': 'borp --timeout 180000 -p "test/cache-interceptor/*.js"',
'test:cache-interceptor:sqlite': 'cross-env NODE_OPTIONS=--experimental-sqlite npm run test:cache-interceptor',
'test:cookies': 'borp --timeout 180000 -p "test/cookie/*.js"',
'test:eventsource': 'npm run build:node && borp --timeout 180000 --expose-gc -p "test/eventsource/*.js"',
'test:fuzzing': 'node test/fuzzing/fuzzing.test.js',
'test:fetch': 'npm run build:node && borp --timeout 180000 --expose-gc --concurrency 1 -p "test/fetch/*.js" && npm run test:webidl && npm run test:busboy',
'test:subresource-integrity': 'borp --timeout 180000 -p "test/subresource-integrity/*.js"',
'test:h2': 'npm run test:h2:core && npm run test:h2:fetch',
'test:h2:core': 'borp --timeout 180000 -p "test/+(http2|h2)*.js"',
'test:h2:fetch': 'npm run build:node && borp --timeout 180000 -p "test/fetch/http2*.js"',
'test:infra': 'borp --timeout 180000 -p "test/infra/*.js"',
'test:interceptors': 'borp --timeout 180000 -p "test/interceptors/*.js"',
'test:jest': 'cross-env NODE_V8_COVERAGE= jest',
'test:unit': 'borp --timeout 180000 --expose-gc -p "test/*.js"',
'test:node-fetch': 'borp --timeout 180000 -p "test/node-fetch/**/*.js"',
'test:node-test': 'borp --timeout 180000 -p "test/node-test/**/*.js"',
'test:tdd': 'borp --timeout 180000 --expose-gc -p "test/*.js"',
'test:tdd:node-test': 'borp --timeout 180000 -p "test/node-test/**/*.js" -w',
'test:typescript': 'tsd && tsc test/imports/undici-import.ts --typeRoots ./types --noEmit && tsc ./types/*.d.ts --noEmit --typeRoots ./types',
'test:webidl': 'borp --timeout 180000 -p "test/webidl/*.js"',
'test:websocket': 'borp --timeout 180000 -p "test/websocket/**/*.js"',
'test:websocket:autobahn': 'node test/autobahn/client.js',
'test:websocket:autobahn:report': 'node test/autobahn/report.js',
'test:wpt:setup': 'node test/web-platform-tests/wpt-runner.mjs setup',
'test:wpt': 'npm run test:wpt:setup && node test/web-platform-tests/wpt-runner.mjs run /fetch /mimesniff /websockets /serviceWorkers /eventsource',
'test:cache-tests': 'node test/cache-interceptor/cache-tests.mjs --ci',
coverage: 'npm run coverage:clean && cross-env NODE_V8_COVERAGE=./coverage/tmp npm run test:javascript && npm run coverage:report',
'coverage:ci': 'npm run coverage:clean && cross-env NODE_V8_COVERAGE=./coverage/tmp npm run test:javascript && npm run coverage:report:ci',
'coverage:clean': 'node ./scripts/clean-coverage.js',
'coverage:report': 'cross-env NODE_V8_COVERAGE= c8 report',
'coverage:report:ci': 'c8 report',
bench: `echo "Error: Benchmarks have been moved to '/benchmarks'" && exit 1`,
'serve:website': `echo "Error: Documentation has been moved to '/docs'" && exit 1`,
prepare: 'husky && node ./scripts/platform-shell.js'
}Aha! undici has a prepare script. prepare runs on install. Wait, does prepare run when installing from npm? Yes, prepare runs on both npm publish and npm install (when installing from a git repo or local path). But for published packages installed from the registry, prepare does NOT run during npm install. The lifecycle scripts that run during registry install are preinstall, install, and postinstall. prepare does not run for registry installs.
Let me check if undici has install or postinstall scripts in its published version. The prepare script won't run for npm registry installs. The output from npm view undici@8.3.0 scripts shows prepare, but that's different.
Actually, let me check the exact version of undici that pi-coding-agent depends on. It's ^8.3.0. But the published tarball doesn't include the prepare script in a way that runs during registry install.
So back to koffi - it has an install script, which definitely runs during registry install.
Now let me check other dependencies for install or postinstall scripts. Maybe chalk, diff, glob, highlight.js, hosted-git-info, ignore, jiti, minimatch, proper-lockfile, typebox, yaml.
npm view chalk@5.5.0 scripts && npm view diff@8.0.2 scripts && npm view glob@13.0.1 scripts && npm view highlight.js@10.7.3 scripts{ test: 'xo && c8 ava && tsd', bench: 'matcha benchmark.js' }
{
lint: 'yarn eslint',
test: 'nyc yarn _test',
_test: 'yarn build && cross-env NODE_ENV=test yarn run-mocha',
build: 'yarn lint && yarn generate-esm && yarn generate-cjs && yarn check-types && yarn run-rollup && yarn run-uglify',
clean: 'rm -rf libcjs/ libesm/ dist/ coverage/ .nyc_output/',
'run-tsd': 'yarn tsd --typings libesm/ && yarn tsd --files test-d/',
'run-attw': 'yarn attw --pack --entrypoints . && yarn attw --pack --entrypoints lib/diff/word.js --profile node16',
'run-mocha': "mocha --require ./runtime 'test/**/*.js'",
'run-rollup': 'rollup -c rollup.config.mjs',
'run-uglify': 'uglifyjs dist/diff.js -c -o dist/diff.min.js',
'check-types': 'yarn run-tsd && yarn run-attw',
'generate-cjs': `yarn tsc --module commonjs --outDir libcjs && node --eval "fs.writeFileSync('libcjs/package.json', JSON.stringify({type:'commonjs',sideEffects:false}))"`,
'generate-esm': `yarn tsc --module nodenext --outDir libesm --target es6 && node --eval "fs.writeFileSync('libesm/package.json', JSON.stringify({type:'module',sideEffects:false}))"`
}
{
prof: 'bash prof.sh',
snap: 'tap',
test: 'tap',
bench: 'bash benchmark.sh',
format: 'prettier --write . --log-level warn',
prepare: 'tshy',
preprof: 'npm run prepare',
presnap: 'npm run prepare',
pretest: 'npm run prepare',
typedoc: 'typedoc',
prebench: 'npm run prepare',
profclean: 'rm -f v8.log profile.txt',
benchclean: 'node benchclean.cjs',
preversion: 'npm test',
'test-regen': 'npm run profclean && TEST_REGEN=1 node --no-warnings --loader ts-node/esm test/00-setup.ts',
postversion: 'npm publish',
prepublishOnly: 'npm run benchclean; git push origin --follow-tags'
}
{
lint: 'eslint src/*.js src/lib/*.js src/plugins/*.js demo/*.js',
test: 'mocha test',
build: 'node ./tools/build.js -t node',
mocha: 'mocha',
'build-cdn': 'node ./tools/build.js -t cdn',
'test-detect': 'mocha test/detect',
'test-markup': 'mocha test/markup',
'test-parser': 'mocha test/parser',
'test-browser': 'mocha test/browser',
'build-browser': 'node ./tools/build.js -t browser :common',
build_and_test: 'npm run build && npm run test',
'lint-languages': 'eslint --no-eslintrc -c .eslintrc.lang.js src/languages/**/*.js'
}glob has a prepare script (tshy), but prepare doesn't run for registry installs. It also doesn't have install or postinstall.
Let me check jiti, minimatch, proper-lockfile, typebox, and the other packages.
npm view jiti@2.7.0 scripts && npm view minimatch@10.2.3 scripts && npm view proper-lockfile@4.1.2 scripts && npm view typebox@1.1.24 scripts{
bench: 'node test/bench.mjs && deno -A test/bench.mjs && bun --bun test/bench.mjs',
build: 'pnpm clean && pnpm rspack',
'build:rolldown': 'pnpm clean && pnpm rolldown -c rolldown.config.mjs',
clean: 'rm -rf dist',
dev: 'pnpm clean && pnpm rspack --watch',
jiti: 'JITI_DEBUG=1 JITI_JSX=1 lib/jiti-cli.mjs',
lint: 'eslint . && prettier -c src lib test stubs',
'lint:fix': 'eslint --fix . && prettier -w src lib test stubs',
prepack: 'pnpm build',
release: 'pnpm build && pnpm test && changelogen --release --push && npm publish',
test: 'pnpm lint && pnpm test:types && vitest run --coverage && pnpm test:node-register && pnpm test:bun && pnpm test:native',
'test:bun': 'bun --bun test test/bun',
'test:native': 'pnpm test:native:bun && pnpm test:native:node && pnpm test:native:deno',
'test:native:bun': 'bun --bun test test/native/bun.test.ts',
'test:native:deno': 'deno test -A --no-check test/native/deno.test.ts',
'test:native:node': 'node --test --experimental-strip-types test/native/node.test.ts',
'test:node-register': 'JITI_JSX=1 node --test test/node-register.test.mjs',
'test:types': 'tsgo --noEmit'
}
{
snap: 'tap',
test: 'tap',
format: 'prettier --write .',
prepare: 'tshy',
presnap: 'npm run prepare',
pretest: 'npm run prepare',
typedoc: 'typedoc --tsconfig .tshy/esm.json ./src/*.ts',
benchmark: 'node benchmark/index.js',
preversion: 'npm test',
postversion: 'npm publish',
prepublishOnly: 'git push origin --follow-tags'
}
{
lint: 'eslint .',
test: 'jest --env node --coverage --runInBand',
prerelease: 'npm t && npm run lint',
release: 'standard-version',
postrelease: 'git push --follow-tags origin HEAD && npm publish'
}Let me check if there are any other packages with install/postinstall scripts. Maybe highlight.js doesn't have one. Let me check the ai package dependencies: @anthropic-ai/sdk, @aws-sdk/client-bedrock-runtime, @google/genai, @mistralai/mistralai, openai, http-proxy-agent, https-proxy-agent, partial-json.
Actually, maybe there are packages deeper in the tree. Let me use npm to check if any of these have install scripts.
npm view @anthropic-ai/sdk@0.91.1 scripts && npm view @mistralai/mistralai@2.2.0 scripts && npm view openai@6.26.0 scripts && npm view http-proxy-agent@7.0.2 scripts && npm view https-proxy-agent@7.0.6 scripts && npm view partial-json@0.1.7 scripts{
fix: './scripts/format',
tsn: 'ts-node -r tsconfig-paths/register',
lint: './scripts/lint',
test: './scripts/test',
build: './scripts/build-all',
format: './scripts/format'
}
{
lint: "oxlint --max-warnings=0 --deny-warnings 'src/**/*.{ts,tsx}'",
build: 'tsgo',
prepublishOnly: 'npm run build'
}
{
fix: './scripts/format',
tsn: 'ts-node -r tsconfig-paths/register',
lint: './scripts/lint',
test: './scripts/test',
build: './scripts/build',
format: './scripts/format'
}
{
lint: 'eslint . --ext .ts',
pack: 'node ../../scripts/pack.mjs',
test: 'jest --env node --verbose --bail',
build: 'tsc'
}
{
lint: 'eslint --ext .ts',
pack: 'node ../../scripts/pack.mjs',
test: 'jest --env node --verbose --bail test/test.ts',
build: 'tsc',
'test-e2e': 'jest --env node --verbose --bail test/e2e.test.ts'
}
{
build: 'tsc',
coverage: 'vitest run --coverage',
test: 'vitest',
'test:ui': 'vitest --ui'
}Let me search more broadly. Maybe the issue is with pnpm's onlyBuiltDependencies prompt. Let me check the pnpm documentation or search the web.
URL: https://pnpm.io/cli/approve-builds
Snippet: Approve dependencies for running scripts during installation. The approved dependencies are added to the allowBuilds map in pnpm-workspace.yaml with a value of true, while unapproved ones are saved with a value of false. You can also update these settings manually if you prefer. You can run pnpm approve-builds without...
URL: https://pnpm.io/scripts
Snippet: How pnpm handles the scripts field of package.json.
URL: https://socket.dev/blog/pnpm-10-0-0-blocks-lifecycle-scripts-by-default
Published: 2025-01-10
Snippet: Introducing a command like pnpm approve-scripts to review and approve post-install scripts interactively, with a prompt after pnpm install if any scripts need attention.
URL: https://pnpm.io/filtering
Snippet: Filtering allows you to restrict commands to specific subsets of packages.
URL: https://pnpm.io/blog/releases/11.0
Snippet: pnpm approve-builds now accepts positional arguments for non-interactive use; prefix a name with ! to deny it. Hidden scripts — scripts starting with . can only be called from other scripts and don't show up in pnpm run. ... pnpm add short flags — -d is now --save-dev, -p is --save-prod, -o is --save-optional, -e...
Aha! This is exactly it. Starting from pnpm 10.0.0, pnpm blocks lifecycle scripts by default and shows an interactive prompt (approve-scripts / approve-builds) asking which packages to allow running build scripts for. npm still runs them all by default.
The user gets a picker because pnpm is showing the interactive approve-builds prompt for packages that have lifecycle scripts (like install, postinstall, etc.). The "three packages" are likely packages in the dependency tree that have install scripts.
We know koffi has an install script. Let me figure out what the other two might be. Maybe the @silvia-odwyer/photon-node doesn't have one, but maybe some other dependency does. Let me check the transitive dependencies.
Actually, undici has a prepare script, but that doesn't run during registry install. However, pnpm might also show the prompt for packages that have postinstall or install scripts. Let me check if there are other packages.
Let me look at the @earendil-works/pi-coding-agent dependency tree to see all packages with install scripts. I can use npm ls or simulate an install.
Wait, actually I can use npm to get the full package tree of @earendil-works/pi-coding-agent and then check which ones have install/postinstall scripts. Let me try npm pack or npm install --dry-run or npm ls after a local install. But I might not want to actually install.
Alternatively, I can check the npm registry metadata for transitive dependencies. But that's hard.
Actually, let me check if @aws-sdk/client-bedrock-runtime or its dependencies have install scripts. AWS SDK packages sometimes don't have them. But maybe protobufjs or something deeper does.
Let me check yaml and some other common packages.
npm view yaml@2.8.2 scripts && npm view hosted-git-info@9.0.2 scripts && npm view ignore@7.0.5 scripts{
docs: 'cd docs-slate && bundle exec middleman server',
lint: 'eslint config/ src/',
test: 'jest --config config/jest.config.js',
build: 'npm run build:node && npm run build:browser',
clean: 'git clean -fdxe node_modules',
start: `node --enable-source-maps -i -e 'YAML=require("./dist/index.js");const{parse,parseDocument,parseAllDocuments}=YAML'`,
predocs: 'node docs/prepare-docs.mjs',
prestart: 'rollup --sourcemap -c config/rollup.node-config.mjs',
prettier: 'prettier --write .',
'test:all': 'npm test && npm run test:types && npm run test:dist && npm run test:dist:types',
'test:dist': 'npm run build:node && jest --config config/jest.config.js',
'build:node': 'rollup -c config/rollup.node-config.mjs',
preversion: 'npm test && npm run build',
'test:types': 'tsc --noEmit && tsc --noEmit -p tests/tsconfig.json',
'docs:deploy': 'cd docs-slate && ./deploy.sh',
'docs:install': 'cd docs-slate && bundle install',
'build:browser': 'rollup -c config/rollup.browser-config.mjs',
'test:browsers': 'cd playground && npm test',
'predocs:deploy': 'node docs/prepare-docs.mjs',
prepublishOnly: 'npm run clean && npm test && npm run build',
'test:dist:types': 'tsc --allowJs --moduleResolution node --noEmit --target es5 dist/index.js'
}
{
lint: 'npm run eslint',
snap: 'tap',
test: 'tap',
eslint: 'eslint "**/*.{js,cjs,ts,mjs,jsx,tsx}"',
lintfix: 'npm run eslint -- --fix',
postlint: 'template-oss-check',
posttest: 'npm run lint',
'test:coverage': 'tap --coverage-report=html',
'template-oss-apply': 'template-oss-apply --force'
}
{
prepublishOnly: 'npm run build',
build: 'babel -o legacy.js index.js',
'==================== linting ======================': '',
lint: 'eslint .',
'===================== import ======================': '',
ts: 'npm run test:ts && npm run test:16',
'test:ts': 'ts-node ./test/import/simple.ts',
'test:16': 'npm run test:ts:16 && npm run test:cjs:16 && npm run test:mjs:16',
'test:ts:16': `ts-node --compilerOptions '{"moduleResolution": "Node16", "module": "Node16"}' ./test/import/simple.ts && tsc ./test/import/simple.ts --lib ES6 --moduleResolution Node16 --module Node16 && node ./test/import/simple.js`,
'test:cjs:16': `ts-node --compilerOptions '{"moduleResolution": "Node16", "module": "Node16"}' ./test/import/simple.cjs`,
'test:mjs:16': `ts-node --compilerOptions '{"moduleResolution": "Node16", "module": "Node16"}' ./test/import/simple.mjs && babel -o ./test/import/simple-mjs.js ./test/import/simple.mjs && node ./test/import/simple-mjs.js`,
'===================== cases =======================': '',
'test:cases': 'npm run tap test/*.test.js -- --coverage',
tap: 'tap --reporter classic',
'===================== debug =======================': '',
'test:git': 'npm run tap test/git-check-ignore.test.js',
'test:ignore': 'npm run tap test/ignore.test.js',
'test:ignore:only': 'IGNORE_ONLY_IGNORES=1 npm run tap test/ignore.test.js',
'test:others': 'npm run tap test/others.test.js',
'test:no-coverage': 'npm run tap test/*.test.js -- --no-check-coverage',
test: 'npm run lint && npm run ts && npm run build && npm run test:cases',
'test:win32': 'IGNORE_TEST_WIN32=1 npm run test',
report: 'tap --coverage-report=html'
}None of those have install or postinstall scripts. Let me try a different approach. Maybe I can use npm install --dry-run or npm pack to get the dependency tree. But I might not need to know the exact three packages to answer the user's question. The core discrepancy is that pnpm blocks lifecycle scripts by default (showing an interactive picker), while npm does not.
However, to give a precise answer, I should try to find all packages in the dependency tree with install scripts. Let me try to use npm install in a temp directory and inspect.
cd /tmp && mkdir -p test-pi-install && cd test-pi-install && npm install @earendil-works/pi-coding-agent --dry-run 2>&1 | grep -i "install\|postinstall\|preinstall" | head -20(no output)
Let me try a different approach. I'll actually install in a temp dir and then look for packages with install/postinstall scripts in node_modules.
cd /tmp && rm -rf test-pi-install && mkdir test-pi-install && cd test-pi-install && npm install @earendil-works/pi-coding-agent 2>&1 | tail -5added 125 packages in 2s 19 packages are looking for funding run `npm fund` for details
Now let me search for packages in node_modules that have install/postinstall scripts.
cd /tmp/test-pi-install && for f in $(find node_modules -name package.json -maxdepth 3 | head -200); do dir=$(dirname "$f"); name=$(node -e "console.log(require('./$f').name)" 2>/dev/null); scripts=$(node -e "const s=require('./$f').scripts||{}; console.log(Object.keys(s).filter(k=>['install','postinstall','preinstall'].includes(k)).join(' '))" 2>/dev/null); if [ -n "$scripts" ]; then echo "$name ($dir): $scripts"; fi; donekoffi (node_modules/koffi): install protobufjs (node_modules/protobufjs): postinstall @google/genai (node_modules/@google/genai): preinstall
There we go! The three packages with install/postinstall/preinstall scripts are:
koffi (install)protobufjs (postinstall)@google/genai (preinstall)So when installing @earendil-works/pi-coding-agent globally with pnpm, pnpm detects these three packages with lifecycle scripts and shows an interactive picker asking which ones to allow building/running scripts for. npm, by default, runs all these scripts automatically without prompting.
The user asks "Why is there a discrepancy?" The answer is: pnpm blocks lifecycle scripts by default for security reasons and interactively prompts the user to approve which ones to run, whereas npm runs them all automatically.
Now let me verify the exact pnpm behavior. Let me search for the specific pnpm version and feature.
URL: https://socket.dev/blog/pnpm-10-0-0-blocks-lifecycle-scripts-by-default
Published: 2025-01-10
Snippet: Introducing a command like <strong>pnpm approve-scripts</strong> to review and approve post-install scripts interactively, with a prompt after pnpm install if any scripts need attention.
URL: https://progosling.com/en/dev-digest/pnpm-10-blocks-lifecycle-scripts
Published: 2025-08-16
Snippet: The change reduces automatic attack surface from untrusted dependency scripts, but it requires a small migration: either pre-approve required packages with the new pnpm approve-builds command or add approved names to pnpm.onlyBuiltDependencies (or use a managed onlyBuiltDependenciesFile supplied by an internal policy...
URL: https://pnpm.io/cli/approve-builds
Snippet: You can also update these settings manually if you prefer. You can <strong>run pnpm approve-builds without arguments to get an interactive prompt</strong>, or pass package names as positional arguments:
URL: https://github.com/pnpm/pnpm/security/advisories/GHSA-379q-355j-w6rj
Snippet: Run pnpm install - the prepare script executes without any warning or approval prompt
URL: https://medium.com/@philip.mutua/my-experience-with-pnpm-approve-builds-from-confusion-to-clarity-7aa42c5956a7
Published: 2025-08-12
Snippet: Ignored build scripts: @parcel/watcher, @swc/core, esbuild, lmdb, msgpackr-extract, nx, unrs-resolver. <strong>Run "pnpm approve-builds" to pick which dependencies should be allowed to run scripts</strong>.
So the answer is clear. Let me also verify which pnpm version the user might be on. But the key answer is that pnpm 10+ blocks dependency lifecycle scripts by default and shows an interactive picker, while npm does not.
Let me also check if @google/genai really has a preinstall script. It showed up in my scan. Let me verify.
cd /tmp/test-pi-install && cat node_modules/@google/genai/package.json | grep -A 10 '"scripts"'"scripts": {
"preinstall": "echo 'preinstall: no-op'",
"prepare": "node scripts/prepare.js",
"prepack": "echo 'prepack: no-op'",
"postpack": "echo 'postpack: no-op'",
"prebuild": "echo 'prebuild: no-op'",
"build": "patch-package && rollup -c && npm-run-all --parallel api-extractor:dev:* && node scripts/ignore_missing_mcp_dep.js",
"prebuild-prod": "echo 'prebuild-prod: no-op'",
"build-prod": "patch-package && rollup -c && npm-run-all --parallel api-extractor:prod:* && node scripts/ignore_missing_mcp_dep.js",
"api-extractor:dev:main": "api-extractor run --local --verbose",
"api-extractor:dev:node": "api-extractor run -c api-extractor.node.json --local --verbose",So @google/genai does have a preinstall script that just echoes. protobufjs has a postinstall script. koffi has an install script.
The answer is straightforward: pnpm 10 and later blocks lifecycle scripts (install, preinstall, postinstall, prepare) of dependencies by default for security reasons. When you install a package globally with pnpm, if any transitive dependency has a lifecycle script, pnpm shows an interactive picker asking which packages should be allowed to run their scripts. npm does not block these scripts and runs them all automatically, so no picker appears.
The three packages in the dependency tree of @earendil-works/pi-coding-agent that have lifecycle scripts are:
koffi (optional dependency of @earendil-works/pi-tui, has an install script)protobufjs (transitive dependency, has a postinstall script)@google/genai (dependency of @earendil-works/pi-ai, has a preinstall script)To avoid the picker in pnpm, the user can:
pnpm approve-builds koffi protobufjs @google/genai beforehandpnpm.onlyBuiltDependencies in .npmrc or package.json--allow-build flag (if available in their pnpm version)But the user asked "Why is there a discrepancy?" so I should answer that directly.
pnpm 10 and later blocks dependency lifecycle scripts by default for security reasons, while npm still runs them automatically.
When you install @earendil-works/pi-coding-agent, three transitive dependencies have install/preinstall/postinstall scripts:
koffi (optional dependency of @earendil-works/pi-tui) — has an install script that compiles native bindingsprotobufjs (transitive dependency) — has a postinstall script@google/genai (dependency of @earendil-works/pi-ai) — has a preinstall scriptpnpm detects these scripts and shows an interactive picker asking which ones you want to allow. npm does not block them, so it installs silently and runs all three scripts automatically.
To avoid the picker with pnpm, pre-approve the packages:
Or add them to your .npmrc: