Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
118 changes: 118 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

FAL CLI is an unofficial command-line interface for FAL AI Models that provides both traditional CLI usage and MCP (Model Context Protocol) server functionality for AI assistants. It enables high-quality image generation using models like FLUX, Imagen, and Qwen Image.

## Common Development Commands

```bash
# Start the CLI application
npm start
npm run dev
node cli.js

# Run specific CLI commands
npm run models # List available models
npm run generate # Start image generation
npm run optimize # Optimize prompts
npm run config # Configure settings

# Start MCP server for AI assistants
npm run mcp-server # Start on default port 3001
PORT=3002 npm run mcp-server # Custom port

# Test CLI functionality
npm test # Shows help output
node cli.js --help # Command documentation
```

## Architecture & Code Structure

### Dual Interface Architecture
The project implements two parallel interfaces that share core business logic:

1. **CLI Interface** (`cli.js`): Traditional command-line interface with interactive prompts using Commander.js and Inquirer.js
2. **MCP Server** (`mcp-server.js`): Protocol-based server for AI assistant integration using Model Context Protocol SDK

Both interfaces rely on shared core modules in the `core/` directory to avoid code duplication.

### Core Module Architecture

**`core/image-generator.js`**: Handles all image generation logic
- `generateSingleImage()`: Single image generation with FAL API
- `generateBatch()`: Batch processing with concurrency control
- `calculateCost()`: Cost estimation before generation

**`core/prompt-optimizer.js`**: AI-powered prompt enhancement
- `optimizePrompt()`: Single prompt optimization using LLM
- `optimizeBatch()`: Batch prompt optimization with model-specific adjustments

**`core/model-manager.js`**: Model configuration and discovery
- `loadModels()`: Loads model configurations from JSON files
- `getModelById()`: Retrieves specific model configuration
- `getFilteredModels()`: Filters models by criteria (cost, category)
- `getModelRecommendations()`: Suggests optimal models for use cases

### Supporting Modules

**`models-new.js`**: Model loader utilities
- Loads model configurations from `models/` directory
- Provides shared parameter definitions
- Handles model validation

**`secure-storage.js`**: API key management
- AES-256-GCM encryption for stored keys
- Machine-specific encryption keys
- Cross-platform secure storage locations

### Model Configuration System
Models are defined as JSON files in the `models/` directory with this structure:
- Model ID, pricing, and metadata
- Default parameters and constraints
- Supported aspect ratios and formats
- Model-specific optimization prompts

## Key Implementation Details

### API Key Management
- Primary: Environment variable `FAL_KEY` in `.env` file
- Secondary: Encrypted storage via `secure-storage.js`
- MCP Server: Can receive key via environment or config

### Cost Control System
- $5 spending limit enforced in MCP server
- Cost calculation before any generation
- User confirmation required for high-cost operations

### Generation Workflow
1. API key validation
2. Model selection and configuration
3. Optional prompt optimization
4. Cost estimation and user confirmation
5. Image generation with progress tracking
6. Result storage with unique filenames
7. Optional browser opening for results

### File Naming Convention
Generated images use this pattern:
```
{model-key}_{prompt-index}_{iteration}_{image-index}_{timestamp}_{random}.png
```

### Error Handling Strategy
- Comprehensive try-catch blocks throughout
- User-friendly error messages with suggestions
- Graceful degradation for missing features
- Validation at multiple levels (API key, model, parameters)

## Important Notes

- This is an **unofficial** CLI tool, not affiliated with FAL
- No test suite currently exists
- No linting or type checking commands configured
- Uses ES modules (type: "module" in package.json)
- Requires Node.js 18+ for ES module support
- MCP server imports from non-existent `core/` modules (need to be implemented or references updated)
1 change: 1 addition & 0 deletions bin/fal-cli
2 changes: 1 addition & 1 deletion cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -1372,7 +1372,7 @@ const runDirectGeneration = async (options) => {
imagesPerModel = 1;

// Set output directory
outputDirectory = path.resolve(options.output);
outputDirectory = options.output ? path.resolve(options.output) : path.join(process.cwd(), 'generated-images');

// Generate
await generateImages();
Expand Down
24 changes: 24 additions & 0 deletions core/image-generator.js
Original file line number Diff line number Diff line change
Expand Up @@ -175,11 +175,35 @@ export function extractImageUrls(result) {
}
});
} else if (result.image && result.image.url) {
// Single image response (common for edit models)
images.push(result.image.url);
} else if (result.image && typeof result.image === 'string') {
// Direct image URL string
images.push(result.image);
} else if (result.edited_image && result.edited_image.url) {
// Some edit models return edited_image
images.push(result.edited_image.url);
} else if (result.output && result.output.url) {
// Direct output URL
images.push(result.output.url);
} else if (result.output && result.output.image) {
// Output with nested image
if (result.output.image.url) {
images.push(result.output.image.url);
} else if (typeof result.output.image === 'string') {
images.push(result.output.image);
}
} else if (result.data && result.data.images) {
result.data.images.forEach(img => {
if (img.url) images.push(img.url);
});
} else if (result.data && result.data.image) {
// Single image in data
if (result.data.image.url) {
images.push(result.data.image.url);
} else if (typeof result.data.image === 'string') {
images.push(result.data.image);
}
} else if (result.output && result.output.images) {
result.output.images.forEach(img => {
if (img.url) images.push(img.url);
Expand Down
Loading