Back to skills
SkillHub ClubAnalyze Data & AIFull StackData / AI

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.

Stars
58
Hot score
92
Updated
March 20, 2026
Overall rating
C3.1
Composite score
3.1
Best-practice grade
B70.7

Install command

npx @skill-hub/cli install artwist-polyakov-polyakov-claude-skills-fal-ai-image

Repository

artwist-polyakov/polyakov-claude-skills

Skill path: plugins/fal-ai-image/skills/fal-ai-image

Generate images using fal.ai nano-banana-pro model

Open repository

Best 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

Claude CodeCodex CLIGemini CLIOpenCode

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"

```

fal-ai-image | SkillHub