Synchronous GitHub Copilot client and CLI with device flow auth, token exchange, model management, and streaming chat. This project exists for educational purposes only and is not an official GitHub Copilot product.
- Python 3.10+
- From PyPI (recommended):
pip install copilot-client
- From GitHub:
pip install "git+https://github.com/AlpMeteSenel/copilot-python-client.git"
The CLI is installed as copilot-client (same version as the package). Credentials live in ~/.copilot_client/config.json and are reused across commands.
- Authenticate via device flow or paste a token:
copilot-client auth - List available models:
copilot-client models - Interactive chat:
copilot-client chat --model gpt-5-mini --system "Be concise"
import os
from copilot_client import CopilotClient
# Use an existing Copilot access token or fetch one via device flow (see below)
client = CopilotClient(copilot_access_token=os.environ["COPILOT_TOKEN"])
print(client.get_user())
print(client.chat([{ "role": "user", "content": "Say hi" }]))- Device flow:
code = CopilotClient.start_device_flow() print(f"Open {code.verification_uri} and enter {code.user_code}") github_token = CopilotClient.poll_device_flow(code.device_code, poll_interval=code.interval) client = CopilotClient(copilot_access_token=github_token) print("Copilot token:", client.copilot_token)
- Existing Copilot access token:
client = CopilotClient(copilot_access_token=os.environ["COPILOT_TOKEN"])
from pathlib import Path
import base64
from copilot_client import CopilotClient
# Start device flow and exchange for a Copilot token
code = CopilotClient.start_device_flow()
print(f"Open {code.verification_uri} and enter {code.user_code}")
token = CopilotClient.poll_device_flow(code.device_code, poll_interval=code.interval)
client = CopilotClient(copilot_access_token=token)
print("copilot_token", client.copilot_token)
print("user", client.get_user())
models = client.list_models()
print([[m["id"], (m.get("policy", {}).get("state", "no policy"))] for m in models])
if models:
try:
print("enable_model", client.enable_model(models[0]["id"]))
except Exception as exc:
print("enable_model skipped", exc)
reply = client.chat([{"role": "user", "content": "Summarize GitHub in 2 bullets"}], system_message="Be concise")
print("chat", reply)
print("chat_stream", end=" ")
for chunk in client.chat_stream([{ "role": "user", "content": "Name two Python async primitives"}], model="copilot-nes-xtab"):
if chunk["type"] == "text-delta":
print(chunk["text"], end="")
print()
image_path = Path("husky.jpg") # replace with your image path
image_base64 = base64.b64encode(image_path.read_bytes()).decode("ascii")
vision_message = [{
"role": "user",
"content": [
{"type": "text", "text": "What is on this image?"},
{"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{image_base64}", "detail": "high"}},
],
}]
for chunk in client.chat_stream(vision_message, model="gpt-5-mini", vision=True):
if chunk["type"] == "text-delta":
print(chunk["text"], end="")
print()- Models: list with
client.list_models(); enable those needing consent viaclient.enable_model(model_id). - Chat (non-streaming):
client.chat([...], model="gpt-5-mini", system_message="Be concise", max_output_tokens=200). - Chat (streaming): iterate over
client.chat_stream([...], model="copilot-nes-xtab")and printchunk["text"]whenchunk["type"] == "text-delta". - User info:
client.get_user().
enable_model only activates models that require explicit consent; many will already be enabled.
All methods raise CopilotError on non-2xx responses. Auth issues raise CopilotAuthError; rate limits raise CopilotRateLimitError.
chat_stream(..., vision=True)forwards the Copilot vision header when prompts include images.- Copilot access tokens are exchanged automatically for Copilot bearer tokens and refreshed every 30 minutes.
Built by Alp Mete Senel. Contributions and issues are welcome.