fal-ai-image
Generate images using fal.ai nano-banana-pro model
Packaged view
This page reorganizes the original catalog entry around fit, installability, and workflow context first. The original raw source lives below.
Install command
npx @skill-hub/cli install artwist-polyakov-polyakov-claude-skills-fal-ai-image
Repository
Skill path: plugins/fal-ai-image/skills/fal-ai-image
Generate images using fal.ai nano-banana-pro model
Open repositoryBest for
Primary workflow: Analyze Data & AI.
Technical facets: Full Stack, Data / AI.
Target audience: everyone.
License: Unknown.
Original source
Catalog source: SkillHub Club.
Repository owner: artwist-polyakov.
This is still a mirrored public skill entry. Review the repository before installing into production workflows.
What it helps with
- Install fal-ai-image into Claude Code, Codex CLI, Gemini CLI, or OpenCode workflows
- Review https://github.com/artwist-polyakov/polyakov-claude-skills before adding fal-ai-image to shared team environments
- Use fal-ai-image for development workflows
Works across
Favorites: 0.
Sub-skills: 0.
Aggregator: No.
Original source / Raw SKILL.md
---
name: fal-ai-image
description: Generate images using fal.ai nano-banana-pro model
---
# fal-ai-image
Generate images via fal.ai nano-banana-pro (Gemini 3 Pro Image).
Best for: infographics, text rendering, complex compositions.
## Config
Requires `FAL_KEY` in `config/.env` or environment.
Get key: https://fal.ai/dashboard/keys
## Two Modes
### 1. Generate (text-to-image)
Create images from text prompt only.
Script: `scripts/generate.sh`
### 2. Edit (image-to-image)
Create images using reference images (up to 14).
Script: `scripts/edit.sh`
## Workflow
**IMPORTANT**: Run generation via Task tool with Haiku subagent to avoid blocking main context.
### For Generate mode:
1. **Clarify params** (if not specified):
- Aspect ratio: `1:1`, `16:9`, `9:16`, `4:3`, etc.
- Resolution: `1K` (default), `2K`, `4K`
2. **Propose save path** based on project structure:
- Check for `./images/`, `./assets/`, `./static/`
- Suggest: "Save to `./images/infographic_coffee.png`?"
3. **Show price & confirm**:
```
Cost: $0.15/image (4K: $0.30)
Confirm? (yes/no)
```
4. **Launch subagent**:
```
Task tool:
- subagent_type: "Bash"
- model: "haiku"
- run_in_background: true
- prompt: Run generate.sh with params
```
5. **Report result**: Parse JSON output, show image URL, read saved file.
### For Edit mode:
1. **Get reference images**:
- If URLs provided → use directly
- If local files → run `upload.sh` first to get URLs
2. **Clarify prompt**: What to do with references?
3. **Show price & confirm**: Same as generate
4. **Launch subagent** with `edit.sh`
5. **Report result**
## Scripts
### generate.sh
```bash
bash scripts/generate.sh \
--prompt "infographic about coffee brewing" \
--aspect-ratio "9:16" \
--resolution "1K" \
--output-dir "./images" \
--filename "coffee_infographic"
```
| Param | Required | Default | Values |
|-------|----------|---------|--------|
| `--prompt` | yes | - | text |
| `--aspect-ratio` | no | 1:1 | 21:9, 16:9, 3:2, 4:3, 5:4, 1:1, 4:5, 3:4, 2:3, 9:16 |
| `--resolution` | no | 1K | 1K, 2K, 4K |
| `--num-images` | no | 1 | 1-4 |
| `--output-dir` | no | - | path |
| `--filename` | no | generated | base name |
| `--web-search` | no | false | flag |
### edit.sh
```bash
bash scripts/edit.sh \
--prompt "combine these into a collage" \
--image-urls "https://example.com/img1.png,https://example.com/img2.png" \
--aspect-ratio "16:9" \
--output-dir "./images" \
--filename "collage"
```
| Param | Required | Default | Values |
|-------|----------|---------|--------|
| `--prompt` | yes | - | text |
| `--image-urls` | yes | - | comma-separated URLs (max 14) |
| `--aspect-ratio` | no | auto | auto, 1:1, 16:9, etc. |
| `--resolution` | no | 1K | 1K, 2K, 4K |
| `--num-images` | no | 1 | 1-4 variations |
| `--output-dir` | no | - | path |
| `--filename` | no | edited | base name |
### upload.sh (for local files)
```bash
# Get URL for local file
URL=$(bash scripts/upload.sh --file /path/to/image.png)
# Or get base64 data URI (for small files)
URI=$(bash scripts/upload.sh --file /path/to/image.png --base64)
```
## Parallel Generation
For multiple images — launch several subagents in parallel:
```
Task 1: generate "cat in space" → cat_space.png
Task 2: generate "dog on moon" → dog_moon.png
Task 3: generate "bird in ocean" → bird_ocean.png
```
Each runs independently via Haiku, results collected when done.
## Pricing
- Generate: **$0.15**/image
- Edit: **$0.15**/edit
- 4K resolution: **$0.30** (2x)
- Web search: **+$0.015**
Formula: `price = num_images * (resolution == "4K" ? 0.30 : 0.15) + (web_search ? 0.015 : 0)`
## Notes
- URLs expire in ~1 hour — save locally if needed
- Uploaded files stored 7 days on fal.ai, then auto-deleted
- Model excels at text rendering and infographics
---
## Referenced Files
> The following files are referenced in this skill and included for context.
### scripts/generate.sh
```bash
#!/bin/bash
# Generate images using fal.ai nano-banana-pro model
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="$SCRIPT_DIR/../config/.env"
API_BASE="https://queue.fal.run/fal-ai/nano-banana-pro"
# Load config
if [[ -f "$CONFIG_FILE" ]]; then
# shellcheck disable=SC1090
source "$CONFIG_FILE"
fi
if [[ -z "$FAL_KEY" ]]; then
echo "Error: FAL_KEY not found. Set in config/.env or environment."
exit 1
fi
# Defaults
PROMPT=""
ASPECT_RATIO="1:1"
RESOLUTION="1K"
NUM_IMAGES=1
OUTPUT_FORMAT="png"
OUTPUT_DIR=""
FILENAME="generated"
WEB_SEARCH="false"
# Parse args
while [[ $# -gt 0 ]]; do
case $1 in
--prompt|-p) PROMPT="$2"; shift 2 ;;
--aspect-ratio|-a) ASPECT_RATIO="$2"; shift 2 ;;
--resolution|-r) RESOLUTION="$2"; shift 2 ;;
--num-images|-n) NUM_IMAGES="$2"; shift 2 ;;
--output-format|-f) OUTPUT_FORMAT="$2"; shift 2 ;;
--output-dir|-o) OUTPUT_DIR="$2"; shift 2 ;;
--filename) FILENAME="$2"; shift 2 ;;
--web-search|-w) WEB_SEARCH="true"; shift ;;
*) echo "Unknown option: $1"; exit 1 ;;
esac
done
if [[ -z "$PROMPT" ]]; then
echo "Error: --prompt is required"
exit 1
fi
# Escape prompt for JSON
PROMPT_ESCAPED=$(echo "$PROMPT" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\n/\\n/g')
# Build JSON
JSON_PAYLOAD="{\"prompt\":\"$PROMPT_ESCAPED\",\"num_images\":$NUM_IMAGES,\"aspect_ratio\":\"$ASPECT_RATIO\",\"resolution\":\"$RESOLUTION\",\"output_format\":\"$OUTPUT_FORMAT\""
if [[ "$WEB_SEARCH" == "true" ]]; then
JSON_PAYLOAD="$JSON_PAYLOAD,\"enable_web_search\":true"
fi
JSON_PAYLOAD="$JSON_PAYLOAD}"
echo "Submitting request..."
echo "Prompt: ${PROMPT:0:100}..."
echo "Settings: $ASPECT_RATIO, $RESOLUTION, $OUTPUT_FORMAT"
echo ""
# Submit to queue
SUBMIT_RESPONSE=$(curl -s -X POST "$API_BASE" \
-H "Authorization: Key $FAL_KEY" \
-H "Content-Type: application/json" \
-d "$JSON_PAYLOAD")
# Extract request_id via grep
REQUEST_ID=$(echo "$SUBMIT_RESPONSE" | grep -o '"request_id":"[^"]*"' | head -1 | cut -d'"' -f4)
if [[ -z "$REQUEST_ID" ]]; then
echo "Error: Failed to submit request"
echo "$SUBMIT_RESPONSE"
exit 1
fi
echo "Request ID: $REQUEST_ID"
echo "Waiting for generation..."
# Poll for completion
MAX_ATTEMPTS=60
ATTEMPT=0
while [[ $ATTEMPT -lt $MAX_ATTEMPTS ]]; do
STATUS_RESPONSE=$(curl -s "$API_BASE/requests/$REQUEST_ID/status" \
-H "Authorization: Key $FAL_KEY")
STATUS=$(echo "$STATUS_RESPONSE" | grep -o '"status":"[^"]*"' | head -1 | cut -d'"' -f4)
case "$STATUS" in
COMPLETED)
echo "Generation complete!"
echo ""
break
;;
FAILED)
echo "Error: Generation failed"
echo "$STATUS_RESPONSE"
exit 1
;;
IN_PROGRESS|IN_QUEUE|PENDING)
echo "Status: $STATUS..."
sleep 2
ATTEMPT=$((ATTEMPT + 1))
;;
*)
echo "Status: $STATUS..."
sleep 2
ATTEMPT=$((ATTEMPT + 1))
;;
esac
done
if [[ $ATTEMPT -ge $MAX_ATTEMPTS ]]; then
echo "Error: Timeout waiting for generation"
exit 1
fi
# Get result
RESULT=$(curl -s "$API_BASE/requests/$REQUEST_ID" \
-H "Authorization: Key $FAL_KEY")
# Download images if output_dir specified
if [[ -n "$OUTPUT_DIR" ]]; then
mkdir -p "$OUTPUT_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# Extract URLs via grep and download
URLS=$(echo "$RESULT" | grep -o '"url":"[^"]*"' | cut -d'"' -f4)
INDEX=0
echo "Downloading images..."
for URL in $URLS; do
SUFFIX=""
[[ $NUM_IMAGES -gt 1 ]] && SUFFIX="_$INDEX"
OUTPUT_PATH="$OUTPUT_DIR/${FILENAME}_${TIMESTAMP}${SUFFIX}.${OUTPUT_FORMAT}"
if curl -s -o "$OUTPUT_PATH" "$URL"; then
echo "Saved: $OUTPUT_PATH"
else
echo "Warning: Failed to download $URL"
fi
INDEX=$((INDEX + 1))
done
echo ""
fi
# Output raw JSON for Claude to parse
echo "=== RESULT JSON ==="
echo "$RESULT"
echo "=== END RESULT ==="
echo ""
echo "Note: URLs expire in ~1 hour"
```
### scripts/edit.sh
```bash
#!/bin/bash
# Generate images with reference images using fal.ai nano-banana-pro/edit
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="$SCRIPT_DIR/../config/.env"
API_BASE="https://queue.fal.run/fal-ai/nano-banana-pro/edit"
# Load config
if [[ -f "$CONFIG_FILE" ]]; then
# shellcheck disable=SC1090
source "$CONFIG_FILE"
fi
if [[ -z "$FAL_KEY" ]]; then
echo "Error: FAL_KEY not found. Set in config/.env or environment."
exit 1
fi
# Defaults
PROMPT=""
IMAGE_URLS=""
ASPECT_RATIO="auto"
RESOLUTION="1K"
NUM_IMAGES=1
OUTPUT_FORMAT="png"
OUTPUT_DIR=""
FILENAME="edited"
# Parse args
while [[ $# -gt 0 ]]; do
case $1 in
--prompt|-p) PROMPT="$2"; shift 2 ;;
--image-urls|-i) IMAGE_URLS="$2"; shift 2 ;;
--aspect-ratio|-a) ASPECT_RATIO="$2"; shift 2 ;;
--resolution|-r) RESOLUTION="$2"; shift 2 ;;
--num-images|-n) NUM_IMAGES="$2"; shift 2 ;;
--output-format|-f) OUTPUT_FORMAT="$2"; shift 2 ;;
--output-dir|-o) OUTPUT_DIR="$2"; shift 2 ;;
--filename) FILENAME="$2"; shift 2 ;;
*) echo "Unknown option: $1"; exit 1 ;;
esac
done
if [[ -z "$PROMPT" ]]; then
echo "Error: --prompt is required"
exit 1
fi
if [[ -z "$IMAGE_URLS" ]]; then
echo "Error: --image-urls is required (comma-separated URLs, max 14)"
exit 1
fi
# Escape prompt for JSON
PROMPT_ESCAPED=$(echo "$PROMPT" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\n/\\n/g')
# Convert comma-separated URLs to JSON array
# Input: "url1,url2,url3"
# Output: ["url1","url2","url3"]
IMAGE_URLS_JSON=$(echo "$IMAGE_URLS" | sed 's/,/","/g' | sed 's/^/["/' | sed 's/$/"]/')
# Build JSON
JSON_PAYLOAD="{\"prompt\":\"$PROMPT_ESCAPED\",\"image_urls\":$IMAGE_URLS_JSON,\"num_images\":$NUM_IMAGES,\"aspect_ratio\":\"$ASPECT_RATIO\",\"resolution\":\"$RESOLUTION\",\"output_format\":\"$OUTPUT_FORMAT\"}"
echo "Submitting edit request..."
echo "Prompt: ${PROMPT:0:100}..."
echo "Reference images: $(echo "$IMAGE_URLS" | tr ',' '\n' | wc -l | tr -d ' ')"
echo "Settings: $ASPECT_RATIO, $RESOLUTION, $OUTPUT_FORMAT"
echo ""
# Submit to queue
SUBMIT_RESPONSE=$(curl -s -X POST "$API_BASE" \
-H "Authorization: Key $FAL_KEY" \
-H "Content-Type: application/json" \
-d "$JSON_PAYLOAD")
# Extract request_id via grep
REQUEST_ID=$(echo "$SUBMIT_RESPONSE" | grep -o '"request_id":"[^"]*"' | head -1 | cut -d'"' -f4)
if [[ -z "$REQUEST_ID" ]]; then
echo "Error: Failed to submit request"
echo "$SUBMIT_RESPONSE"
exit 1
fi
echo "Request ID: $REQUEST_ID"
echo "Waiting for generation..."
# Poll for completion
MAX_ATTEMPTS=60
ATTEMPT=0
while [[ $ATTEMPT -lt $MAX_ATTEMPTS ]]; do
STATUS_RESPONSE=$(curl -s "$API_BASE/requests/$REQUEST_ID/status" \
-H "Authorization: Key $FAL_KEY")
STATUS=$(echo "$STATUS_RESPONSE" | grep -o '"status":"[^"]*"' | head -1 | cut -d'"' -f4)
case "$STATUS" in
COMPLETED)
echo "Generation complete!"
echo ""
break
;;
FAILED)
echo "Error: Generation failed"
echo "$STATUS_RESPONSE"
exit 1
;;
IN_PROGRESS|IN_QUEUE|PENDING)
echo "Status: $STATUS..."
sleep 2
ATTEMPT=$((ATTEMPT + 1))
;;
*)
echo "Status: $STATUS..."
sleep 2
ATTEMPT=$((ATTEMPT + 1))
;;
esac
done
if [[ $ATTEMPT -ge $MAX_ATTEMPTS ]]; then
echo "Error: Timeout waiting for generation"
exit 1
fi
# Get result
RESULT=$(curl -s "$API_BASE/requests/$REQUEST_ID" \
-H "Authorization: Key $FAL_KEY")
# Download images if output_dir specified
if [[ -n "$OUTPUT_DIR" ]]; then
mkdir -p "$OUTPUT_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
# Extract URLs via grep and download
URLS=$(echo "$RESULT" | grep -o '"url":"[^"]*"' | cut -d'"' -f4)
INDEX=0
echo "Downloading images..."
for URL in $URLS; do
SUFFIX=""
[[ $NUM_IMAGES -gt 1 ]] && SUFFIX="_$INDEX"
OUTPUT_PATH="$OUTPUT_DIR/${FILENAME}_${TIMESTAMP}${SUFFIX}.${OUTPUT_FORMAT}"
if curl -s -o "$OUTPUT_PATH" "$URL"; then
echo "Saved: $OUTPUT_PATH"
else
echo "Warning: Failed to download $URL"
fi
INDEX=$((INDEX + 1))
done
echo ""
fi
# Output raw JSON for Claude to parse
echo "=== RESULT JSON ==="
echo "$RESULT"
echo "=== END RESULT ==="
echo ""
echo "Note: URLs expire in ~1 hour"
```
### scripts/upload.sh
```bash
#!/bin/bash
# Upload local file to fal.ai storage and return URL
# Usage: ./upload.sh --file /path/to/image.png
# Output: URL that can be used in edit.sh --image-urls
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
CONFIG_FILE="$SCRIPT_DIR/../config/.env"
# Load config
if [[ -f "$CONFIG_FILE" ]]; then
# shellcheck disable=SC1090
source "$CONFIG_FILE"
fi
if [[ -z "$FAL_KEY" ]]; then
echo "Error: FAL_KEY not found. Set in config/.env or environment."
exit 1
fi
# Defaults
FILE_PATH=""
OUTPUT_MODE="url" # url or base64
# Parse args
while [[ $# -gt 0 ]]; do
case $1 in
--file|-f) FILE_PATH="$2"; shift 2 ;;
--base64) OUTPUT_MODE="base64"; shift ;;
*) echo "Unknown option: $1"; exit 1 ;;
esac
done
if [[ -z "$FILE_PATH" ]]; then
echo "Error: --file is required"
echo "Usage: $0 --file /path/to/image.png"
exit 1
fi
if [[ ! -f "$FILE_PATH" ]]; then
echo "Error: File not found: $FILE_PATH"
exit 1
fi
# Detect MIME type
MIME_TYPE=$(file -b --mime-type "$FILE_PATH")
# If base64 mode, just output data URI
if [[ "$OUTPUT_MODE" == "base64" ]]; then
BASE64_DATA=$(base64 -w0 "$FILE_PATH" 2>/dev/null || base64 "$FILE_PATH")
echo "data:$MIME_TYPE;base64,$BASE64_DATA"
exit 0
fi
# Upload to fal.ai storage
FILENAME=$(basename "$FILE_PATH")
echo "Uploading $FILENAME to fal.ai storage..." >&2
# Step 1: Get presigned upload URL
INITIATE_RESPONSE=$(curl -s -X POST "https://rest.alpha.fal.ai/storage/upload/initiate" \
-H "Authorization: Key $FAL_KEY" \
-H "Content-Type: application/json" \
-d "{\"file_name\": \"$FILENAME\", \"content_type\": \"$MIME_TYPE\"}")
UPLOAD_URL=$(echo "$INITIATE_RESPONSE" | grep -o '"upload_url":"[^"]*"' | head -1 | cut -d'"' -f4)
FILE_URL=$(echo "$INITIATE_RESPONSE" | grep -o '"file_url":"[^"]*"' | head -1 | cut -d'"' -f4)
if [[ -z "$UPLOAD_URL" || -z "$FILE_URL" ]]; then
echo "Error: Failed to get upload URL" >&2
echo "$INITIATE_RESPONSE" >&2
# Fallback to base64 data URI
echo "Falling back to base64 data URI..." >&2
BASE64_DATA=$(base64 -w0 "$FILE_PATH" 2>/dev/null || base64 "$FILE_PATH")
echo "data:$MIME_TYPE;base64,$BASE64_DATA"
exit 0
fi
# Step 2: Upload file to presigned URL
UPLOAD_RESULT=$(curl -s -X PUT "$UPLOAD_URL" \
-H "Content-Type: $MIME_TYPE" \
--data-binary "@$FILE_PATH")
echo "Uploaded: $FILENAME" >&2
echo "$FILE_URL"
```