Skip to content

Commit e92acdd

Browse files
claudemldangelo
andcommitted
refactor: prefer global promptfoo installation over npx
Check for globally installed promptfoo first and use it directly. Only fall back to npx if promptfoo is not found. This improves: - Performance: Faster execution when promptfoo is installed - Reliability: Avoids npm cache corruption issues - User experience: Uses user's preferred promptfoo version Co-Authored-By: Michael D'Angelo <michael@promptfoo.dev>
1 parent 4447c90 commit e92acdd

File tree

1 file changed

+38
-20
lines changed

1 file changed

+38
-20
lines changed

src/promptfoo/cli.py

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -40,37 +40,55 @@ def main() -> NoReturn:
4040
"""
4141
Main entry point for the promptfoo CLI wrapper.
4242
43-
Executes `npx promptfoo@latest <args>` and passes through all arguments.
43+
Tries to use globally installed promptfoo first, falls back to npx.
4444
Exits with the same exit code as the underlying promptfoo command.
4545
"""
4646
# Check for Node.js installation
4747
if not check_node_installed():
4848
print_installation_help()
4949
sys.exit(1)
5050

51-
npx_path = shutil.which("npx")
52-
if not npx_path:
53-
print("ERROR: npx is not available. Please ensure Node.js is properly installed.", file=sys.stderr)
54-
sys.exit(1)
55-
56-
# Build the command: npx promptfoo@latest <args>
57-
# Use @latest to always get the most recent version
5851
is_windows = platform.system() == "Windows"
5952

60-
# On Windows, we need special handling for .cmd files
53+
# Try to find a globally installed promptfoo first
54+
promptfoo_path = shutil.which("promptfoo")
55+
56+
# Build the command
6157
cmd: Union[str, list[str]]
62-
if is_windows:
63-
# On Windows, build command as string for shell=True
64-
# This properly handles npx.cmd batch file execution
65-
import shlex
66-
67-
args = ["npx", "--yes", "promptfoo@latest"] + sys.argv[1:]
68-
cmd = " ".join(shlex.quote(arg) for arg in args)
69-
use_shell = True
58+
use_shell: bool
59+
60+
if promptfoo_path:
61+
# Use the globally installed promptfoo
62+
if is_windows:
63+
# On Windows, build command as string for shell=True to handle .cmd files
64+
import shlex
65+
66+
args = ["promptfoo"] + sys.argv[1:]
67+
cmd = " ".join(shlex.quote(arg) for arg in args)
68+
use_shell = True
69+
else:
70+
# On Unix, use list format with shell=False (more secure)
71+
cmd = [promptfoo_path] + sys.argv[1:]
72+
use_shell = False
7073
else:
71-
# On Unix, use list format with shell=False (more secure)
72-
cmd = [npx_path, "--yes", "promptfoo@latest"] + sys.argv[1:]
73-
use_shell = False
74+
# Fall back to npx promptfoo@latest
75+
npx_path = shutil.which("npx")
76+
if not npx_path:
77+
print("ERROR: promptfoo is not installed and npx is not available.", file=sys.stderr)
78+
print("Please install promptfoo globally: npm install -g promptfoo", file=sys.stderr)
79+
sys.exit(1)
80+
81+
if is_windows:
82+
# On Windows, build command as string for shell=True to handle npx.cmd
83+
import shlex
84+
85+
args = ["npx", "--yes", "promptfoo@latest"] + sys.argv[1:]
86+
cmd = " ".join(shlex.quote(arg) for arg in args)
87+
use_shell = True
88+
else:
89+
# On Unix, use list format with shell=False (more secure)
90+
cmd = [npx_path, "--yes", "promptfoo@latest"] + sys.argv[1:]
91+
use_shell = False
7492

7593
try:
7694
# Execute the command and pass through stdio

0 commit comments

Comments
 (0)