#!/usr/bin/env bun import { existsSync, mkdirSync, appendFileSync, writeFileSync } from "fs"; import { join } from "path"; import { randomUUID } from "crypto"; import { parseArgs } from "util"; // Constants const SKILL_NAME = "banner-ad-suite"; const SESSION_ID = randomUUID().slice(0, 8); const SESSION_TIMESTAMP = new Date().toISOString().replace(/[:.]/g, "_").slice(0, 19); // Environment const SKILLS_OUTPUT_DIR = process.env.SKILLS_OUTPUT_DIR || join(process.cwd(), ".skills"); const EXPORTS_DIR = join(SKILLS_OUTPUT_DIR, "exports", SKILL_NAME, SESSION_ID); const LOGS_DIR = join(SKILLS_OUTPUT_DIR, "logs", SKILL_NAME); const OPENAI_API_KEY = process.env.OPENAI_API_KEY; const OPENAI_IMAGE_URL = "https://api.openai.com/v1/images/generations"; // Types interface BannerSize { name: string; width: number; height: number; popular: boolean; } interface GeneratedBanner { size: string; dimensions: string; variant: string; filename: string; prompt: string; } interface Options { message: string; sizes: string[]; variants: number; brandColor: string; accent: string; cta: string; style: string; format: string; verbose: boolean; } // IAB Standard Banner Sizes const BANNER_SIZES: Record = { "300x250": { name: "Medium Rectangle", width: 300, height: 250, popular: true }, "728x90": { name: "Leaderboard", width: 728, height: 90, popular: true }, "160x600": { name: "Wide Skyscraper", width: 160, height: 600, popular: true }, "320x50": { name: "Mobile Leaderboard", width: 320, height: 50, popular: true }, "300x600": { name: "Half Page", width: 300, height: 600, popular: true }, "336x280": { name: "Large Rectangle", width: 336, height: 280, popular: false }, "970x250": { name: "Billboard", width: 970, height: 250, popular: false }, "970x90": { name: "Large Leaderboard", width: 970, height: 90, popular: false }, "320x100": { name: "Large Mobile Banner", width: 320, height: 100, popular: false }, "250x250": { name: "Square", width: 250, height: 250, popular: false }, "200x200": { name: "Small Square", width: 200, height: 200, popular: false }, "468x60": { name: "Banner", width: 468, height: 60, popular: false }, "120x600": { name: "Skyscraper", width: 120, height: 600, popular: false }, }; // Utilities function ensureDir(dir: string): void { if (!existsSync(dir)) mkdirSync(dir, { recursive: true }); } function log(message: string, level: "info" | "error" | "success" = "info"): void { ensureDir(LOGS_DIR); appendFileSync(join(LOGS_DIR, `log_${SESSION_TIMESTAMP}_${SESSION_ID}.log`), `[${new Date().toISOString()}] [${level.toUpperCase()}] ${message}\n`); const prefixes = { info: "â„šī¸ ", error: "❌ ", success: "✅ " }; if (level === "error") { console.error(`${prefixes[level]} ${message}`); } else { console.log(`${prefixes[level]} ${message}`); } } function parseArguments(): Options { const { values, positionals } = parseArgs({ args: process.argv.slice(2), options: { sizes: { type: "string", default: "popular" }, variants: { type: "string", default: "1" }, "brand-color": { type: "string", default: "auto" }, accent: { type: "string", default: "auto" }, cta: { type: "string", default: "auto" }, style: { type: "string", default: "modern" }, format: { type: "string", default: "png" }, verbose: { type: "boolean", default: false }, help: { type: "boolean", short: "h" }, }, allowPositionals: true, }); if (values.help) { printHelp(); process.exit(0); } const message = positionals.join(" "); let sizes = (values.sizes as string).split(",").map(s => s.trim()); if (sizes.includes("popular")) { sizes = Object.entries(BANNER_SIZES) .filter(([, config]) => config.popular) .map(([size]) => size); } else if (sizes.includes("all")) { sizes = Object.keys(BANNER_SIZES); } return { message, sizes, variants: parseInt(values.variants as string, 10), brandColor: values["brand-color"] as string, accent: values.accent as string, cta: values.cta as string, style: values.style as string, format: values.format as string, verbose: values.verbose as boolean, }; } function printHelp(): void { console.log(` Banner Ad Suite - Generate display banners in all IAB sizes Usage: skills run banner-ad-suite -- "" [options] Options: --sizes Sizes: popular, all, or comma-separated (300x250,728x90) --variants Number of design variants (default: 1) --brand-color Primary brand color --accent Accent color --cta Call-to-action text --style