# Content Automation
Automated content publishing for Pinterest and Instagram with AI-powered content generation and scheduling.
## Features
- β
**MCP Integration** β - Natural language control through AI assistants (primary way of working)
- π [Detailed MCP description](./MCP.md) - setup, prompt examples, troubleshooting
- β
**TypeScript** - Full type safety for reliability and better IDE support
- β
**Late API** - Publishing to Instagram and Pinterest through a unified API
- β
**Local Image Processing** - Automatic conversion (HEIC/PNG/GIF β JPG) and center-crop to platform aspect ratios via `sharp`
- β
**SCP Upload to CDN** - Processed images are automatically uploaded to your CDN server via `scp`
- β
Unique AI-powered content generation for Pinterest and Instagram (OpenAI with customizable prompts)
- β
Publication scheduling through Late API (no local scheduler needed)
- β
**Instagram Stories Support** - Publishing Stories through Late API
- β
**Instagram Carousel** - Publish 2β10 photos in one post (via MCP)
- β
Publishing to both platforms with a single request
- β
Processed image tracking
- β
OpenAI integration for content generation (optional)
- β
CLI commands - alternative way of working through terminal
## Installation
1. Install dependencies:
```bash
npm install
```
2. Build the TypeScript project:
```bash
npm run build
```
3. Copy `.env.example` to `.env` and fill it:
```bash
cp .env.example .env
```
4. Configure environment variables in `.env`:
- **Late API** (required): Get API key at [getlate.dev](https://getlate.dev)
- **CDN Base URL** (required): Specify the base CDN URL where images are hosted
- **SCP Upload** (optional): Configure SCP to auto-upload processed local images to CDN
- **OpenAI API** (optional): For AI-powered content generation
- **Content Prompt** (optional): Customize content generation by copying `prompts/content-prompt.example.txt` to `prompts/content-prompt.txt` and editing it
## Getting API Keys
### Late API (Required) β
The project uses **only Late API** for publishing to Instagram and Pinterest.
1. Register at [getlate.dev](https://getlate.dev)
2. Get API key
3. Connect Instagram and Pinterest accounts
4. Get Profile ID and Account IDs from the dashboard
5. Add to `.env`:
```
LATE_API_KEY=your_key
LATE_PROFILE_ID=your_profile_id
LATE_INSTAGRAM_ACCOUNT_ID=your_instagram_account_id
LATE_PINTEREST_ACCOUNT_ID=your_pinterest_account_id
```
**Late API Benefits:**
- β
No local scheduler needed - Late API publishes on schedule automatically
- β
Reliable scheduling on Late servers
- β
**Publishing to Instagram AND Pinterest with a single request** - saves API requests!
- β
Free plan available (60 requests/minute)
- β
Support for 13 platforms (Instagram, Pinterest, Facebook, LinkedIn, Twitter/X, TikTok, YouTube, Reddit, Bluesky, Threads, Google Business, Telegram, Snapchat)
**Supported Platforms:** Instagram, Pinterest, Facebook, LinkedIn, Twitter/X, TikTok, YouTube, Reddit, Bluesky, Threads, Google Business, Telegram, Snapchat
## Usage
The project supports **two ways of working**:
1. **Through MCP agent** (recommended) - Natural language control through AI assistant
2. **Through CLI commands** - Traditional way through terminal
### Setup Check
Before starting, check API connection:
```bash
npm run check
```
This will check:
- Late API connection (required)
- CDN configuration (required)
- SCP upload configuration (optional)
- OpenAI API availability (optional)
### CLI Commands
#### Publishing Images
```bash
npm run publish -- --image=IMG_5857.jpg [options]
```
**Image Input:**
- **Local file** (recommended): Place source images (HEIC, JPG, PNG, GIF) in the images directory. By default the system looks in `images/` (relative to project root); you can override with `IMAGES_SOURCE_DIR` in `.env` (e.g. an absolute path like `/path/to/story-matcher`). The system will convert to JPG, center-crop to platform aspect ratios, upload to CDN via SCP, and use the CDN URLs for publishing.
- **CDN URL**: If the image is already on CDN, pass the full URL β processing is skipped.
- **Filename only**: If SCP is not configured, the filename is resolved against `CDN_BASE_URL` directly (existing behavior).
**Examples:**
```bash
# Local image: auto-process + upload + publish to all platforms
npm run publish -- --image=IMG_5857.HEIC
# Publish in 1 hour
npm run publish -- --image=IMG_5857.jpg 1
# Draft with publication date
npm run publish -- --image=IMG_5857.jpg --date="tomorrow 18:00" --draft
# With context
npm run publish -- --image=IMG_5857.jpg 1 --context="City center"
# Instagram post only
npm run publish -- --image=IMG_5857.jpg --post
# Instagram story only
npm run publish -- --image=IMG_5857.jpg --story
# Instagram story + Pinterest pin (comma-separated)
npm run publish -- --image=IMG_5857.jpg --types=story,pin
# Instagram post + Pinterest pin
npm run publish -- --image=IMG_5857.jpg --types=post,pin
# Pinterest pin only
npm run publish -- --image=IMG_5857.jpg --pin
# With draft
npm run publish -- --image=IMG_5857.jpg --story --draft
```
**Options:**
- `--image=<filename>` - Image filename (required in CLI mode)
- `--context=<text>` - Context for content generation
- `--types=<list>` - Content types comma-separated: `post`, `story`, `pin` (e.g., `--types=story,pin`)
- `--post` - Instagram post only (short form for `--types=post`)
- `--story` - Instagram story only (short form for `--types=story`)
- `--pin` - Pinterest pin only (short form for `--types=pin`)
- (no flags) - All three types (default): Instagram post, Instagram story, Pinterest pin
- `--date=<date>` - Publication date (YYYY-MM-DD HH:MM or "tomorrow 18:00")
- `<hours>` - Alternative to --date: number of hours from current moment
- `--draft` or `-d` - Create as draft
**Content Types:**
- Default (nothing specified) - Publishes all three types: Instagram post, Instagram story, Pinterest pin
- `--types=post,story,pin` - Specify types comma-separated (e.g., `--types=story,pin`)
- `--post` - Publishes only Instagram post
- `--story` - Publishes only Instagram story
- `--pin` - Publishes only Pinterest pin (without Instagram)
**Detailed Documentation:**
- [CLI.md](./CLI.md) - Detailed CLI description with usage examples
### Building the Project
```bash
# Build TypeScript to JavaScript
npm run build
# Build and watch for changes (auto-recompiles on file changes)
npm run watch
```
### Using MCP in Dev Mode
When developing with MCP, use automatic recompilation:
```bash
# Terminal: Run watch for automatic recompilation
npm run watch
```
**How it works:**
1. `watch` automatically recompiles TypeScript when files change
2. Cursor automatically restarts MCP server when `dist/mcp/mcp-server.js` changes
3. You can immediately use updated MCP tools in Cursor chat
**Important:** After changing code in `src/mcp/` or related modules, changes will apply automatically through watch + MCP restart in Cursor.
**π§ Dev Mode (dry-run):**
- In `watch` mode, `DEV_MODE=true` is automatically enabled
- **Requests to Late API are NOT sent** - instead, JSON ready to send is output
- This allows testing logic without real publications
- For real requests, use `npm run watch:prod` or simply `npm run build` + commands
### Using MCP Agent (Recommended) β
**MCP (Model Context Protocol)** - This is the primary and recommended way to work with the project. Manage publications through AI assistant using natural language directly in Cursor, Claude, or other AI tools.
#### Quick Start with MCP
1. **Build the project:**
```bash
npm run build
```
2. **MCP server is automatically available in Cursor** (via `.cursor/mcp.json`)
3. **Use natural language:**
```
"Publish IMG_0802.HEIC tomorrow at 18:00 with context 'Concert hall, evening'"
"Show list of available images"
"Generate content for IMG_1460.JPG"
"Schedule Story for IMG_2228.JPG today at 20:00"
```
**MCP Benefits:**
- β
**Natural language** - No need to remember commands
- β
**AI Integration** - Works directly in Cursor/Claude
- β
**Smart automation** - AI can plan publications itself
- β
**Contextual understanding** - AI understands your intentions
**Detailed Documentation:**
- [MCP.md](./MCP.md) - Setup, prompt examples, troubleshooting
### Using CLI Commands
Alternative way of working through terminal. Detailed documentation: [CLI.md](./CLI.md)
## Local Image Processing
The system can process local source images automatically β convert formats, crop to platform aspect ratios, and upload to CDN.
### How It Works
```
IMG_123.HEIC (source image from IMAGES_SOURCE_DIR)
β
ββ sharp: read β center-crop 4:5 β .processed/post/IMG_123.jpg
ββ sharp: read β center-crop 9:16 β .processed/story/IMG_123.jpg
ββ sharp: read β center-crop 2:3 β .processed/pin/IMG_123.jpg
β
ββ scp: upload to CDN server
β
ββ CDN URLs used for publishing via Late API
```
### Supported Source Formats
HEIC, JPG, JPEG, PNG, GIF β all converted to JPEG (quality 90).
### Aspect Ratios (center-crop)
| Content Type | Ratio | Example Resolution |
|---|---|---|
| Instagram Post (`post`) | 4:5 | 1080Γ1350 |
| Instagram Story (`story`) | 9:16 | 1080Γ1920 |
| Pinterest Pin (`pin`) | 2:3 | 1000Γ1500 |
### Setup
1. **Place source images** in the images directory (default: `images/`, override with `IMAGES_SOURCE_DIR` in `.env`).
2. **Configure SCP upload** in `.env` (all 4 variables required together):
```
SCP_HOST=cdn.example.com
SCP_USER=deploy
SCP_KEY_PATH=~/.ssh/id_rsa
SCP_REMOTE_BASE_PATH=/var/www/cdn/images
```
3. **Ensure CDN_BASE_URL is set** β used to build public URLs from uploaded files.
### Caching
Processed images are cached in `.processed/`. If the output file already exists, processing is skipped. Delete `.processed/` to force reprocessing.
### Without SCP (URL-only mode)
If SCP is not configured, the system works as before β pass image filenames or URLs directly, and CDN URLs are built from `CDN_BASE_URL`. Local image processing is only triggered when a local filename is provided and SCP is configured.
## Project Structure
The project is organized on a **modular principle** for better scalability and support.
### Main Modules:
- **`src/core/`** - Core infrastructure (cache with LRU eviction, retry with exponential backoff, rate limiting, logger, error definitions, config, validation schemas)
- **`src/services/`** - API services (Late API, CDN uploader)
- **`src/generators/`** - Content generators (AI-powered with customizable prompts)
- **`src/processors/`** - Data processors (image pipeline, sharp processor)
- **`src/utils/`** - Utilities (CDN URL builder, scheduling, date and shared helpers)
- **`src/cli/`** - CLI commands and entry points
- **`src/mcp/`** - MCP integration for AI assistants
- **`src/types/`** - TypeScript types and interfaces
- **`prompts/`** - Content generation prompts (customizable)
- **`.processed/`** - Processed image cache (gitignored)
### Modular Structure Benefits:
β
**Separation of concerns** - Each module is responsible for its area
β
**Singleton pattern** - Services are exported as singleton instances
β
**Direct imports** - Simplified structure without intermediate index.ts
β
**Scalability** - Easy to add new services
β
**Testability** - Each module can be tested separately
## Content Format
### Pinterest
- **Title**: SEO-optimized headline
- **Description**: Description with keywords
- **Keywords**: List of keywords
- **CTA**: Call to action (leads to Instagram)
### Instagram Posts
- **Caption**: Emotional story text
- **Hashtags**: 3-5 relevant hashtags (customizable via content prompt)
### Instagram Stories
- Stories do not support long captions
- Text overlays can be used (via API)
- User mentions (user_tags) are supported
- Automatically disappear after 24 hours
## Limitations and Capabilities
### Late API
- β
**Only Late API is used** for publishing to Instagram and Pinterest
- β
Native scheduling through Late API
- β
Does not require local scheduler
- β
Publishing to both platforms with a single request
- β
Support for media file upload or using public CDN URLs
- β
**Supports Stories** - Can publish Stories through Late API
- β οΈ **Stories limitations**: Can only schedule for the next 24 hours
- β οΈ Free plan: 60 requests/minute
### CDN for Images
Two modes of operation:
**1. Local processing + SCP upload (recommended):**
- Place source images in the images directory (default: `images/`, configurable via `IMAGES_SOURCE_DIR`)
- Configure SCP variables in `.env`
- System auto-converts, crops, uploads, and builds CDN URLs
- Supports HEIC, JPG, PNG, GIF input formats
**2. Direct CDN URLs (no local processing):**
- Images must already be on CDN in correct aspect ratios
- Specify `CDN_BASE_URL` in `.env`
- Pass filename or full URL in commands
- The system builds CDN URLs by content type:
- `post` β `{CDN_BASE_URL}/post/{filename}`
- `story` β `{CDN_BASE_URL}/story/{filename}`
- `pin` β `{CDN_BASE_URL}/pin/{filename}`
- **Important:** Filenames are case-sensitive - use exact case as on CDN
## Content Generation Customization
The content generation prompt can be customized to match your brand voice and target audience:
1. **Copy the example prompt:**
```bash
cp prompts/content-prompt.example.txt prompts/content-prompt.txt
```
2. **Edit `prompts/content-prompt.txt`** with your custom instructions:
- Adjust tone and style
- Modify hashtag requirements
- Change content focus and goals
- Update output format if needed
3. **The custom prompt is automatically loaded** - no code changes needed!
**Note:** `prompts/content-prompt.txt` is ignored by git, so your customizations remain private. The example file (`content-prompt.example.txt`) serves as a template and is version-controlled.
## Security
- Never commit `.env` file
- Never commit `prompts/content-prompt.txt` (already in `.gitignore`)
- Store API keys in a secure place
- Regularly check Late API key expiration
- Do not share API keys publicly
- `SCP_KEY_PATH` points to your SSH key β ensure it has proper file permissions (`chmod 600`)
## Troubleshooting
### Late API Connection Error
- Check API key correctness: `npm run check`
- Make sure both accounts (Instagram and Pinterest) are connected in Late
- Check API key expiration
### Publication Error
- Make sure Instagram and Pinterest accounts are connected in Late
- Check account access rights
- Make sure images are available on CDN at specified URLs
### Images Not Processing
- Check file formats (supported: HEIC, JPG, PNG, GIF)
- Make sure files are not corrupted
- **Check filename case** - Filename is case-sensitive (e.g., `IMG_5857.jpg` β `img_5857.jpg`)
- Verify the source file exists in the images source directory (default: `images/`, or `IMAGES_SOURCE_DIR` if set)
- Check `.processed/` for cached output β delete to force reprocessing
### SCP Upload Failing
- Verify all 4 SCP variables are set in `.env`: `SCP_HOST`, `SCP_USER`, `SCP_KEY_PATH`, `SCP_REMOTE_BASE_PATH`
- Check that your SSH key exists at the specified `SCP_KEY_PATH` and has correct permissions (`chmod 600`)
- Test SSH connectivity manually: `ssh -i ~/.ssh/id_rsa user@host`
- Check that the remote base path exists and is writable
### CDN Not Working
- Make sure `CDN_BASE_URL` is specified correctly
- Check that images are available at specified URLs
- Make sure URLs are publicly accessible
## License
MIT