export type AppPasswordScopePreset = "full" | "readonly" | "post" | "custom"; export const READ_ONLY_APP_PASSWORD_SCOPES = "account:*?action=read"; export const POST_ONLY_APP_PASSWORD_SCOPES = "repo:*?action=create blob:*/*"; export function normalizeAppPasswordScopes(scopes: string): string { return [...new Set(scopes.trim().split(/\s+/).filter(Boolean))] .sort() .join(" "); } export function classifyAppPasswordScopes( scopes: string | null | undefined, ): AppPasswordScopePreset { if (!scopes) return "full"; const normalized = normalizeAppPasswordScopes(scopes); if (normalized.split(" ").includes("transition:generic")) return "full"; const presetScopes = normalizeAppPasswordScopes( normalized .split(" ") .filter((scope) => scope !== "atproto") .join(" "), ); if (presetScopes === normalizeAppPasswordScopes(READ_ONLY_APP_PASSWORD_SCOPES)) { return "readonly"; } if (presetScopes === normalizeAppPasswordScopes(POST_ONLY_APP_PASSWORD_SCOPES)) { return "post"; } return "custom"; }