A CLI for publishing standard.site documents to ATProto sequoia.pub
standard site lexicon cli publishing
59
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 140 lines 3.8 kB view raw
1import { command, flag } from "cmd-ts"; 2import { select, spinner, log } from "@clack/prompts"; 3import * as path from "node:path"; 4import { loadConfig, findConfig } from "../lib/config"; 5import { 6 loadCredentials, 7 listAllCredentials, 8 getCredentials, 9} from "../lib/credentials"; 10import { getOAuthHandle, getOAuthSession } from "../lib/oauth-store"; 11import { createAgent } from "../lib/atproto"; 12import { syncStateFromPDS } from "../lib/sync"; 13import { exitOnCancel } from "../lib/prompts"; 14 15export const syncCommand = command({ 16 name: "sync", 17 description: "Sync state from ATProto to restore .sequoia-state.json", 18 args: { 19 updateFrontmatter: flag({ 20 long: "update-frontmatter", 21 short: "u", 22 description: "Update frontmatter atUri fields in local markdown files", 23 }), 24 dryRun: flag({ 25 long: "dry-run", 26 short: "n", 27 description: "Preview what would be synced without making changes", 28 }), 29 }, 30 handler: async ({ updateFrontmatter, dryRun }) => { 31 // Load config 32 const configPath = await findConfig(); 33 if (!configPath) { 34 log.error("No sequoia.json found. Run 'sequoia init' first."); 35 process.exit(1); 36 } 37 38 const config = await loadConfig(configPath); 39 const configDir = path.dirname(configPath); 40 41 log.info(`Site: ${config.siteUrl}`); 42 log.info(`Publication: ${config.publicationUri}`); 43 44 // Load credentials 45 let credentials = await loadCredentials(config.identity); 46 47 if (!credentials) { 48 const identities = await listAllCredentials(); 49 if (identities.length === 0) { 50 log.error( 51 "No credentials found. Run 'sequoia login' or 'sequoia auth' first.", 52 ); 53 process.exit(1); 54 } 55 56 // Build labels with handles for OAuth sessions 57 const options = await Promise.all( 58 identities.map(async (cred) => { 59 if (cred.type === "oauth") { 60 const handle = await getOAuthHandle(cred.id); 61 return { 62 value: cred.id, 63 label: `${handle || cred.id} (OAuth)`, 64 }; 65 } 66 return { 67 value: cred.id, 68 label: `${cred.id} (App Password)`, 69 }; 70 }), 71 ); 72 73 log.info("Multiple identities found. Select one to use:"); 74 const selected = exitOnCancel( 75 await select({ 76 message: "Identity:", 77 options, 78 }), 79 ); 80 81 // Load the selected credentials 82 const selectedCred = identities.find((c) => c.id === selected); 83 if (selectedCred?.type === "oauth") { 84 const session = await getOAuthSession(selected); 85 if (session) { 86 const handle = await getOAuthHandle(selected); 87 credentials = { 88 type: "oauth", 89 did: selected, 90 handle: handle || selected, 91 }; 92 } 93 } else { 94 credentials = await getCredentials(selected); 95 } 96 97 if (!credentials) { 98 log.error("Failed to load selected credentials."); 99 process.exit(1); 100 } 101 } 102 103 // Create agent 104 const s = spinner(); 105 const connectingTo = 106 credentials.type === "oauth" ? credentials.handle : credentials.pdsUrl; 107 s.start(`Connecting as ${connectingTo}...`); 108 let agent: Awaited<ReturnType<typeof createAgent>> | undefined; 109 try { 110 agent = await createAgent(credentials); 111 s.stop(`Logged in as ${agent.did}`); 112 } catch (error) { 113 s.stop("Failed to login"); 114 log.error(`Failed to login: ${error}`); 115 process.exit(1); 116 } 117 118 // Sync state from PDS 119 s.start("Fetching documents from PDS..."); 120 const result = await syncStateFromPDS(agent, config, configDir, { 121 updateFrontmatter, 122 dryRun, 123 quiet: false, 124 }); 125 s.stop(`Found documents on PDS`); 126 127 if (!dryRun) { 128 const stateCount = Object.keys(result.state.posts).length; 129 log.success(`\nSaved .sequoia-state.json (${stateCount} entries)`); 130 131 if (result.frontmatterUpdatesApplied > 0) { 132 log.success( 133 `Updated frontmatter in ${result.frontmatterUpdatesApplied} files`, 134 ); 135 } 136 } 137 138 log.success("\nSync complete!"); 139 }, 140});