Something went wrong. Try again.
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 fro
Something went wrong. Try again.
3.2 kB · 87 lines
TypeScript
at main
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788import { AtpAgent } from '@atproto/api';import { resolveHandleToDid, resolvePdsFromIdentifier } from './pds-resolver.js';
export { resolveHandleToDid };
/** * Parse an AT-URI into its components * @param uri - AT-URI string (e.g., "at://did:plc:abc/collection/rkey") * @returns Parsed components or null if invalid */export function parseAtUri(uri: string): { did: string; collection: string; rkey?: string;} | null { // AT-URI format: at://did:method:identifier/collection[/rkey] const match = uri.match( /^at:\/\/(did:[a-z]+:[a-zA-Z0-9._:%-]+)\/([a-zA-Z0-9._-]+(?:\.[a-zA-Z0-9._-]+)*)(?:\/([a-zA-Z0-9._-]+))?$/ );
if (!match) { return null; }
const [, did, collection, rkey] = match; return { did, collection, ...(rkey && { rkey }), };}
/** * Resolve a repository identifier to its repo DID (the identifier used in issue records). * Resolves the owner's PDS independently of the caller's authenticated session, * so cross-PDS lookups (e.g., a tngl.sh user looking up a bsky.social repo) work. * * Issue records store `repoDid` in their `.repo` field, not the AT-URI of the * `sh.tangled.repo` record. Falls back to the record's AT-URI if `repoDid` is absent. * * @param ownerDidOrHandle - DID (e.g., "did:plc:abc") or handle (e.g., "mark.bsky.social") * @param repoName - Repository name * @returns repoDid string, or record AT-URI as fallback * @throws Error if repository not found */export async function resolveRepoId(ownerDidOrHandle: string, repoName: string): Promise<string> { // Resolve owner to DID const isDid = ownerDidOrHandle.startsWith('did:'); const did = isDid ? ownerDidOrHandle : await resolveHandleToDid(ownerDidOrHandle);
try { // Resolve the owner's PDS and query their records directly, // bypassing the caller's authenticated agent entirely const ownerPds = await resolvePdsFromIdentifier(did); const ownerAgent = new AtpAgent({ service: ownerPds });
// Query for sh.tangled.repo records // TODO: does not paginate; will silently fail to find a repo for users with >100 repos. const response = await ownerAgent.com.atproto.repo.listRecords({ repo: did, collection: 'sh.tangled.repo', limit: 100, });
// Find the record matching the repo name. // Newer records use the repo name as the rkey; older records store the name // in value.name with an opaque tid as the rkey. Check both. const repoRecord = response.data.records.find((record) => { const rkey = record.uri.split('/').pop(); const recordName = (record.value as { name?: string }).name; return rkey === repoName || recordName === repoName; });
if (!repoRecord) { throw new Error(`Repository '${repoName}' not found for ${ownerDidOrHandle}`); }
// Return repoDid if present; fall back to record URI const repoDid = (repoRecord.value as { repoDid?: string }).repoDid; return repoDid ?? repoRecord.uri; } catch (error) { if (error instanceof Error) { throw new Error(`Failed to resolve repository AT-URI: ${error.message}`); } throw new Error('Failed to resolve repository AT-URI: Unknown error'); }}