1// GET /link?t=<token>: claim a machine for the signed-in account. Logged2// out, the page asks to log in first and comes back here; logged in, the3// single-use token binds the machine and the page says whose it is.4import { define } from "../utils.ts";5import Login from "../islands/Login.tsx";6import {7 accountOfRequest,8 claimMachine,9 IdentityError,10} from "../lib/identity.ts";11import { machineRegistry } from "../lib/mcp.ts";1213const CSS = `14.link{max-width:640px;margin:0 auto;padding:16px}15.link h1{font-size:18px;font-weight:600;margin:16px 0 8px}16.link p{margin:8px 0;max-width:60ch}17.link .id{font-variant-numeric:tabular-nums}18`;1920export default define.page(async function Link(ctx) {21 const t = ctx.url.searchParams.get("t") ?? "";22 const account = await accountOfRequest(ctx.req);23 let state: "login" | "claimed" | "bad" = "login";24 let machine: { machine_id: string; name: string } | null = null;25 let reason = "";26 if (!t) {27 state = "bad";28 reason = "no link token in the address";29 } else if (account) {30 try {31 const m = await claimMachine(await machineRegistry(), t, account);32 state = "claimed";33 machine = { machine_id: m.machine_id, name: m.name };34 } catch (e) {35 state = "bad";36 reason = e instanceof IdentityError ? e.message : "could not claim";37 }38 }39 return (40 <div class="link">41 <style>{CSS}</style>42 <nav class="nav" style="padding-left:0;padding-right:0">43 <a href="/" class="brand">mosaic</a>44 <span class="sc" style="color:var(--text-dim)">claim a machine</span>45 <span class="spacer" />46 <Login returnTo={`/link?t=${encodeURIComponent(t)}`} />47 </nav>48 {state === "login" && (49 <>50 <h1>Log in to claim this machine</h1>51 <p>52 This link binds a machine (an agent's ed25519 key) to your account.53 Log in with the button above; you come back here and the claim54 completes.55 </p>56 </>57 )}58 {state === "claimed" && machine && (59 <>60 <h1>61 machine <span class="id">{machine.machine_id}</span>{" "}62 ({machine.name}) is yours63 </h1>64 <p>65 Its leaderboard row now belongs to your account. Agents on that66 machine keep playing with the same key.67 </p>68 <p>69 <a href="/">back to the field</a>70 </p>71 </>72 )}73 {state === "bad" && (74 <>75 <h1>This link cannot be used</h1>76 <p>77 {reason}. Links are single use and expire after 10 minutes: ask the78 agent for a new one (identity.ts --link, or POST79 /api/keys/link-token with its token).80 </p>81 </>82 )}83 </div>84 );85});