From 6b3d4fb57f2dd16d11c149733bd99d5edfc4b811 Mon Sep 17 00:00:00 2001 From: Chenxin Yan Date: Mon, 27 Apr 2026 07:46:49 -0400 Subject: [PATCH] fix: resolve Pi CLI via process.argv[1] for Nix and non-standard installs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When taskplane runs as a Pi extension, the current process is already 'node dist/cli.js' — so process.argv[1] is exactly the CLI entrypoint we need to spawn worker agents. The previous implementation only checked npm global roots and static paths, none of which cover Nix (where pi lives in the Nix store, not in any npm prefix). The process.argv[1] candidate: - Is always correct when running inside Pi (it IS the file we're looking for) - Requires no subprocess overhead unlike npm root -g - Covers any non-standard install: Nix, custom node wrappers, symlinked bins - Is consistent with how resolveTaskplanePackageFile() already uses process.argv[1] to locate taskplane's own package files Also adds ~/.nix-profile and /run/current-system/sw as static Nix fallbacks for cases where process.argv[1] is a shell wrapper rather than cli.js directly. --- extensions/taskplane/path-resolver.ts | 40 +++++++++++++++++++-------- 1 file changed, 29 insertions(+), 11 deletions(-) diff --git a/extensions/taskplane/path-resolver.ts b/extensions/taskplane/path-resolver.ts index 17eb4b91..9b150994 100644 --- a/extensions/taskplane/path-resolver.ts +++ b/extensions/taskplane/path-resolver.ts @@ -95,12 +95,17 @@ export function getNpmGlobalRoot(): string { * `node` directly, without a shell intermediary. * * Resolution order: - * 1. `npm root -g` result (dynamic — covers all setups: nvm, Homebrew, volta, etc.) - * 2. `%APPDATA%\npm\node_modules\...` (Windows, APPDATA env var) - * 3. `%USERPROFILE%\AppData\Roaming\npm\node_modules\...` (Windows, HOME-relative) - * 4. `~/.npm-global/lib/node_modules/...` (macOS/Linux custom global prefix) - * 5. `/usr/local/lib/node_modules/...` (macOS system Node, Linux) - * 6. `/opt/homebrew/lib/node_modules/...` (macOS Homebrew) + * 1. `process.argv[1]` — when taskplane runs as a Pi extension, this process IS + * the Pi CLI (`node dist/cli.js`), so argv[1] is already the exact path we need. + * This covers Nix, custom node wrappers, and any non-standard install location. + * 2. `npm root -g` result (dynamic — covers all setups: nvm, Homebrew, volta, etc.) + * 3. `%APPDATA%\npm\node_modules\...` (Windows, APPDATA env var) + * 4. `%USERPROFILE%\AppData\Roaming\npm\node_modules\...` (Windows, HOME-relative) + * 5. `~/.npm-global/lib/node_modules/...` (macOS/Linux custom global prefix) + * 6. `~/.nix-profile/lib/node_modules/...` (Nix — single-user profile) + * 7. `/run/current-system/sw/lib/node_modules/...` (NixOS — system profile) + * 8. `/usr/local/lib/node_modules/...` (macOS system Node, Linux) + * 9. `/opt/homebrew/lib/node_modules/...` (macOS Homebrew) * * @returns Absolute path to `@mariozechner/pi-coding-agent/dist/cli.js` * @throws {Error} If the CLI entrypoint cannot be found in any known location. @@ -110,23 +115,36 @@ export function resolvePiCliPath(): string { const relPath = join("@mariozechner", "pi-coding-agent", "dist", "cli.js"); const candidates: string[] = []; - // 1. Dynamic: npm root -g (covers nvm, Homebrew, volta, custom npm prefix, etc.) + // 1. Direct: process.argv[1] is the Pi CLI entrypoint when taskplane is running + // as a Pi extension — the current process was spawned as `node dist/cli.js`. + // This is the most reliable candidate for non-standard installs (Nix, custom + // node wrappers, symlinked binaries, etc.) and avoids subprocess overhead. + // Consistent with how resolveTaskplanePackageFile() already uses process.argv[1] + // to locate taskplane's own package files. + const argv1 = process.argv[1] || ""; + if (argv1 && existsSync(argv1)) return argv1; + + // 2. Dynamic: npm root -g (covers nvm, Homebrew, volta, custom npm prefix, etc.) const npmRoot = getNpmGlobalRoot(); if (npmRoot) candidates.push(join(npmRoot, relPath)); - // 2-3. Static Windows fallbacks + // 3-4. Static Windows fallbacks const home = process.env.HOME || process.env.USERPROFILE || ""; if (process.env.APPDATA) { candidates.push(join(process.env.APPDATA, "npm", "node_modules", relPath)); } if (home) { candidates.push(join(home, "AppData", "Roaming", "npm", "node_modules", relPath)); - // 4. macOS/Linux custom global prefix + // 5. macOS/Linux custom global prefix candidates.push(join(home, ".npm-global", "lib", "node_modules", relPath)); + // 6. Nix single-user profile + candidates.push(join(home, ".nix-profile", "lib", "node_modules", relPath)); } - // 5. macOS system Node / Linux + // 7. NixOS system profile + candidates.push(join("/run", "current-system", "sw", "lib", "node_modules", relPath)); + // 8. macOS system Node / Linux candidates.push(join("/usr", "local", "lib", "node_modules", relPath)); - // 6. macOS Homebrew + // 9. macOS Homebrew candidates.push(join("/opt", "homebrew", "lib", "node_modules", relPath)); for (const candidate of candidates) {