WIP: A simple cli for daily tangled use cases and AI integration. This is for my personal use right now, but happy if others get mileage from it! :)
at main 105 lines 3.3 kB view raw
1import type { TangledApiClient } from '../lib/api-client.js'; 2 3/** 4 * Parse an AT-URI into its components 5 * @param uri - AT-URI string (e.g., "at://did:plc:abc/collection/rkey") 6 * @returns Parsed components or null if invalid 7 */ 8export function parseAtUri(uri: string): { 9 did: string; 10 collection: string; 11 rkey?: string; 12} | null { 13 // AT-URI format: at://did:method:identifier/collection[/rkey] 14 const match = uri.match( 15 /^at:\/\/(did:[a-z]+:[a-zA-Z0-9._:%-]+)\/([a-zA-Z0-9._-]+(?:\.[a-zA-Z0-9._-]+)*)(?:\/([a-zA-Z0-9._-]+))?$/ 16 ); 17 18 if (!match) { 19 return null; 20 } 21 22 const [, did, collection, rkey] = match; 23 return { 24 did, 25 collection, 26 ...(rkey && { rkey }), 27 }; 28} 29 30/** 31 * Resolve a handle to a DID using the AT Protocol identity resolution 32 * @param handle - Handle string (e.g., "mark.bsky.social" or "@mark.bsky.social") 33 * @param client - Authenticated API client 34 * @returns DID string (e.g., "did:plc:abc123") 35 * @throws Error if handle cannot be resolved 36 */ 37export async function resolveHandleToDid( 38 handle: string, 39 client: TangledApiClient 40): Promise<string> { 41 // Strip leading @ if present 42 const cleanHandle = handle.startsWith('@') ? handle.slice(1) : handle; 43 44 try { 45 const response = await client.getAgent().com.atproto.identity.resolveHandle({ 46 handle: cleanHandle, 47 }); 48 49 if (!response.data.did) { 50 throw new Error(`No DID found for handle: ${cleanHandle}`); 51 } 52 53 return response.data.did; 54 } catch (error) { 55 if (error instanceof Error) { 56 throw new Error(`Failed to resolve handle '${cleanHandle}': ${error.message}`); 57 } 58 throw new Error(`Failed to resolve handle '${cleanHandle}': Unknown error`); 59 } 60} 61 62/** 63 * Build a repository AT-URI from owner and repository name 64 * @param ownerDidOrHandle - DID (e.g., "did:plc:abc") or handle (e.g., "mark.bsky.social") 65 * @param repoName - Repository name 66 * @param client - Authenticated API client 67 * @returns AT-URI string (e.g., "at://did:plc:abc/sh.tangled.repo/3mef23waqwq22") 68 * @throws Error if repository not found 69 */ 70export async function buildRepoAtUri( 71 ownerDidOrHandle: string, 72 repoName: string, 73 client: TangledApiClient 74): Promise<string> { 75 // Resolve owner to DID 76 const isDid = ownerDidOrHandle.startsWith('did:'); 77 const did = isDid ? ownerDidOrHandle : await resolveHandleToDid(ownerDidOrHandle, client); 78 79 try { 80 // Query for sh.tangled.repo records 81 const response = await client.getAgent().com.atproto.repo.listRecords({ 82 repo: did, 83 collection: 'sh.tangled.repo', 84 limit: 100, // Reasonable limit for most users 85 }); 86 87 // Find the record matching the repo name 88 const repoRecord = response.data.records.find((record) => { 89 const recordData = record.value as { name?: string }; 90 return recordData.name === repoName; 91 }); 92 93 if (!repoRecord) { 94 throw new Error(`Repository '${repoName}' not found for ${ownerDidOrHandle}`); 95 } 96 97 // Return the record's URI (which includes the correct rkey) 98 return repoRecord.uri; 99 } catch (error) { 100 if (error instanceof Error) { 101 throw new Error(`Failed to resolve repository AT-URI: ${error.message}`); 102 } 103 throw new Error('Failed to resolve repository AT-URI: Unknown error'); 104 } 105}