diff --git a/SKILL.md b/SKILL.md index c09ce1db..3ae4707e 100644 --- a/SKILL.md +++ b/SKILL.md @@ -1,12 +1,31 @@ --- name: clawra-selfie -description: Edit Clawra's reference image with Grok Imagine (xAI Aurora) and send selfies to messaging channels via OpenClaw +description: Generate and edit Clawra's reference image with a configurable image generation provider and send selfies to messaging channels via OpenClaw allowed-tools: Bash(npm:*) Bash(npx:*) Bash(openclaw:*) Bash(curl:*) Read Write WebFetch --- # Clawra Selfie -Edit a fixed reference image using xAI's Grok Imagine model and distribute it across messaging platforms (WhatsApp, Telegram, Discord, Slack, etc.) via OpenClaw. +Generate and edit a fixed reference image using a configurable image generation provider and distribute it across messaging platforms (WhatsApp, Telegram, Discord, Slack, etc.) via OpenClaw. + +## Providers + +The skill supports two image generation providers, selected with the `PROVIDER` environment variable: + +| Provider | `PROVIDER` | API key env | Models | Endpoint | +|----------|------------|-------------|--------|----------| +| Grok Imagine (xAI) via fal.ai | `grok` (default) | `FAL_KEY` | `xai/grok-imagine-image` | `https://fal.run/xai/grok-imagine-image` | +| MiniMax image_generation | `minimax` | `MINIMAX_API_KEY` | `image-01`, `image-01-live` | regional `image_generation` endpoint | + +### MiniMax image_generation + +The MiniMax provider calls the regional `image_generation` endpoint with Bearer authorization and parses the `data.image_urls` response field. + +- **Regions:** `global_en` (default) -> `https://api.minimax.io/v1/image_generation`; `cn_zh` -> `https://api.minimaxi.com/v1/image_generation` +- **Models:** `image-01` (default), `image-01-live` +- **Authorization:** `Bearer $MINIMAX_API_KEY` +- **Request fields:** `model`, `prompt`, `subject_reference`, `aspect_ratio`, `width`, `height`, `response_format`, `seed`, `n`, `prompt_optimizer` +- **Response fields:** `data.image_urls`, `metadata.success_count`, `metadata.failed_count`, `base_resp.status_code` (success = `0`) ## Reference Image @@ -29,15 +48,32 @@ https://cdn.jsdelivr.net/gh/SumeLabs/clawra@main/assets/clawra.png ### Required Environment Variables ```bash +# Provider selection (optional): "grok" (default) or "minimax" +PROVIDER=grok + +# Grok Imagine provider (fal.ai) FAL_KEY=your_fal_api_key # Get from https://fal.ai/dashboard/keys + +# MiniMax provider (image_generation) +MINIMAX_API_KEY=your_minimax_key # Get from https://platform.minimax.io +MINIMAX_REGION=global_en # global_en (default) or cn_zh +MINIMAX_MODEL=image-01 # image-01 (default) or image-01-live +MINIMAX_RESPONSE_FORMAT=url # url (default) or base64 +MINIMAX_SUBJECT_REFERENCE=https://cdn.jsdelivr.net/gh/SumeLabs/clawra@main/assets/clawra.png +MINIMAX_WIDTH=1024 # optional; set together with MINIMAX_HEIGHT +MINIMAX_HEIGHT=1024 # optional; set together with MINIMAX_WIDTH +MINIMAX_SEED=12345 # optional integer +MINIMAX_N=1 # optional, 1-9 +MINIMAX_PROMPT_OPTIMIZER=false # optional, true or false + OPENCLAW_GATEWAY_TOKEN=your_token # From: openclaw doctor --generate-gateway-token ``` ### Workflow 1. **Get user prompt** for how to edit the image -2. **Edit image** via fal.ai Grok Imagine Edit API with fixed reference -3. **Extract image URL** from response +2. **Generate/edit image** via the configured provider (Grok Imagine via fal.ai, or MiniMax image_generation) +3. **Extract image URL** from the response (`images[0].url` for Grok, `data.image_urls[0]` for MiniMax) 4. **Send to OpenClaw** with target channel(s) ## Step-by-Step Instructions @@ -85,7 +121,22 @@ a close-up selfie taken by herself at a cozy cafe with warm lighting, direct eye | close-up, portrait, face, eyes, smile | `direct` | | full-body, mirror, reflection | `mirror` | -### Step 2: Edit Image with Grok Imagine +### Step 2: Generate or Edit with the Configured Provider + +Use the bundled executable for normal skill operation. It selects the regional endpoint, sends Bearer authorization, includes the Clawra reference image as `subject_reference`, and parses `data.image_urls`. + +```bash +# MiniMax with the default global endpoint, image-01 model, and Clawra reference image +PROVIDER=minimax MINIMAX_API_KEY="$MINIMAX_API_KEY" \ + ./scripts/clawra-selfie.sh "$PROMPT" "$CHANNEL" "$CAPTION" "1:1" + +# China endpoint and image-01-live model +PROVIDER=minimax MINIMAX_REGION=cn_zh MINIMAX_MODEL=image-01-live \ + MINIMAX_API_KEY="$MINIMAX_API_KEY" \ + ./scripts/clawra-selfie.sh "$PROMPT" "$CHANNEL" "$CAPTION" "1:1" +``` + +The direct API example below applies only to the `grok` provider. Use the fal.ai API to edit the reference image: @@ -150,7 +201,7 @@ curl -X POST "http://localhost:18789/message" \ }' ``` -## Complete Script Example +## Grok-only Direct API Example ```bash #!/bin/bash @@ -233,7 +284,7 @@ openclaw message send \ echo "Done!" ``` -## Node.js/TypeScript Implementation +## Grok-only Node.js/TypeScript Example ```typescript import { fal } from "@fal-ai/client"; @@ -365,7 +416,7 @@ OpenClaw supports sending to: ## Setup Requirements -### 1. Install fal.ai client (for Node.js usage) +### 1. Install fal.ai client (for Grok Node.js usage) ```bash npm install @fal-ai/client ``` @@ -389,6 +440,8 @@ openclaw gateway start ## Error Handling - **FAL_KEY missing**: Ensure the API key is set in environment +- **MINIMAX_API_KEY missing**: Set the MiniMax API key when `PROVIDER=minimax` +- **MiniMax request failed**: Check the selected region, model, request options, and API quota - **Image edit failed**: Check prompt content and API quota - **OpenClaw send failed**: Verify gateway is running and channel exists - **Rate limits**: fal.ai has rate limits; implement retry logic if needed diff --git a/scripts/clawra-selfie.sh b/scripts/clawra-selfie.sh index 72d176d5..fe791346 100755 --- a/scripts/clawra-selfie.sh +++ b/scripts/clawra-selfie.sh @@ -1,14 +1,29 @@ #!/bin/bash -# grok-imagine-send.sh -# Generate an image with Grok Imagine and send it via OpenClaw +# clawra-selfie.sh +# Generate an image with a configurable provider and send it via OpenClaw. # -# Usage: ./grok-imagine-send.sh "" "" [""] +# Supported providers: +# grok - xAI Grok Imagine via fal.ai (default) +# minimax - MiniMax image-01 / image-01-live via regional image_generation endpoints # -# Environment variables required: -# FAL_KEY - Your fal.ai API key +# Usage: ./clawra-selfie.sh "" "" [""] [aspect_ratio] [output_format] +# +# Environment variables: +# PROVIDER - "grok" (default) or "minimax" +# FAL_KEY - Your fal.ai API key (required for the grok provider) +# MINIMAX_API_KEY - Your MiniMax API key (required for the minimax provider) +# MINIMAX_REGION - "global_en" (default) or "cn_zh" +# MINIMAX_MODEL - "image-01" (default) or "image-01-live" +# MINIMAX_RESPONSE_FORMAT - "url" (default) or "base64" +# MINIMAX_SUBJECT_REFERENCE - Reference image URL or data URL (defaults to Clawra) +# MINIMAX_WIDTH / MINIMAX_HEIGHT - Optional image dimensions, set together +# MINIMAX_SEED - Optional integer seed +# MINIMAX_N - Optional image count from 1 to 9 +# MINIMAX_PROMPT_OPTIMIZER - Optional "true" or "false" # # Example: -# FAL_KEY=your_key ./grok-imagine-send.sh "A sunset over mountains" "#art" "Check this out!" +# FAL_KEY=your_key ./clawra-selfie.sh "A sunset over mountains" "#art" "Check this out!" +# PROVIDER=minimax MINIMAX_API_KEY=your_key ./clawra-selfie.sh "A sunset over mountains" "#art" "Check this out!" set -euo pipefail @@ -30,12 +45,17 @@ log_error() { echo -e "${RED}[ERROR]${NC} $1" } -# Check required environment variables -if [ -z "${FAL_KEY:-}" ]; then - log_error "FAL_KEY environment variable not set" - echo "Get your API key from: https://fal.ai/dashboard/keys" - exit 1 -fi +# Provider selection +PROVIDER="${PROVIDER:-grok}" +case "$PROVIDER" in + grok|minimax) ;; + *) + log_error "Unknown PROVIDER '$PROVIDER'. Supported: grok, minimax" + exit 1 + ;; +esac + +log_info "Provider: $PROVIDER" # Check for jq if ! command -v jq &> /dev/null; then @@ -55,8 +75,8 @@ fi # Parse arguments PROMPT="${1:-}" CHANNEL="${2:-}" -CAPTION="${3:-Generated with Grok Imagine}" -ASPECT_RATIO="${4:-1:1}" +CAPTION="${3:-Generated with Clawra Selfie}" +ASPECT_RATIO="${4:-}" OUTPUT_FORMAT="${5:-jpeg}" if [ -z "$PROMPT" ] || [ -z "$CHANNEL" ]; then @@ -65,55 +85,211 @@ if [ -z "$PROMPT" ] || [ -z "$CHANNEL" ]; then echo "Arguments:" echo " prompt - Image description (required)" echo " channel - Target channel (required) e.g., #general, @user" - echo " caption - Message caption (default: 'Generated with Grok Imagine')" - echo " aspect_ratio - Image ratio (default: 1:1) Options: 2:1, 16:9, 4:3, 1:1, 3:4, 9:16" - echo " output_format - Image format (default: jpeg) Options: jpeg, png, webp" + echo " caption - Message caption (default: 'Generated with Clawra Selfie')" + echo " aspect_ratio - Image ratio (default: 1:1); MiniMax also supports 3:2, 2:3, 21:9" + echo " output_format - Image format (default: jpeg) Options: jpeg, png, webp (grok provider)" + echo "" + echo "Environment:" + echo " PROVIDER - 'grok' (default) or 'minimax'" + echo " FAL_KEY - Your fal.ai API key (required for the grok provider)" + echo " MINIMAX_API_KEY - Your MiniMax API key (required for the minimax provider)" + echo " MINIMAX_REGION - 'global_en' (default) or 'cn_zh'" + echo " MINIMAX_MODEL - 'image-01' (default) or 'image-01-live'" + echo " MINIMAX_RESPONSE_FORMAT - 'url' (default) or 'base64'" + echo " MINIMAX_SUBJECT_REFERENCE - Reference image URL or data URL (defaults to Clawra)" + echo " MINIMAX_WIDTH / MINIMAX_HEIGHT - Optional image dimensions, set together" + echo " MINIMAX_SEED - Optional integer seed" + echo " MINIMAX_N - Optional image count from 1 to 9" + echo " MINIMAX_PROMPT_OPTIMIZER - Optional 'true' or 'false'" echo "" - echo "Example:" - echo " $0 \"A cyberpunk city at night\" \"#art-gallery\" \"AI Art!\"" + echo "Example (Grok):" + echo " FAL_KEY=your_key $0 \"A cyberpunk city at night\" \"#art-gallery\" \"AI Art!\"" + echo "Example (MiniMax):" + echo " PROVIDER=minimax MINIMAX_API_KEY=your_key $0 \"A cyberpunk city at night\" \"#art-gallery\" \"AI Art!\"" exit 1 fi -log_info "Generating image with Grok Imagine..." log_info "Prompt: $PROMPT" -log_info "Aspect ratio: $ASPECT_RATIO" - -# Generate image via fal.ai -RESPONSE=$(curl -s -X POST "https://fal.run/xai/grok-imagine-image" \ - -H "Authorization: Key $FAL_KEY" \ - -H "Content-Type: application/json" \ - -d "{ - \"prompt\": $(echo "$PROMPT" | jq -Rs .), - \"num_images\": 1, - \"aspect_ratio\": \"$ASPECT_RATIO\", - \"output_format\": \"$OUTPUT_FORMAT\" - }") - -# Check for errors in response -if echo "$RESPONSE" | jq -e '.error' > /dev/null 2>&1; then - ERROR_MSG=$(echo "$RESPONSE" | jq -r '.error // .detail // "Unknown error"') - log_error "Image generation failed: $ERROR_MSG" - exit 1 -fi +log_info "Aspect ratio: ${ASPECT_RATIO:-provider default}" -# Extract image URL -IMAGE_URL=$(echo "$RESPONSE" | jq -r '.images[0].url // empty') +IMAGE_URL="" -if [ -z "$IMAGE_URL" ]; then - log_error "Failed to extract image URL from response" - echo "Response: $RESPONSE" - exit 1 +if [ "$PROVIDER" = "minimax" ]; then + # MiniMax image_generation provider + if [ -z "${MINIMAX_API_KEY:-}" ]; then + log_error "MINIMAX_API_KEY environment variable not set" + echo "Get your API key from: https://platform.minimax.io" + exit 1 + fi + + MINIMAX_REGION="${MINIMAX_REGION:-global_en}" + MINIMAX_MODEL="${MINIMAX_MODEL:-image-01}" + MINIMAX_RESPONSE_FORMAT="${MINIMAX_RESPONSE_FORMAT:-url}" + MINIMAX_SUBJECT_REFERENCE="${MINIMAX_SUBJECT_REFERENCE:-https://cdn.jsdelivr.net/gh/SumeLabs/clawra@main/assets/clawra.png}" + MINIMAX_WIDTH="${MINIMAX_WIDTH:-}" + MINIMAX_HEIGHT="${MINIMAX_HEIGHT:-}" + MINIMAX_SEED="${MINIMAX_SEED:-}" + MINIMAX_N="${MINIMAX_N:-}" + MINIMAX_PROMPT_OPTIMIZER="${MINIMAX_PROMPT_OPTIMIZER:-}" + + case "$MINIMAX_REGION" in + global_en) ENDPOINT="https://api.minimax.io/v1/image_generation" ;; + cn_zh) ENDPOINT="https://api.minimaxi.com/v1/image_generation" ;; + *) + log_error "Unknown MINIMAX_REGION '$MINIMAX_REGION'. Supported: global_en, cn_zh" + exit 1 + ;; + esac + + case "$MINIMAX_MODEL" in + image-01|image-01-live) ;; + *) + log_error "Unknown MINIMAX_MODEL '$MINIMAX_MODEL'. Supported: image-01, image-01-live" + exit 1 + ;; + esac + + case "$MINIMAX_RESPONSE_FORMAT" in + url|base64) ;; + *) + log_error "Unknown MINIMAX_RESPONSE_FORMAT '$MINIMAX_RESPONSE_FORMAT'. Supported: url, base64" + exit 1 + ;; + esac + + case "$ASPECT_RATIO" in + ""|1:1|16:9|4:3|3:2|2:3|3:4|9:16|21:9) ;; + *) + log_error "Unsupported MiniMax aspect ratio '$ASPECT_RATIO'" + exit 1 + ;; + esac + + if { [ -n "$MINIMAX_WIDTH" ] && [ -z "$MINIMAX_HEIGHT" ]; } || \ + { [ -z "$MINIMAX_WIDTH" ] && [ -n "$MINIMAX_HEIGHT" ]; }; then + log_error "MINIMAX_WIDTH and MINIMAX_HEIGHT must be set together" + exit 1 + fi + + for VALUE in "$MINIMAX_WIDTH" "$MINIMAX_HEIGHT" "$MINIMAX_SEED" "$MINIMAX_N"; do + if [ -n "$VALUE" ] && ! [[ "$VALUE" =~ ^-?[0-9]+$ ]]; then + log_error "MiniMax numeric options must be integers" + exit 1 + fi + done + + if [ -n "$MINIMAX_N" ] && { [ "$MINIMAX_N" -lt 1 ] || [ "$MINIMAX_N" -gt 9 ]; }; then + log_error "MINIMAX_N must be between 1 and 9" + exit 1 + fi + + case "$MINIMAX_PROMPT_OPTIMIZER" in + ""|true|false) ;; + *) + log_error "MINIMAX_PROMPT_OPTIMIZER must be 'true' or 'false'" + exit 1 + ;; + esac + + log_info "MiniMax region: $MINIMAX_REGION" + log_info "MiniMax model: $MINIMAX_MODEL" + log_info "Endpoint: $ENDPOINT" + + # Build the request body with the documented image_generation request fields. + JSON_PAYLOAD=$(jq -n \ + --arg model "$MINIMAX_MODEL" \ + --arg prompt "$PROMPT" \ + --arg subject_reference "$MINIMAX_SUBJECT_REFERENCE" \ + --arg aspect_ratio "$ASPECT_RATIO" \ + --arg response_format "$MINIMAX_RESPONSE_FORMAT" \ + --arg width "$MINIMAX_WIDTH" \ + --arg height "$MINIMAX_HEIGHT" \ + --arg seed "$MINIMAX_SEED" \ + --arg n "$MINIMAX_N" \ + --arg prompt_optimizer "$MINIMAX_PROMPT_OPTIMIZER" \ + '{ + model: $model, + prompt: $prompt, + subject_reference: [{type: "character", image_file: $subject_reference}], + response_format: $response_format + } + + (if $aspect_ratio != "" then {aspect_ratio: $aspect_ratio} else {} end) + + (if $width != "" then {width: ($width | tonumber)} else {} end) + + (if $height != "" then {height: ($height | tonumber)} else {} end) + + (if $seed != "" then {seed: ($seed | tonumber)} else {} end) + + (if $n != "" then {n: ($n | tonumber)} else {} end) + + (if $prompt_optimizer != "" then {prompt_optimizer: ($prompt_optimizer == "true")} else {} end)') + + RESPONSE=$(curl -s -X POST "$ENDPOINT" \ + -H "Authorization: Bearer $MINIMAX_API_KEY" \ + -H "Content-Type: application/json" \ + -d "$JSON_PAYLOAD") + + # Parse the documented response fields: base_resp.status_code, metadata, data.image_urls. + STATUS_CODE=$(echo "$RESPONSE" | jq -r '.base_resp.status_code // empty') + if [ -n "$STATUS_CODE" ] && [ "$STATUS_CODE" != "0" ]; then + STATUS_MSG=$(echo "$RESPONSE" | jq -r '.base_resp.status_msg // "unknown error"') + log_error "MiniMax image generation failed (status_code=$STATUS_CODE): $STATUS_MSG" + exit 1 + fi + + SUCCESS_COUNT=$(echo "$RESPONSE" | jq -r '.metadata.success_count // empty') + FAILED_COUNT=$(echo "$RESPONSE" | jq -r '.metadata.failed_count // empty') + if [ -n "$SUCCESS_COUNT" ] || [ -n "$FAILED_COUNT" ]; then + log_info "MiniMax metadata: success_count=${SUCCESS_COUNT:-n/a} failed_count=${FAILED_COUNT:-n/a}" + fi + + IMAGE_URL=$(echo "$RESPONSE" | jq -r '.data.image_urls[0] // empty') + + if [ -z "$IMAGE_URL" ]; then + log_error "Failed to extract image URL from MiniMax response (data.image_urls)" + echo "Response: $RESPONSE" + exit 1 + fi +else + # Grok Imagine provider via fal.ai + if [ -z "${FAL_KEY:-}" ]; then + log_error "FAL_KEY environment variable not set" + echo "Get your API key from: https://fal.ai/dashboard/keys" + exit 1 + fi + + log_info "Generating image with Grok Imagine..." + GROK_ASPECT_RATIO="${ASPECT_RATIO:-1:1}" + + RESPONSE=$(curl -s -X POST "https://fal.run/xai/grok-imagine-image" \ + -H "Authorization: Key $FAL_KEY" \ + -H "Content-Type: application/json" \ + -d "{ + \"prompt\": $(echo "$PROMPT" | jq -Rs .), + \"num_images\": 1, + \"aspect_ratio\": \"$GROK_ASPECT_RATIO\", + \"output_format\": \"$OUTPUT_FORMAT\" + }") + + if echo "$RESPONSE" | jq -e '.error' > /dev/null 2>&1; then + ERROR_MSG=$(echo "$RESPONSE" | jq -r '.error // .detail // "Unknown error"') + log_error "Image generation failed: $ERROR_MSG" + exit 1 + fi + + IMAGE_URL=$(echo "$RESPONSE" | jq -r '.images[0].url // empty') + + if [ -z "$IMAGE_URL" ]; then + log_error "Failed to extract image URL from response" + echo "Response: $RESPONSE" + exit 1 + fi + + REVISED_PROMPT=$(echo "$RESPONSE" | jq -r '.revised_prompt // empty') + if [ -n "$REVISED_PROMPT" ]; then + log_info "Revised prompt: $REVISED_PROMPT" + fi fi log_info "Image generated successfully!" log_info "URL: $IMAGE_URL" -# Get revised prompt if available -REVISED_PROMPT=$(echo "$RESPONSE" | jq -r '.revised_prompt // empty') -if [ -n "$REVISED_PROMPT" ]; then - log_info "Revised prompt: $REVISED_PROMPT" -fi - # Send via OpenClaw log_info "Sending to channel: $CHANNEL" @@ -129,11 +305,6 @@ else GATEWAY_URL="${OPENCLAW_GATEWAY_URL:-http://localhost:18789}" GATEWAY_TOKEN="${OPENCLAW_GATEWAY_TOKEN:-}" - HEADERS="-H \"Content-Type: application/json\"" - if [ -n "$GATEWAY_TOKEN" ]; then - HEADERS="$HEADERS -H \"Authorization: Bearer $GATEWAY_TOKEN\"" - fi - curl -s -X POST "$GATEWAY_URL/message" \ -H "Content-Type: application/json" \ ${GATEWAY_TOKEN:+-H "Authorization: Bearer $GATEWAY_TOKEN"} \ @@ -154,9 +325,11 @@ jq -n \ --arg url "$IMAGE_URL" \ --arg channel "$CHANNEL" \ --arg prompt "$PROMPT" \ + --arg provider "$PROVIDER" \ '{ success: true, image_url: $url, channel: $channel, - prompt: $prompt + prompt: $prompt, + provider: $provider }' diff --git a/scripts/clawra-selfie.ts b/scripts/clawra-selfie.ts index e15f644a..7455b7ab 100644 --- a/scripts/clawra-selfie.ts +++ b/scripts/clawra-selfie.ts @@ -1,14 +1,29 @@ /** - * Grok Imagine to OpenClaw Integration + * Clawra Selfie - Image Generation to OpenClaw Integration * - * Generates images using xAI's Grok Imagine model via fal.ai - * and sends them to messaging channels via OpenClaw. + * Generates images using a configurable image generation provider and + * sends them to messaging channels via OpenClaw. + * + * Supported providers: + * - grok: xAI Grok Imagine via fal.ai (default) + * - minimax: MiniMax image-01 / image-01-live via the regional + * image_generation endpoints * * Usage: - * npx ts-node grok-imagine-send.ts "" "" [""] + * npx ts-node clawra-selfie.ts "" "" [""] * * Environment variables: - * FAL_KEY - Your fal.ai API key + * PROVIDER - "grok" (default) or "minimax" + * FAL_KEY - Your fal.ai API key (required for the grok provider) + * MINIMAX_API_KEY - Your MiniMax API key (required for the minimax provider) + * MINIMAX_REGION - "global_en" (default) or "cn_zh" + * MINIMAX_MODEL - "image-01" (default) or "image-01-live" + * MINIMAX_RESPONSE_FORMAT - "url" (default) or "base64" + * MINIMAX_SUBJECT_REFERENCE - Reference image URL or data URL (defaults to Clawra) + * MINIMAX_WIDTH / MINIMAX_HEIGHT - Optional image dimensions, set together + * MINIMAX_SEED - Optional integer seed + * MINIMAX_N - Optional image count from 1 to 9 + * MINIMAX_PROMPT_OPTIMIZER - Optional "true" or "false" * OPENCLAW_GATEWAY_URL - OpenClaw gateway URL (default: http://localhost:18789) * OPENCLAW_GATEWAY_TOKEN - Gateway auth token (optional) */ @@ -17,6 +32,8 @@ import { exec } from "child_process"; import { promisify } from "util"; const execAsync = promisify(exec); +const REFERENCE_IMAGE = + "https://cdn.jsdelivr.net/gh/SumeLabs/clawra@main/assets/clawra.png"; // Types interface GrokImagineInput { @@ -63,6 +80,65 @@ type AspectRatio = type OutputFormat = "jpeg" | "png" | "webp"; +type Provider = "grok" | "minimax"; +type MiniMaxResponseFormat = "url" | "base64"; +type MiniMaxRegion = "global_en" | "cn_zh"; +type MiniMaxModel = "image-01" | "image-01-live"; +type MiniMaxAspectRatio = + | "1:1" + | "16:9" + | "4:3" + | "3:2" + | "2:3" + | "3:4" + | "9:16" + | "21:9"; + +// MiniMax configuration (derived from the MiniMax image_generation reference). +// Regional endpoints for the image_generation operation. +const MINIMAX_ENDPOINTS: Record = { + global_en: "https://api.minimax.io/v1/image_generation", + cn_zh: "https://api.minimaxi.com/v1/image_generation", +}; + +// Supported MiniMax image models. The first entry is the default. +const MINIMAX_MODELS: MiniMaxModel[] = ["image-01", "image-01-live"]; +const MINIMAX_DEFAULT_MODEL: MiniMaxModel = MINIMAX_MODELS[0]; +const MINIMAX_DEFAULT_REGION: MiniMaxRegion = "global_en"; + +interface MiniMaxSubjectReference { + type: "character"; + image_file: string; +} + +// MiniMax image_generation request fields (per the image reference). +interface MiniMaxImageRequest { + model: MiniMaxModel; + prompt: string; + subject_reference?: MiniMaxSubjectReference[]; + aspect_ratio?: MiniMaxAspectRatio; + width?: number; + height?: number; + response_format?: "url" | "base64"; + seed?: number; + n?: number; + prompt_optimizer?: boolean; +} + +interface MiniMaxResponse { + data?: { + image_urls?: string[]; + }; + metadata?: { + success_count?: number; + failed_count?: number; + }; + base_resp?: { + status_code?: number; + status_msg?: string; + }; +} + interface GenerateAndSendOptions { prompt: string; channel: string; @@ -70,6 +146,17 @@ interface GenerateAndSendOptions { aspectRatio?: AspectRatio; outputFormat?: OutputFormat; useClaudeCodeCLI?: boolean; + provider?: Provider; + // MiniMax-only options + minimaxRegion?: MiniMaxRegion; + minimaxModel?: MiniMaxModel; + minimaxResponseFormat?: MiniMaxResponseFormat; + subjectReference?: MiniMaxSubjectReference[]; + width?: number; + height?: number; + seed?: number; + n?: number; + promptOptimizer?: boolean; } interface Result { @@ -78,6 +165,7 @@ interface Result { channel: string; prompt: string; revisedPrompt?: string; + provider: Provider; } // Check for fal.ai client @@ -93,7 +181,7 @@ try { /** * Generate image using Grok Imagine via fal.ai */ -async function generateImage( +async function generateImageGrok( input: GrokImagineInput ): Promise { const falKey = process.env.FAL_KEY; @@ -143,6 +231,197 @@ async function generateImage( return response.json(); } +/** + * Generate image using MiniMax image_generation. + * + * Calls the regional image_generation endpoint with Bearer authorization, + * sends the documented request fields, and parses `data.image_urls`. + */ +async function generateImageMiniMax( + options: GenerateAndSendOptions +): Promise { + const apiKey = process.env.MINIMAX_API_KEY; + if (!apiKey) { + throw new Error( + "MINIMAX_API_KEY environment variable not set. Get your key from https://platform.minimax.io" + ); + } + + const regionValue = + options.minimaxRegion || + process.env.MINIMAX_REGION || + MINIMAX_DEFAULT_REGION; + if (!(regionValue in MINIMAX_ENDPOINTS)) { + throw new Error( + `Unknown MINIMAX_REGION "${regionValue}". Supported regions: ${Object.keys( + MINIMAX_ENDPOINTS + ).join(", ")}` + ); + } + const region = regionValue as MiniMaxRegion; + const endpoint = MINIMAX_ENDPOINTS[region]; + + const modelValue = + options.minimaxModel || process.env.MINIMAX_MODEL || MINIMAX_DEFAULT_MODEL; + if (!MINIMAX_MODELS.includes(modelValue as MiniMaxModel)) { + throw new Error( + `Unknown MiniMax model "${modelValue}". Supported models: ${MINIMAX_MODELS.join( + ", " + )}` + ); + } + const model = modelValue as MiniMaxModel; + + // Build the request body from the documented request fields, only including + // optional fields when they are provided. + const body: MiniMaxImageRequest = { + model, + prompt: options.prompt, + }; + + const responseFormat = + options.minimaxResponseFormat || + process.env.MINIMAX_RESPONSE_FORMAT || + "url"; + if (responseFormat !== "url" && responseFormat !== "base64") { + throw new Error( + `Unknown MINIMAX_RESPONSE_FORMAT "${responseFormat}". Supported formats: url, base64` + ); + } + body.response_format = responseFormat; + + body.subject_reference = options.subjectReference || [ + { + type: "character", + image_file: process.env.MINIMAX_SUBJECT_REFERENCE || REFERENCE_IMAGE, + }, + ]; + + if (options.aspectRatio) { + const supportedRatios: MiniMaxAspectRatio[] = [ + "1:1", + "16:9", + "4:3", + "3:2", + "2:3", + "3:4", + "9:16", + "21:9", + ]; + if (!supportedRatios.includes(options.aspectRatio as MiniMaxAspectRatio)) { + throw new Error( + `Unsupported MiniMax aspect ratio "${options.aspectRatio}". Supported ratios: ${supportedRatios.join( + ", " + )}` + ); + } + body.aspect_ratio = options.aspectRatio as MiniMaxAspectRatio; + } + + const width = + options.width ?? parseOptionalIntegerEnv("MINIMAX_WIDTH"); + const height = + options.height ?? parseOptionalIntegerEnv("MINIMAX_HEIGHT"); + if ((width === undefined) !== (height === undefined)) { + throw new Error("MINIMAX_WIDTH and MINIMAX_HEIGHT must be set together"); + } + if (width !== undefined && height !== undefined) { + body.width = width; + body.height = height; + } + + const seed = options.seed ?? parseOptionalIntegerEnv("MINIMAX_SEED"); + if (seed !== undefined) { + body.seed = seed; + } + + const imageCount = options.n ?? parseOptionalIntegerEnv("MINIMAX_N"); + if (imageCount !== undefined) { + if (imageCount < 1 || imageCount > 9) { + throw new Error("MINIMAX_N must be between 1 and 9"); + } + body.n = imageCount; + } + + const promptOptimizer = + options.promptOptimizer ?? + parseOptionalBooleanEnv("MINIMAX_PROMPT_OPTIMIZER"); + if (promptOptimizer !== undefined) { + body.prompt_optimizer = promptOptimizer; + } + + console.log(`[INFO] MiniMax region: ${region}`); + console.log(`[INFO] MiniMax model: ${model}`); + console.log(`[INFO] Endpoint: ${endpoint}`); + + const response = await fetch(endpoint, { + method: "POST", + headers: { + Authorization: `Bearer ${apiKey}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + + if (!response.ok) { + const error = await response.text(); + throw new Error(`MiniMax image generation failed: ${error}`); + } + + const result = (await response.json()) as MiniMaxResponse; + + // Parse the documented response fields. + const statusCode = result.base_resp?.status_code; + if (statusCode !== undefined && statusCode !== 0) { + const statusMsg = result.base_resp?.status_msg || "unknown error"; + throw new Error( + `MiniMax image generation failed (status_code=${statusCode}): ${statusMsg}` + ); + } + + const imageUrls = result.data?.image_urls; + if (!imageUrls || imageUrls.length === 0) { + throw new Error( + "MiniMax image generation returned no image URLs in data.image_urls" + ); + } + + const successCount = result.metadata?.success_count; + const failedCount = result.metadata?.failed_count; + if (successCount !== undefined || failedCount !== undefined) { + console.log( + `[INFO] MiniMax metadata: success_count=${successCount ?? "n/a"} failed_count=${failedCount ?? "n/a"}` + ); + } + + return imageUrls[0]; +} + +function parseOptionalIntegerEnv(name: string): number | undefined { + const value = process.env[name]; + if (!value) { + return undefined; + } + if (!/^-?\d+$/.test(value)) { + throw new Error(`${name} must be an integer`); + } + return Number(value); +} + +function parseOptionalBooleanEnv(name: string): boolean | undefined { + const value = process.env[name]; + if (!value) { + return undefined; + } + if (value === "true") { + return true; + } + if (value === "false") { + return false; + } + throw new Error(`${name} must be "true" or "false"`); +} + /** * Send image via OpenClaw */ @@ -182,6 +461,25 @@ async function sendViaOpenClaw( } } +/** + * Resolve the image generation provider from the environment. + */ +function resolveProvider(optional?: Provider): Provider { + const fromEnv = (process.env.PROVIDER || "").toLowerCase(); + if (optional) { + return optional; + } + if (!fromEnv || fromEnv === "grok") { + return "grok"; + } + if (fromEnv === "minimax") { + return "minimax"; + } + throw new Error( + `Unknown PROVIDER "${process.env.PROVIDER}". Supported providers: grok, minimax` + ); +} + /** * Main function: Generate image and send to channel */ @@ -189,29 +487,39 @@ async function generateAndSend(options: GenerateAndSendOptions): Promise const { prompt, channel, - caption = "Generated with Grok Imagine", + caption = "Generated with Clawra Selfie", aspectRatio = "1:1", outputFormat = "jpeg", useClaudeCodeCLI = true, } = options; - console.log(`[INFO] Generating image with Grok Imagine...`); + const provider = resolveProvider(options.provider); + + console.log(`[INFO] Provider: ${provider}`); + console.log(`[INFO] Generating image...`); console.log(`[INFO] Prompt: ${prompt}`); console.log(`[INFO] Aspect ratio: ${aspectRatio}`); - // Generate image - const imageResult = await generateImage({ - prompt, - num_images: 1, - aspect_ratio: aspectRatio, - output_format: outputFormat, - }); + let imageUrl: string; + let revisedPrompt: string | undefined; + + if (provider === "minimax") { + imageUrl = await generateImageMiniMax(options); + } else { + const imageResult = await generateImageGrok({ + prompt, + num_images: 1, + aspect_ratio: aspectRatio, + output_format: outputFormat, + }); + imageUrl = imageResult.images[0].url; + revisedPrompt = imageResult.revised_prompt; + } - const imageUrl = imageResult.images[0].url; console.log(`[INFO] Image generated: ${imageUrl}`); - if (imageResult.revised_prompt) { - console.log(`[INFO] Revised prompt: ${imageResult.revised_prompt}`); + if (revisedPrompt) { + console.log(`[INFO] Revised prompt: ${revisedPrompt}`); } // Send via OpenClaw @@ -234,7 +542,8 @@ async function generateAndSend(options: GenerateAndSendOptions): Promise imageUrl, channel, prompt, - revisedPrompt: imageResult.revised_prompt, + revisedPrompt, + provider, }; } @@ -244,20 +553,33 @@ async function main() { if (args.length < 2) { console.log(` -Usage: npx ts-node grok-imagine-send.ts [caption] [aspect_ratio] [output_format] +Usage: npx ts-node clawra-selfie.ts [caption] [aspect_ratio] [output_format] Arguments: prompt - Image description (required) channel - Target channel (required) e.g., #general, @user - caption - Message caption (default: 'Generated with Grok Imagine') - aspect_ratio - Image ratio (default: 1:1) Options: 2:1, 16:9, 4:3, 1:1, 3:4, 9:16 - output_format - Image format (default: jpeg) Options: jpeg, png, webp + caption - Message caption (default: 'Generated with Clawra Selfie') + aspect_ratio - Image ratio (default: 1:1); MiniMax also supports 3:2, 2:3, 21:9 + output_format - Image format (default: jpeg) Options: jpeg, png, webp (grok provider) Environment: - FAL_KEY - Your fal.ai API key (required) - -Example: - FAL_KEY=your_key npx ts-node grok-imagine-send.ts "A cyberpunk city" "#art" "Check this out!" + PROVIDER - "grok" (default) or "minimax" + FAL_KEY - Your fal.ai API key (required for the grok provider) + MINIMAX_API_KEY - Your MiniMax API key (required for the minimax provider) + MINIMAX_REGION - "global_en" (default) or "cn_zh" + MINIMAX_MODEL - "image-01" (default) or "image-01-live" + MINIMAX_RESPONSE_FORMAT - "url" (default) or "base64" + MINIMAX_SUBJECT_REFERENCE - Reference image URL or data URL (defaults to Clawra) + MINIMAX_WIDTH / MINIMAX_HEIGHT - Optional image dimensions, set together + MINIMAX_SEED - Optional integer seed + MINIMAX_N - Optional image count from 1 to 9 + MINIMAX_PROMPT_OPTIMIZER - Optional "true" or "false" + +Example (Grok): + FAL_KEY=your_key npx ts-node clawra-selfie.ts "A cyberpunk city" "#art" "Check this out!" + +Example (MiniMax): + PROVIDER=minimax MINIMAX_API_KEY=your_key npx ts-node clawra-selfie.ts "A cyberpunk city" "#art" "Check this out!" `); process.exit(1); } @@ -283,11 +605,15 @@ Example: // Export for module use export { - generateImage, + generateImageGrok, + generateImageMiniMax, sendViaOpenClaw, generateAndSend, GrokImagineInput, GrokImagineResponse, + MiniMaxImageRequest, + MiniMaxResponse, + MiniMaxSubjectReference, OpenClawMessage, GenerateAndSendOptions, Result, diff --git a/skill/SKILL.md b/skill/SKILL.md index c09ce1db..3ae4707e 100644 --- a/skill/SKILL.md +++ b/skill/SKILL.md @@ -1,12 +1,31 @@ --- name: clawra-selfie -description: Edit Clawra's reference image with Grok Imagine (xAI Aurora) and send selfies to messaging channels via OpenClaw +description: Generate and edit Clawra's reference image with a configurable image generation provider and send selfies to messaging channels via OpenClaw allowed-tools: Bash(npm:*) Bash(npx:*) Bash(openclaw:*) Bash(curl:*) Read Write WebFetch --- # Clawra Selfie -Edit a fixed reference image using xAI's Grok Imagine model and distribute it across messaging platforms (WhatsApp, Telegram, Discord, Slack, etc.) via OpenClaw. +Generate and edit a fixed reference image using a configurable image generation provider and distribute it across messaging platforms (WhatsApp, Telegram, Discord, Slack, etc.) via OpenClaw. + +## Providers + +The skill supports two image generation providers, selected with the `PROVIDER` environment variable: + +| Provider | `PROVIDER` | API key env | Models | Endpoint | +|----------|------------|-------------|--------|----------| +| Grok Imagine (xAI) via fal.ai | `grok` (default) | `FAL_KEY` | `xai/grok-imagine-image` | `https://fal.run/xai/grok-imagine-image` | +| MiniMax image_generation | `minimax` | `MINIMAX_API_KEY` | `image-01`, `image-01-live` | regional `image_generation` endpoint | + +### MiniMax image_generation + +The MiniMax provider calls the regional `image_generation` endpoint with Bearer authorization and parses the `data.image_urls` response field. + +- **Regions:** `global_en` (default) -> `https://api.minimax.io/v1/image_generation`; `cn_zh` -> `https://api.minimaxi.com/v1/image_generation` +- **Models:** `image-01` (default), `image-01-live` +- **Authorization:** `Bearer $MINIMAX_API_KEY` +- **Request fields:** `model`, `prompt`, `subject_reference`, `aspect_ratio`, `width`, `height`, `response_format`, `seed`, `n`, `prompt_optimizer` +- **Response fields:** `data.image_urls`, `metadata.success_count`, `metadata.failed_count`, `base_resp.status_code` (success = `0`) ## Reference Image @@ -29,15 +48,32 @@ https://cdn.jsdelivr.net/gh/SumeLabs/clawra@main/assets/clawra.png ### Required Environment Variables ```bash +# Provider selection (optional): "grok" (default) or "minimax" +PROVIDER=grok + +# Grok Imagine provider (fal.ai) FAL_KEY=your_fal_api_key # Get from https://fal.ai/dashboard/keys + +# MiniMax provider (image_generation) +MINIMAX_API_KEY=your_minimax_key # Get from https://platform.minimax.io +MINIMAX_REGION=global_en # global_en (default) or cn_zh +MINIMAX_MODEL=image-01 # image-01 (default) or image-01-live +MINIMAX_RESPONSE_FORMAT=url # url (default) or base64 +MINIMAX_SUBJECT_REFERENCE=https://cdn.jsdelivr.net/gh/SumeLabs/clawra@main/assets/clawra.png +MINIMAX_WIDTH=1024 # optional; set together with MINIMAX_HEIGHT +MINIMAX_HEIGHT=1024 # optional; set together with MINIMAX_WIDTH +MINIMAX_SEED=12345 # optional integer +MINIMAX_N=1 # optional, 1-9 +MINIMAX_PROMPT_OPTIMIZER=false # optional, true or false + OPENCLAW_GATEWAY_TOKEN=your_token # From: openclaw doctor --generate-gateway-token ``` ### Workflow 1. **Get user prompt** for how to edit the image -2. **Edit image** via fal.ai Grok Imagine Edit API with fixed reference -3. **Extract image URL** from response +2. **Generate/edit image** via the configured provider (Grok Imagine via fal.ai, or MiniMax image_generation) +3. **Extract image URL** from the response (`images[0].url` for Grok, `data.image_urls[0]` for MiniMax) 4. **Send to OpenClaw** with target channel(s) ## Step-by-Step Instructions @@ -85,7 +121,22 @@ a close-up selfie taken by herself at a cozy cafe with warm lighting, direct eye | close-up, portrait, face, eyes, smile | `direct` | | full-body, mirror, reflection | `mirror` | -### Step 2: Edit Image with Grok Imagine +### Step 2: Generate or Edit with the Configured Provider + +Use the bundled executable for normal skill operation. It selects the regional endpoint, sends Bearer authorization, includes the Clawra reference image as `subject_reference`, and parses `data.image_urls`. + +```bash +# MiniMax with the default global endpoint, image-01 model, and Clawra reference image +PROVIDER=minimax MINIMAX_API_KEY="$MINIMAX_API_KEY" \ + ./scripts/clawra-selfie.sh "$PROMPT" "$CHANNEL" "$CAPTION" "1:1" + +# China endpoint and image-01-live model +PROVIDER=minimax MINIMAX_REGION=cn_zh MINIMAX_MODEL=image-01-live \ + MINIMAX_API_KEY="$MINIMAX_API_KEY" \ + ./scripts/clawra-selfie.sh "$PROMPT" "$CHANNEL" "$CAPTION" "1:1" +``` + +The direct API example below applies only to the `grok` provider. Use the fal.ai API to edit the reference image: @@ -150,7 +201,7 @@ curl -X POST "http://localhost:18789/message" \ }' ``` -## Complete Script Example +## Grok-only Direct API Example ```bash #!/bin/bash @@ -233,7 +284,7 @@ openclaw message send \ echo "Done!" ``` -## Node.js/TypeScript Implementation +## Grok-only Node.js/TypeScript Example ```typescript import { fal } from "@fal-ai/client"; @@ -365,7 +416,7 @@ OpenClaw supports sending to: ## Setup Requirements -### 1. Install fal.ai client (for Node.js usage) +### 1. Install fal.ai client (for Grok Node.js usage) ```bash npm install @fal-ai/client ``` @@ -389,6 +440,8 @@ openclaw gateway start ## Error Handling - **FAL_KEY missing**: Ensure the API key is set in environment +- **MINIMAX_API_KEY missing**: Set the MiniMax API key when `PROVIDER=minimax` +- **MiniMax request failed**: Check the selected region, model, request options, and API quota - **Image edit failed**: Check prompt content and API quota - **OpenClaw send failed**: Verify gateway is running and channel exists - **Rate limits**: fal.ai has rate limits; implement retry logic if needed diff --git a/skill/scripts/clawra-selfie.sh b/skill/scripts/clawra-selfie.sh index 72d176d5..fe791346 100755 --- a/skill/scripts/clawra-selfie.sh +++ b/skill/scripts/clawra-selfie.sh @@ -1,14 +1,29 @@ #!/bin/bash -# grok-imagine-send.sh -# Generate an image with Grok Imagine and send it via OpenClaw +# clawra-selfie.sh +# Generate an image with a configurable provider and send it via OpenClaw. # -# Usage: ./grok-imagine-send.sh "" "" [""] +# Supported providers: +# grok - xAI Grok Imagine via fal.ai (default) +# minimax - MiniMax image-01 / image-01-live via regional image_generation endpoints # -# Environment variables required: -# FAL_KEY - Your fal.ai API key +# Usage: ./clawra-selfie.sh "" "" [""] [aspect_ratio] [output_format] +# +# Environment variables: +# PROVIDER - "grok" (default) or "minimax" +# FAL_KEY - Your fal.ai API key (required for the grok provider) +# MINIMAX_API_KEY - Your MiniMax API key (required for the minimax provider) +# MINIMAX_REGION - "global_en" (default) or "cn_zh" +# MINIMAX_MODEL - "image-01" (default) or "image-01-live" +# MINIMAX_RESPONSE_FORMAT - "url" (default) or "base64" +# MINIMAX_SUBJECT_REFERENCE - Reference image URL or data URL (defaults to Clawra) +# MINIMAX_WIDTH / MINIMAX_HEIGHT - Optional image dimensions, set together +# MINIMAX_SEED - Optional integer seed +# MINIMAX_N - Optional image count from 1 to 9 +# MINIMAX_PROMPT_OPTIMIZER - Optional "true" or "false" # # Example: -# FAL_KEY=your_key ./grok-imagine-send.sh "A sunset over mountains" "#art" "Check this out!" +# FAL_KEY=your_key ./clawra-selfie.sh "A sunset over mountains" "#art" "Check this out!" +# PROVIDER=minimax MINIMAX_API_KEY=your_key ./clawra-selfie.sh "A sunset over mountains" "#art" "Check this out!" set -euo pipefail @@ -30,12 +45,17 @@ log_error() { echo -e "${RED}[ERROR]${NC} $1" } -# Check required environment variables -if [ -z "${FAL_KEY:-}" ]; then - log_error "FAL_KEY environment variable not set" - echo "Get your API key from: https://fal.ai/dashboard/keys" - exit 1 -fi +# Provider selection +PROVIDER="${PROVIDER:-grok}" +case "$PROVIDER" in + grok|minimax) ;; + *) + log_error "Unknown PROVIDER '$PROVIDER'. Supported: grok, minimax" + exit 1 + ;; +esac + +log_info "Provider: $PROVIDER" # Check for jq if ! command -v jq &> /dev/null; then @@ -55,8 +75,8 @@ fi # Parse arguments PROMPT="${1:-}" CHANNEL="${2:-}" -CAPTION="${3:-Generated with Grok Imagine}" -ASPECT_RATIO="${4:-1:1}" +CAPTION="${3:-Generated with Clawra Selfie}" +ASPECT_RATIO="${4:-}" OUTPUT_FORMAT="${5:-jpeg}" if [ -z "$PROMPT" ] || [ -z "$CHANNEL" ]; then @@ -65,55 +85,211 @@ if [ -z "$PROMPT" ] || [ -z "$CHANNEL" ]; then echo "Arguments:" echo " prompt - Image description (required)" echo " channel - Target channel (required) e.g., #general, @user" - echo " caption - Message caption (default: 'Generated with Grok Imagine')" - echo " aspect_ratio - Image ratio (default: 1:1) Options: 2:1, 16:9, 4:3, 1:1, 3:4, 9:16" - echo " output_format - Image format (default: jpeg) Options: jpeg, png, webp" + echo " caption - Message caption (default: 'Generated with Clawra Selfie')" + echo " aspect_ratio - Image ratio (default: 1:1); MiniMax also supports 3:2, 2:3, 21:9" + echo " output_format - Image format (default: jpeg) Options: jpeg, png, webp (grok provider)" + echo "" + echo "Environment:" + echo " PROVIDER - 'grok' (default) or 'minimax'" + echo " FAL_KEY - Your fal.ai API key (required for the grok provider)" + echo " MINIMAX_API_KEY - Your MiniMax API key (required for the minimax provider)" + echo " MINIMAX_REGION - 'global_en' (default) or 'cn_zh'" + echo " MINIMAX_MODEL - 'image-01' (default) or 'image-01-live'" + echo " MINIMAX_RESPONSE_FORMAT - 'url' (default) or 'base64'" + echo " MINIMAX_SUBJECT_REFERENCE - Reference image URL or data URL (defaults to Clawra)" + echo " MINIMAX_WIDTH / MINIMAX_HEIGHT - Optional image dimensions, set together" + echo " MINIMAX_SEED - Optional integer seed" + echo " MINIMAX_N - Optional image count from 1 to 9" + echo " MINIMAX_PROMPT_OPTIMIZER - Optional 'true' or 'false'" echo "" - echo "Example:" - echo " $0 \"A cyberpunk city at night\" \"#art-gallery\" \"AI Art!\"" + echo "Example (Grok):" + echo " FAL_KEY=your_key $0 \"A cyberpunk city at night\" \"#art-gallery\" \"AI Art!\"" + echo "Example (MiniMax):" + echo " PROVIDER=minimax MINIMAX_API_KEY=your_key $0 \"A cyberpunk city at night\" \"#art-gallery\" \"AI Art!\"" exit 1 fi -log_info "Generating image with Grok Imagine..." log_info "Prompt: $PROMPT" -log_info "Aspect ratio: $ASPECT_RATIO" - -# Generate image via fal.ai -RESPONSE=$(curl -s -X POST "https://fal.run/xai/grok-imagine-image" \ - -H "Authorization: Key $FAL_KEY" \ - -H "Content-Type: application/json" \ - -d "{ - \"prompt\": $(echo "$PROMPT" | jq -Rs .), - \"num_images\": 1, - \"aspect_ratio\": \"$ASPECT_RATIO\", - \"output_format\": \"$OUTPUT_FORMAT\" - }") - -# Check for errors in response -if echo "$RESPONSE" | jq -e '.error' > /dev/null 2>&1; then - ERROR_MSG=$(echo "$RESPONSE" | jq -r '.error // .detail // "Unknown error"') - log_error "Image generation failed: $ERROR_MSG" - exit 1 -fi +log_info "Aspect ratio: ${ASPECT_RATIO:-provider default}" -# Extract image URL -IMAGE_URL=$(echo "$RESPONSE" | jq -r '.images[0].url // empty') +IMAGE_URL="" -if [ -z "$IMAGE_URL" ]; then - log_error "Failed to extract image URL from response" - echo "Response: $RESPONSE" - exit 1 +if [ "$PROVIDER" = "minimax" ]; then + # MiniMax image_generation provider + if [ -z "${MINIMAX_API_KEY:-}" ]; then + log_error "MINIMAX_API_KEY environment variable not set" + echo "Get your API key from: https://platform.minimax.io" + exit 1 + fi + + MINIMAX_REGION="${MINIMAX_REGION:-global_en}" + MINIMAX_MODEL="${MINIMAX_MODEL:-image-01}" + MINIMAX_RESPONSE_FORMAT="${MINIMAX_RESPONSE_FORMAT:-url}" + MINIMAX_SUBJECT_REFERENCE="${MINIMAX_SUBJECT_REFERENCE:-https://cdn.jsdelivr.net/gh/SumeLabs/clawra@main/assets/clawra.png}" + MINIMAX_WIDTH="${MINIMAX_WIDTH:-}" + MINIMAX_HEIGHT="${MINIMAX_HEIGHT:-}" + MINIMAX_SEED="${MINIMAX_SEED:-}" + MINIMAX_N="${MINIMAX_N:-}" + MINIMAX_PROMPT_OPTIMIZER="${MINIMAX_PROMPT_OPTIMIZER:-}" + + case "$MINIMAX_REGION" in + global_en) ENDPOINT="https://api.minimax.io/v1/image_generation" ;; + cn_zh) ENDPOINT="https://api.minimaxi.com/v1/image_generation" ;; + *) + log_error "Unknown MINIMAX_REGION '$MINIMAX_REGION'. Supported: global_en, cn_zh" + exit 1 + ;; + esac + + case "$MINIMAX_MODEL" in + image-01|image-01-live) ;; + *) + log_error "Unknown MINIMAX_MODEL '$MINIMAX_MODEL'. Supported: image-01, image-01-live" + exit 1 + ;; + esac + + case "$MINIMAX_RESPONSE_FORMAT" in + url|base64) ;; + *) + log_error "Unknown MINIMAX_RESPONSE_FORMAT '$MINIMAX_RESPONSE_FORMAT'. Supported: url, base64" + exit 1 + ;; + esac + + case "$ASPECT_RATIO" in + ""|1:1|16:9|4:3|3:2|2:3|3:4|9:16|21:9) ;; + *) + log_error "Unsupported MiniMax aspect ratio '$ASPECT_RATIO'" + exit 1 + ;; + esac + + if { [ -n "$MINIMAX_WIDTH" ] && [ -z "$MINIMAX_HEIGHT" ]; } || \ + { [ -z "$MINIMAX_WIDTH" ] && [ -n "$MINIMAX_HEIGHT" ]; }; then + log_error "MINIMAX_WIDTH and MINIMAX_HEIGHT must be set together" + exit 1 + fi + + for VALUE in "$MINIMAX_WIDTH" "$MINIMAX_HEIGHT" "$MINIMAX_SEED" "$MINIMAX_N"; do + if [ -n "$VALUE" ] && ! [[ "$VALUE" =~ ^-?[0-9]+$ ]]; then + log_error "MiniMax numeric options must be integers" + exit 1 + fi + done + + if [ -n "$MINIMAX_N" ] && { [ "$MINIMAX_N" -lt 1 ] || [ "$MINIMAX_N" -gt 9 ]; }; then + log_error "MINIMAX_N must be between 1 and 9" + exit 1 + fi + + case "$MINIMAX_PROMPT_OPTIMIZER" in + ""|true|false) ;; + *) + log_error "MINIMAX_PROMPT_OPTIMIZER must be 'true' or 'false'" + exit 1 + ;; + esac + + log_info "MiniMax region: $MINIMAX_REGION" + log_info "MiniMax model: $MINIMAX_MODEL" + log_info "Endpoint: $ENDPOINT" + + # Build the request body with the documented image_generation request fields. + JSON_PAYLOAD=$(jq -n \ + --arg model "$MINIMAX_MODEL" \ + --arg prompt "$PROMPT" \ + --arg subject_reference "$MINIMAX_SUBJECT_REFERENCE" \ + --arg aspect_ratio "$ASPECT_RATIO" \ + --arg response_format "$MINIMAX_RESPONSE_FORMAT" \ + --arg width "$MINIMAX_WIDTH" \ + --arg height "$MINIMAX_HEIGHT" \ + --arg seed "$MINIMAX_SEED" \ + --arg n "$MINIMAX_N" \ + --arg prompt_optimizer "$MINIMAX_PROMPT_OPTIMIZER" \ + '{ + model: $model, + prompt: $prompt, + subject_reference: [{type: "character", image_file: $subject_reference}], + response_format: $response_format + } + + (if $aspect_ratio != "" then {aspect_ratio: $aspect_ratio} else {} end) + + (if $width != "" then {width: ($width | tonumber)} else {} end) + + (if $height != "" then {height: ($height | tonumber)} else {} end) + + (if $seed != "" then {seed: ($seed | tonumber)} else {} end) + + (if $n != "" then {n: ($n | tonumber)} else {} end) + + (if $prompt_optimizer != "" then {prompt_optimizer: ($prompt_optimizer == "true")} else {} end)') + + RESPONSE=$(curl -s -X POST "$ENDPOINT" \ + -H "Authorization: Bearer $MINIMAX_API_KEY" \ + -H "Content-Type: application/json" \ + -d "$JSON_PAYLOAD") + + # Parse the documented response fields: base_resp.status_code, metadata, data.image_urls. + STATUS_CODE=$(echo "$RESPONSE" | jq -r '.base_resp.status_code // empty') + if [ -n "$STATUS_CODE" ] && [ "$STATUS_CODE" != "0" ]; then + STATUS_MSG=$(echo "$RESPONSE" | jq -r '.base_resp.status_msg // "unknown error"') + log_error "MiniMax image generation failed (status_code=$STATUS_CODE): $STATUS_MSG" + exit 1 + fi + + SUCCESS_COUNT=$(echo "$RESPONSE" | jq -r '.metadata.success_count // empty') + FAILED_COUNT=$(echo "$RESPONSE" | jq -r '.metadata.failed_count // empty') + if [ -n "$SUCCESS_COUNT" ] || [ -n "$FAILED_COUNT" ]; then + log_info "MiniMax metadata: success_count=${SUCCESS_COUNT:-n/a} failed_count=${FAILED_COUNT:-n/a}" + fi + + IMAGE_URL=$(echo "$RESPONSE" | jq -r '.data.image_urls[0] // empty') + + if [ -z "$IMAGE_URL" ]; then + log_error "Failed to extract image URL from MiniMax response (data.image_urls)" + echo "Response: $RESPONSE" + exit 1 + fi +else + # Grok Imagine provider via fal.ai + if [ -z "${FAL_KEY:-}" ]; then + log_error "FAL_KEY environment variable not set" + echo "Get your API key from: https://fal.ai/dashboard/keys" + exit 1 + fi + + log_info "Generating image with Grok Imagine..." + GROK_ASPECT_RATIO="${ASPECT_RATIO:-1:1}" + + RESPONSE=$(curl -s -X POST "https://fal.run/xai/grok-imagine-image" \ + -H "Authorization: Key $FAL_KEY" \ + -H "Content-Type: application/json" \ + -d "{ + \"prompt\": $(echo "$PROMPT" | jq -Rs .), + \"num_images\": 1, + \"aspect_ratio\": \"$GROK_ASPECT_RATIO\", + \"output_format\": \"$OUTPUT_FORMAT\" + }") + + if echo "$RESPONSE" | jq -e '.error' > /dev/null 2>&1; then + ERROR_MSG=$(echo "$RESPONSE" | jq -r '.error // .detail // "Unknown error"') + log_error "Image generation failed: $ERROR_MSG" + exit 1 + fi + + IMAGE_URL=$(echo "$RESPONSE" | jq -r '.images[0].url // empty') + + if [ -z "$IMAGE_URL" ]; then + log_error "Failed to extract image URL from response" + echo "Response: $RESPONSE" + exit 1 + fi + + REVISED_PROMPT=$(echo "$RESPONSE" | jq -r '.revised_prompt // empty') + if [ -n "$REVISED_PROMPT" ]; then + log_info "Revised prompt: $REVISED_PROMPT" + fi fi log_info "Image generated successfully!" log_info "URL: $IMAGE_URL" -# Get revised prompt if available -REVISED_PROMPT=$(echo "$RESPONSE" | jq -r '.revised_prompt // empty') -if [ -n "$REVISED_PROMPT" ]; then - log_info "Revised prompt: $REVISED_PROMPT" -fi - # Send via OpenClaw log_info "Sending to channel: $CHANNEL" @@ -129,11 +305,6 @@ else GATEWAY_URL="${OPENCLAW_GATEWAY_URL:-http://localhost:18789}" GATEWAY_TOKEN="${OPENCLAW_GATEWAY_TOKEN:-}" - HEADERS="-H \"Content-Type: application/json\"" - if [ -n "$GATEWAY_TOKEN" ]; then - HEADERS="$HEADERS -H \"Authorization: Bearer $GATEWAY_TOKEN\"" - fi - curl -s -X POST "$GATEWAY_URL/message" \ -H "Content-Type: application/json" \ ${GATEWAY_TOKEN:+-H "Authorization: Bearer $GATEWAY_TOKEN"} \ @@ -154,9 +325,11 @@ jq -n \ --arg url "$IMAGE_URL" \ --arg channel "$CHANNEL" \ --arg prompt "$PROMPT" \ + --arg provider "$PROVIDER" \ '{ success: true, image_url: $url, channel: $channel, - prompt: $prompt + prompt: $prompt, + provider: $provider }' diff --git a/skill/scripts/clawra-selfie.ts b/skill/scripts/clawra-selfie.ts index e15f644a..7455b7ab 100644 --- a/skill/scripts/clawra-selfie.ts +++ b/skill/scripts/clawra-selfie.ts @@ -1,14 +1,29 @@ /** - * Grok Imagine to OpenClaw Integration + * Clawra Selfie - Image Generation to OpenClaw Integration * - * Generates images using xAI's Grok Imagine model via fal.ai - * and sends them to messaging channels via OpenClaw. + * Generates images using a configurable image generation provider and + * sends them to messaging channels via OpenClaw. + * + * Supported providers: + * - grok: xAI Grok Imagine via fal.ai (default) + * - minimax: MiniMax image-01 / image-01-live via the regional + * image_generation endpoints * * Usage: - * npx ts-node grok-imagine-send.ts "" "" [""] + * npx ts-node clawra-selfie.ts "" "" [""] * * Environment variables: - * FAL_KEY - Your fal.ai API key + * PROVIDER - "grok" (default) or "minimax" + * FAL_KEY - Your fal.ai API key (required for the grok provider) + * MINIMAX_API_KEY - Your MiniMax API key (required for the minimax provider) + * MINIMAX_REGION - "global_en" (default) or "cn_zh" + * MINIMAX_MODEL - "image-01" (default) or "image-01-live" + * MINIMAX_RESPONSE_FORMAT - "url" (default) or "base64" + * MINIMAX_SUBJECT_REFERENCE - Reference image URL or data URL (defaults to Clawra) + * MINIMAX_WIDTH / MINIMAX_HEIGHT - Optional image dimensions, set together + * MINIMAX_SEED - Optional integer seed + * MINIMAX_N - Optional image count from 1 to 9 + * MINIMAX_PROMPT_OPTIMIZER - Optional "true" or "false" * OPENCLAW_GATEWAY_URL - OpenClaw gateway URL (default: http://localhost:18789) * OPENCLAW_GATEWAY_TOKEN - Gateway auth token (optional) */ @@ -17,6 +32,8 @@ import { exec } from "child_process"; import { promisify } from "util"; const execAsync = promisify(exec); +const REFERENCE_IMAGE = + "https://cdn.jsdelivr.net/gh/SumeLabs/clawra@main/assets/clawra.png"; // Types interface GrokImagineInput { @@ -63,6 +80,65 @@ type AspectRatio = type OutputFormat = "jpeg" | "png" | "webp"; +type Provider = "grok" | "minimax"; +type MiniMaxResponseFormat = "url" | "base64"; +type MiniMaxRegion = "global_en" | "cn_zh"; +type MiniMaxModel = "image-01" | "image-01-live"; +type MiniMaxAspectRatio = + | "1:1" + | "16:9" + | "4:3" + | "3:2" + | "2:3" + | "3:4" + | "9:16" + | "21:9"; + +// MiniMax configuration (derived from the MiniMax image_generation reference). +// Regional endpoints for the image_generation operation. +const MINIMAX_ENDPOINTS: Record = { + global_en: "https://api.minimax.io/v1/image_generation", + cn_zh: "https://api.minimaxi.com/v1/image_generation", +}; + +// Supported MiniMax image models. The first entry is the default. +const MINIMAX_MODELS: MiniMaxModel[] = ["image-01", "image-01-live"]; +const MINIMAX_DEFAULT_MODEL: MiniMaxModel = MINIMAX_MODELS[0]; +const MINIMAX_DEFAULT_REGION: MiniMaxRegion = "global_en"; + +interface MiniMaxSubjectReference { + type: "character"; + image_file: string; +} + +// MiniMax image_generation request fields (per the image reference). +interface MiniMaxImageRequest { + model: MiniMaxModel; + prompt: string; + subject_reference?: MiniMaxSubjectReference[]; + aspect_ratio?: MiniMaxAspectRatio; + width?: number; + height?: number; + response_format?: "url" | "base64"; + seed?: number; + n?: number; + prompt_optimizer?: boolean; +} + +interface MiniMaxResponse { + data?: { + image_urls?: string[]; + }; + metadata?: { + success_count?: number; + failed_count?: number; + }; + base_resp?: { + status_code?: number; + status_msg?: string; + }; +} + interface GenerateAndSendOptions { prompt: string; channel: string; @@ -70,6 +146,17 @@ interface GenerateAndSendOptions { aspectRatio?: AspectRatio; outputFormat?: OutputFormat; useClaudeCodeCLI?: boolean; + provider?: Provider; + // MiniMax-only options + minimaxRegion?: MiniMaxRegion; + minimaxModel?: MiniMaxModel; + minimaxResponseFormat?: MiniMaxResponseFormat; + subjectReference?: MiniMaxSubjectReference[]; + width?: number; + height?: number; + seed?: number; + n?: number; + promptOptimizer?: boolean; } interface Result { @@ -78,6 +165,7 @@ interface Result { channel: string; prompt: string; revisedPrompt?: string; + provider: Provider; } // Check for fal.ai client @@ -93,7 +181,7 @@ try { /** * Generate image using Grok Imagine via fal.ai */ -async function generateImage( +async function generateImageGrok( input: GrokImagineInput ): Promise { const falKey = process.env.FAL_KEY; @@ -143,6 +231,197 @@ async function generateImage( return response.json(); } +/** + * Generate image using MiniMax image_generation. + * + * Calls the regional image_generation endpoint with Bearer authorization, + * sends the documented request fields, and parses `data.image_urls`. + */ +async function generateImageMiniMax( + options: GenerateAndSendOptions +): Promise { + const apiKey = process.env.MINIMAX_API_KEY; + if (!apiKey) { + throw new Error( + "MINIMAX_API_KEY environment variable not set. Get your key from https://platform.minimax.io" + ); + } + + const regionValue = + options.minimaxRegion || + process.env.MINIMAX_REGION || + MINIMAX_DEFAULT_REGION; + if (!(regionValue in MINIMAX_ENDPOINTS)) { + throw new Error( + `Unknown MINIMAX_REGION "${regionValue}". Supported regions: ${Object.keys( + MINIMAX_ENDPOINTS + ).join(", ")}` + ); + } + const region = regionValue as MiniMaxRegion; + const endpoint = MINIMAX_ENDPOINTS[region]; + + const modelValue = + options.minimaxModel || process.env.MINIMAX_MODEL || MINIMAX_DEFAULT_MODEL; + if (!MINIMAX_MODELS.includes(modelValue as MiniMaxModel)) { + throw new Error( + `Unknown MiniMax model "${modelValue}". Supported models: ${MINIMAX_MODELS.join( + ", " + )}` + ); + } + const model = modelValue as MiniMaxModel; + + // Build the request body from the documented request fields, only including + // optional fields when they are provided. + const body: MiniMaxImageRequest = { + model, + prompt: options.prompt, + }; + + const responseFormat = + options.minimaxResponseFormat || + process.env.MINIMAX_RESPONSE_FORMAT || + "url"; + if (responseFormat !== "url" && responseFormat !== "base64") { + throw new Error( + `Unknown MINIMAX_RESPONSE_FORMAT "${responseFormat}". Supported formats: url, base64` + ); + } + body.response_format = responseFormat; + + body.subject_reference = options.subjectReference || [ + { + type: "character", + image_file: process.env.MINIMAX_SUBJECT_REFERENCE || REFERENCE_IMAGE, + }, + ]; + + if (options.aspectRatio) { + const supportedRatios: MiniMaxAspectRatio[] = [ + "1:1", + "16:9", + "4:3", + "3:2", + "2:3", + "3:4", + "9:16", + "21:9", + ]; + if (!supportedRatios.includes(options.aspectRatio as MiniMaxAspectRatio)) { + throw new Error( + `Unsupported MiniMax aspect ratio "${options.aspectRatio}". Supported ratios: ${supportedRatios.join( + ", " + )}` + ); + } + body.aspect_ratio = options.aspectRatio as MiniMaxAspectRatio; + } + + const width = + options.width ?? parseOptionalIntegerEnv("MINIMAX_WIDTH"); + const height = + options.height ?? parseOptionalIntegerEnv("MINIMAX_HEIGHT"); + if ((width === undefined) !== (height === undefined)) { + throw new Error("MINIMAX_WIDTH and MINIMAX_HEIGHT must be set together"); + } + if (width !== undefined && height !== undefined) { + body.width = width; + body.height = height; + } + + const seed = options.seed ?? parseOptionalIntegerEnv("MINIMAX_SEED"); + if (seed !== undefined) { + body.seed = seed; + } + + const imageCount = options.n ?? parseOptionalIntegerEnv("MINIMAX_N"); + if (imageCount !== undefined) { + if (imageCount < 1 || imageCount > 9) { + throw new Error("MINIMAX_N must be between 1 and 9"); + } + body.n = imageCount; + } + + const promptOptimizer = + options.promptOptimizer ?? + parseOptionalBooleanEnv("MINIMAX_PROMPT_OPTIMIZER"); + if (promptOptimizer !== undefined) { + body.prompt_optimizer = promptOptimizer; + } + + console.log(`[INFO] MiniMax region: ${region}`); + console.log(`[INFO] MiniMax model: ${model}`); + console.log(`[INFO] Endpoint: ${endpoint}`); + + const response = await fetch(endpoint, { + method: "POST", + headers: { + Authorization: `Bearer ${apiKey}`, + "Content-Type": "application/json", + }, + body: JSON.stringify(body), + }); + + if (!response.ok) { + const error = await response.text(); + throw new Error(`MiniMax image generation failed: ${error}`); + } + + const result = (await response.json()) as MiniMaxResponse; + + // Parse the documented response fields. + const statusCode = result.base_resp?.status_code; + if (statusCode !== undefined && statusCode !== 0) { + const statusMsg = result.base_resp?.status_msg || "unknown error"; + throw new Error( + `MiniMax image generation failed (status_code=${statusCode}): ${statusMsg}` + ); + } + + const imageUrls = result.data?.image_urls; + if (!imageUrls || imageUrls.length === 0) { + throw new Error( + "MiniMax image generation returned no image URLs in data.image_urls" + ); + } + + const successCount = result.metadata?.success_count; + const failedCount = result.metadata?.failed_count; + if (successCount !== undefined || failedCount !== undefined) { + console.log( + `[INFO] MiniMax metadata: success_count=${successCount ?? "n/a"} failed_count=${failedCount ?? "n/a"}` + ); + } + + return imageUrls[0]; +} + +function parseOptionalIntegerEnv(name: string): number | undefined { + const value = process.env[name]; + if (!value) { + return undefined; + } + if (!/^-?\d+$/.test(value)) { + throw new Error(`${name} must be an integer`); + } + return Number(value); +} + +function parseOptionalBooleanEnv(name: string): boolean | undefined { + const value = process.env[name]; + if (!value) { + return undefined; + } + if (value === "true") { + return true; + } + if (value === "false") { + return false; + } + throw new Error(`${name} must be "true" or "false"`); +} + /** * Send image via OpenClaw */ @@ -182,6 +461,25 @@ async function sendViaOpenClaw( } } +/** + * Resolve the image generation provider from the environment. + */ +function resolveProvider(optional?: Provider): Provider { + const fromEnv = (process.env.PROVIDER || "").toLowerCase(); + if (optional) { + return optional; + } + if (!fromEnv || fromEnv === "grok") { + return "grok"; + } + if (fromEnv === "minimax") { + return "minimax"; + } + throw new Error( + `Unknown PROVIDER "${process.env.PROVIDER}". Supported providers: grok, minimax` + ); +} + /** * Main function: Generate image and send to channel */ @@ -189,29 +487,39 @@ async function generateAndSend(options: GenerateAndSendOptions): Promise const { prompt, channel, - caption = "Generated with Grok Imagine", + caption = "Generated with Clawra Selfie", aspectRatio = "1:1", outputFormat = "jpeg", useClaudeCodeCLI = true, } = options; - console.log(`[INFO] Generating image with Grok Imagine...`); + const provider = resolveProvider(options.provider); + + console.log(`[INFO] Provider: ${provider}`); + console.log(`[INFO] Generating image...`); console.log(`[INFO] Prompt: ${prompt}`); console.log(`[INFO] Aspect ratio: ${aspectRatio}`); - // Generate image - const imageResult = await generateImage({ - prompt, - num_images: 1, - aspect_ratio: aspectRatio, - output_format: outputFormat, - }); + let imageUrl: string; + let revisedPrompt: string | undefined; + + if (provider === "minimax") { + imageUrl = await generateImageMiniMax(options); + } else { + const imageResult = await generateImageGrok({ + prompt, + num_images: 1, + aspect_ratio: aspectRatio, + output_format: outputFormat, + }); + imageUrl = imageResult.images[0].url; + revisedPrompt = imageResult.revised_prompt; + } - const imageUrl = imageResult.images[0].url; console.log(`[INFO] Image generated: ${imageUrl}`); - if (imageResult.revised_prompt) { - console.log(`[INFO] Revised prompt: ${imageResult.revised_prompt}`); + if (revisedPrompt) { + console.log(`[INFO] Revised prompt: ${revisedPrompt}`); } // Send via OpenClaw @@ -234,7 +542,8 @@ async function generateAndSend(options: GenerateAndSendOptions): Promise imageUrl, channel, prompt, - revisedPrompt: imageResult.revised_prompt, + revisedPrompt, + provider, }; } @@ -244,20 +553,33 @@ async function main() { if (args.length < 2) { console.log(` -Usage: npx ts-node grok-imagine-send.ts [caption] [aspect_ratio] [output_format] +Usage: npx ts-node clawra-selfie.ts [caption] [aspect_ratio] [output_format] Arguments: prompt - Image description (required) channel - Target channel (required) e.g., #general, @user - caption - Message caption (default: 'Generated with Grok Imagine') - aspect_ratio - Image ratio (default: 1:1) Options: 2:1, 16:9, 4:3, 1:1, 3:4, 9:16 - output_format - Image format (default: jpeg) Options: jpeg, png, webp + caption - Message caption (default: 'Generated with Clawra Selfie') + aspect_ratio - Image ratio (default: 1:1); MiniMax also supports 3:2, 2:3, 21:9 + output_format - Image format (default: jpeg) Options: jpeg, png, webp (grok provider) Environment: - FAL_KEY - Your fal.ai API key (required) - -Example: - FAL_KEY=your_key npx ts-node grok-imagine-send.ts "A cyberpunk city" "#art" "Check this out!" + PROVIDER - "grok" (default) or "minimax" + FAL_KEY - Your fal.ai API key (required for the grok provider) + MINIMAX_API_KEY - Your MiniMax API key (required for the minimax provider) + MINIMAX_REGION - "global_en" (default) or "cn_zh" + MINIMAX_MODEL - "image-01" (default) or "image-01-live" + MINIMAX_RESPONSE_FORMAT - "url" (default) or "base64" + MINIMAX_SUBJECT_REFERENCE - Reference image URL or data URL (defaults to Clawra) + MINIMAX_WIDTH / MINIMAX_HEIGHT - Optional image dimensions, set together + MINIMAX_SEED - Optional integer seed + MINIMAX_N - Optional image count from 1 to 9 + MINIMAX_PROMPT_OPTIMIZER - Optional "true" or "false" + +Example (Grok): + FAL_KEY=your_key npx ts-node clawra-selfie.ts "A cyberpunk city" "#art" "Check this out!" + +Example (MiniMax): + PROVIDER=minimax MINIMAX_API_KEY=your_key npx ts-node clawra-selfie.ts "A cyberpunk city" "#art" "Check this out!" `); process.exit(1); } @@ -283,11 +605,15 @@ Example: // Export for module use export { - generateImage, + generateImageGrok, + generateImageMiniMax, sendViaOpenClaw, generateAndSend, GrokImagineInput, GrokImagineResponse, + MiniMaxImageRequest, + MiniMaxResponse, + MiniMaxSubjectReference, OpenClawMessage, GenerateAndSendOptions, Result,