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 } 44 45 try { 46 switch (provider) { 47 case 'gemini': 48 // apiKey is guaranteed by check above 49 - return await callGemini(apiKey!, model || 'models/gemini-2.5-flash', buffer, mimeType, contextText); 50 case 'openai': 51 case 'custom': 52 - return await callOpenAICompatible(apiKey, model || 'gpt-4o', baseUrl, buffer, mimeType, contextText); 53 case 'anthropic': 54 // apiKey is guaranteed by check above 55 return await callAnthropic( ··· 58 baseUrl, 59 buffer, 60 mimeType, 61 - contextText, 62 ); 63 default: 64 console.warn(`[AI] ⚠️ Unknown provider: ${provider}`); ··· 70 } 71 } 72 73 async function callGemini( 74 apiKey: string, 75 modelName: string, 76 buffer: Buffer, 77 mimeType: string, 78 - contextText: string, 79 ): Promise<string | undefined> { 80 const genAI = new GoogleGenerativeAI(apiKey); 81 const model = genAI.getGenerativeModel({ model: modelName }); 82 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 const result = await model.generateContent([ 88 prompt, 89 { ··· 103 baseUrl: string | undefined, 104 buffer: Buffer, 105 mimeType: string, 106 - contextText: string, 107 ): Promise<string | undefined> { 108 const url = baseUrl 109 ? `${baseUrl.replace(/\/+$/, '')}/chat/completions` ··· 119 content: [ 120 { 121 type: 'text', 122 - text: `Describe this image for alt text. Be concise but descriptive. Context from the tweet text: "${contextText}".`, 123 }, 124 { 125 type: 'image_url', ··· 158 baseUrl: string | undefined, 159 buffer: Buffer, 160 mimeType: string, 161 - contextText: string, 162 ): Promise<string | undefined> { 163 const url = baseUrl ? `${baseUrl.replace(/\/+$/, '')}/v1/messages` : 'https://api.anthropic.com/v1/messages'; 164 ··· 181 }, 182 { 183 type: 'text', 184 - text: `Describe this image for alt text. Be concise but descriptive. Context from the tweet text: "${contextText}".`, 185 }, 186 ], 187 },
··· 43 } 44 45 try { 46 + const prompt = buildAltTextPrompt(contextText); 47 switch (provider) { 48 case 'gemini': 49 // apiKey is guaranteed by check above 50 + return await callGemini(apiKey!, model || 'models/gemini-2.5-flash', buffer, mimeType, prompt); 51 case 'openai': 52 case 'custom': 53 + return await callOpenAICompatible(apiKey, model || 'gpt-4o', baseUrl, buffer, mimeType, prompt); 54 case 'anthropic': 55 // apiKey is guaranteed by check above 56 return await callAnthropic( ··· 59 baseUrl, 60 buffer, 61 mimeType, 62 + prompt, 63 ); 64 default: 65 console.warn(`[AI] ⚠️ Unknown provider: ${provider}`); ··· 71 } 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 + 93 async function callGemini( 94 apiKey: string, 95 modelName: string, 96 buffer: Buffer, 97 mimeType: string, 98 + prompt: string, 99 ): Promise<string | undefined> { 100 const genAI = new GoogleGenerativeAI(apiKey); 101 const model = genAI.getGenerativeModel({ model: modelName }); 102 103 const result = await model.generateContent([ 104 prompt, 105 { ··· 119 baseUrl: string | undefined, 120 buffer: Buffer, 121 mimeType: string, 122 + prompt: string, 123 ): Promise<string | undefined> { 124 const url = baseUrl 125 ? `${baseUrl.replace(/\/+$/, '')}/chat/completions` ··· 135 content: [ 136 { 137 type: 'text', 138 + text: prompt, 139 }, 140 { 141 type: 'image_url', ··· 174 baseUrl: string | undefined, 175 buffer: Buffer, 176 mimeType: string, 177 + prompt: string, 178 ): Promise<string | undefined> { 179 const url = baseUrl ? `${baseUrl.replace(/\/+$/, '')}/v1/messages` : 'https://api.anthropic.com/v1/messages'; 180 ··· 197 }, 198 { 199 type: 'text', 200 + text: prompt, 201 }, 202 ], 203 },