···
204
-
await bootstrapFromDisk();
204
+
const didBootstrap = await bootstrapFromDisk();
const argv = process.argv.slice(2);
if (argv.includes("-h") || argv.includes("--help")) {
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`);
211
+
` anthropic Start UI and flow; prints token on success and exits.\n`,
215
+
` PORT=xxxx anthropic Override port (default 8787).\n`,
219
+
`\nTokens are cached at ~/.config/crush/anthropic and reused on later runs.\n`,
217
-
development: { console: false },
219
-
const url = new URL(req.url);
224
+
if (!didBootstrap) {
227
+
development: { console: false },
229
+
const url = new URL(req.url);
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() });
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 });
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 });
231
-
if (url.pathname === "/api/auth/complete" && req.method === "POST") {
232
-
const body = (await req.json().catch(() => ({}))) as {
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);
242
-
Math.floor(Date.now() / 1000) + (tokens.expires_in ?? 0);
244
-
accessToken: tokens.access_token,
245
-
refreshToken: tokens.refresh_token,
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 });
241
+
if (url.pathname === "/api/auth/complete" && req.method === "POST") {
242
+
const body = (await req.json().catch(() => ({}))) as {
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);
252
+
Math.floor(Date.now() / 1000) + (tokens.expires_in ?? 0);
254
+
accessToken: tokens.access_token,
255
+
refreshToken: tokens.refresh_token,
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 });
255
-
if (url.pathname === "/api/token" && req.method === "GET") {
256
-
let entry = memory.get("tokens");
258
-
const disk = await loadFromDisk();
265
+
if (url.pathname === "/api/token" && req.method === "GET") {
266
+
let entry = memory.get("tokens");
268
+
const disk = await loadFromDisk();
271
+
memory.set("tokens", 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;
281
+
Math.floor(Date.now() / 1000) + refreshed.expires_in;
282
+
if (refreshed.refresh_token)
283
+
entry.refreshToken = refreshed.refresh_token;
memory.set("tokens", entry);
285
+
await saveToDisk(entry);
288
+
accessToken: entry.accessToken,
289
+
expiresAt: entry.expiresAt,
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;
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);
278
-
accessToken: entry.accessToken,
279
-
expiresAt: entry.expiresAt,
296
+
const staticResp = await serveStatic(url.pathname);
297
+
if (staticResp) return staticResp;
286
-
const staticResp = await serveStatic(url.pathname);
287
-
if (staticResp) return staticResp;
294
-
if (!serverStarted) {
295
-
serverStarted = true;
296
-
const url = `http://localhost:${PORT}`;
297
-
const tryRun = async (cmd: string, ...args: string[]) => {
299
-
await Bun.$`${[cmd, ...args]}`.quiet();
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;
311
-
if (await tryRun("xdg-open", url)) return;
304
+
if (!serverStarted) {
305
+
serverStarted = true;
306
+
const url = `http://localhost:${PORT}`;
307
+
const tryRun = async (cmd: string, ...args: string[]) => {
309
+
await Bun.$`${[cmd, ...args]}`.quiet();
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;
321
+
if (await tryRun("xdg-open", url)) return;