From 981ecb4699388384b9246f39399a749637a88174 Mon Sep 17 00:00:00 2001 From: binyangzhu000-sudo <224954946+binyangzhu000-sudo@users.noreply.github.com> Date: Fri, 21 Aug 2026 19:04:18 +0800 Subject: [PATCH] feat: add Atlas Cloud upscale provider Signed-off-by: binyangzhu000-sudo <224954946+binyangzhu000-sudo@users.noreply.github.com> --- skills/fal-upscale/SKILL.md | 40 ++++++- skills/fal-upscale/scripts/upscale.sh | 144 +++++++++++++++++++++-- skills/fal-upscale/tests/test_upscale.sh | 124 +++++++++++++++++++ 3 files changed, 292 insertions(+), 16 deletions(-) create mode 100755 skills/fal-upscale/tests/test_upscale.sh diff --git a/skills/fal-upscale/SKILL.md b/skills/fal-upscale/SKILL.md index c27a7391..9770f7c2 100644 --- a/skills/fal-upscale/SKILL.md +++ b/skills/fal-upscale/SKILL.md @@ -1,20 +1,21 @@ --- name: fal-upscale -description: Upscale and enhance image resolution using AI. Use when the user requests "Upscale image", "Enhance resolution", "Make image bigger", "Increase quality", or similar upscaling tasks. +description: Upscale and enhance image resolution using fal.ai or Atlas Cloud. Use when the user requests "Upscale image", "Enhance resolution", "Make image bigger", "Increase quality", or similar upscaling tasks. metadata: author: fal-ai version: "1.0.0" --- -# fal.ai Upscale +# Image Upscale -Upscale and enhance image resolution using state-of-the-art AI models. +Upscale and enhance image resolution using fal.ai by default, with Atlas Cloud +available as an optional image provider. ## How It Works 1. User provides image URL and optional scale factor 2. Script selects appropriate upscaling model -3. Sends request to fal.ai API +3. Sends the request to the selected provider 4. Returns upscaled image URL ## Image Upscale Models @@ -24,6 +25,7 @@ Upscale and enhance image resolution using state-of-the-art AI models. | `fal-ai/aura-sr` | 4x | General upscaling, fast | | `fal-ai/clarity-upscaler` | 2-4x | Detail preservation | | `fal-ai/creative-upscaler` | 2-4x | Creative enhancement | +| `atlascloud/image-upscaler` | 1-4x | Optional Atlas Cloud provider | ## Video Upscale Models @@ -45,8 +47,10 @@ bash /mnt/skills/user/fal-upscale/scripts/upscale.sh [options] **Arguments:** - `--image-url` - URL of image to upscale (required) +- `--provider` - `fal` (default) or `atlas` - `--model` - Model ID (defaults to `fal-ai/aura-sr`) -- `--scale` - Upscale factor: 2 or 4 (default: 4) +- `--scale` - Upscale factor (default: 4; Atlas accepts 1-4) +- `--output-format` - Atlas output format: `jpeg`, `png`, `webp`, or `jpg` **Examples:** @@ -61,6 +65,14 @@ bash /mnt/skills/user/fal-upscale/scripts/upscale.sh \ --model "fal-ai/clarity-upscaler" \ --scale 2 +# Image upscale with Atlas Cloud (4x, PNG) +export ATLASCLOUD_API_KEY="your_key_here" +bash /mnt/skills/user/fal-upscale/scripts/upscale.sh \ + --provider atlas \ + --image-url "https://example.com/image.jpg" \ + --scale 4 \ + --output-format png + # Video upscale (general purpose) bash /mnt/skills/user/fal-upscale/scripts/upscale.sh \ --video-url "https://example.com/video.mp4" \ @@ -84,6 +96,19 @@ mcp__fal-ai__generate({ }) ``` +## Atlas Cloud Provider + +Atlas uses the `atlascloud/image-upscaler` model. The script submits one image +generation request and polls the returned prediction with bounded backoff; it +does not retry the generation submission. Input images must be available at a +public URL. Video upscaling remains available through the default fal provider. + +Set the credential before use: + +```bash +export ATLASCLOUD_API_KEY="your_key_here" +``` + ## Output ``` @@ -144,6 +169,9 @@ To fix: 2. Set: export FAL_KEY=your_key_here ``` +For Atlas Cloud, set `ATLASCLOUD_API_KEY` instead. Get a key from +https://www.atlascloud.ai/. + ### Image URL Error ``` Error: Could not fetch image from URL @@ -159,6 +187,6 @@ Make sure: Network error. To fix on claude.ai: 1. Go to https://claude.ai/settings/capabilities -2. Add *.fal.ai to the allowed domains +2. Add `*.fal.ai` for fal, or `api.atlascloud.ai` for Atlas Cloud, to the allowed domains 3. Try again ``` diff --git a/skills/fal-upscale/scripts/upscale.sh b/skills/fal-upscale/scripts/upscale.sh index b46ff635..6fd52a83 100644 --- a/skills/fal-upscale/scripts/upscale.sh +++ b/skills/fal-upscale/scripts/upscale.sh @@ -1,17 +1,23 @@ #!/bin/bash -# fal.ai Upscale Script -# Usage: ./upscale.sh --image-url URL [--model MODEL] [--scale SCALE] +# Image Upscale Script +# Usage: ./upscale.sh --image-url URL [--provider fal|atlas] [options] # Returns: JSON with upscaled image URL set -e FAL_API_ENDPOINT="https://fal.run" +ATLAS_API_ENDPOINT="${ATLAS_API_ENDPOINT:-https://api.atlascloud.ai/api/v1}" +CURL_BIN="${CURL_BIN:-curl}" +SLEEP_BIN="${SLEEP_BIN:-sleep}" # Default values +PROVIDER="fal" MODEL="fal-ai/aura-sr" +MODEL_SET=false IMAGE_URL="" SCALE=4 +OUTPUT_FORMAT="png" # Check for --add-fal-key first for arg in "$@"; do @@ -49,22 +55,33 @@ while [[ $# -gt 0 ]]; do ;; --model) MODEL="$2" + MODEL_SET=true + shift 2 + ;; + --provider) + PROVIDER="$2" shift 2 ;; --scale) SCALE="$2" shift 2 ;; + --output-format) + OUTPUT_FORMAT="$2" + shift 2 + ;; --help|-h) - echo "fal.ai Upscale Script" >&2 + echo "Image Upscale Script" >&2 echo "" >&2 echo "Usage:" >&2 echo " ./upscale.sh --image-url URL [options]" >&2 echo "" >&2 echo "Options:" >&2 echo " --image-url Image URL to upscale (required)" >&2 - echo " --model Model ID (default: fal-ai/aura-sr)" >&2 + echo " --provider Provider: fal or atlas (default: fal)" >&2 + echo " --model Provider model ID" >&2 echo " --scale Scale factor (default: 4)" >&2 + echo " --output-format Atlas output format: jpeg, png, webp, or jpg" >&2 echo " --add-fal-key Setup FAL_KEY in .env" >&2 exit 0 ;; @@ -74,8 +91,17 @@ while [[ $# -gt 0 ]]; do esac done -# Validate required inputs -if [ -z "$FAL_KEY" ]; then +if [ -z "$IMAGE_URL" ]; then + echo "Error: --image-url is required" >&2 + exit 1 +fi + +if [[ "$PROVIDER" != "fal" && "$PROVIDER" != "atlas" ]]; then + echo "Error: --provider must be fal or atlas" >&2 + exit 1 +fi + +if [ "$PROVIDER" = "fal" ] && [ -z "$FAL_KEY" ]; then echo "Error: FAL_KEY not set" >&2 echo "" >&2 echo "Run: ./upscale.sh --add-fal-key" >&2 @@ -83,9 +109,33 @@ if [ -z "$FAL_KEY" ]; then exit 1 fi -if [ -z "$IMAGE_URL" ]; then - echo "Error: --image-url is required" >&2 - exit 1 +if [ "$PROVIDER" = "atlas" ]; then + if [ -z "$ATLASCLOUD_API_KEY" ]; then + echo "Error: ATLASCLOUD_API_KEY not set" >&2 + echo "Get a key at https://www.atlascloud.ai/ and export it before running." >&2 + exit 1 + fi + if ! command -v jq >/dev/null 2>&1; then + echo "Error: jq is required for the Atlas provider" >&2 + exit 1 + fi + if [ "$MODEL_SET" = false ]; then + MODEL="atlascloud/image-upscaler" + elif [ "$MODEL" != "atlascloud/image-upscaler" ]; then + echo "Error: Atlas currently supports atlascloud/image-upscaler only" >&2 + exit 1 + fi + if [[ ! "$SCALE" =~ ^[1-4]([.]0)?$ ]]; then + echo "Error: Atlas --scale must be between 1 and 4" >&2 + exit 1 + fi + case "$OUTPUT_FORMAT" in + jpeg|png|webp|jpg) ;; + *) + echo "Error: --output-format must be jpeg, png, webp, or jpg" >&2 + exit 1 + ;; + esac fi # Create temp directory @@ -94,6 +144,80 @@ trap 'rm -rf "$TEMP_DIR"' EXIT echo "Upscaling with $MODEL..." >&2 +if [ "$PROVIDER" = "atlas" ]; then + PAYLOAD=$(jq -nc \ + --arg model "$MODEL" \ + --arg image "$IMAGE_URL" \ + --argjson outscale "$SCALE" \ + --arg output_format "$OUTPUT_FORMAT" \ + '{model:$model,image:$image,outscale:$outscale,output_format:$output_format}') + + RESPONSE_FILE="$TEMP_DIR/atlas-submit.json" + HTTP_STATUS=$("$CURL_BIN" -sS -o "$RESPONSE_FILE" -w "%{http_code}" \ + -X POST "$ATLAS_API_ENDPOINT/model/generateImage" \ + -H "Authorization: Bearer $ATLASCLOUD_API_KEY" \ + -H "Content-Type: application/json" \ + -d "$PAYLOAD") + API_CODE=$(jq -r '.code // 200' "$RESPONSE_FILE" 2>/dev/null || echo "invalid") + + if [[ ! "$HTTP_STATUS" =~ ^2 ]] || [ "$API_CODE" != "200" ]; then + ERROR_MSG=$(jq -r '.msg // .message // .error // empty' "$RESPONSE_FILE" 2>/dev/null) + echo "Error: Atlas submission failed: ${ERROR_MSG:-HTTP $HTTP_STATUS}" >&2 + exit 1 + fi + + PREDICTION_ID=$(jq -r '.data.id // .id // empty' "$RESPONSE_FILE") + if [ -z "$PREDICTION_ID" ]; then + echo "Error: Atlas submission returned no prediction ID" >&2 + exit 1 + fi + + MAX_POLLS="${ATLAS_MAX_POLLS:-30}" + POLL_DELAY="${ATLAS_POLL_INTERVAL:-2}" + MAX_DELAY="${ATLAS_MAX_POLL_INTERVAL:-10}" + POLL_FILE="$TEMP_DIR/atlas-poll.json" + + for ((attempt = 1; attempt <= MAX_POLLS; attempt++)); do + "$SLEEP_BIN" "$POLL_DELAY" + HTTP_STATUS=$("$CURL_BIN" -sS -o "$POLL_FILE" -w "%{http_code}" \ + "$ATLAS_API_ENDPOINT/model/prediction/$PREDICTION_ID" \ + -H "Authorization: Bearer $ATLASCLOUD_API_KEY") + API_CODE=$(jq -r '.code // 200' "$POLL_FILE" 2>/dev/null || echo "invalid") + + if [[ "$HTTP_STATUS" =~ ^2 ]] && [ "$API_CODE" = "200" ]; then + STATUS=$(jq -r '(.data.status // .status // "") | ascii_downcase' "$POLL_FILE") + if [[ "$STATUS" == "completed" || "$STATUS" == "succeeded" ]]; then + OUTPUT_URL=$(jq -r '.data.outputs[0] // .outputs[0] // .data.output[0] // .output[0] // empty' "$POLL_FILE") + if [ -z "$OUTPUT_URL" ]; then + echo "Error: Atlas prediction returned no output URL" >&2 + exit 1 + fi + echo "Upscale complete!" >&2 + echo "" >&2 + echo "Image URL: $OUTPUT_URL" >&2 + jq -nc --arg url "$OUTPUT_URL" --arg model "$MODEL" \ + '{image:{url:$url},model:$model,provider:"atlas"}' + exit 0 + fi + if [ "$STATUS" = "failed" ]; then + ERROR_MSG=$(jq -r '.data.error // .error // "unknown error"' "$POLL_FILE") + echo "Error: Atlas prediction failed: $ERROR_MSG" >&2 + exit 1 + fi + fi + + if (( POLL_DELAY < MAX_DELAY )); then + POLL_DELAY=$((POLL_DELAY * 2)) + if (( POLL_DELAY > MAX_DELAY )); then + POLL_DELAY="$MAX_DELAY" + fi + fi + done + + echo "Error: Atlas prediction timed out after $MAX_POLLS checks" >&2 + exit 1 +fi + # Build payload based on model if [[ "$MODEL" == *"aura-sr"* ]]; then # AuraSR has fixed 4x scale @@ -115,7 +239,7 @@ EOF fi # Make API request -RESPONSE=$(curl -s -X POST "$FAL_API_ENDPOINT/$MODEL" \ +RESPONSE=$("$CURL_BIN" -s -X POST "$FAL_API_ENDPOINT/$MODEL" \ -H "Authorization: Key $FAL_KEY" \ -H "Content-Type: application/json" \ -d "$PAYLOAD") diff --git a/skills/fal-upscale/tests/test_upscale.sh b/skills/fal-upscale/tests/test_upscale.sh new file mode 100755 index 00000000..e0a37202 --- /dev/null +++ b/skills/fal-upscale/tests/test_upscale.sh @@ -0,0 +1,124 @@ +#!/bin/bash + +set -euo pipefail + +SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd) +UPSCALE_SCRIPT="$SCRIPT_DIR/../scripts/upscale.sh" +TEMP_DIR=$(mktemp -d) +trap 'rm -rf "$TEMP_DIR"' EXIT + +MOCK_CURL="$TEMP_DIR/curl" +CALL_LOG="$TEMP_DIR/calls.log" +POLL_COUNT="$TEMP_DIR/poll-count" + +cat > "$MOCK_CURL" <<'EOF' +#!/bin/bash +set -euo pipefail + +OUTPUT_FILE="" +METHOD="GET" +URL="" +PAYLOAD="" + +while [[ $# -gt 0 ]]; do + case "$1" in + -o) + OUTPUT_FILE="$2" + shift 2 + ;; + -w|-H) + shift 2 + ;; + -X) + METHOD="$2" + shift 2 + ;; + -d) + PAYLOAD="$2" + shift 2 + ;; + -s|-sS) + shift + ;; + http*) + URL="$1" + shift + ;; + *) + shift + ;; + esac +done + +printf '%s %s %s\n' "$METHOD" "$URL" "$PAYLOAD" >> "$CALL_LOG" + +if [[ "$URL" == https://fal.run/* ]]; then + printf '%s\n' '{"image":{"url":"https://output.test/fal.png"}}' + exit 0 +fi + +if [ "$METHOD" = "POST" ]; then + if [ "${MOCK_POST_STATUS:-200}" = "402" ]; then + printf '%s\n' '{"code":402,"msg":"insufficient balance"}' > "$OUTPUT_FILE" + printf '402' + else + printf '%s\n' '{"code":200,"data":{"id":"prediction-1","status":"created"}}' > "$OUTPUT_FILE" + printf '200' + fi + exit 0 +fi + +COUNT=0 +[ -f "$POLL_COUNT" ] && COUNT=$(cat "$POLL_COUNT") +COUNT=$((COUNT + 1)) +printf '%s' "$COUNT" > "$POLL_COUNT" +if [ "$COUNT" -eq 1 ]; then + printf '%s\n' '{"code":200,"data":{"id":"prediction-1","status":"processing"}}' > "$OUTPUT_FILE" +else + printf '%s\n' '{"code":200,"data":{"id":"prediction-1","status":"completed","outputs":["https://output.test/atlas.png"]}}' > "$OUTPUT_FILE" +fi +printf '200' +EOF +chmod +x "$MOCK_CURL" + +export CURL_BIN="$MOCK_CURL" +export SLEEP_BIN=true +export CALL_LOG POLL_COUNT + +ATLASCLOUD_API_KEY=test-key bash "$UPSCALE_SCRIPT" \ + --provider atlas \ + --image-url 'https://input.test/image.png' \ + --scale 4 \ + --output-format png > "$TEMP_DIR/atlas-output.json" + +jq -e '.provider == "atlas" and .model == "atlascloud/image-upscaler" and .image.url == "https://output.test/atlas.png"' \ + "$TEMP_DIR/atlas-output.json" >/dev/null +[ "$(grep -c '^POST .*generateImage' "$CALL_LOG")" -eq 1 ] +[ "$(grep -c '^GET .*prediction/prediction-1' "$CALL_LOG")" -eq 2 ] +PAYLOAD=$(awk '/^POST .*generateImage/ {sub(/^[^ ]+ [^ ]+ /, ""); print}' "$CALL_LOG") +jq -e '.model == "atlascloud/image-upscaler" and .image == "https://input.test/image.png" and .outscale == 4 and .output_format == "png"' \ + <<< "$PAYLOAD" >/dev/null + +if bash "$UPSCALE_SCRIPT" --provider atlas --image-url 'https://input.test/image.png' > /dev/null 2>&1; then + echo "expected missing Atlas credential to fail" >&2 + exit 1 +fi + +: > "$CALL_LOG" +if MOCK_POST_STATUS=402 ATLASCLOUD_API_KEY=test-key bash "$UPSCALE_SCRIPT" \ + --provider atlas --image-url 'https://input.test/image.png' > /dev/null 2>&1; then + echo "expected failed Atlas submission to fail" >&2 + exit 1 +fi +[ "$(grep -c '^POST .*generateImage' "$CALL_LOG")" -eq 1 ] +if grep -q 'prediction/' "$CALL_LOG"; then + echo "failed Atlas submission must not start prediction polling" >&2 + exit 1 +fi + +: > "$CALL_LOG" +FAL_KEY=test-key bash "$UPSCALE_SCRIPT" --image-url 'https://input.test/image.png' > "$TEMP_DIR/fal-output.json" +jq -e '.image.url == "https://output.test/fal.png"' "$TEMP_DIR/fal-output.json" >/dev/null +grep -q '^POST https://fal.run/fal-ai/aura-sr' "$CALL_LOG" + +echo "All fal-upscale tests passed"