A CLI for publishing standard.site documents to ATProto sequoia.pub
standard site lexicon cli publishing
at main 161 lines 3.9 kB view raw
1import * as fs from "node:fs/promises"; 2import * as os from "node:os"; 3import * as path from "node:path"; 4import type { 5 NodeSavedSession, 6 NodeSavedSessionStore, 7 NodeSavedState, 8 NodeSavedStateStore, 9} from "@atproto/oauth-client-node"; 10 11const CONFIG_DIR = path.join(os.homedir(), ".config", "sequoia"); 12const OAUTH_FILE = path.join(CONFIG_DIR, "oauth.json"); 13 14interface OAuthStore { 15 states: Record<string, NodeSavedState>; 16 sessions: Record<string, NodeSavedSession>; 17 handles?: Record<string, string>; // DID -> handle mapping (optional for backwards compat) 18} 19 20async function fileExists(filePath: string): Promise<boolean> { 21 try { 22 await fs.access(filePath); 23 return true; 24 } catch { 25 return false; 26 } 27} 28 29async function loadOAuthStore(): Promise<OAuthStore> { 30 if (!(await fileExists(OAUTH_FILE))) { 31 return { states: {}, sessions: {} }; 32 } 33 34 try { 35 const content = await fs.readFile(OAUTH_FILE, "utf-8"); 36 return JSON.parse(content) as OAuthStore; 37 } catch { 38 return { states: {}, sessions: {} }; 39 } 40} 41 42async function saveOAuthStore(store: OAuthStore): Promise<void> { 43 await fs.mkdir(CONFIG_DIR, { recursive: true }); 44 await fs.writeFile(OAUTH_FILE, JSON.stringify(store, null, 2)); 45 await fs.chmod(OAUTH_FILE, 0o600); 46} 47 48/** 49 * State store for PKCE flow (temporary, used during auth) 50 */ 51export const stateStore: NodeSavedStateStore = { 52 async set(key: string, state: NodeSavedState): Promise<void> { 53 const store = await loadOAuthStore(); 54 store.states[key] = state; 55 await saveOAuthStore(store); 56 }, 57 58 async get(key: string): Promise<NodeSavedState | undefined> { 59 const store = await loadOAuthStore(); 60 return store.states[key]; 61 }, 62 63 async del(key: string): Promise<void> { 64 const store = await loadOAuthStore(); 65 delete store.states[key]; 66 await saveOAuthStore(store); 67 }, 68}; 69 70/** 71 * Session store for OAuth tokens (persistent) 72 */ 73export const sessionStore: NodeSavedSessionStore = { 74 async set(sub: string, session: NodeSavedSession): Promise<void> { 75 const store = await loadOAuthStore(); 76 store.sessions[sub] = session; 77 await saveOAuthStore(store); 78 }, 79 80 async get(sub: string): Promise<NodeSavedSession | undefined> { 81 const store = await loadOAuthStore(); 82 return store.sessions[sub]; 83 }, 84 85 async del(sub: string): Promise<void> { 86 const store = await loadOAuthStore(); 87 delete store.sessions[sub]; 88 await saveOAuthStore(store); 89 }, 90}; 91 92/** 93 * List all stored OAuth session DIDs 94 */ 95export async function listOAuthSessions(): Promise<string[]> { 96 const store = await loadOAuthStore(); 97 return Object.keys(store.sessions); 98} 99 100/** 101 * Get an OAuth session by DID 102 */ 103export async function getOAuthSession( 104 did: string, 105): Promise<NodeSavedSession | undefined> { 106 const store = await loadOAuthStore(); 107 return store.sessions[did]; 108} 109 110/** 111 * Delete an OAuth session by DID 112 */ 113export async function deleteOAuthSession(did: string): Promise<boolean> { 114 const store = await loadOAuthStore(); 115 if (!store.sessions[did]) { 116 return false; 117 } 118 delete store.sessions[did]; 119 await saveOAuthStore(store); 120 return true; 121} 122 123export function getOAuthStorePath(): string { 124 return OAUTH_FILE; 125} 126 127/** 128 * Store handle for an OAuth session (DID -> handle mapping) 129 */ 130export async function setOAuthHandle( 131 did: string, 132 handle: string, 133): Promise<void> { 134 const store = await loadOAuthStore(); 135 if (!store.handles) { 136 store.handles = {}; 137 } 138 store.handles[did] = handle; 139 await saveOAuthStore(store); 140} 141 142/** 143 * Get handle for an OAuth session by DID 144 */ 145export async function getOAuthHandle(did: string): Promise<string | undefined> { 146 const store = await loadOAuthStore(); 147 return store.handles?.[did]; 148} 149 150/** 151 * List all stored OAuth sessions with their handles 152 */ 153export async function listOAuthSessionsWithHandles(): Promise< 154 Array<{ did: string; handle?: string }> 155> { 156 const store = await loadOAuthStore(); 157 return Object.keys(store.sessions).map((did) => ({ 158 did, 159 handle: store.handles?.[did], 160 })); 161}