···
+
# GitHub Copilot client ID
+
CLIENT_ID="Iv1.b507a08c87ecfe98"
+
# Token cache file location
+
CACHE_DIR="${HOME}/.config/crush/github-copilot"
+
CACHE_FILE="${CACHE_DIR}/bearer_token"
+
GITHUB_TOKEN_FILE="${CACHE_DIR}/github_token"
+
GITHUB_COPILOT_APPS_FILE="${HOME}/.config/github-copilot/apps.json"
+
# Function to extract OAuth token from GitHub Copilot apps.json
+
extract_oauth_token() {
+
if [ -f "$GITHUB_COPILOT_APPS_FILE" ]; then
+
# Extract the oauth_token for our client ID (key format is "github.com:CLIENT_ID")
+
local oauth_token=$(grep -o "\"github.com:${client_id}\":{[^}]*\"oauth_token\":\"[^\"]*" "$GITHUB_COPILOT_APPS_FILE" | grep -o '"oauth_token":"[^"]*' | cut -d'"' -f4)
+
# Function to extract expiration from token
+
echo "$token" | grep -o 'exp=[0-9]*' | cut -d'=' -f2
+
# Function to check if token is valid
+
local exp=$(extract_expiration "$token")
+
local current_time=$(date +%s)
+
# Add 60 second buffer before expiration
+
local buffer_time=$((exp - 60))
+
if [ "$current_time" -lt "$buffer_time" ]; then
+
# Function to exchange GitHub token for bearer token
+
exchange_github_token() {
+
local github_token="$1"
+
local bearer_response=$(curl -s -X GET "https://api.github.com/copilot_internal/v2/token" \
+
-H "Authorization: Token ${github_token}" \
+
-H "User-Agent: CRUSH/1.0")
+
local bearer_token=$(echo "$bearer_response" | grep -o '"token":"[^"]*' | cut -d'"' -f4)
+
# Check for cached bearer token
+
if [ -f "$CACHE_FILE" ]; then
+
CACHED_TOKEN=$(cat "$CACHE_FILE")
+
if is_token_valid "$CACHED_TOKEN"; then
+
# Token is still valid, output and exit
+
# Bearer token is expired/missing, try to use cached GitHub token
+
if [ -f "$GITHUB_TOKEN_FILE" ]; then
+
GITHUB_TOKEN=$(cat "$GITHUB_TOKEN_FILE")
+
if [ -n "$GITHUB_TOKEN" ]; then
+
# Try to exchange GitHub token for new bearer token
+
BEARER_TOKEN=$(exchange_github_token "$GITHUB_TOKEN")
+
if [ -n "$BEARER_TOKEN" ]; then
+
# Successfully got new bearer token, cache it
+
echo "$BEARER_TOKEN" > "$CACHE_FILE"
+
chmod 600 "$CACHE_FILE"
+
# Try to get OAuth token from GitHub Copilot apps.json
+
OAUTH_TOKEN=$(extract_oauth_token "$CLIENT_ID")
+
if [ -n "$OAUTH_TOKEN" ]; then
+
# Try to exchange OAuth token for new bearer token
+
BEARER_TOKEN=$(exchange_github_token "$OAUTH_TOKEN")
+
if [ -n "$BEARER_TOKEN" ]; then
+
# Successfully got new bearer token, cache it
+
echo "$BEARER_TOKEN" > "$CACHE_FILE"
+
chmod 600 "$CACHE_FILE"
+
echo "$OAUTH_TOKEN" > "$GITHUB_TOKEN_FILE"
+
chmod 600 "$GITHUB_TOKEN_FILE"
+
# Step 1: Get device code
+
DEVICE_RESPONSE=$(curl -s -X POST "https://github.com/login/device/code" \
+
-H "Content-Type: application/x-www-form-urlencoded" \
+
-H "User-Agent: CRUSH/1.0" \
+
-H "Accept: application/json" \
+
-d "client_id=${CLIENT_ID}&scope=user:email read:user copilot")
+
# Extract values from response
+
DEVICE_CODE=$(echo "$DEVICE_RESPONSE" | grep -o '"device_code":"[^"]*' | cut -d'"' -f4)
+
USER_CODE=$(echo "$DEVICE_RESPONSE" | grep -o '"user_code":"[^"]*' | cut -d'"' -f4)
+
VERIFICATION_URI=$(echo "$DEVICE_RESPONSE" | grep -o '"verification_uri":"[^"]*' | cut -d'"' -f4)
+
INTERVAL=$(echo "$DEVICE_RESPONSE" | grep -o '"interval":[0-9]*' | cut -d':' -f2)
+
EXPIRES_IN=$(echo "$DEVICE_RESPONSE" | grep -o '"expires_in":[0-9]*' | cut -d':' -f2)
+
# Ensure minimum interval
+
if [ "$INTERVAL" -lt 5 ]; then
+
# Step 2: Create a temporary HTML file with the code
+
TEMP_HTML="/tmp/copilot_auth_$$.html"
+
cat > "$TEMP_HTML" << EOF
+
<title>GitHub Copilot Authentication</title>
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
+
justify-content: center;
+
background-color: #f6f8fa;
+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
+
font-family: monospace;
+
<div class="container">
+
<h1>GitHub Copilot Authentication</h1>
+
<div class="code" onclick="navigator.clipboard.writeText('$USER_CODE')">$USER_CODE</div>
+
<a href="$VERIFICATION_URI" class="button" target="_blank" onclick="navigator.clipboard.writeText('$USER_CODE')">Copy Code & Continue to GitHub</a>
+
<p class="info">Click the button to copy the code and go to GitHub</p>
+
// Auto-close after 2 minutes
+
setTimeout(() => window.close(), 120000);
+
# Open the HTML file (suppress all output to avoid issues in POSIX shell)
+
if command -v xdg-open >/dev/null 2>&1; then
+
xdg-open "$TEMP_HTML" >/dev/null 2>&1 &
+
elif command -v open >/dev/null 2>&1; then
+
open "$TEMP_HTML" >/dev/null 2>&1 &
+
elif command -v start >/dev/null 2>&1; then
+
start "$TEMP_HTML" >/dev/null 2>&1 &
+
# Step 3: Poll for token
+
while [ $POLL_COUNT -lt $MAX_POLLS ] && [ -z "$GITHUB_TOKEN" ]; do
+
TOKEN_RESPONSE=$(curl -s -X POST "https://github.com/login/oauth/access_token" \
+
-H "Content-Type: application/x-www-form-urlencoded" \
+
-H "User-Agent: CRUSH/1.0" \
+
-H "Accept: application/json" \
+
-d "client_id=${CLIENT_ID}&device_code=${DEVICE_CODE}&grant_type=urn:ietf:params:oauth:grant-type:device_code")
+
# Check for access token
+
ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | grep -o '"access_token":"[^"]*' | cut -d'"' -f4)
+
if [ -n "$ACCESS_TOKEN" ]; then
+
GITHUB_TOKEN="$ACCESS_TOKEN"
+
ERROR=$(echo "$TOKEN_RESPONSE" | grep -o '"error":"[^"]*' | cut -d'"' -f4)
+
"authorization_pending")
+
# Still waiting, continue
+
if [ -n "$ERROR" ]; then
+
POLL_COUNT=$((POLL_COUNT + 1))
+
if [ -z "$GITHUB_TOKEN" ]; then
+
# Step 4: Exchange GitHub token for Copilot bearer token
+
BEARER_TOKEN=$(exchange_github_token "$GITHUB_TOKEN")
+
if [ -z "$BEARER_TOKEN" ]; then
+
# Create cache directory if it doesn't exist
+
echo "$BEARER_TOKEN" > "$CACHE_FILE"
+
chmod 600 "$CACHE_FILE"
+
echo "$GITHUB_TOKEN" > "$GITHUB_TOKEN_FILE"
+
chmod 600 "$GITHUB_TOKEN_FILE"
+
# Output only the bearer token to stdout