···
3
+
# GitHub Copilot client ID
4
+
CLIENT_ID="Iv1.b507a08c87ecfe98"
6
+
# Token cache file location
7
+
CACHE_DIR="${HOME}/.config/crush/github-copilot"
8
+
CACHE_FILE="${CACHE_DIR}/bearer_token"
9
+
GITHUB_TOKEN_FILE="${CACHE_DIR}/github_token"
10
+
GITHUB_COPILOT_APPS_FILE="${HOME}/.config/github-copilot/apps.json"
12
+
# Function to extract OAuth token from GitHub Copilot apps.json
13
+
extract_oauth_token() {
14
+
local client_id="$1"
15
+
if [ -f "$GITHUB_COPILOT_APPS_FILE" ]; then
16
+
# Extract the oauth_token for our client ID (key format is "github.com:CLIENT_ID")
17
+
local oauth_token=$(grep -o "\"github.com:${client_id}\":{[^}]*\"oauth_token\":\"[^\"]*" "$GITHUB_COPILOT_APPS_FILE" | grep -o '"oauth_token":"[^"]*' | cut -d'"' -f4)
22
+
# Function to extract expiration from token
23
+
extract_expiration() {
25
+
echo "$token" | grep -o 'exp=[0-9]*' | cut -d'=' -f2
28
+
# Function to check if token is valid
31
+
local exp=$(extract_expiration "$token")
33
+
if [ -z "$exp" ]; then
37
+
local current_time=$(date +%s)
38
+
# Add 60 second buffer before expiration
39
+
local buffer_time=$((exp - 60))
41
+
if [ "$current_time" -lt "$buffer_time" ]; then
48
+
# Function to exchange GitHub token for bearer token
49
+
exchange_github_token() {
50
+
local github_token="$1"
51
+
local bearer_response=$(curl -s -X GET "https://api.github.com/copilot_internal/v2/token" \
52
+
-H "Authorization: Token ${github_token}" \
53
+
-H "User-Agent: CRUSH/1.0")
55
+
local bearer_token=$(echo "$bearer_response" | grep -o '"token":"[^"]*' | cut -d'"' -f4)
56
+
echo "$bearer_token"
59
+
# Check for cached bearer token
60
+
if [ -f "$CACHE_FILE" ]; then
61
+
CACHED_TOKEN=$(cat "$CACHE_FILE")
62
+
if is_token_valid "$CACHED_TOKEN"; then
63
+
# Token is still valid, output and exit
64
+
echo "$CACHED_TOKEN"
69
+
# Bearer token is expired/missing, try to use cached GitHub token
70
+
if [ -f "$GITHUB_TOKEN_FILE" ]; then
71
+
GITHUB_TOKEN=$(cat "$GITHUB_TOKEN_FILE")
72
+
if [ -n "$GITHUB_TOKEN" ]; then
73
+
# Try to exchange GitHub token for new bearer token
74
+
BEARER_TOKEN=$(exchange_github_token "$GITHUB_TOKEN")
75
+
if [ -n "$BEARER_TOKEN" ]; then
76
+
# Successfully got new bearer token, cache it
77
+
echo "$BEARER_TOKEN" > "$CACHE_FILE"
78
+
chmod 600 "$CACHE_FILE"
79
+
echo "$BEARER_TOKEN"
85
+
# Try to get OAuth token from GitHub Copilot apps.json
86
+
OAUTH_TOKEN=$(extract_oauth_token "$CLIENT_ID")
87
+
if [ -n "$OAUTH_TOKEN" ]; then
88
+
# Try to exchange OAuth token for new bearer token
89
+
BEARER_TOKEN=$(exchange_github_token "$OAUTH_TOKEN")
90
+
if [ -n "$BEARER_TOKEN" ]; then
91
+
# Successfully got new bearer token, cache it
92
+
mkdir -p "$CACHE_DIR"
93
+
echo "$BEARER_TOKEN" > "$CACHE_FILE"
94
+
chmod 600 "$CACHE_FILE"
95
+
echo "$OAUTH_TOKEN" > "$GITHUB_TOKEN_FILE"
96
+
chmod 600 "$GITHUB_TOKEN_FILE"
97
+
echo "$BEARER_TOKEN"
102
+
# Step 1: Get device code
103
+
DEVICE_RESPONSE=$(curl -s -X POST "https://github.com/login/device/code" \
104
+
-H "Content-Type: application/x-www-form-urlencoded" \
105
+
-H "User-Agent: CRUSH/1.0" \
106
+
-H "Accept: application/json" \
107
+
-d "client_id=${CLIENT_ID}&scope=user:email read:user copilot")
109
+
# Extract values from response
110
+
DEVICE_CODE=$(echo "$DEVICE_RESPONSE" | grep -o '"device_code":"[^"]*' | cut -d'"' -f4)
111
+
USER_CODE=$(echo "$DEVICE_RESPONSE" | grep -o '"user_code":"[^"]*' | cut -d'"' -f4)
112
+
VERIFICATION_URI=$(echo "$DEVICE_RESPONSE" | grep -o '"verification_uri":"[^"]*' | cut -d'"' -f4)
113
+
INTERVAL=$(echo "$DEVICE_RESPONSE" | grep -o '"interval":[0-9]*' | cut -d':' -f2)
114
+
EXPIRES_IN=$(echo "$DEVICE_RESPONSE" | grep -o '"expires_in":[0-9]*' | cut -d':' -f2)
116
+
# Ensure minimum interval
117
+
if [ "$INTERVAL" -lt 5 ]; then
121
+
# Step 2: Create a temporary HTML file with the code
122
+
TEMP_HTML="/tmp/copilot_auth_$$.html"
123
+
cat > "$TEMP_HTML" << EOF
127
+
<title>GitHub Copilot Authentication</title>
130
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;
132
+
justify-content: center;
133
+
align-items: center;
136
+
background-color: #f6f8fa;
139
+
text-align: center;
142
+
border-radius: 8px;
143
+
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
149
+
font-family: monospace;
150
+
background: #f3f4f6;
152
+
border-radius: 6px;
158
+
display: inline-block;
159
+
background: #2ea44f;
161
+
padding: 12px 24px;
162
+
text-decoration: none;
163
+
border-radius: 6px;
168
+
background: #2c974b;
178
+
<div class="container">
179
+
<h1>GitHub Copilot Authentication</h1>
180
+
<p>Copy this code:</p>
181
+
<div class="code" onclick="navigator.clipboard.writeText('$USER_CODE')">$USER_CODE</div>
182
+
<a href="$VERIFICATION_URI" class="button" target="_blank" onclick="navigator.clipboard.writeText('$USER_CODE')">Copy Code & Continue to GitHub</a>
183
+
<p class="info">Click the button to copy the code and go to GitHub</p>
186
+
// Auto-close after 2 minutes
187
+
setTimeout(() => window.close(), 120000);
193
+
# Open the HTML file (suppress all output to avoid issues in POSIX shell)
194
+
if command -v xdg-open >/dev/null 2>&1; then
195
+
xdg-open "$TEMP_HTML" >/dev/null 2>&1 &
196
+
elif command -v open >/dev/null 2>&1; then
197
+
open "$TEMP_HTML" >/dev/null 2>&1 &
198
+
elif command -v start >/dev/null 2>&1; then
199
+
start "$TEMP_HTML" >/dev/null 2>&1 &
202
+
# Step 3: Poll for token
210
+
while [ $POLL_COUNT -lt $MAX_POLLS ] && [ -z "$GITHUB_TOKEN" ]; do
211
+
TOKEN_RESPONSE=$(curl -s -X POST "https://github.com/login/oauth/access_token" \
212
+
-H "Content-Type: application/x-www-form-urlencoded" \
213
+
-H "User-Agent: CRUSH/1.0" \
214
+
-H "Accept: application/json" \
215
+
-d "client_id=${CLIENT_ID}&device_code=${DEVICE_CODE}&grant_type=urn:ietf:params:oauth:grant-type:device_code")
217
+
# Check for access token
218
+
ACCESS_TOKEN=$(echo "$TOKEN_RESPONSE" | grep -o '"access_token":"[^"]*' | cut -d'"' -f4)
220
+
if [ -n "$ACCESS_TOKEN" ]; then
221
+
GITHUB_TOKEN="$ACCESS_TOKEN"
226
+
ERROR=$(echo "$TOKEN_RESPONSE" | grep -o '"error":"[^"]*' | cut -d'"' -f4)
229
+
"authorization_pending")
230
+
# Still waiting, continue
241
+
if [ -n "$ERROR" ]; then
247
+
POLL_COUNT=$((POLL_COUNT + 1))
251
+
if [ -z "$GITHUB_TOKEN" ]; then
255
+
# Step 4: Exchange GitHub token for Copilot bearer token
256
+
BEARER_TOKEN=$(exchange_github_token "$GITHUB_TOKEN")
258
+
if [ -z "$BEARER_TOKEN" ]; then
262
+
# Create cache directory if it doesn't exist
263
+
mkdir -p "$CACHE_DIR"
265
+
# Cache both tokens
266
+
echo "$BEARER_TOKEN" > "$CACHE_FILE"
267
+
chmod 600 "$CACHE_FILE"
268
+
echo "$GITHUB_TOKEN" > "$GITHUB_TOKEN_FILE"
269
+
chmod 600 "$GITHUB_TOKEN_FILE"
271
+
# Output only the bearer token to stdout
272
+
echo "$BEARER_TOKEN"