import sharp from "sharp"; import path from "path"; import { mkdirSync } from "fs"; import { runAgentStep } from "./claude"; const PIPELINE_ROOT = process.env.PIPELINE_ROOT || path.join(process.cwd(), "pipeline"); const PLATFORM_FORMATS: Record = { "instagram-feed": { width: 1080, height: 1080, label: "Instagram Feed" }, "instagram-stories": { width: 1080, height: 1920, label: "Instagram Stories" }, tiktok: { width: 1080, height: 1920, label: "TikTok" }, "nextdoor-spotlight": { width: 1200, height: 1200, label: "Nextdoor Spotlight" }, "nextdoor-display": { width: 1200, height: 628, label: "Nextdoor Display" }, }; export function getAvailableFormats(currentDimensions: string | null): string[] { return Object.keys(PLATFORM_FORMATS).filter((key) => { const fmt = PLATFORM_FORMATS[key]; return `${fmt.width}x${fmt.height}` !== currentDimensions; }); } export function getPlatformFormat(key: string) { return PLATFORM_FORMATS[key] || null; } /** * Resize an image to target platform dimensions using Sharp. * Uses cover fit + center crop for aspect ratio changes. */ export async function resizeImage( sourcePath: string, targetFormat: string, outputDir: string ): Promise<{ filePath: string; fileName: string; dimensions: string; platform: string }> { const fmt = PLATFORM_FORMATS[targetFormat]; if (!fmt) throw new Error(`Unknown format: ${targetFormat}`); mkdirSync(path.join(PIPELINE_ROOT, outputDir), { recursive: true }); const sourceBase = path.basename(sourcePath, path.extname(sourcePath)); const fileName = `${sourceBase}_${targetFormat}_${fmt.width}x${fmt.height}.png`; const outputPath = path.join(outputDir, fileName); const fullOutputPath = path.join(PIPELINE_ROOT, outputPath); const fullSourcePath = path.join(PIPELINE_ROOT, sourcePath); await sharp(fullSourcePath) .resize(fmt.width, fmt.height, { fit: "cover", position: "centre" }) .png() .toFile(fullOutputPath); const platform = targetFormat.split("-")[0]; return { filePath: outputPath, fileName, dimensions: `${fmt.width}x${fmt.height}`, platform }; } /** * Re-tone a caption for a different platform using Claude CLI. */ export async function retoneCaption( originalCaption: string, originalPlatform: string, targetPlatform: string ): Promise { const prompt = `You are a social media copywriter. Adapt this ${originalPlatform} caption for ${targetPlatform}. Original caption: ${originalCaption} Rules: - ${targetPlatform === "tiktok" ? "Raw, authentic tone. Short. Trending hashtags. Hook in first line." : ""} - ${targetPlatform === "instagram" ? "Polished, aspirational. Hook → Value → CTA → Hashtags. 150-300 chars." : ""} - ${targetPlatform === "nextdoor" ? "Warm, neighborly. No hashtags. Reference local/community context." : ""} - Keep the core message but match the platform's voice. - Return ONLY the new caption text, nothing else.`; const { output } = await runAgentStep("caption-retone", prompt, PIPELINE_ROOT, {}); return output.trim(); } /** * Repurpose an image asset to specific platform formats. */ export async function repurposeImage( sourcePath: string, targetFormats: string[], outputDir: string ): Promise> { const results = []; for (const fmt of targetFormats) { const result = await resizeImage(sourcePath, fmt, outputDir); results.push(result); } return results; }