22CLI wrapper for promptfoo
33
44This module provides a thin wrapper around the promptfoo Node.js CLI tool.
5- It executes the npx promptfoo command and passes through all arguments .
5+ It executes a global promptfoo binary when available, falling back to npx .
66"""
77
8+ import os
89import shutil
910import subprocess
1011import sys
11- from typing import NoReturn
12+ from typing import NoReturn , Optional
13+
14+ _WRAPPER_ENV = "PROMPTFOO_PY_WRAPPER"
1215
1316
1417def check_node_installed () -> bool :
@@ -34,6 +37,42 @@ def print_installation_help() -> None:
3437 print (" https://github.com/nvm-sh/nvm" , file = sys .stderr )
3538
3639
40+ def _normalize_path (path : str ) -> str :
41+ return os .path .normcase (os .path .abspath (path ))
42+
43+
44+ def _resolve_argv0 () -> Optional [str ]:
45+ if not sys .argv :
46+ return None
47+ argv0 = sys .argv [0 ]
48+ if not argv0 :
49+ return None
50+ if os .path .sep in argv0 or (os .path .altsep and os .path .altsep in argv0 ):
51+ return _normalize_path (argv0 )
52+ resolved = shutil .which (argv0 )
53+ if resolved :
54+ return _normalize_path (resolved )
55+ return None
56+
57+
58+ def _find_external_promptfoo () -> Optional [str ]:
59+ promptfoo_path = shutil .which ("promptfoo" )
60+ if not promptfoo_path :
61+ return None
62+ argv0_path = _resolve_argv0 ()
63+ if argv0_path and _normalize_path (promptfoo_path ) == argv0_path :
64+ wrapper_dir = _normalize_path (os .path .dirname (promptfoo_path ))
65+ path_entries = [
66+ entry
67+ for entry in os .environ .get ("PATH" , "" ).split (os .pathsep )
68+ if entry and _normalize_path (entry ) != wrapper_dir
69+ ]
70+ if not path_entries :
71+ return None
72+ return shutil .which ("promptfoo" , path = os .pathsep .join (path_entries ))
73+ return promptfoo_path
74+
75+
3776def main () -> NoReturn :
3877 """
3978 Main entry point for the promptfoo CLI wrapper.
@@ -45,19 +84,22 @@ def main() -> NoReturn:
4584 print_installation_help ()
4685 sys .exit (1 )
4786
48- # Build command: try global promptfoo first, fall back to npx
49- if shutil .which ("promptfoo" ):
50- cmd = ["promptfoo" ] + sys .argv [1 :]
87+ # Build command: try external promptfoo first, fall back to npx
88+ promptfoo_path = None if os .environ .get (_WRAPPER_ENV ) else _find_external_promptfoo ()
89+ if promptfoo_path :
90+ cmd = [promptfoo_path ] + sys .argv [1 :]
91+ env = os .environ .copy ()
92+ env [_WRAPPER_ENV ] = "1"
93+ result = subprocess .run (cmd , env = env )
5194 elif shutil .which ("npx" ):
5295 cmd = ["npx" , "-y" , "promptfoo@latest" ] + sys .argv [1 :]
96+ result = subprocess .run (cmd )
5397 else :
5498 print ("ERROR: Neither promptfoo nor npx is available." , file = sys .stderr )
5599 print ("Please install promptfoo: npm install -g promptfoo" , file = sys .stderr )
56100 print ("Or ensure Node.js is properly installed." , file = sys .stderr )
57101 sys .exit (1 )
58102
59- # Execute with absolute minimal configuration
60- result = subprocess .run (cmd )
61103 sys .exit (result .returncode )
62104
63105
0 commit comments