A CLI for publishing standard.site documents to ATProto sequoia.pub
standard site lexicon cli publishing
at main 94 lines 2.4 kB view raw
1import { 2 NodeOAuthClient, 3 type NodeOAuthClientOptions, 4} from "@atproto/oauth-client-node"; 5import { sessionStore, stateStore } from "./oauth-store"; 6 7const CALLBACK_PORT = 4000; 8const CALLBACK_HOST = "127.0.0.1"; 9const CALLBACK_URL = `http://${CALLBACK_HOST}:${CALLBACK_PORT}/oauth/callback`; 10 11// OAuth scope for Sequoia CLI - includes atproto base scope plus our collections 12const OAUTH_SCOPE = 13 "atproto repo:site.standard.document repo:site.standard.publication repo:app.bsky.feed.post blob:*/*"; 14 15let oauthClient: NodeOAuthClient | null = null; 16 17// Simple lock implementation for CLI (single process, no contention) 18// This prevents the "No lock mechanism provided" warning 19const locks = new Map<string, Promise<void>>(); 20 21async function requestLock<T>( 22 key: string, 23 fn: () => T | PromiseLike<T>, 24): Promise<T> { 25 // Wait for any existing lock on this key 26 while (locks.has(key)) { 27 await locks.get(key); 28 } 29 30 // Create our lock 31 let resolve: () => void; 32 const lockPromise = new Promise<void>((r) => { 33 resolve = r; 34 }); 35 locks.set(key, lockPromise); 36 37 try { 38 return await fn(); 39 } finally { 40 locks.delete(key); 41 resolve!(); 42 } 43} 44 45/** 46 * Get or create the OAuth client singleton 47 */ 48export async function getOAuthClient(): Promise<NodeOAuthClient> { 49 if (oauthClient) { 50 return oauthClient; 51 } 52 53 // Build client_id with required parameters 54 const clientIdParams = new URLSearchParams(); 55 clientIdParams.append("redirect_uri", CALLBACK_URL); 56 clientIdParams.append("scope", OAUTH_SCOPE); 57 58 const clientOptions: NodeOAuthClientOptions = { 59 clientMetadata: { 60 client_id: `http://localhost?${clientIdParams.toString()}`, 61 client_name: "Sequoia CLI", 62 client_uri: "https://github.com/stevedylandev/sequoia", 63 redirect_uris: [CALLBACK_URL], 64 grant_types: ["authorization_code", "refresh_token"], 65 response_types: ["code"], 66 token_endpoint_auth_method: "none", 67 application_type: "web", 68 scope: OAUTH_SCOPE, 69 dpop_bound_access_tokens: false, 70 }, 71 stateStore, 72 sessionStore, 73 // Configure identity resolution 74 plcDirectoryUrl: "https://plc.directory", 75 // Provide lock mechanism to prevent warning 76 requestLock, 77 }; 78 79 oauthClient = new NodeOAuthClient(clientOptions); 80 81 return oauthClient; 82} 83 84export function getOAuthScope(): string { 85 return OAUTH_SCOPE; 86} 87 88export function getCallbackUrl(): string { 89 return CALLBACK_URL; 90} 91 92export function getCallbackPort(): number { 93 return CALLBACK_PORT; 94}