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