A simple tool which lets you scrape twitter accounts and crosspost them to bluesky accounts! Comes with a CLI and a webapp for managing profiles! Works with images/videos/link embeds/threads.

chore: shorten alt text prompt

Jack G ba862fc0 86015a55

+28 -12
+28 -12
src/ai-manager.ts
··· 43 43 } 44 44 45 45 try { 46 + const prompt = buildAltTextPrompt(contextText); 46 47 switch (provider) { 47 48 case 'gemini': 48 49 // apiKey is guaranteed by check above 49 - return await callGemini(apiKey!, model || 'models/gemini-2.5-flash', buffer, mimeType, contextText); 50 + return await callGemini(apiKey!, model || 'models/gemini-2.5-flash', buffer, mimeType, prompt); 50 51 case 'openai': 51 52 case 'custom': 52 - return await callOpenAICompatible(apiKey, model || 'gpt-4o', baseUrl, buffer, mimeType, contextText); 53 + return await callOpenAICompatible(apiKey, model || 'gpt-4o', baseUrl, buffer, mimeType, prompt); 53 54 case 'anthropic': 54 55 // apiKey is guaranteed by check above 55 56 return await callAnthropic( ··· 58 59 baseUrl, 59 60 buffer, 60 61 mimeType, 61 - contextText, 62 + prompt, 62 63 ); 63 64 default: 64 65 console.warn(`[AI] ⚠️ Unknown provider: ${provider}`); ··· 70 71 } 71 72 } 72 73 74 + const ALT_TEXT_CONTEXT_MAX_CHARS = 400; 75 + 76 + function buildAltTextPrompt(contextText: string): string { 77 + const normalized = contextText.replace(/\s+/g, ' ').trim(); 78 + const trimmed = 79 + normalized.length > ALT_TEXT_CONTEXT_MAX_CHARS 80 + ? `${normalized.slice(0, ALT_TEXT_CONTEXT_MAX_CHARS).trim()}...` 81 + : normalized; 82 + 83 + return [ 84 + 'Write alt text (1-2 sentences).', 85 + 'Describe only what is visible.', 86 + 'Use context to identify people/places/objects if relevant.', 87 + 'Include key names for search.', 88 + 'No hashtags or emojis.', 89 + `Context: "${trimmed}"`, 90 + ].join(' '); 91 + } 92 + 73 93 async function callGemini( 74 94 apiKey: string, 75 95 modelName: string, 76 96 buffer: Buffer, 77 97 mimeType: string, 78 - contextText: string, 98 + prompt: string, 79 99 ): Promise<string | undefined> { 80 100 const genAI = new GoogleGenerativeAI(apiKey); 81 101 const model = genAI.getGenerativeModel({ model: modelName }); 82 102 83 - const prompt = `Describe this image for alt text. Be concise but descriptive. 84 - Context from the tweet text: "${contextText}". 85 - Use the context to identify specific people, objects, or context mentioned, but describe what is visually present in the image.`; 86 - 87 103 const result = await model.generateContent([ 88 104 prompt, 89 105 { ··· 103 119 baseUrl: string | undefined, 104 120 buffer: Buffer, 105 121 mimeType: string, 106 - contextText: string, 122 + prompt: string, 107 123 ): Promise<string | undefined> { 108 124 const url = baseUrl 109 125 ? `${baseUrl.replace(/\/+$/, '')}/chat/completions` ··· 119 135 content: [ 120 136 { 121 137 type: 'text', 122 - text: `Describe this image for alt text. Be concise but descriptive. Context from the tweet text: "${contextText}".`, 138 + text: prompt, 123 139 }, 124 140 { 125 141 type: 'image_url', ··· 158 174 baseUrl: string | undefined, 159 175 buffer: Buffer, 160 176 mimeType: string, 161 - contextText: string, 177 + prompt: string, 162 178 ): Promise<string | undefined> { 163 179 const url = baseUrl ? `${baseUrl.replace(/\/+$/, '')}/v1/messages` : 'https://api.anthropic.com/v1/messages'; 164 180 ··· 181 197 }, 182 198 { 183 199 type: 'text', 184 - text: `Describe this image for alt text. Be concise but descriptive. Context from the tweet text: "${contextText}".`, 200 + text: prompt, 185 201 }, 186 202 ], 187 203 },