mirror of
https://github.com/github/awesome-copilot.git
synced 2026-04-30 12:15:56 +00:00
Add error recovery hooks and PyInstaller frozen build recipes (#1388)
* Add error recovery hooks and PyInstaller frozen build recipes * fixed datas to data
This commit is contained in:
128
cookbook/copilot-sdk/python/recipe/pyinstaller_frozen_build.py
Normal file
128
cookbook/copilot-sdk/python/recipe/pyinstaller_frozen_build.py
Normal file
@@ -0,0 +1,128 @@
|
||||
"""
|
||||
PyInstaller / Frozen Build Compatibility
|
||||
=========================================
|
||||
Demonstrates how to create a CopilotClient that works correctly inside
|
||||
a PyInstaller (or Nuitka) frozen executable.
|
||||
|
||||
Run normally:
|
||||
python pyinstaller_frozen_build.py
|
||||
|
||||
Build with PyInstaller:
|
||||
pyinstaller --onefile pyinstaller_frozen_build.py
|
||||
|
||||
Requirements:
|
||||
pip install copilot-sdk certifi
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from copilot import CopilotClient, SubprocessConfig
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# CLI binary resolution
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def resolve_cli_path() -> str | None:
|
||||
"""Find the Copilot CLI binary in a frozen build.
|
||||
|
||||
Searches the SDK's standard location first, then falls back to
|
||||
PyInstaller's _MEIPASS temporary directory.
|
||||
"""
|
||||
candidates: list[Path] = []
|
||||
binary = "copilot.exe" if sys.platform == "win32" else "copilot"
|
||||
|
||||
# 1. SDK's normal resolution (works in non-frozen builds)
|
||||
try:
|
||||
import copilot as pkg
|
||||
candidates.append(Path(pkg.__file__).parent / "bin" / binary)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
# 2. PyInstaller _MEIPASS fallback
|
||||
if getattr(sys, "frozen", False) and hasattr(sys, "_MEIPASS"):
|
||||
meipass = Path(sys._MEIPASS)
|
||||
candidates.append(meipass / "copilot" / "bin" / binary)
|
||||
candidates.append(meipass.parent / "copilot" / "bin" / binary)
|
||||
|
||||
for c in candidates:
|
||||
if c.exists():
|
||||
# Restore execute permissions on Unix (lost during archive extraction)
|
||||
if sys.platform != "win32" and not os.access(str(c), os.X_OK):
|
||||
os.chmod(str(c), c.stat().st_mode | 0o755)
|
||||
return str(c)
|
||||
|
||||
return None
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# SSL certificate setup
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def ensure_ssl_certs():
|
||||
"""Inject certifi's CA bundle into the environment.
|
||||
|
||||
On macOS frozen builds the system certificate store is unreachable,
|
||||
so the CLI subprocess fails TLS handshakes unless we set these vars.
|
||||
"""
|
||||
if os.environ.get("SSL_CERT_FILE"):
|
||||
return # Already configured
|
||||
|
||||
try:
|
||||
import certifi
|
||||
ca = certifi.where()
|
||||
if Path(ca).is_file():
|
||||
os.environ["SSL_CERT_FILE"] = ca
|
||||
os.environ["REQUESTS_CA_BUNDLE"] = ca
|
||||
os.environ.setdefault("NODE_EXTRA_CA_CERTS", ca)
|
||||
except ImportError:
|
||||
pass # CLI will fall back to platform defaults
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Client factory
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def create_frozen_client() -> CopilotClient:
|
||||
"""Create a CopilotClient that works in both normal and frozen builds."""
|
||||
ensure_ssl_certs()
|
||||
|
||||
kwargs: dict = {"log_level": "info", "use_stdio": True}
|
||||
|
||||
if getattr(sys, "frozen", False):
|
||||
cli = resolve_cli_path()
|
||||
if cli:
|
||||
kwargs["cli_path"] = cli
|
||||
print(f"[frozen] Using CLI at: {cli}")
|
||||
else:
|
||||
print("[frozen] WARNING: Could not locate Copilot CLI binary")
|
||||
|
||||
client = CopilotClient(SubprocessConfig(**kwargs), auto_start=True)
|
||||
await client.start()
|
||||
return client
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Demo
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
async def main():
|
||||
frozen = getattr(sys, "frozen", False)
|
||||
print(f"Running as {'frozen' if frozen else 'normal'} Python process")
|
||||
|
||||
client = await create_frozen_client()
|
||||
try:
|
||||
session = await client.create_session()
|
||||
response = await session.send_message(
|
||||
"Say 'Hello from a frozen build!' if you can read this."
|
||||
)
|
||||
print(f"Response: {response}")
|
||||
finally:
|
||||
await client.stop()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user