···11+import type { ActorIdentifier } from "@atcute/lexicons";
12import { Elysia } from "elysia";
22-import { OAUTH_SCOPE } from "../lib/constants.ts";
33import { escapeHtml } from "../lib/html.ts";
44import { fmt, resolveLocale, t } from "../lib/i18n/index.ts";
55import { LIMITS } from "../lib/limits.ts";
66import { getClientIp, rateLimit } from "../lib/rate-limit.ts";
77import { htmlResponse } from "../lib/response.ts";
88import { loginPage } from "../views/login.ts";
99-import { getDevAccounts, getDevPdsUrl, getHandleResolverUrl } from "./env.ts";
99+import { getDevAccounts, getDevPdsUrl } from "./env.ts";
1010import { getClient } from "./session.ts";
11111212function getSafeReturnTo(cookieHeader: string | null): string {
···1515 return raw?.startsWith("/") && !raw.includes("//") ? raw : "/";
1616}
17171818-async function resolveHandle(handle: string): Promise<string> {
1919- const base = getHandleResolverUrl();
2020- const resp = await fetch(
2121- `${base}/xrpc/com.atproto.identity.resolveHandle?handle=${encodeURIComponent(handle)}`,
2222- );
2323- if (!resp.ok) {
2424- throw new Error(`Failed to resolve handle: ${handle}`);
2525- }
2626- const data = (await resp.json()) as { did: string };
2727- return data.did;
2828-}
2929-3018export function atprotoRoutes() {
3119 const app = new Elysia()
3220 .get("/client-metadata.json", async () => {
···3422 if (!client) {
3523 return new Response("OAuth not configured", { status: 503 });
3624 }
3737- return new Response(JSON.stringify(client.clientMetadata), {
2525+ return new Response(JSON.stringify(client.metadata), {
3826 headers: { "Content-Type": "application/json" },
3927 });
4028 })
···9684 }
97859886 try {
9999- const did = await resolveHandle(handle);
100100- const authUrl = await client.authorize(did, {
101101- scope: OAUTH_SCOPE,
8787+ const { url } = await client.authorize({
8888+ target: {
8989+ type: "account",
9090+ identifier: handle as ActorIdentifier,
9191+ },
10292 });
103103- return Response.redirect(authUrl.toString());
9393+ return Response.redirect(url.toString());
10494 } catch {
10595 return htmlResponse(
10696 loginPage({
+19-22
src/atproto/session.ts
···11-import { Agent, AtpAgent } from "@atproto/api";
22-import type { NodeOAuthClient, OAuthSession } from "@atproto/oauth-client-node";
11+import { Client } from "@atcute/client";
22+import type { Did } from "@atcute/lexicons/syntax";
33+import type { OAuthClient, OAuthSession } from "@atcute/oauth-node-client";
44+import { PasswordSession } from "@atcute/password-session";
35import { resolveProfile } from "../lib/profile.ts";
46import { createOAuthClient } from "./client.ts";
57import { getAtprotoEnv, getDevAccounts, getDevPdsUrl } from "./env.ts";
···1113}
12141315async function getSession(
1414- client: NodeOAuthClient,
1616+ client: OAuthClient,
1517 cookieHeader: string | undefined,
1618): Promise<Session | null> {
1719 if (!cookieHeader) return null;
···2224 const did = decodeURIComponent(didMatch[1]);
23252426 try {
2525- const oauthSession = await client.restore(did);
2727+ const oauthSession = await client.restore(did as Did);
2628 const profile = await resolveProfile(did);
2729 return { did, handle: profile.handle ?? did, oauthSession };
2830 } catch {
···32343335/**
3436 * Get a dev-mode session from the DID cookie when OAuth is not configured.
3535- * Returns a Session without oauthSession — use createAgent() to get an
3636- * agent that authenticates via app password against the dev PDS.
3737+ * Returns a Session without oauthSession — use getAgent() to get an
3838+ * authenticated client that uses an app-password session against the dev PDS.
3739 */
3840function getDevSession(cookieHeader: string | undefined): Session | null {
3941 if (!cookieHeader) return null;
···5254}
53555456/**
5555- * Get a fully authenticated agent for the session.
5757+ * Get an RPC client authenticated for the session.
5658 * In production: wraps the OAuth session.
5757- * In dev-full mode (no oauthSession): logs in with app password.
5858- * Use this in orchestrators instead of createAgent().
5959+ * In dev-full mode (no oauthSession): logs in with the configured app password.
5960 */
6060-export async function getAgent(session: Session): Promise<Agent> {
6161+export async function getAgent(session: Session): Promise<Client> {
6162 if (session.oauthSession) {
6262- return new Agent(session.oauthSession);
6363+ return new Client({ handler: session.oauthSession });
6364 }
6464- return loginDevAgent(session);
6565+ return loginDevClient(session);
6566}
66676767-/**
6868- * Create and log in a dev-mode agent. Use this instead of createAgent()
6969- * when in dev mode (session.oauthSession is undefined).
7070- */
7171-async function loginDevAgent(session: Session): Promise<Agent> {
6868+async function loginDevClient(session: Session): Promise<Client> {
7269 const devPdsUrl = getDevPdsUrl();
7370 if (!devPdsUrl) {
7471 throw new Error("DEV_PDS_URL not configured");
···8481 throw new Error(`No dev account for DID: ${session.did}`);
8582 }
86838787- const agent = new AtpAgent({ service: devPdsUrl });
8888- await agent.login({
8484+ const passwordSession = await PasswordSession.login({
8585+ service: devPdsUrl,
8986 identifier: account.handle,
9087 password: account.password,
9188 });
9292- return agent as unknown as Agent;
8989+ return new Client({ handler: passwordSession });
9390}
94919595-let oauthClient: NodeOAuthClient | null = null;
9292+let oauthClient: OAuthClient | null = null;
96939797-export async function getClient(): Promise<NodeOAuthClient | null> {
9494+export async function getClient(): Promise<OAuthClient | null> {
9895 if (oauthClient) return oauthClient;
9996 const env = getAtprotoEnv();
10097 if (!env) return null;