#!/usr/bin/env node
/**
* Universal CLI for publishing images to Instagram and/or Pinterest
* Images are on CDN - specify filename only (e.g., IMG_123.jpg)
*
* Usage:
* npm run publish -- --image=IMG_123.jpg [options]
*/
import dotenv from 'dotenv';
dotenv.config();
import { imageProcessor } from '../processors/images/image.processor.js';
import { cdnUrlBuilder } from '../utils/cdn/cdn-url-builder.js';
import type { ProcessingResult } from '../types/index.js';
import path from 'path';
import { parseScheduleDate, assertScheduleDateNotInPast } from '../utils/helpers/date-helpers.js';
/**
* Main function
*/
const main = async (): Promise<void> => {
console.log('Content Automation - Image Publisher');
console.log('===============================================\n');
// Parse command line arguments
const imagePathArg = process.argv.find(arg => arg.startsWith('--image='))?.split('=')[1];
const contextArg = process.argv.find(arg => arg.startsWith('--context='))?.split('=')[1] || '';
const dateArg = process.argv.find(arg => arg.startsWith('--date='))?.split('=')[1];
const hoursArg = process.argv.find(arg => !arg.startsWith('--') && !isNaN(parseFloat(arg)))?.trim();
const isDraft = process.argv.includes('--draft') || process.argv.includes('-d');
// Parse content type: --types=post,story,pin or individual flags --post, --story, --pin
const typesArg = process.argv.find(arg => arg.startsWith('--types='))?.split('=')[1];
const hasPost = process.argv.includes('--post');
const hasStory = process.argv.includes('--story');
const hasPin = process.argv.includes('--pin');
// Determine content type and platforms
// --types=post,story,pin → comma-separated list
// --post, --story, --pin → individual flags (for backward compatibility)
// If none specified → all three types (Instagram post, Instagram story, Pinterest pin)
let contentType: 'post' | 'story' | 'pin' | 'all' | string = 'all';
let publishToInstagramPost = false;
let publishToInstagramStory = false;
let publishToPinterest = false;
if (typesArg) {
// Parse comma-separated types
const types = typesArg.split(',').map(t => t.trim().toLowerCase());
publishToInstagramPost = types.includes('post');
publishToInstagramStory = types.includes('story');
publishToPinterest = types.includes('pin');
contentType = types.join(',');
} else if (hasPost || hasStory || hasPin) {
// Individual flags (backward compatibility)
if (hasPost) publishToInstagramPost = true;
if (hasStory) publishToInstagramStory = true;
if (hasPin) publishToPinterest = true;
// Build contentType string for display
const activeTypes: string[] = [];
if (publishToInstagramPost) activeTypes.push('post');
if (publishToInstagramStory) activeTypes.push('story');
if (publishToPinterest) activeTypes.push('pin');
contentType = activeTypes.join(',') || 'all';
} else {
// Default: all three types
contentType = 'all';
publishToInstagramPost = true;
publishToInstagramStory = true;
publishToPinterest = true;
}
// Image filename is required (files are on CDN, not locally)
if (!imagePathArg) {
console.error('❌ Image filename required. Use --image=<filename>');
console.error('\nUsage:');
console.error(' npm run publish -- --image=IMG_123.jpg [options]');
console.error('\nOptions:');
console.error(' --image=<filename> Image filename (file is on CDN)');
console.error(' --context=<text> Context for content generation');
console.error(' --types=<list> Comma-separated types: post,story,pin (e.g., --types=story,pin)');
console.error(' --post Only Instagram post (shortcut for --types=post)');
console.error(' --story Only Instagram story (shortcut for --types=story)');
console.error(' --pin Only Pinterest pin (shortcut for --types=pin)');
console.error(' (no flags) All three types (default: Instagram post, Instagram story, Pinterest pin)');
console.error(' --date=<date> Publish date (YYYY-MM-DD HH:MM or "tomorrow 18:00")');
console.error(' <hours> Hours from now (alternative to --date)');
console.error(' --draft Create as draft');
console.error('\nExamples:');
console.error(' # All three types (default)');
console.error(' npm run publish -- --image=IMG_123.jpg');
console.error(' # Only Instagram post');
console.error(' npm run publish -- --image=IMG_123.jpg --post');
console.error(' # Only Instagram story');
console.error(' npm run publish -- --image=IMG_123.jpg --story');
console.error(' # Instagram story + Pinterest pin');
console.error(' npm run publish -- --image=IMG_123.jpg --types=story,pin');
console.error(' # All three types explicitly');
console.error(' npm run publish -- --image=IMG_123.jpg --types=post,story,pin');
console.error(' # Only Pinterest pin');
console.error(' npm run publish -- --image=IMG_123.jpg --pin');
process.exit(1);
}
// Check CDN configuration
if (!cdnUrlBuilder.isConfigured()) {
console.error('❌ CDN is not configured. Please set CDN_BASE_URL in your environment variables.');
process.exit(1);
}
const imagePath = imagePathArg;
const userContext = contextArg;
let publishDate: Date | null = null;
console.log(`Using image from CDN: ${imagePath}`);
try {
// Get publish date
if (dateArg) {
publishDate = parseScheduleDate(dateArg);
if (!publishDate) {
console.error(`❌ Invalid date format: ${dateArg}`);
process.exit(1);
}
} else if (hoursArg) {
publishDate = parseScheduleDate(hoursArg);
if (!publishDate) {
console.error(`❌ Invalid hours format: ${hoursArg}`);
process.exit(1);
}
}
if (publishDate) assertScheduleDateNotInPast(publishDate);
// Process image
console.log('\nProcessing image...\n');
const results: ProcessingResult[] = [];
// Process Instagram post if needed
if (publishToInstagramPost) {
console.log('📱 Processing Instagram post...');
const result = await imageProcessor.processImage(
imagePath,
userContext,
publishDate,
'post',
isDraft,
true, // publishToInstagram
false // publishToPinterest
);
results.push(result);
console.log(` ✅ Instagram post: ${'id' in result.instagram ? result.instagram.id : ('containerId' in result.instagram ? result.instagram.containerId : 'N/A')}`);
}
// Process Instagram story if needed
if (publishToInstagramStory) {
console.log('📱 Processing Instagram story...');
const result = await imageProcessor.processImage(
imagePath,
userContext,
publishDate,
'story',
isDraft,
true, // publishToInstagram
false // publishToPinterest
);
results.push(result);
console.log(` ✅ Instagram story: ${'id' in result.instagram ? result.instagram.id : ('containerId' in result.instagram ? result.instagram.containerId : 'N/A')}`);
}
// Process Pinterest if needed
if (publishToPinterest) {
console.log('📌 Processing Pinterest pin...');
const result = await imageProcessor.processImage(
imagePath,
userContext,
publishDate,
'post', // instagramType not used for Pinterest, but required parameter
isDraft,
false, // publishToInstagram
true // publishToPinterest
);
results.push(result);
console.log(` ✅ Pinterest pin: ${result.pinterest.id || 'N/A'}`);
}
// Use first result for display (they all have the same imagePath and publishDate)
const firstResult = results[0];
console.log('\n✅ Image processed successfully!');
console.log('\nResult:');
console.log(` Image: ${path.basename(firstResult.imagePath)}`);
if (publishToInstagramPost) {
const postResult = results.find(r => r.instagramType === 'post');
if (postResult) {
const instagramId = 'id' in postResult.instagram ? postResult.instagram.id : ('containerId' in postResult.instagram ? postResult.instagram.containerId : 'N/A');
console.log(` Instagram post: ${instagramId}`);
}
}
if (publishToInstagramStory) {
const storyResult = results.find(r => r.instagramType === 'story');
if (storyResult) {
const instagramId = 'id' in storyResult.instagram ? storyResult.instagram.id : ('containerId' in storyResult.instagram ? storyResult.instagram.containerId : 'N/A');
console.log(` Instagram story: ${instagramId}`);
}
}
if (publishToPinterest) {
const pinterestResult = results.find(r => r.pinterest.id);
if (pinterestResult) {
console.log(` Pinterest pin: ${pinterestResult.pinterest.id || 'N/A'}`);
}
}
console.log(` Content type: ${contentType}`);
console.log(` Scheduled for: ${new Date(firstResult.publishDate).toLocaleString()}`);
if (isDraft) {
console.log(` Status: Draft`);
}
} catch (error) {
console.error('\n❌ Error:', error instanceof Error ? error.message : String(error));
process.exit(1);
}
};
// Run if called directly
main().catch(error => {
console.error('Error:', error);
process.exit(1);
});