# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Build & Run Commands
```bash
npm run build # Compile TypeScript → dist/
npm run publish # CLI entry point: node dist/cli/publish.js
npm run check # Verify API connections and config
npm run mcp # Start MCP server (stdio transport, auto-started by Cursor)
npm run watch # Auto-recompile with DEV_MODE=true (dry-run, no real API calls)
npm run watch:prod # Auto-recompile without dev mode
```
There are no tests in this project.
## Architecture
**Flow:** CLI/MCP → ImagePipeline → ContentGenerator → LateService → External APIs
### Module Layers
- **Entry points** (`src/cli/`, `src/mcp/`) — CLI publish command and MCP server with 3 tools (generate_content, schedule_post, get_cdn_url)
- **Processors** (`src/processors/images/`) — `ImageProcessor` orchestrates publishing; `ImagePipeline` coordinates sharp processing + SCP upload; `SharpProcessor` does HEIC→JPG conversion (via macOS `sips`) and center-crop to platform aspect ratios
- **Generators** (`src/generators/content/`) — OpenAI Vision API (gpt-4o) content generation with custom prompt loading from `prompts/content-prompt.txt`
- **Services** (`src/services/`) — `LateService` for Instagram+Pinterest scheduling, `CDNUploader` for SCP uploads
- **Core** (`src/core/`) — Config (Zod validation), retry (exponential backoff), rate-limit (time-window), cache (LRU with TTL), logger, error classes
- **Utils** (`src/utils/`) — CDN URL builder, scheduler, date parsing, shared helpers
### Key Patterns
**Singletons everywhere.** Every service exports a singleton instance: `export const lateService = new LateService()`. No DI framework.
**Direct imports with .js extensions.** ES Modules (`"module": "ESNext"` in tsconfig). All imports must use `.js` extension: `import { foo } from './bar.js'`.
**Multi-platform publishing requires separate requests.** Instagram content is emotion-first, Pinterest is SEO-first — they always have different content. `scheduleToMultiplePlatforms(opts)` accepts an options object (`ScheduleMultiPlatformOptions`) and has a safety check that throws if Post+Pinterest are combined in one request. Story+Pinterest also use separate requests. Partial failure handling returns `status: 'partial_success'` with `succeededPlatforms`/`failedPlatforms`. Instagram carousel (2–10 photos) is supported via `carouselImageUrls` option.
**Image pipeline branching.** If input is a URL → skip processing, build CDN URLs directly. If input is a local filename → resolve from `IMAGES_SOURCE_DIR` (default: `images/`), sharp process, SCP upload, return CDN URLs. HEIC files are pre-converted via `sips` (macOS) before sharp processing.
**Content type routing.** Three content types flow through the entire system: `'post'` (4:5), `'story'` (9:16), `'pin'` (2:3). These map to CDN directories, aspect ratios, and platform-specific API behavior.
**Dev mode.** `DEV_MODE=true` (set automatically by `npm run watch`) skips all real API calls and outputs JSON payloads instead. Checked dynamically via `appConfig.isDevMode()` in each service.
### Configuration
All env vars validated on startup via Zod schema in `src/core/config/config.schema.ts`. The `appConfig` singleton (`src/core/config/config.ts`) provides typed access. Required: `LATE_API_KEY`, `LATE_PROFILE_ID`, `LATE_INSTAGRAM_ACCOUNT_ID`, `LATE_PINTEREST_ACCOUNT_ID`, `INSTAGRAM_USERNAME`. SCP upload needs all 4 vars together: `SCP_HOST`, `SCP_USER`, `SCP_KEY_PATH`, `SCP_REMOTE_BASE_PATH`.
### TypeScript Strictness
`strict: true` with `noUnusedLocals`, `noUnusedParameters`, `noImplicitReturns`, `noFallthroughCasesInSwitch` — all enabled. Unused variables/parameters are compile errors.