get your claude code tokens here

bug: don't open server when bootstrapped

dunkirk.sh 25367c4b d107108a

verified
+104 -117
+103 -92
bin/anthropic.ts
··· 201 201 } 202 202 } 203 203 204 - await bootstrapFromDisk(); 204 + const didBootstrap = await bootstrapFromDisk(); 205 205 206 206 const argv = process.argv.slice(2); 207 207 if (argv.includes("-h") || argv.includes("--help")) { 208 208 Bun.write(Bun.stdout, `Usage: anthropic\n\n`); 209 - Bun.write(Bun.stdout, ` anthropic Start UI and flow; prints token on success and exits.\n`); 210 - Bun.write(Bun.stdout, ` PORT=xxxx anthropic Override port (default 8787).\n`); 211 - Bun.write(Bun.stdout, `\nTokens are cached at ~/.config/crush/anthropic and reused on later runs.\n`); 209 + Bun.write( 210 + Bun.stdout, 211 + ` anthropic Start UI and flow; prints token on success and exits.\n`, 212 + ); 213 + Bun.write( 214 + Bun.stdout, 215 + ` PORT=xxxx anthropic Override port (default 8787).\n`, 216 + ); 217 + Bun.write( 218 + Bun.stdout, 219 + `\nTokens are cached at ~/.config/crush/anthropic and reused on later runs.\n`, 220 + ); 212 221 process.exit(0); 213 222 } 214 223 215 - serve({ 216 - port: PORT, 217 - development: { console: false }, 218 - async fetch(req) { 219 - const url = new URL(req.url); 224 + if (!didBootstrap) { 225 + serve({ 226 + port: PORT, 227 + development: { console: false }, 228 + async fetch(req) { 229 + const url = new URL(req.url); 220 230 221 - if (url.pathname.startsWith("/api/")) { 222 - if (url.pathname === "/api/ping") 223 - return json({ ok: true, ts: Date.now() }); 231 + if (url.pathname.startsWith("/api/")) { 232 + if (url.pathname === "/api/ping") 233 + return json({ ok: true, ts: Date.now() }); 224 234 225 - if (url.pathname === "/api/auth/start" && req.method === "POST") { 226 - const { verifier, challenge } = await pkcePair(); 227 - const authUrl = authorizeUrl(verifier, challenge); 228 - return json({ authUrl, verifier }); 229 - } 235 + if (url.pathname === "/api/auth/start" && req.method === "POST") { 236 + const { verifier, challenge } = await pkcePair(); 237 + const authUrl = authorizeUrl(verifier, challenge); 238 + return json({ authUrl, verifier }); 239 + } 230 240 231 - if (url.pathname === "/api/auth/complete" && req.method === "POST") { 232 - const body = (await req.json().catch(() => ({}))) as { 233 - code?: string; 234 - verifier?: string; 235 - }; 236 - const code = String(body.code ?? ""); 237 - const verifier = String(body.verifier ?? ""); 238 - if (!code || !verifier) 239 - return json({ error: "missing code or verifier" }, { status: 400 }); 240 - const tokens = await exchangeAuthorizationCode(code, verifier); 241 - const expiresAt = 242 - Math.floor(Date.now() / 1000) + (tokens.expires_in ?? 0); 243 - const entry = { 244 - accessToken: tokens.access_token, 245 - refreshToken: tokens.refresh_token, 246 - expiresAt, 247 - }; 248 - memory.set("tokens", entry); 249 - await saveToDisk(entry); 250 - Bun.write(Bun.stdout, `${entry.accessToken}\n`); 251 - setTimeout(() => process.exit(0), 100); 252 - return json({ ok: true }); 253 - } 241 + if (url.pathname === "/api/auth/complete" && req.method === "POST") { 242 + const body = (await req.json().catch(() => ({}))) as { 243 + code?: string; 244 + verifier?: string; 245 + }; 246 + const code = String(body.code ?? ""); 247 + const verifier = String(body.verifier ?? ""); 248 + if (!code || !verifier) 249 + return json({ error: "missing code or verifier" }, { status: 400 }); 250 + const tokens = await exchangeAuthorizationCode(code, verifier); 251 + const expiresAt = 252 + Math.floor(Date.now() / 1000) + (tokens.expires_in ?? 0); 253 + const entry = { 254 + accessToken: tokens.access_token, 255 + refreshToken: tokens.refresh_token, 256 + expiresAt, 257 + }; 258 + memory.set("tokens", entry); 259 + await saveToDisk(entry); 260 + Bun.write(Bun.stdout, `${entry.accessToken}\n`); 261 + setTimeout(() => process.exit(0), 100); 262 + return json({ ok: true }); 263 + } 254 264 255 - if (url.pathname === "/api/token" && req.method === "GET") { 256 - let entry = memory.get("tokens"); 257 - if (!entry) { 258 - const disk = await loadFromDisk(); 259 - if (disk) { 260 - entry = disk; 265 + if (url.pathname === "/api/token" && req.method === "GET") { 266 + let entry = memory.get("tokens"); 267 + if (!entry) { 268 + const disk = await loadFromDisk(); 269 + if (disk) { 270 + entry = disk; 271 + memory.set("tokens", entry); 272 + } 273 + } 274 + if (!entry) 275 + return json({ error: "not_authenticated" }, { status: 401 }); 276 + const now = Math.floor(Date.now() / 1000); 277 + if (now >= entry.expiresAt - 60) { 278 + const refreshed = await exchangeRefreshToken(entry.refreshToken); 279 + entry.accessToken = refreshed.access_token; 280 + entry.expiresAt = 281 + Math.floor(Date.now() / 1000) + refreshed.expires_in; 282 + if (refreshed.refresh_token) 283 + entry.refreshToken = refreshed.refresh_token; 261 284 memory.set("tokens", entry); 285 + await saveToDisk(entry); 262 286 } 287 + return json({ 288 + accessToken: entry.accessToken, 289 + expiresAt: entry.expiresAt, 290 + }); 263 291 } 264 - if (!entry) 265 - return json({ error: "not_authenticated" }, { status: 401 }); 266 - const now = Math.floor(Date.now() / 1000); 267 - if (now >= entry.expiresAt - 60) { 268 - const refreshed = await exchangeRefreshToken(entry.refreshToken); 269 - entry.accessToken = refreshed.access_token; 270 - entry.expiresAt = 271 - Math.floor(Date.now() / 1000) + refreshed.expires_in; 272 - if (refreshed.refresh_token) 273 - entry.refreshToken = refreshed.refresh_token; 274 - memory.set("tokens", entry); 275 - await saveToDisk(entry); 276 - } 277 - return json({ 278 - accessToken: entry.accessToken, 279 - expiresAt: entry.expiresAt, 280 - }); 292 + 293 + return notFound(); 281 294 } 282 295 296 + const staticResp = await serveStatic(url.pathname); 297 + if (staticResp) return staticResp; 298 + 283 299 return notFound(); 284 - } 300 + }, 301 + error() {}, 302 + }); 285 303 286 - const staticResp = await serveStatic(url.pathname); 287 - if (staticResp) return staticResp; 288 - 289 - return notFound(); 290 - }, 291 - error() {}, 292 - }); 293 - 294 - if (!serverStarted) { 295 - serverStarted = true; 296 - const url = `http://localhost:${PORT}`; 297 - const tryRun = async (cmd: string, ...args: string[]) => { 298 - try { 299 - await Bun.$`${[cmd, ...args]}`.quiet(); 300 - return true; 301 - } catch { 302 - return false; 303 - } 304 - }; 305 - (async () => { 306 - if (process.platform === "darwin") { 307 - if (await tryRun("open", url)) return; 308 - } else if (process.platform === "win32") { 309 - if (await tryRun("cmd", "/c", "start", "", url)) return; 310 - } else { 311 - if (await tryRun("xdg-open", url)) return; 312 - } 313 - })(); 304 + if (!serverStarted) { 305 + serverStarted = true; 306 + const url = `http://localhost:${PORT}`; 307 + const tryRun = async (cmd: string, ...args: string[]) => { 308 + try { 309 + await Bun.$`${[cmd, ...args]}`.quiet(); 310 + return true; 311 + } catch { 312 + return false; 313 + } 314 + }; 315 + (async () => { 316 + if (process.platform === "darwin") { 317 + if (await tryRun("open", url)) return; 318 + } else if (process.platform === "win32") { 319 + if (await tryRun("cmd", "/c", "start", "", url)) return; 320 + } else { 321 + if (await tryRun("xdg-open", url)) return; 322 + } 323 + })(); 324 + } 314 325 }
-23
bin/open.ts
··· 1 - #!/usr/bin/env bun 2 - 3 - const PORT = Number(Bun.env.PORT || 8787); 4 - 5 - async function open(url: string) { 6 - const tryRun = async (cmd: string, ...args: string[]) => { 7 - try { 8 - await Bun.$`${[cmd, ...args]}`.quiet(); 9 - return true; 10 - } catch { 11 - return false; 12 - } 13 - }; 14 - if (process.platform === "darwin") { 15 - if (await tryRun("open", url)) return; 16 - } else if (process.platform === "win32") { 17 - if (await tryRun("cmd", "/c", "start", "", url)) return; 18 - } else { 19 - if (await tryRun("xdg-open", url)) return; 20 - } 21 - } 22 - 23 - await open(`http://localhost:${PORT}`);
+1 -1
package.json
··· 1 1 { 2 2 "name": "anthropic-api-key", 3 - "version": "0.1.1", 3 + "version": "0.1.2", 4 4 "description": "CLI to fetch Anthropic API access tokens via OAuth with PKCE using Bun.", 5 5 "type": "module", 6 6 "private": false,
-1
src/server.ts
··· 1 - export {};