Claude-code-plugins-plus-skills ideogram-data-handling
install
source · Clone the upstream repo
git clone https://github.com/jeremylongshore/claude-code-plugins-plus-skills
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/jeremylongshore/claude-code-plugins-plus-skills "$T" && mkdir -p ~/.claude/skills && cp -r "$T/plugins/saas-packs/ideogram-pack/skills/ideogram-data-handling" ~/.claude/skills/jeremylongshore-claude-code-plugins-plus-skills-ideogram-data-handling && rm -rf "$T"
manifest:
plugins/saas-packs/ideogram-pack/skills/ideogram-data-handling/SKILL.mdsource content
Ideogram Data Handling
Overview
Manage generated image assets from Ideogram's API. Critical concern: Ideogram image URLs expire (approximately 1 hour). Every generation must be downloaded and persisted immediately. This skill covers metadata tracking, download pipelines, local and cloud storage, lifecycle management, and generation history for reproducibility.
Prerequisites
configuredIDEOGRAM_API_KEY- Storage solution (local filesystem, S3, or GCS)
- Database for generation metadata (SQLite, Postgres, or JSON files)
Instructions
Step 1: Generation Record Schema
interface GenerationRecord { id: string; // Unique identifier prompt: string; // Original prompt expandedPrompt?: string; // Magic Prompt expansion (from response) negativePrompt?: string; // Negative prompt used model: string; // V_2, V_2_TURBO, etc. styleType: string; // DESIGN, REALISTIC, etc. aspectRatio: string; // ASPECT_16_9, etc. seed: number; // For reproducibility resolution: string; // e.g., "1024x1024" isSafe: boolean; // is_image_safe from response originalUrl: string; // Temporary Ideogram URL storedPath: string; // Local or S3 path createdAt: string; // ISO timestamp sizeBytes?: number; // Downloaded file size tags?: string[]; // User-defined tags }
Step 2: Generate, Download, and Track
import { writeFileSync, mkdirSync, statSync } from "fs"; import { join } from "path"; import { randomUUID } from "crypto"; const STORAGE_DIR = "./generated-images"; const records: GenerationRecord[] = []; async function generateAndPersist( prompt: string, options: { model?: string; style_type?: string; aspect_ratio?: string; negative_prompt?: string; seed?: number; tags?: string[]; } = {} ): Promise<GenerationRecord> { // Generate const response = await fetch("https://api.ideogram.ai/generate", { method: "POST", headers: { "Api-Key": process.env.IDEOGRAM_API_KEY!, "Content-Type": "application/json", }, body: JSON.stringify({ image_request: { prompt, model: options.model ?? "V_2", style_type: options.style_type ?? "AUTO", aspect_ratio: options.aspect_ratio ?? "ASPECT_1_1", magic_prompt_option: "AUTO", negative_prompt: options.negative_prompt, seed: options.seed, }, }), }); if (!response.ok) throw new Error(`Generation failed: ${response.status}`); const result = await response.json(); const image = result.data[0]; // Download IMMEDIATELY (URLs expire ~1 hour) const imgResp = await fetch(image.url); if (!imgResp.ok) throw new Error(`Download failed: ${imgResp.status}`); const buffer = Buffer.from(await imgResp.arrayBuffer()); mkdirSync(STORAGE_DIR, { recursive: true }); const filename = `${image.seed}-${Date.now()}.png`; const storedPath = join(STORAGE_DIR, filename); writeFileSync(storedPath, buffer); // Track metadata const record: GenerationRecord = { id: randomUUID(), prompt, expandedPrompt: image.prompt !== prompt ? image.prompt : undefined, negativePrompt: options.negative_prompt, model: options.model ?? "V_2", styleType: image.style_type ?? options.style_type ?? "AUTO", aspectRatio: options.aspect_ratio ?? "ASPECT_1_1", seed: image.seed, resolution: image.resolution, isSafe: image.is_image_safe, originalUrl: image.url, storedPath, createdAt: new Date().toISOString(), sizeBytes: buffer.length, tags: options.tags, }; records.push(record); saveRecords(); return record; } function saveRecords() { writeFileSync( join(STORAGE_DIR, "generations.json"), JSON.stringify(records, null, 2) ); }
Step 3: Cloud Storage (S3)
import { S3Client, PutObjectCommand, DeleteObjectCommand } from "@aws-sdk/client-s3"; const s3 = new S3Client({ region: process.env.AWS_REGION }); async function persistToS3(imageUrl: string, seed: number): Promise<string> { const response = await fetch(imageUrl); const buffer = Buffer.from(await response.arrayBuffer()); const key = `ideogram/${seed}-${Date.now()}.png`; await s3.send(new PutObjectCommand({ Bucket: process.env.S3_BUCKET!, Key: key, Body: buffer, ContentType: "image/png", CacheControl: "public, max-age=31536000, immutable", Metadata: { seed: String(seed), source: "ideogram" }, })); return `https://${process.env.CDN_DOMAIN}/${key}`; }
Step 4: Reproduction from Seed
// Reproduce an image using the stored seed and prompt async function reproduceImage(record: GenerationRecord) { return generateAndPersist(record.prompt, { model: record.model, style_type: record.styleType, aspect_ratio: record.aspectRatio, negative_prompt: record.negativePrompt, seed: record.seed, // Same seed = same image tags: [...(record.tags ?? []), "reproduced"], }); }
Step 5: Lifecycle Management
import { unlinkSync, existsSync, readdirSync, statSync } from "fs"; function cleanupOldAssets(retentionDays: number = 30) { const cutoffMs = Date.now() - retentionDays * 86400000; let deleted = 0; let kept = 0; for (const record of records) { const createdMs = new Date(record.createdAt).getTime(); if (createdMs < cutoffMs) { if (existsSync(record.storedPath)) { unlinkSync(record.storedPath); deleted++; } } else { kept++; } } // Remove expired records const activeRecords = records.filter( r => new Date(r.createdAt).getTime() >= cutoffMs ); records.length = 0; records.push(...activeRecords); saveRecords(); console.log(`Cleanup: deleted ${deleted}, kept ${kept}`); } function storageReport() { const totalBytes = records.reduce((sum, r) => sum + (r.sizeBytes ?? 0), 0); const byModel = Object.groupBy(records, r => r.model); console.log("=== Image Storage Report ==="); console.log(`Total images: ${records.length}`); console.log(`Total size: ${(totalBytes / 1024 / 1024).toFixed(1)} MB`); for (const [model, recs] of Object.entries(byModel)) { console.log(` ${model}: ${recs?.length ?? 0} images`); } }
Step 6: Search and Query
function findByPrompt(searchTerm: string): GenerationRecord[] { return records.filter(r => r.prompt.toLowerCase().includes(searchTerm.toLowerCase()) ); } function findBySeed(seed: number): GenerationRecord | undefined { return records.find(r => r.seed === seed); } function findByTags(tags: string[]): GenerationRecord[] { return records.filter(r => tags.every(t => r.tags?.includes(t)) ); }
Error Handling
| Issue | Cause | Solution |
|---|---|---|
| Expired URL | Downloaded too late | Always download in same function |
| Disk full | Too many stored images | Run regularly |
| Missing metadata | Not tracked at generation | Use wrapper |
| Duplicate prompts | Same prompt run twice | Check by prompt hash before generating |
| Lost seed | Not recorded | Always store seed from response |
Output
- Generation records with full metadata tracking
- Immediate download preventing URL expiration
- S3 cloud storage with CDN delivery
- Seed-based reproduction for exact image regeneration
- Lifecycle management with configurable retention
Resources
Next Steps
For access control, see
ideogram-enterprise-rbac.