This commit is contained in:
admin
2026-01-30 03:04:10 +00:00
parent bcc4d242c4
commit 2a3dedde11
1218 changed files with 214731 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
{
"source": "github.com/fal-ai-community/skills/tree/main/skills/claude.ai/fal-generate",
"type": "github-subdir",
"installed_at": "2026-01-30T02:27:01.659331774Z",
"repo_url": "https://github.com/fal-ai-community/skills.git",
"subdir": "skills/claude.ai/fal-generate",
"version": "69efe6e"
}

315
fal-generate/SKILL.md Normal file
View File

@@ -0,0 +1,315 @@
---
name: fal-generate
description: Generate images and videos using fal.ai AI models with queue support. Use when the user requests "Generate image", "Create video", "Make a picture of...", "Text to image", "Image to video", "Search models", or similar generation tasks.
metadata:
author: fal-ai
version: "3.0.0"
---
# fal.ai Generate
Generate images and videos using state-of-the-art AI models on fal.ai.
## Scripts
| Script | Purpose |
|--------|---------|
| `generate.sh` | Generate images/videos (queue-based) |
| `upload.sh` | Upload local files to fal CDN |
| `search-models.sh` | Search and discover models |
| `get-schema.sh` | Get OpenAPI schema for any model |
## Queue System (Default)
All requests use the queue system by default for reliability:
```
User Request → Queue Submit → Poll Status → Get Result
request_id
```
**Benefits:**
- Long-running tasks (video) won't timeout
- Can check status anytime
- Can cancel queued requests
- Results retrievable even if connection drops
## Generate Content
```bash
bash /mnt/skills/user/fal-generate/scripts/generate.sh [options]
```
### Basic Usage (Queue Mode)
```bash
# Image - submits to queue, waits for completion
bash generate.sh --prompt "A serene mountain landscape" --model "fal-ai/nano-banana-pro"
# Video - same, but takes longer
bash generate.sh --prompt "Ocean waves crashing" --model "fal-ai/veo3.1"
# Image-to-Video
bash generate.sh \
--prompt "Camera slowly zooms in" \
--model "fal-ai/kling-video/v2.6/pro/image-to-video" \
--image-url "https://example.com/image.jpg"
```
### Async Mode (Return Immediately)
For long video jobs, use `--async` to get request_id immediately:
```bash
# Submit and return immediately
bash generate.sh --prompt "Epic battle scene" --model "fal-ai/veo3.1" --async
# Output:
# Request ID: abc123-def456
# Request submitted. Use these commands to check:
# Status: ./generate.sh --status "abc123-def456" --model "fal-ai/veo3.1"
# Result: ./generate.sh --result "abc123-def456" --model "fal-ai/veo3.1"
```
### Queue Operations
```bash
# Check status
bash generate.sh --status "request_id" --model "fal-ai/veo3.1"
# → IN_QUEUE (position: 3) | IN_PROGRESS | COMPLETED
# Get result (when COMPLETED)
bash generate.sh --result "request_id" --model "fal-ai/veo3.1"
# Cancel (only if still queued)
bash generate.sh --cancel "request_id" --model "fal-ai/veo3.1"
```
### Show Logs During Generation
```bash
bash generate.sh --prompt "A sunset" --model "fal-ai/flux/dev" --logs
# Status: IN_QUEUE (position: 2)
# Status: IN_PROGRESS
# > Loading model...
# > Generating image...
# Status: COMPLETED
```
## File Upload
### Option 1: Auto-upload with --file
```bash
# Local file is automatically uploaded to fal CDN
bash generate.sh \
--file "/path/to/photo.jpg" \
--model "fal-ai/kling-video/v2.6/pro/image-to-video" \
--prompt "Camera zooms in slowly"
```
### Option 2: Manual upload with upload.sh
```bash
# Upload first
URL=$(bash upload.sh --file "/path/to/photo.jpg")
# → https://v3.fal.media/files/xxx/photo.jpg
# Then generate
bash generate.sh --image-url "$URL" --model "..." --prompt "..."
```
### Option 3: Use existing URL
```bash
# Any public URL works
bash generate.sh --image-url "https://example.com/image.jpg" ...
```
**Supported file types:**
- Images: jpg, jpeg, png, gif, webp
- Videos: mp4, mov, webm
- Audio: mp3, wav, flac
**Upload flow (two-step):**
```
1. POST rest.alpha.fal.ai/storage/auth/token?storage_type=fal-cdn-v3
→ {"token": "...", "base_url": "https://v3b.fal.media"}
2. POST {base_url}/files/upload
Authorization: Bearer {token}
→ {"access_url": "https://v3b.fal.media/files/..."}
```
**Max file size:** 100MB (simple upload)
## Arguments Reference
| Argument | Description | Default |
|----------|-------------|---------|
| `--prompt`, `-p` | Text description | (required) |
| `--model`, `-m` | Model ID | `fal-ai/flux/dev` |
| `--image-url` | Input image URL for I2V | - |
| `--file`, `--image` | Local file (auto-uploads) | - |
| `--size` | `square`, `portrait`, `landscape` | `landscape_4_3` |
| `--num-images` | Number of images | 1 |
**Mode Options:**
| Argument | Description |
|----------|-------------|
| (default) | Queue mode - submit and poll until complete |
| `--async` | Submit to queue, return request_id immediately |
| `--sync` | Synchronous (not recommended for video) |
| `--logs` | Show generation logs while polling |
**Queue Operations:**
| Argument | Description |
|----------|-------------|
| `--status ID` | Check status of a queued request |
| `--result ID` | Get result of a completed request |
| `--cancel ID` | Cancel a queued request |
**Advanced:**
| Argument | Description | Default |
|----------|-------------|---------|
| `--poll-interval` | Seconds between status checks | 2 |
| `--timeout` | Max seconds to wait | 600 |
| `--lifecycle N` | Object expiration in seconds | - |
| `--schema [MODEL]` | Get OpenAPI schema | - |
## Recommended Models
### Text-to-Image
| Model | Notes |
|-------|-------|
| `fal-ai/nano-banana-pro` | **Best overall** - T2I and editing |
| `fal-ai/flux-2-turbo` | Open source, high quality |
| `fal-ai/flux-2-klein-9b` | Open source, fast |
| `fal-ai/flux/dev` | Good balance (default) |
| `fal-ai/flux/schnell` | ~1 second |
| `fal-ai/ideogram/v3` | Best for text rendering |
### Text-to-Video
| Model | Notes |
|-------|-------|
| `fal-ai/veo3.1` | High quality |
| `fal-ai/bytedance/seedance/v1/pro` | Fast, good quality |
| `fal-ai/sora-2/pro` | OpenAI Sora |
| `fal-ai/kling-video/v2.5-turbo/pro` | Fast, reliable |
| `fal-ai/minimax/hailuo-02/pro` | Good for characters |
### Image-to-Video
| Model | Notes |
|-------|-------|
| `fal-ai/kling-video/v2.6/pro/image-to-video` | **Best overall** |
| `fal-ai/veo3/fast` | Fast, high quality |
| `fal-ai/bytedance/seedance/v1.5/pro/image-to-video` | Smooth motion |
| `fal-ai/minimax/hailuo-02/standard/image-to-video` | Good balance |
## Search Models
```bash
# Search by keyword
bash search-models.sh --query "flux"
# Filter by category
bash search-models.sh --category "text-to-video"
```
**Categories:** `text-to-image`, `image-to-image`, `text-to-video`, `image-to-video`, `text-to-speech`, `speech-to-text`
## Get Model Schema (OpenAPI)
**IMPORTANT:** Fetch schema to see exact parameters for any model.
```bash
# Get schema
bash get-schema.sh --model "fal-ai/nano-banana-pro"
# Show only input parameters
bash get-schema.sh --model "fal-ai/kling-video/v2.6/pro/image-to-video" --input
# Quick schema via generate.sh
bash generate.sh --schema "fal-ai/veo3.1"
```
**API Endpoint:**
```
https://fal.ai/api/openapi/queue/openapi.json?endpoint_id={model-id}
```
## Output
**Queue Submit Response:**
```json
{
"request_id": "abc123-def456",
"status": "IN_QUEUE",
"response_url": "https://queue.fal.run/.../requests/abc123-def456",
"status_url": "https://queue.fal.run/.../requests/abc123-def456/status",
"cancel_url": "https://queue.fal.run/.../requests/abc123-def456/cancel"
}
```
**Final Result:**
```json
{
"images": [{ "url": "https://v3.fal.media/files/...", "width": 1024, "height": 768 }]
}
```
## Present Results to User
**Images:**
```
![Generated Image](https://v3.fal.media/files/...)
• 1024×768 | Generated in 2.2s
```
**Videos:**
```
[Click to view video](https://v3.fal.media/files/.../video.mp4)
• Duration: 5s | Generated in 45s
```
**Async Submission:**
```
Request submitted to queue.
• Request ID: abc123-def456
• Model: fal-ai/veo3
• Check status: --status "abc123-def456"
```
## Object Lifecycle (Optional)
Control how long generated files remain accessible:
```bash
# Files expire after 1 hour (3600 seconds)
bash generate.sh --prompt "..." --lifecycle 3600
# Files expire after 24 hours
bash generate.sh --prompt "..." --lifecycle 86400
```
## Troubleshooting
### Timeout Error
```
Error: Timeout after 600s
Request ID: abc123-def456
```
**Solution:** Use `--status` and `--result` to check manually, or increase `--timeout`.
### API Key Error
```
Error: FAL_KEY not set
```
**Solution:** Run `./generate.sh --add-fal-key` or `export FAL_KEY=your_key`.
### Network Error (claude.ai)
Go to `claude.ai/settings/capabilities` and add `*.fal.ai` to allowed domains.

519
fal-generate/scripts/generate.sh Executable file
View File

@@ -0,0 +1,519 @@
#!/bin/bash
# fal.ai Generation Script with Queue Support
# Usage: ./generate.sh --prompt "..." [--model MODEL] [options]
# Returns: JSON with generated media URLs
#
# Queue Mode (default): Submits to queue, polls for completion
# Async Mode: Returns request_id immediately
# Sync Mode: Direct request (not recommended for long tasks)
set -e
FAL_QUEUE_ENDPOINT="https://queue.fal.run"
FAL_SYNC_ENDPOINT="https://fal.run"
FAL_TOKEN_ENDPOINT="https://rest.alpha.fal.ai/storage/auth/token?storage_type=fal-cdn-v3"
# Default values
MODEL="fal-ai/flux/dev"
PROMPT=""
IMAGE_URL=""
IMAGE_FILE=""
IMAGE_SIZE="landscape_4_3"
NUM_IMAGES=1
MODE="queue" # queue (default), async, sync
REQUEST_ID=""
ACTION="generate" # generate, status, result, cancel
POLL_INTERVAL=2
MAX_POLL_TIME=600
LIFECYCLE=""
SHOW_LOGS=false
# Check for --add-fal-key first
for arg in "$@"; do
if [ "$arg" = "--add-fal-key" ]; then
shift
KEY_VALUE=""
if [[ -n "$1" && ! "$1" =~ ^-- ]]; then
KEY_VALUE="$1"
fi
if [ -z "$KEY_VALUE" ]; then
echo "Enter your fal.ai API key:" >&2
read -r KEY_VALUE
fi
if [ -n "$KEY_VALUE" ]; then
grep -v "^FAL_KEY=" .env > .env.tmp 2>/dev/null || true
mv .env.tmp .env 2>/dev/null || true
echo "FAL_KEY=$KEY_VALUE" >> .env
echo "FAL_KEY saved to .env" >&2
fi
exit 0
fi
done
# Load .env if exists
if [ -f ".env" ]; then
source .env 2>/dev/null || true
fi
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--prompt|-p)
PROMPT="$2"
shift 2
;;
--model|-m)
MODEL="$2"
shift 2
;;
--image-url)
IMAGE_URL="$2"
shift 2
;;
--file|--image)
IMAGE_FILE="$2"
shift 2
;;
--size)
case $2 in
square) IMAGE_SIZE="square" ;;
portrait) IMAGE_SIZE="portrait_4_3" ;;
landscape) IMAGE_SIZE="landscape_4_3" ;;
*) IMAGE_SIZE="$2" ;;
esac
shift 2
;;
--num-images)
NUM_IMAGES="$2"
shift 2
;;
# Mode options
--async)
MODE="async"
shift
;;
--sync)
MODE="sync"
shift
;;
--logs)
SHOW_LOGS=true
shift
;;
# Queue operations
--status)
ACTION="status"
REQUEST_ID="$2"
shift 2
;;
--result)
ACTION="result"
REQUEST_ID="$2"
shift 2
;;
--cancel)
ACTION="cancel"
REQUEST_ID="$2"
shift 2
;;
# Polling options
--poll-interval)
POLL_INTERVAL="$2"
shift 2
;;
--timeout)
MAX_POLL_TIME="$2"
shift 2
;;
# Object lifecycle (optional)
--lifecycle)
LIFECYCLE="$2"
shift 2
;;
# Schema lookup
--schema)
SCHEMA_MODEL="${2:-$MODEL}"
ENCODED=$(echo "$SCHEMA_MODEL" | sed 's/\//%2F/g')
echo "Fetching schema for $SCHEMA_MODEL..." >&2
curl -s "https://fal.ai/api/openapi/queue/openapi.json?endpoint_id=$ENCODED"
exit 0
;;
--help|-h)
echo "fal.ai Generation Script (Queue-based)" >&2
echo "" >&2
echo "Usage:" >&2
echo " ./generate.sh --prompt \"...\" [options]" >&2
echo "" >&2
echo "Generation Options:" >&2
echo " --prompt, -p Text description (required for generate)" >&2
echo " --model, -m Model ID (default: fal-ai/flux/dev)" >&2
echo " --image-url Input image URL for I2V models" >&2
echo " --file, --image Local file (auto-uploads to fal CDN)" >&2
echo " --size square, portrait, landscape" >&2
echo " --num-images Number of images (default: 1)" >&2
echo "" >&2
echo "Mode Options:" >&2
echo " (default) Queue mode - submit and poll until complete" >&2
echo " --async Submit to queue, return request_id immediately" >&2
echo " --sync Synchronous request (not recommended for video)" >&2
echo " --logs Show generation logs while polling" >&2
echo "" >&2
echo "Queue Operations:" >&2
echo " --status ID Check status of a queued request" >&2
echo " --result ID Get result of a completed request" >&2
echo " --cancel ID Cancel a queued request" >&2
echo "" >&2
echo "Advanced Options:" >&2
echo " --poll-interval Seconds between status checks (default: 2)" >&2
echo " --timeout Max seconds to wait (default: 600)" >&2
echo " --lifecycle N Object expiration in seconds (optional)" >&2
echo " --schema [MODEL] Get OpenAPI schema for model" >&2
echo " --add-fal-key Setup FAL_KEY in .env" >&2
echo "" >&2
echo "Examples:" >&2
echo " # Generate image (waits for completion)" >&2
echo " ./generate.sh --prompt \"a sunset\" --model \"fal-ai/flux/dev\"" >&2
echo "" >&2
echo " # Generate video async (returns immediately)" >&2
echo " ./generate.sh --prompt \"ocean waves\" --model \"fal-ai/veo3\" --async" >&2
echo "" >&2
echo " # Check status" >&2
echo " ./generate.sh --status \"request_id\" --model \"fal-ai/veo3\"" >&2
echo "" >&2
echo " # Get result" >&2
echo " ./generate.sh --result \"request_id\" --model \"fal-ai/veo3\"" >&2
exit 0
;;
*)
shift
;;
esac
done
# Validate FAL_KEY
if [ -z "$FAL_KEY" ]; then
echo "Error: FAL_KEY not set" >&2
echo "" >&2
echo "Run: ./generate.sh --add-fal-key" >&2
echo "Or: export FAL_KEY=your_key_here" >&2
exit 1
fi
# Handle local file upload
if [ -n "$IMAGE_FILE" ]; then
if [ ! -f "$IMAGE_FILE" ]; then
echo "Error: File not found: $IMAGE_FILE" >&2
exit 1
fi
FILENAME=$(basename "$IMAGE_FILE")
EXTENSION="${FILENAME##*.}"
EXTENSION_LOWER=$(echo "$EXTENSION" | tr '[:upper:]' '[:lower:]')
# Detect content type
case "$EXTENSION_LOWER" in
jpg|jpeg) CONTENT_TYPE="image/jpeg" ;;
png) CONTENT_TYPE="image/png" ;;
gif) CONTENT_TYPE="image/gif" ;;
webp) CONTENT_TYPE="image/webp" ;;
mp4) CONTENT_TYPE="video/mp4" ;;
mov) CONTENT_TYPE="video/quicktime" ;;
*) CONTENT_TYPE="application/octet-stream" ;;
esac
echo "Uploading $FILENAME..." >&2
# Step 1: Get CDN token
TOKEN_RESPONSE=$(curl -s -X POST "$FAL_TOKEN_ENDPOINT" \
-H "Authorization: Key $FAL_KEY" \
-H "Content-Type: application/json" \
-d '{}')
CDN_TOKEN=$(echo "$TOKEN_RESPONSE" | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
CDN_TOKEN_TYPE=$(echo "$TOKEN_RESPONSE" | grep -o '"token_type":"[^"]*"' | cut -d'"' -f4)
CDN_BASE_URL=$(echo "$TOKEN_RESPONSE" | grep -o '"base_url":"[^"]*"' | cut -d'"' -f4)
if [ -z "$CDN_TOKEN" ] || [ -z "$CDN_BASE_URL" ]; then
echo "Error: Failed to get CDN token" >&2
exit 1
fi
# Step 2: Upload file
UPLOAD_RESPONSE=$(curl -s -X POST "${CDN_BASE_URL}/files/upload" \
-H "Authorization: $CDN_TOKEN_TYPE $CDN_TOKEN" \
-H "Content-Type: $CONTENT_TYPE" \
-H "X-Fal-File-Name: $FILENAME" \
--data-binary "@$IMAGE_FILE")
# Check for upload error
if echo "$UPLOAD_RESPONSE" | grep -q '"error"'; then
ERROR_MSG=$(echo "$UPLOAD_RESPONSE" | grep -o '"message":"[^"]*"' | head -1 | cut -d'"' -f4)
echo "Upload error: $ERROR_MSG" >&2
exit 1
fi
# Extract URL
IMAGE_URL=$(echo "$UPLOAD_RESPONSE" | grep -o '"access_url":"[^"]*"' | cut -d'"' -f4)
if [ -z "$IMAGE_URL" ]; then
echo "Error: Failed to get URL from upload response" >&2
exit 1
fi
echo "Uploaded: $IMAGE_URL" >&2
fi
# Build headers
HEADERS=(-H "Authorization: Key $FAL_KEY" -H "Content-Type: application/json")
# Add lifecycle header if specified
if [ -n "$LIFECYCLE" ]; then
HEADERS+=(-H "X-Fal-Object-Lifecycle-Preference: {\"expiration_duration_seconds\": $LIFECYCLE}")
fi
# Handle queue operations
case $ACTION in
status)
if [ -z "$REQUEST_ID" ]; then
echo "Error: Request ID required for --status" >&2
exit 1
fi
LOGS_PARAM=""
if [ "$SHOW_LOGS" = true ]; then
LOGS_PARAM="?logs=1"
fi
echo "Checking status for $REQUEST_ID..." >&2
RESPONSE=$(curl -s -X GET "$FAL_QUEUE_ENDPOINT/$MODEL/requests/$REQUEST_ID/status$LOGS_PARAM" "${HEADERS[@]}")
# Parse and display status
STATUS=$(echo "$RESPONSE" | grep -oE '"status"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
echo "Status: $STATUS" >&2
if [ "$STATUS" = "IN_QUEUE" ]; then
POSITION=$(echo "$RESPONSE" | grep -o '"queue_position":[0-9]*' | cut -d':' -f2)
[ -n "$POSITION" ] && echo "Queue position: $POSITION" >&2
fi
echo "$RESPONSE"
exit 0
;;
result)
if [ -z "$REQUEST_ID" ]; then
echo "Error: Request ID required for --result" >&2
exit 1
fi
echo "Getting result for $REQUEST_ID..." >&2
RESPONSE=$(curl -s -X GET "$FAL_QUEUE_ENDPOINT/$MODEL/requests/$REQUEST_ID" "${HEADERS[@]}")
# Check for error
if echo "$RESPONSE" | grep -q '"error"'; then
ERROR_MSG=$(echo "$RESPONSE" | grep -o '"message":"[^"]*"' | head -1 | cut -d'"' -f4)
echo "Error: $ERROR_MSG" >&2
exit 1
fi
# Extract URL
if echo "$RESPONSE" | grep -q '"video"'; then
URL=$(echo "$RESPONSE" | grep -o '"url":"[^"]*"' | head -1 | cut -d'"' -f4)
echo "Video URL: $URL" >&2
elif echo "$RESPONSE" | grep -q '"images"'; then
URL=$(echo "$RESPONSE" | grep -o '"url":"[^"]*"' | head -1 | cut -d'"' -f4)
echo "Image URL: $URL" >&2
fi
echo "$RESPONSE"
exit 0
;;
cancel)
if [ -z "$REQUEST_ID" ]; then
echo "Error: Request ID required for --cancel" >&2
exit 1
fi
echo "Cancelling request $REQUEST_ID..." >&2
RESPONSE=$(curl -s -X PUT "$FAL_QUEUE_ENDPOINT/$MODEL/requests/$REQUEST_ID/cancel" "${HEADERS[@]}")
echo "$RESPONSE"
exit 0
;;
esac
# Generate action requires prompt
if [ -z "$PROMPT" ]; then
echo "Error: --prompt is required" >&2
exit 1
fi
# Build the request payload based on model type
if [[ "$MODEL" == *"image-to-video"* ]] || [[ "$MODEL" == *"i2v"* ]]; then
if [ -z "$IMAGE_URL" ]; then
echo "Error: --image-url is required for image-to-video models" >&2
exit 1
fi
PAYLOAD=$(cat <<EOF
{"prompt": "$PROMPT", "image_url": "$IMAGE_URL"}
EOF
)
elif [[ "$MODEL" == *"video"* ]] || [[ "$MODEL" == *"veo"* ]] || [[ "$MODEL" == *"text-to-video"* ]]; then
PAYLOAD=$(cat <<EOF
{"prompt": "$PROMPT"}
EOF
)
else
PAYLOAD=$(cat <<EOF
{"prompt": "$PROMPT", "image_size": "$IMAGE_SIZE", "num_images": $NUM_IMAGES}
EOF
)
fi
# Synchronous mode
if [ "$MODE" = "sync" ]; then
echo "Generating with $MODEL (sync mode)..." >&2
RESPONSE=$(curl -s -X POST "$FAL_SYNC_ENDPOINT/$MODEL" "${HEADERS[@]}" -d "$PAYLOAD")
if echo "$RESPONSE" | grep -q '"error"'; then
ERROR_MSG=$(echo "$RESPONSE" | grep -o '"message":"[^"]*"' | head -1 | cut -d'"' -f4)
echo "Error: $ERROR_MSG" >&2
exit 1
fi
echo "Generation complete!" >&2
if echo "$RESPONSE" | grep -q '"video"'; then
URL=$(echo "$RESPONSE" | grep -o '"url":"[^"]*"' | head -1 | cut -d'"' -f4)
echo "Video URL: $URL" >&2
elif echo "$RESPONSE" | grep -q '"images"'; then
URL=$(echo "$RESPONSE" | grep -o '"url":"[^"]*"' | head -1 | cut -d'"' -f4)
echo "Image URL: $URL" >&2
fi
echo "$RESPONSE"
exit 0
fi
# Queue mode (async or poll)
echo "Submitting to queue: $MODEL..." >&2
SUBMIT_RESPONSE=$(curl -s -X POST "$FAL_QUEUE_ENDPOINT/$MODEL" "${HEADERS[@]}" -d "$PAYLOAD")
# Check for submit error
if echo "$SUBMIT_RESPONSE" | grep -q '"error"'; then
ERROR_MSG=$(echo "$SUBMIT_RESPONSE" | grep -o '"message":"[^"]*"' | head -1 | cut -d'"' -f4)
echo "Error: $ERROR_MSG" >&2
exit 1
fi
# Extract request_id and URLs (handle both "key": "value" and "key":"value" formats)
REQUEST_ID=$(echo "$SUBMIT_RESPONSE" | grep -oE '"request_id"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
STATUS_URL=$(echo "$SUBMIT_RESPONSE" | grep -oE '"status_url"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
RESPONSE_URL=$(echo "$SUBMIT_RESPONSE" | grep -oE '"response_url"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
CANCEL_URL=$(echo "$SUBMIT_RESPONSE" | grep -oE '"cancel_url"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
if [ -z "$REQUEST_ID" ]; then
echo "Error: Failed to get request_id" >&2
echo "$SUBMIT_RESPONSE" >&2
exit 1
fi
echo "Request ID: $REQUEST_ID" >&2
# Async mode - return immediately
if [ "$MODE" = "async" ]; then
echo "" >&2
echo "Request submitted. Use these commands to check:" >&2
echo " Status: ./generate.sh --status \"$REQUEST_ID\" --model \"$MODEL\"" >&2
echo " Result: ./generate.sh --result \"$REQUEST_ID\" --model \"$MODEL\"" >&2
echo " Cancel: ./generate.sh --cancel \"$REQUEST_ID\" --model \"$MODEL\"" >&2
echo "$SUBMIT_RESPONSE"
exit 0
fi
# Queue mode - poll until complete
echo "Waiting for completion..." >&2
ELAPSED=0
LAST_STATUS=""
while [ $ELAPSED -lt $MAX_POLL_TIME ]; do
sleep $POLL_INTERVAL
ELAPSED=$((ELAPSED + POLL_INTERVAL))
LOGS_PARAM=""
if [ "$SHOW_LOGS" = true ]; then
LOGS_PARAM="?logs=1"
fi
STATUS_RESPONSE=$(curl -s -X GET "${STATUS_URL}${LOGS_PARAM}" "${HEADERS[@]}")
STATUS=$(echo "$STATUS_RESPONSE" | grep -oE '"status"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//' | sed 's/"$//')
# Show status change
if [ "$STATUS" != "$LAST_STATUS" ]; then
case $STATUS in
IN_QUEUE)
POSITION=$(echo "$STATUS_RESPONSE" | grep -o '"queue_position":[0-9]*' | cut -d':' -f2)
echo "Status: IN_QUEUE (position: ${POSITION:-?})" >&2
;;
IN_PROGRESS)
echo "Status: IN_PROGRESS" >&2
;;
COMPLETED)
echo "Status: COMPLETED" >&2
;;
*)
echo "Status: $STATUS" >&2
;;
esac
LAST_STATUS="$STATUS"
fi
# Show logs if enabled
if [ "$SHOW_LOGS" = true ]; then
LOGS=$(echo "$STATUS_RESPONSE" | grep -o '"logs":\[[^]]*\]' | head -1)
if [ -n "$LOGS" ] && [ "$LOGS" != "[]" ]; then
echo "$LOGS" | tr ',' '\n' | grep -o '"message":"[^"]*"' | cut -d'"' -f4 | while read -r log; do
echo " > $log" >&2
done
fi
fi
if [ "$STATUS" = "COMPLETED" ]; then
break
fi
if [ "$STATUS" = "FAILED" ]; then
echo "Error: Generation failed" >&2
echo "$STATUS_RESPONSE"
exit 1
fi
done
if [ "$STATUS" != "COMPLETED" ]; then
echo "Error: Timeout after ${MAX_POLL_TIME}s" >&2
echo "Request ID: $REQUEST_ID" >&2
echo "Check status with: ./generate.sh --status \"$REQUEST_ID\" --model \"$MODEL\"" >&2
exit 1
fi
# Get final result
echo "Fetching result..." >&2
RESULT=$(curl -s -X GET "$RESPONSE_URL" "${HEADERS[@]}")
# Check for error in result
if echo "$RESULT" | grep -q '"error"'; then
ERROR_MSG=$(echo "$RESULT" | grep -o '"message":"[^"]*"' | head -1 | cut -d'"' -f4)
echo "Error: $ERROR_MSG" >&2
exit 1
fi
echo "" >&2
echo "Generation complete!" >&2
# Extract and display URL
if echo "$RESULT" | grep -q '"video"'; then
URL=$(echo "$RESULT" | grep -o '"url":"[^"]*"' | head -1 | cut -d'"' -f4)
echo "Video URL: $URL" >&2
elif echo "$RESULT" | grep -q '"images"'; then
URL=$(echo "$RESULT" | grep -o '"url":"[^"]*"' | head -1 | cut -d'"' -f4)
echo "Image URL: $URL" >&2
fi
# Output JSON
echo "$RESULT"

View File

@@ -0,0 +1,216 @@
#!/bin/bash
# fal.ai Model Schema Script
# Usage: ./get-schema.sh --model MODEL
# Returns: OpenAPI 3.0 schema for the model
set -e
FAL_SCHEMA_ENDPOINT="https://fal.ai/api/openapi/queue/openapi.json"
# Default values
MODEL=""
OUTPUT_JSON=false
SHOW_INPUT=false
SHOW_OUTPUT=false
# Check for --add-fal-key first
for arg in "$@"; do
if [ "$arg" = "--add-fal-key" ]; then
shift
KEY_VALUE=""
if [[ -n "$1" && ! "$1" =~ ^-- ]]; then
KEY_VALUE="$1"
fi
if [ -z "$KEY_VALUE" ]; then
echo "Enter your fal.ai API key:" >&2
read -r KEY_VALUE
fi
if [ -n "$KEY_VALUE" ]; then
grep -v "^FAL_KEY=" .env > .env.tmp 2>/dev/null || true
mv .env.tmp .env 2>/dev/null || true
echo "FAL_KEY=$KEY_VALUE" >> .env
echo "FAL_KEY saved to .env" >&2
fi
exit 0
fi
done
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--model|-m)
MODEL="$2"
shift 2
;;
--input|-i)
SHOW_INPUT=true
shift
;;
--output|-o)
SHOW_OUTPUT=true
shift
;;
--json)
OUTPUT_JSON=true
shift
;;
--help|-h)
echo "fal.ai Model Schema Script" >&2
echo "" >&2
echo "Usage:" >&2
echo " ./get-schema.sh --model MODEL [options]" >&2
echo "" >&2
echo "Options:" >&2
echo " --model, -m Model ID (required)" >&2
echo " --input, -i Show only input schema" >&2
echo " --output, -o Show only output schema" >&2
echo " --json Output raw JSON" >&2
echo " --add-fal-key Setup FAL_KEY in .env" >&2
echo "" >&2
echo "Examples:" >&2
echo " ./get-schema.sh --model \"fal-ai/flux-pro/v1.1-ultra\"" >&2
echo " ./get-schema.sh --model \"fal-ai/kling-video/v2.1/pro/image-to-video\" --input" >&2
exit 0
;;
*)
shift
;;
esac
done
if [ -z "$MODEL" ]; then
echo "Error: --model is required" >&2
exit 1
fi
# URL encode the model ID
ENCODED_MODEL=$(echo "$MODEL" | sed 's/\//%2F/g')
echo "Fetching schema for $MODEL..." >&2
# Fetch OpenAPI schema
RESPONSE=$(curl -s -X GET "$FAL_SCHEMA_ENDPOINT?endpoint_id=$ENCODED_MODEL" \
-H "Content-Type: application/json")
# Check for errors
if echo "$RESPONSE" | grep -q '"error"'; then
ERROR_MSG=$(echo "$RESPONSE" | grep -o '"message":"[^"]*"' | head -1 | cut -d'"' -f4)
echo "Error: $ERROR_MSG" >&2
exit 1
fi
if [ "$OUTPUT_JSON" = true ]; then
echo "$RESPONSE"
exit 0
fi
# Parse and display schema summary
if command -v python3 &> /dev/null; then
python3 << 'PYTHON_EOF' - "$RESPONSE" "$MODEL" "$SHOW_INPUT" "$SHOW_OUTPUT"
import json
import sys
response = json.loads(sys.argv[1])
model = sys.argv[2]
show_input = sys.argv[3] == "true"
show_output = sys.argv[4] == "true"
# Get metadata
info = response.get('info', {})
metadata = info.get('x-fal-metadata', {})
schemas = response.get('components', {}).get('schemas', {})
print("", file=sys.stderr)
print(f"Model: {model}", file=sys.stderr)
print("=" * (len(model) + 7), file=sys.stderr)
# Category
category = metadata.get('category', 'unknown')
print(f"Category: {category}", file=sys.stderr)
# URLs
if metadata.get('playgroundUrl'):
print(f"Playground: {metadata['playgroundUrl']}", file=sys.stderr)
if metadata.get('documentationUrl'):
print(f"Docs: {metadata['documentationUrl']}", file=sys.stderr)
# Find input/output schemas
input_schema = None
output_schema = None
for name, schema in schemas.items():
if 'Input' in name and name != 'QueueStatus':
input_schema = schema
elif 'Output' in name and name != 'QueueStatus':
output_schema = schema
# Show input schema
if input_schema and (show_input or (not show_input and not show_output)):
print("", file=sys.stderr)
print("Input Parameters", file=sys.stderr)
print("-" * 16, file=sys.stderr)
props = input_schema.get('properties', {})
required = input_schema.get('required', [])
order = input_schema.get('x-fal-order-properties', list(props.keys()))
for prop_name in order:
if prop_name not in props:
continue
prop = props[prop_name]
# Type
prop_type = prop.get('type', 'any')
if 'enum' in prop:
prop_type = f"enum: {prop['enum']}"
elif 'anyOf' in prop:
types = [t.get('type', t.get('enum', ['?'])) for t in prop['anyOf']]
prop_type = f"oneOf: {types}"
# Required marker
req_mark = "*" if prop_name in required else " "
# Default value
default = prop.get('default', '')
default_str = f" (default: {default})" if default != '' else ""
print(f" {req_mark} {prop_name}: {prop_type}{default_str}", file=sys.stderr)
# Description
desc = prop.get('description', '')
if desc:
# Truncate long descriptions
if len(desc) > 60:
desc = desc[:57] + "..."
print(f" {desc}", file=sys.stderr)
# Show output schema
if output_schema and (show_output or (not show_input and not show_output)):
print("", file=sys.stderr)
print("Output Fields", file=sys.stderr)
print("-" * 13, file=sys.stderr)
props = output_schema.get('properties', {})
order = output_schema.get('x-fal-order-properties', list(props.keys()))
for prop_name in order:
if prop_name not in props:
continue
prop = props[prop_name]
prop_type = prop.get('type', 'any')
if prop_type == 'array':
items = prop.get('items', {})
item_type = items.get('type', items.get('$ref', '').split('/')[-1])
prop_type = f"array<{item_type}>"
print(f" {prop_name}: {prop_type}", file=sys.stderr)
print("", file=sys.stderr)
print("* = required parameter", file=sys.stderr)
PYTHON_EOF
fi
# Output full JSON for programmatic use
echo "$RESPONSE"

View File

@@ -0,0 +1,154 @@
#!/bin/bash
# fal.ai Model Search Script
# Usage: ./search-models.sh [--query QUERY] [--category CATEGORY] [--limit N]
# Returns: JSON with matching models
set -e
FAL_API_ENDPOINT="https://api.fal.ai/v1/models"
# Default values
QUERY=""
CATEGORY=""
LIMIT=20
# Load .env if exists
if [ -f ".env" ]; then
source .env 2>/dev/null || true
fi
# Check for --add-fal-key first
for arg in "$@"; do
if [ "$arg" = "--add-fal-key" ]; then
shift
KEY_VALUE=""
if [[ -n "$1" && ! "$1" =~ ^-- ]]; then
KEY_VALUE="$1"
fi
if [ -z "$KEY_VALUE" ]; then
echo "Enter your fal.ai API key:" >&2
read -r KEY_VALUE
fi
if [ -n "$KEY_VALUE" ]; then
grep -v "^FAL_KEY=" .env > .env.tmp 2>/dev/null || true
mv .env.tmp .env 2>/dev/null || true
echo "FAL_KEY=$KEY_VALUE" >> .env
echo "FAL_KEY saved to .env" >&2
fi
exit 0
fi
done
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--query|-q)
QUERY="$2"
shift 2
;;
--category|-c)
CATEGORY="$2"
shift 2
;;
--limit|-l)
LIMIT="$2"
shift 2
;;
--help|-h)
echo "fal.ai Model Search Script" >&2
echo "" >&2
echo "Usage:" >&2
echo " ./search-models.sh [options]" >&2
echo "" >&2
echo "Options:" >&2
echo " --query, -q Search query (e.g., 'flux', 'video', 'upscale')" >&2
echo " --category, -c Filter by category:" >&2
echo " text-to-image, image-to-image, text-to-video," >&2
echo " image-to-video, text-to-speech, speech-to-text" >&2
echo " --limit, -l Max results (default: 20)" >&2
echo " --add-fal-key Setup FAL_KEY in .env" >&2
echo "" >&2
echo "Examples:" >&2
echo " ./search-models.sh --query 'flux'" >&2
echo " ./search-models.sh --category 'text-to-video'" >&2
echo " ./search-models.sh --query 'upscale' --limit 5" >&2
exit 0
;;
*)
shift
;;
esac
done
# Validate FAL_KEY
if [ -z "$FAL_KEY" ]; then
echo "Error: FAL_KEY not set" >&2
echo "" >&2
echo "Run: ./search-models.sh --add-fal-key" >&2
echo "Or: export FAL_KEY=your_key_here" >&2
exit 1
fi
echo "Searching fal.ai models..." >&2
# Build query parameters
PARAMS="limit=$LIMIT"
if [ -n "$QUERY" ]; then
PARAMS="$PARAMS&q=$(echo "$QUERY" | sed 's/ /%20/g')"
fi
if [ -n "$CATEGORY" ]; then
PARAMS="$PARAMS&category=$CATEGORY"
fi
# Make API request
AUTH_HEADER=""
if [ -n "$FAL_KEY" ]; then
AUTH_HEADER="-H \"Authorization: Key $FAL_KEY\""
fi
RESPONSE=$(curl -s -X GET "$FAL_API_ENDPOINT?$PARAMS" \
-H "Content-Type: application/json" \
-H "Authorization: Key $FAL_KEY")
# Check for errors
if echo "$RESPONSE" | grep -q '"error"'; then
ERROR_MSG=$(echo "$RESPONSE" | grep -o '"message":"[^"]*"' | head -1 | cut -d'"' -f4)
echo "Error: $ERROR_MSG" >&2
exit 1
fi
# Count results
COUNT=$(echo "$RESPONSE" | grep -o '"endpoint_id"' | wc -l | tr -d ' ')
echo "Found $COUNT models" >&2
echo "" >&2
# Display summary to stderr
if command -v python3 &> /dev/null; then
python3 << 'PYTHON_EOF' - "$RESPONSE"
import json
import sys
response = json.loads(sys.argv[1])
models = response.get('data', response) if isinstance(response, dict) else response
if isinstance(models, list):
for m in models[:10]:
name = m.get('display_name', m.get('endpoint_id', 'Unknown'))
endpoint = m.get('endpoint_id', '')
category = m.get('category', '')
print(f" {name}", file=sys.stderr)
print(f" ID: {endpoint}", file=sys.stderr)
if category:
print(f" Category: {category}", file=sys.stderr)
print("", file=sys.stderr)
if len(models) > 10:
print(f" ... and {len(models) - 10} more", file=sys.stderr)
PYTHON_EOF
fi
# Output JSON for programmatic use
echo "$RESPONSE"

View File

@@ -0,0 +1,216 @@
#!/bin/bash
# fal.ai File Upload Script
# Usage: ./upload.sh --file /path/to/file
# Returns: CDN URL for the uploaded file
#
# Upload flow:
# 1. Get CDN token from rest.alpha.fal.ai
# 2. Upload file to CDN (v3b.fal.media)
# 3. Return access_url
set -e
FAL_TOKEN_ENDPOINT="https://rest.alpha.fal.ai/storage/auth/token?storage_type=fal-cdn-v3"
# Default values
FILE_PATH=""
OUTPUT_JSON=false
# Check for --add-fal-key first
for arg in "$@"; do
if [ "$arg" = "--add-fal-key" ]; then
shift
KEY_VALUE=""
if [[ -n "$1" && ! "$1" =~ ^-- ]]; then
KEY_VALUE="$1"
fi
if [ -z "$KEY_VALUE" ]; then
echo "Enter your fal.ai API key:" >&2
read -r KEY_VALUE
fi
if [ -n "$KEY_VALUE" ]; then
grep -v "^FAL_KEY=" .env > .env.tmp 2>/dev/null || true
mv .env.tmp .env 2>/dev/null || true
echo "FAL_KEY=$KEY_VALUE" >> .env
echo "FAL_KEY saved to .env" >&2
fi
exit 0
fi
done
# Load .env if exists
if [ -f ".env" ]; then
source .env 2>/dev/null || true
fi
# Parse arguments
while [[ $# -gt 0 ]]; do
case $1 in
--file|-f)
FILE_PATH="$2"
shift 2
;;
--json)
OUTPUT_JSON=true
shift
;;
--help|-h)
echo "fal.ai File Upload Script" >&2
echo "" >&2
echo "Usage:" >&2
echo " ./upload.sh --file /path/to/file" >&2
echo "" >&2
echo "Options:" >&2
echo " --file, -f Local file path (required)" >&2
echo " --json Output raw JSON response" >&2
echo " --add-fal-key Setup FAL_KEY in .env" >&2
echo "" >&2
echo "Supported file types:" >&2
echo " Images: jpg, jpeg, png, gif, webp, bmp, tiff" >&2
echo " Videos: mp4, mov, avi, webm, mkv" >&2
echo " Audio: mp3, wav, flac, ogg, m4a" >&2
echo "" >&2
echo "Examples:" >&2
echo " ./upload.sh --file photo.jpg" >&2
echo " ./upload.sh --file video.mp4" >&2
echo "" >&2
echo "Usage with generate.sh:" >&2
echo " URL=\$(./upload.sh --file photo.jpg)" >&2
echo " ./generate.sh --image-url \"\$URL\" --prompt \"...\"" >&2
exit 0
;;
*)
# If no flag, treat as file path
if [ -z "$FILE_PATH" ] && [ -f "$1" ]; then
FILE_PATH="$1"
fi
shift
;;
esac
done
# Validate FAL_KEY
if [ -z "$FAL_KEY" ]; then
echo "Error: FAL_KEY not set" >&2
echo "" >&2
echo "Run: ./upload.sh --add-fal-key" >&2
echo "Or: export FAL_KEY=your_key_here" >&2
exit 1
fi
# Validate file
if [ -z "$FILE_PATH" ]; then
echo "Error: --file is required" >&2
exit 1
fi
if [ ! -f "$FILE_PATH" ]; then
echo "Error: File not found: $FILE_PATH" >&2
exit 1
fi
# Get filename
FILENAME=$(basename "$FILE_PATH")
# Detect content type
EXTENSION="${FILENAME##*.}"
EXTENSION_LOWER=$(echo "$EXTENSION" | tr '[:upper:]' '[:lower:]')
case "$EXTENSION_LOWER" in
jpg|jpeg) CONTENT_TYPE="image/jpeg" ;;
png) CONTENT_TYPE="image/png" ;;
gif) CONTENT_TYPE="image/gif" ;;
webp) CONTENT_TYPE="image/webp" ;;
bmp) CONTENT_TYPE="image/bmp" ;;
tiff|tif) CONTENT_TYPE="image/tiff" ;;
mp4) CONTENT_TYPE="video/mp4" ;;
mov) CONTENT_TYPE="video/quicktime" ;;
avi) CONTENT_TYPE="video/x-msvideo" ;;
webm) CONTENT_TYPE="video/webm" ;;
mkv) CONTENT_TYPE="video/x-matroska" ;;
mp3) CONTENT_TYPE="audio/mpeg" ;;
wav) CONTENT_TYPE="audio/wav" ;;
flac) CONTENT_TYPE="audio/flac" ;;
ogg) CONTENT_TYPE="audio/ogg" ;;
m4a) CONTENT_TYPE="audio/mp4" ;;
*) CONTENT_TYPE="application/octet-stream" ;;
esac
# Get file size
FILE_SIZE=$(stat -f%z "$FILE_PATH" 2>/dev/null || stat -c%s "$FILE_PATH" 2>/dev/null)
FILE_SIZE_MB=$((FILE_SIZE / 1024 / 1024))
# Check if file is too large (>100MB needs multipart)
if [ "$FILE_SIZE" -gt 104857600 ]; then
echo "Error: File too large (${FILE_SIZE_MB}MB > 100MB)" >&2
echo "Large file multipart upload not yet supported." >&2
echo "Please use the Python client for files >100MB." >&2
exit 1
fi
echo "Uploading: $FILENAME (${FILE_SIZE_MB}MB)" >&2
# Step 1: Get CDN token
echo "Getting CDN token..." >&2
TOKEN_RESPONSE=$(curl -s -X POST "$FAL_TOKEN_ENDPOINT" \
-H "Authorization: Key $FAL_KEY" \
-H "Content-Type: application/json" \
-d '{}')
# Check for token error
if echo "$TOKEN_RESPONSE" | grep -q '"detail"'; then
ERROR_MSG=$(echo "$TOKEN_RESPONSE" | grep -o '"msg":"[^"]*"' | head -1 | cut -d'"' -f4)
echo "Token error: $ERROR_MSG" >&2
exit 1
fi
TOKEN=$(echo "$TOKEN_RESPONSE" | grep -o '"token":"[^"]*"' | cut -d'"' -f4)
TOKEN_TYPE=$(echo "$TOKEN_RESPONSE" | grep -o '"token_type":"[^"]*"' | cut -d'"' -f4)
BASE_URL=$(echo "$TOKEN_RESPONSE" | grep -o '"base_url":"[^"]*"' | cut -d'"' -f4)
if [ -z "$TOKEN" ] || [ -z "$BASE_URL" ]; then
echo "Error: Failed to get CDN token" >&2
echo "$TOKEN_RESPONSE" >&2
exit 1
fi
# Step 2: Upload file
echo "Uploading to CDN..." >&2
UPLOAD_RESPONSE=$(curl -s -X POST "${BASE_URL}/files/upload" \
-H "Authorization: $TOKEN_TYPE $TOKEN" \
-H "Content-Type: $CONTENT_TYPE" \
-H "X-Fal-File-Name: $FILENAME" \
--data-binary "@$FILE_PATH")
# Check for upload error
if echo "$UPLOAD_RESPONSE" | grep -q '"error"'; then
ERROR_MSG=$(echo "$UPLOAD_RESPONSE" | grep -o '"message":"[^"]*"' | head -1 | cut -d'"' -f4)
if [ -z "$ERROR_MSG" ]; then
ERROR_MSG=$(echo "$UPLOAD_RESPONSE" | grep -o '"error":"[^"]*"' | cut -d'"' -f4)
fi
echo "Upload error: $ERROR_MSG" >&2
exit 1
fi
# Extract URL
ACCESS_URL=$(echo "$UPLOAD_RESPONSE" | grep -o '"access_url":"[^"]*"' | cut -d'"' -f4)
if [ -z "$ACCESS_URL" ]; then
echo "Error: Failed to get URL from response" >&2
echo "$UPLOAD_RESPONSE" >&2
exit 1
fi
echo "Upload complete!" >&2
echo "URL: $ACCESS_URL" >&2
# Output
if [ "$OUTPUT_JSON" = true ]; then
echo "$UPLOAD_RESPONSE"
else
# Output just the URL for easy piping
echo "$ACCESS_URL"
fi