snapai
Version:
AI-powered icon generation CLI for mobile app developers
158 lines (157 loc) • 6.59 kB
JavaScript
import { Command, Flags } from "@oclif/core";
import fs from "fs-extra";
import path from "path";
import chalk from "chalk";
import { OpenAIService } from "../services/openai.js";
import { ValidationService } from "../utils/validation.js";
export default class IconCommand extends Command {
static description = "Generate AI-powered app icons using OpenAI models (GPT-Image-1, DALL-E 3, DALL-E 2)";
static examples = [
'<%= config.bin %> <%= command.id %> --prompt "minimalist calculator app icon"',
'<%= config.bin %> <%= command.id %> --prompt "fitness tracker" --output ./assets/icons',
"",
'<%= config.bin %> <%= command.id %> --prompt "artistic icon" --model dall-e-3 --quality hd',
'<%= config.bin %> <%= command.id %> --prompt "quick concept" --model dall-e-2',
"",
'<%= config.bin %> <%= command.id %> --prompt "logo" --background transparent --output-format png',
'<%= config.bin %> <%= command.id %> --prompt "variations" --num-images 3 --model gpt-image-1',
'<%= config.bin %> <%= command.id %> --prompt "custom design" --raw-prompt',
];
static flags = {
prompt: Flags.string({
char: "p",
description: "Description of the icon to generate",
required: true,
}),
output: Flags.string({
char: "o",
description: "Output directory",
default: "./assets",
}),
model: Flags.string({
char: "m",
description: "AI model: gpt-image-1 (best), dall-e-3 (creative), dall-e-2 (fast)",
default: "gpt-image-1",
options: ["dall-e-2", "dall-e-3", "gpt-image-1"],
}),
size: Flags.string({
char: "s",
description: "Image size (depends on model)",
default: "1024x1024",
options: [
"256x256",
"512x512",
"1024x1024",
"1536x1024",
"1024x1536",
"1792x1024",
"1024x1792",
"auto",
],
}),
quality: Flags.string({
char: "q",
description: "Quality level (depends on model)",
default: "auto",
options: ["auto", "standard", "hd", "high", "medium", "low"],
}),
background: Flags.string({
char: "b",
description: "Background: transparent, opaque, auto (GPT-Image-1 only)",
default: "auto",
options: ["transparent", "opaque", "auto"],
}),
"output-format": Flags.string({
char: "f",
description: "Output format: png, jpeg, webp (GPT-Image-1 only)",
default: "png",
options: ["png", "jpeg", "webp"],
}),
"num-images": Flags.integer({
char: "n",
description: "Number of images 1-10 (DALL-E 3 supports 1 only)",
default: 1,
min: 1,
max: 10,
}),
moderation: Flags.string({
description: "Content filtering: low, auto (GPT-Image-1 only)",
default: "auto",
options: ["low", "auto"],
}),
"raw-prompt": Flags.boolean({
description: "Skip iOS-specific prompt enhancement",
default: false,
}),
};
async run() {
const { flags } = await this.parse(IconCommand);
try {
const promptError = ValidationService.validatePrompt(flags.prompt);
if (promptError) {
this.error(promptError);
}
const outputError = ValidationService.validateOutputPath(flags.output);
if (outputError) {
this.error(outputError);
}
this.log(chalk.blue("🎨 Generating your app icon..."));
this.log("");
this.log(chalk.dim("Built with ❤️ by \u001b]8;;https://codewithbeto.dev\u001b\\codewithbeto.dev\u001b]8;;\u001b\\ - Ship faster, contribute more, lead with confidence"));
this.log("");
this.log(chalk.gray(`Prompt: ${flags.prompt}`));
if (flags["raw-prompt"]) {
this.log(chalk.yellow("⚠️ Using raw prompt (no iOS enhancement)"));
}
const imageBase64Array = await OpenAIService.generateIcon({
prompt: flags.prompt,
output: flags.output,
model: flags.model,
size: flags.size,
quality: flags.quality,
background: flags.background,
outputFormat: flags["output-format"],
numImages: flags["num-images"],
moderation: flags.moderation,
rawPrompt: flags["raw-prompt"],
});
const outputPaths = await this.saveBase64Images(imageBase64Array, flags.output, flags["output-format"]);
this.log(chalk.green("✅ Icon(s) generated successfully!"));
if (outputPaths.length === 1) {
this.log(chalk.gray(`Saved to: ${outputPaths[0]}`));
}
else {
this.log(chalk.gray(`Saved ${outputPaths.length} images to:`));
outputPaths.forEach((path, index) => {
this.log(chalk.gray(` ${index + 1}. ${path}`));
});
}
}
catch (error) {
this.error(chalk.red(`Failed to generate icon: ${error.message}`));
}
}
async saveBase64Images(base64DataArray, outputDir, outputFormat) {
await fs.ensureDir(outputDir);
const outputPaths = [];
const timestamp = Date.now();
try {
this.log(chalk.gray(`💾 Saving ${base64DataArray.length} image(s)...`));
for (let i = 0; i < base64DataArray.length; i++) {
const base64Data = base64DataArray[i];
const extension = outputFormat || "png";
const filename = base64DataArray.length === 1
? `icon-${timestamp}.${extension}`
: `icon-${timestamp}-${i + 1}.${extension}`;
const outputPath = path.join(outputDir, filename);
const buffer = Buffer.from(base64Data, "base64");
await fs.writeFile(outputPath, buffer);
outputPaths.push(outputPath);
}
return outputPaths;
}
catch (error) {
throw new Error(`Failed to save image(s): ${error.message}`);
}
}
}