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! :)
10
fork

Configure Feed

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

at f4056f7901fca651977b2b95bfefefe9e685a00a 185 lines 5.0 kB view raw
1import { z } from 'zod'; 2 3/** 4 * Validation schema for AT Protocol handle 5 * Supports standard Bluesky handles (user.bsky.social) and custom domains (example.com) 6 */ 7export const handleSchema = z 8 .string() 9 .min(1, 'Handle cannot be empty') 10 .regex( 11 /^([a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\.)+[a-zA-Z]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?$/, 12 'Invalid handle format. Must be a valid domain (e.g., user.bsky.social or example.com)' 13 ); 14 15/** 16 * Validation schema for AT Protocol DID 17 */ 18export const didSchema = z 19 .string() 20 .min(1, 'DID cannot be empty') 21 .regex( 22 /^did:[a-z]+:[a-zA-Z0-9._:%-]*[a-zA-Z0-9._-]$/, 23 'Invalid DID format. Must start with "did:" followed by method and identifier' 24 ); 25 26/** 27 * Validation schema for app password 28 * AT Protocol app passwords are typically 19 characters with dashes 29 */ 30export const appPasswordSchema = z 31 .string() 32 .min(1, 'Password cannot be empty') 33 .max(1000, 'Password is too long'); 34 35/** 36 * Validation schema for identifier (handle or DID) 37 */ 38export const identifierSchema = z.union([handleSchema, didSchema]); 39 40/** 41 * Validate a handle 42 * @throws {z.ZodError} if validation fails 43 */ 44export function validateHandle(handle: string): string { 45 return handleSchema.parse(handle); 46} 47 48/** 49 * Validate a DID 50 * @throws {z.ZodError} if validation fails 51 */ 52export function validateDid(did: string): string { 53 return didSchema.parse(did); 54} 55 56/** 57 * Validate an identifier (handle or DID) 58 * @throws {z.ZodError} if validation fails 59 */ 60export function validateIdentifier(identifier: string): string { 61 return identifierSchema.parse(identifier); 62} 63 64/** 65 * Validate an app password 66 * @throws {z.ZodError} if validation fails 67 */ 68export function validateAppPassword(password: string): string { 69 return appPasswordSchema.parse(password); 70} 71 72/** 73 * Safe validation that returns success/error instead of throwing 74 */ 75export function safeValidateHandle( 76 handle: string 77): { success: true; data: string } | { success: false; error: string } { 78 const result = handleSchema.safeParse(handle); 79 if (result.success) { 80 return { success: true, data: result.data }; 81 } 82 return { success: false, error: result.error.issues[0]?.message ?? 'Validation failed' }; 83} 84 85/** 86 * Safe validation that returns success/error instead of throwing 87 */ 88export function safeValidateDid( 89 did: string 90): { success: true; data: string } | { success: false; error: string } { 91 const result = didSchema.safeParse(did); 92 if (result.success) { 93 return { success: true, data: result.data }; 94 } 95 return { success: false, error: result.error.issues[0]?.message ?? 'Validation failed' }; 96} 97 98/** 99 * Safe validation that returns success/error instead of throwing 100 */ 101export function safeValidateIdentifier( 102 identifier: string 103): { success: true; data: string } | { success: false; error: string } { 104 const result = identifierSchema.safeParse(identifier); 105 if (result.success) { 106 return { success: true, data: result.data }; 107 } 108 return { success: false, error: result.error.issues[0]?.message ?? 'Validation failed' }; 109} 110 111/** 112 * Validation schema for Tangled-specific DID (did:plc: format only) 113 */ 114export const tangledDidSchema = z 115 .string() 116 .regex(/^did:plc:[a-z0-9]+$/, 'Invalid Tangled DID format. Expected: did:plc:...'); 117 118/** 119 * Check if a string is a valid AT Protocol handle 120 * Returns true/false without throwing 121 */ 122export function isValidHandle(handle: string): boolean { 123 return handleSchema.safeParse(handle).success; 124} 125 126/** 127 * Check if a string is a valid Tangled DID (did:plc: format) 128 * Returns true/false without throwing 129 */ 130export function isValidTangledDid(did: string): boolean { 131 return tangledDidSchema.safeParse(did).success; 132} 133 134/** 135 * Validation schema for issue title 136 * Titles must be 1-256 characters 137 */ 138export const issueTitleSchema = z 139 .string() 140 .min(1, 'Issue title cannot be empty') 141 .max(256, 'Issue title must be 256 characters or less'); 142 143/** 144 * Validation schema for issue body 145 * Body is optional but limited to 50,000 characters 146 */ 147export const issueBodySchema = z 148 .string() 149 .max(50000, 'Issue body must be 50,000 characters or less') 150 .optional(); 151 152/** 153 * Validation schema for AT-URI 154 * Format: at://did:method:identifier/collection[/rkey] 155 */ 156export const atUriSchema = z 157 .string() 158 .regex( 159 /^at:\/\/did:[a-z]+:[a-zA-Z0-9._:%-]+\/[a-zA-Z0-9._-]+(?:\.[a-zA-Z0-9._-]+)*(?:\/[a-zA-Z0-9._-]+)?$/, 160 'Invalid AT-URI format. Expected: at://did:method:id/collection[/rkey]', 161 ); 162 163/** 164 * Validate an issue title 165 * @throws {z.ZodError} if validation fails 166 */ 167export function validateIssueTitle(title: string): string { 168 return issueTitleSchema.parse(title); 169} 170 171/** 172 * Validate an issue body 173 * @throws {z.ZodError} if validation fails 174 */ 175export function validateIssueBody(body: string): string { 176 return issueBodySchema.parse(body) ?? ''; 177} 178 179/** 180 * Check if a string is a valid AT-URI 181 * Returns true/false without throwing 182 */ 183export function isValidAtUri(uri: string): boolean { 184 return atUriSchema.safeParse(uri).success; 185}