get your claude code tokens here
at main 3.2 kB view raw
1import { chmodSync, existsSync } from "node:fs"; 2import fs from "node:fs/promises"; 3import path from "node:path"; 4 5const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e"; 6 7const HOME = process.env.HOME || process.env.USERPROFILE || "."; 8const CACHE_DIR = path.join(HOME, ".config/crush/anthropic"); 9const BEARER_FILE = path.join(CACHE_DIR, "bearer_token"); 10const REFRESH_FILE = path.join(CACHE_DIR, "refresh_token"); 11const EXPIRES_FILE = path.join(CACHE_DIR, "bearer_token.expires"); 12 13export type TokenEntry = { 14 accessToken: string; 15 refreshToken: string; 16 expiresAt: number; 17}; 18 19export async function ensureDir() { 20 await fs.mkdir(CACHE_DIR, { recursive: true }); 21} 22 23export async function writeSecret(filePath: string, data: string) { 24 await fs.writeFile(filePath, data, { encoding: "utf8", mode: 0o600 }); 25 chmodSync(filePath, 0o600); 26} 27 28export async function readText(filePath: string) { 29 if (!existsSync(filePath)) return undefined; 30 return await fs.readFile(filePath, "utf8"); 31} 32 33export async function loadFromDisk(): Promise<TokenEntry | undefined> { 34 const [bearer, refresh, expires] = await Promise.all([ 35 readText(BEARER_FILE), 36 readText(REFRESH_FILE), 37 readText(EXPIRES_FILE), 38 ]); 39 if (!bearer || !refresh || !expires) return undefined; 40 const exp = Number.parseInt(expires, 10) || 0; 41 return { 42 accessToken: bearer.trim(), 43 refreshToken: refresh.trim(), 44 expiresAt: exp, 45 }; 46} 47 48export async function saveToDisk(entry: TokenEntry) { 49 await ensureDir(); 50 await writeSecret(BEARER_FILE, `${entry.accessToken}\n`); 51 await writeSecret(REFRESH_FILE, `${entry.refreshToken}\n`); 52 await writeSecret(EXPIRES_FILE, `${String(entry.expiresAt)}\n`); 53} 54 55export async function exchangeRefreshToken(refreshToken: string) { 56 const res = await fetch("https://console.anthropic.com/v1/oauth/token", { 57 method: "POST", 58 headers: { 59 "content-type": "application/json", 60 "user-agent": "anthropic", 61 }, 62 body: JSON.stringify({ 63 grant_type: "refresh_token", 64 refresh_token: refreshToken, 65 client_id: CLIENT_ID, 66 }), 67 }); 68 if (!res.ok) throw new Error(`refresh failed: ${res.status}`); 69 return (await res.json()) as { 70 access_token: string; 71 refresh_token?: string; 72 expires_in: number; 73 }; 74} 75 76/** 77 * Attempts to load a valid token from disk, refresh if needed, and print it to stdout. 78 * Returns true if a valid token was found and printed, false otherwise. 79 */ 80export async function bootstrapFromDisk(): Promise<boolean> { 81 const entry = await loadFromDisk(); 82 if (!entry) return false; 83 const now = Math.floor(Date.now() / 1000); 84 if (now < entry.expiresAt - 60) { 85 process.stdout.write(`${entry.accessToken}\n`); 86 setTimeout(() => process.exit(0), 50); 87 return true; 88 } 89 try { 90 const refreshed = await exchangeRefreshToken(entry.refreshToken); 91 entry.accessToken = refreshed.access_token; 92 entry.expiresAt = Math.floor(Date.now() / 1000) + refreshed.expires_in; 93 if (refreshed.refresh_token) entry.refreshToken = refreshed.refresh_token; 94 await saveToDisk(entry); 95 process.stdout.write(`${entry.accessToken}\n`); 96 setTimeout(() => process.exit(0), 50); 97 return true; 98 } catch { 99 return false; 100 } 101}