1616import inspect
1717import os
1818import re
19- import shutil
2019import subprocess
20+ import sys
2121import threading
2222from dataclasses import asdict , is_dataclass
23+ from pathlib import Path
2324from typing import Any , Callable , Optional , cast
2425
2526from .generated .session_events import session_event_from_dict
4849)
4950
5051
52+ def _get_bundled_cli_path () -> Optional [str ]:
53+ """Get the path to the bundled CLI binary, if available."""
54+ # The binary is bundled in copilot/bin/ within the package
55+ bin_dir = Path (__file__ ).parent / "bin"
56+ if not bin_dir .exists ():
57+ return None
58+
59+ # Determine binary name based on platform
60+ if sys .platform == "win32" :
61+ binary_name = "copilot.exe"
62+ else :
63+ binary_name = "copilot"
64+
65+ binary_path = bin_dir / binary_name
66+ if binary_path .exists ():
67+ return str (binary_path )
68+
69+ return None
70+
71+
5172class CopilotClient :
5273 """
5374 Main client for interacting with the Copilot CLI.
@@ -130,8 +151,18 @@ def __init__(self, options: Optional[CopilotClientOptions] = None):
130151 else :
131152 self ._actual_port = None
132153
133- # Check environment variable for CLI path
134- default_cli_path = os .environ .get ("COPILOT_CLI_PATH" , "copilot" )
154+ # Determine CLI path: explicit option > bundled binary
155+ if opts .get ("cli_path" ):
156+ default_cli_path = opts ["cli_path" ]
157+ else :
158+ bundled_path = _get_bundled_cli_path ()
159+ if bundled_path :
160+ default_cli_path = bundled_path
161+ else :
162+ raise RuntimeError (
163+ "Copilot CLI not found. The bundled CLI binary is not available. "
164+ "Ensure you installed a platform-specific wheel, or provide cli_path."
165+ )
135166
136167 # Default use_logged_in_user to False when github_token is provided
137168 github_token = opts .get ("github_token" )
@@ -140,7 +171,7 @@ def __init__(self, options: Optional[CopilotClientOptions] = None):
140171 use_logged_in_user = False if github_token else True
141172
142173 self .options : CopilotClientOptions = {
143- "cli_path" : opts . get ( "cli_path" , default_cli_path ) ,
174+ "cli_path" : default_cli_path ,
144175 "cwd" : opts .get ("cwd" , os .getcwd ()),
145176 "port" : opts .get ("port" , 0 ),
146177 "use_stdio" : False if opts .get ("cli_url" ) else opts .get ("use_stdio" , True ),
@@ -1037,12 +1068,9 @@ async def _start_cli_server(self) -> None:
10371068 """
10381069 cli_path = self .options ["cli_path" ]
10391070
1040- # Resolve the full path on Windows (handles .cmd/.bat files)
1041- # On Windows, subprocess.Popen doesn't use PATHEXT to resolve extensions,
1042- # so we need to use shutil.which() to find the actual executable
1043- resolved_path = shutil .which (cli_path )
1044- if resolved_path :
1045- cli_path = resolved_path
1071+ # Verify CLI exists
1072+ if not os .path .exists (cli_path ):
1073+ raise RuntimeError (f"Copilot CLI not found at { cli_path } " )
10461074
10471075 args = ["--headless" , "--log-level" , self .options ["log_level" ]]
10481076
0 commit comments