
Image Edit
- 2.1k installs
- 18 repo stars
- Updated July 27, 2026
- starchild-ai-agent/official-skills
|
About
The image edit skill |. Documentation covers workflows, commands, and guardrails agents should follow when users invoke this capability. Key documented areas include **image-edit** → user wants to EDIT, ENHANCE, or TRANSFORM an existing image; **image-portrait** → user wants a portrait with their face/identity preserved from a reference photo; **image-create** → user wants to CREATE something from a text description (no source image); Use each image's `local_path` (e.g. `output/images/xxx.png`) - the script always downloads on success. Reference commands include exec(open('skills/image-edit/edit_image.py').read()); result = edit_image(. Use when developers or agents need structured guidance for image edit tasks with evidence grounded in the bundled SKILL.md rather than generic advice. **image-edit** → user wants to EDIT, ENHANCE, or TRANSFORM an existing image **image-portrait** → user wants a portrait with their face/identity preserved from a reference photo **image-create** → user wants to CREATE something from a text description (no source image) Use each image's `local_path` (e.g. `output/images/xxx.png`) - the script always downloads on success. Tell the user the files are sa.
- **image-edit** → user wants to EDIT, ENHANCE, or TRANSFORM an existing image
- **image-portrait** → user wants a portrait with their face/identity preserved from a reference photo
- **image-create** → user wants to CREATE something from a text description (no source image)
- Use each image's `local_path` (e.g. `output/images/xxx.png`) - the script always downloads on success.
- Tell the user the files are saved to `output/images/` and viewable in the workspace file panel.
Image Edit by the numbers
- 2,098 all-time installs (skills.sh)
- +85 installs in the week ending Jul 28, 2026 (Skillselion tracking)
- Ranked #143 of 1,340 Generative Media skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
image-edit capabilities & compatibility
- Capabilities
- **image edit** → user wants to edit, enhance, or · **image portrait** → user wants a portrait with · **image create** → user wants to create somethin · use each image's `local_path` (e.g. `output/imag · tell the user the files are saved to `output/ima
npx skills add https://github.com/starchild-ai-agent/official-skills --skill image-editAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2.1k |
|---|---|
| repo stars | ★ 18 |
| Last updated | July 27, 2026 |
| Repository | starchild-ai-agent/official-skills ↗ |
How do I handle image edit tasks with agent guidance?
|
Who is it for?
Teams needing documented image edit workflows.
Skip if: Projects not using Starchild sc-proxy or agents that do not require per-turn paid API cost tracking in a cost ledger.
When should I use this skill?
|
What you get
Structured workflow from image edit documentation applied to the user request.
- SC-CALLER-ID tagged requests
- Cost-ledger rows
- Per-turn cost_summary data
Files
image-edit
Use this skill for all image editing and enhancement requests on Starchild.
Covers: general editing, background replacement, super-resolution, old photo restoration, colorization, person removal, portrait retouching (skin smoothing, blemish removal, teeth whitening), slimming, color grading, artistic filters, image blending, outpainting, local editing, text rendering, multi-angle generation, before/after comparison, car recoloring, car wrap preview, and fitness/medical transformation comparisons.
Core principle: call the provided script. Do not re-implement proxy/billing plumbing.
When to use image-edit vs other image skills:
- image-edit → user wants to EDIT, ENHANCE, or TRANSFORM an existing image
- image-portrait → user wants a portrait with their face/identity preserved from a reference photo
- image-create → user wants to CREATE something from a text description (no source image)
---
1. Quick start — basic edit (most common)
exec(open('skills/image-edit/edit_image.py').read())
result = edit_image(
image_path="uploads/photo.jpg",
prompt="make the sky more dramatic with golden sunset colors",
action="enhance",
)
# result -> {"success": True, "images": [{"local_path": "output/images/..."}], ...}The script reads the local file, base64-encodes it, and sends it to fal.ai as a data URI — no manual URL publishing needed.
2. Quick start — public URL
exec(open('skills/image-edit/edit_image.py').read())
result = edit_image(
image_url="https://example.com/photo.jpg",
prompt="replace the background with a tropical beach",
action="replace_bg",
)Delivering the result to the user — IMPORTANT
Never hand the user the raw fal.media URL. fal serves files with restrictive CSP headers. The only reliable delivery path is the already-downloaded local file:
1. Use each image's local_path (e.g. output/images/xxx.png) — the script always downloads on success. 2. Tell the user the files are saved to output/images/ and viewable in the workspace file panel. 3. On Web channel, embed inline so the user can preview in chat:
4. On Telegram / WeChat: send via send_to_telegram(file_path="output/images/...", message_type="image") or send_to_wechat(file_path="output/images/...", message_type="image").
---
3. Parameters
| Parameter | Required | Default | Description |
|---|---|---|---|
image_path | yes* | — | Local workspace file path to the source image |
image_url | yes* | — | Public HTTPS URL of the source image |
prompt | no | auto | Editing instruction (what to change) |
action | no | "edit" | Operation type (see §4) |
model | no | "nanopro" | Model: "nanopro" (fast ~25s) or "gpt" (best quality ~150s) |
aspect_ratio | no | None | Output ratio: 1:1, 3:4, 4:3, 9:16, 16:9. None = preserve original. |
*At least one of image_path or image_url must be provided. If both are given, image_path takes priority.
---
4. Actions — operation types
F: Multi-image / general editing
| Action | Key | Description |
|---|---|---|
| General edit | edit | Modify the image according to the prompt |
| Image blending | blend | Place a person/subject into a new background or scene |
| Outpainting | extend | Extend the image beyond its current boundaries |
| Local edit | local_edit | Modify only a specific region of the image |
| Text rendering | text_render | Add or modify text within the image |
| Multi-angle | multi_angle | Generate different viewing angles from one photo |
| Before/after | before_after | Generate a side-by-side comparison image |
G: Professional editing
| Action | Key | Description |
|---|---|---|
| Background replacement | replace_bg | Swap the background while keeping the subject |
| Super-resolution | upscale | Upscale and enhance image resolution |
| Photo restoration | restore | Repair scratches, tears, fading in old photos |
| Colorization | colorize | Add realistic colors to black-and-white photos |
| Person removal | remove_person | Remove a specific person from the photo |
V: Retouching / beauty
| Action | Key | Description |
|---|---|---|
| Portrait retouching | retouch | Skin smoothing, blemish removal, teeth whitening |
| Slimming | slim | Adjust facial and body proportions subtly |
| Enhancement | enhance | Color correction, lighting improvement, quality boost |
| Artistic filter | filter | Apply a specific artistic style or filter effect |
W: Medical / fitness comparison
| Action | Key | Description |
|---|---|---|
| Transformation comparison | comparison | Before/after for medical, fitness, or transformation |
X: Automotive
| Action | Key | Description |
|---|---|---|
| Car recolor | car_color | Change the color of a vehicle |
| Car wrap preview | car_wrap | Visualize a wrap or film on a vehicle |
---
5. Model selection guide
| Model | Key | Speed | Quality | Best for |
|---|---|---|---|---|
| NanoPro | nanopro | ~25s | Good | Default for all requests. Fast iteration. |
| GPT Image 2 | gpt | ~150s | Best | When user explicitly asks for "highest quality" or "best quality". Complex edits. |
Decision rules: 1. Default: always use nanopro unless the user explicitly requests higher quality. 2. Use `gpt` when: user says "highest quality", "best quality", "premium", or the edit requires very precise detail preservation (e.g., complex text rendering, fine inpainting). 3. Use `nanopro` when: user wants fast results, is iterating on edits, or the edit is straightforward.
# Default (fast)
result = edit_image(image_path="photo.jpg", prompt="remove background", action="replace_bg")
# High quality (user requested)
result = edit_image(image_path="photo.jpg", prompt="remove background", action="replace_bg", model="gpt")---
6. Intent recognition guide
Use this table to map user requests to the correct action:
General editing
| User says | Action | Prompt hint |
|---|---|---|
| "edit this photo", "modify this image" | edit | Pass user's instruction as prompt |
| "put me on a beach", "change the scene" | blend | Describe the target scene |
| "extend the image", "make it wider", "outpaint" | extend | Describe what to add |
| "change just the shirt color", "edit only the sky" | local_edit | Specify the region and change |
| "add text", "write 'Hello' on the image" | text_render | Specify text content and placement |
| "show from the side", "different angle" | multi_angle | Describe the desired angle |
| "before and after", "show the difference" | before_after | Describe the transformation |
Professional editing
| User says | Action | Prompt hint |
|---|---|---|
| "remove background", "change background", "换背景" | replace_bg | Describe the new background |
| "upscale", "make it higher resolution", "enhance quality" | upscale | Optionally specify target quality |
| "restore old photo", "fix this damaged photo", "修复老照片" | restore | Describe specific damage to fix |
| "colorize", "add color to B&W photo", "上色" | colorize | Optionally describe expected colors |
| "remove this person", "P掉某人" | remove_person | Describe which person to remove |
Retouching / beauty
| User says | Action | Prompt hint |
|---|---|---|
| "retouch", "smooth skin", "remove blemishes", "磨皮美白" | retouch | Specify retouching level |
| "make me thinner", "slim face", "瘦脸" | slim | Specify areas to adjust |
| "enhance colors", "improve lighting", "调色" | enhance | Describe desired look |
| "apply filter", "make it look vintage", "滤镜" | filter | Describe the filter style |
Medical / fitness
| User says | Action | Prompt hint |
|---|---|---|
| "before and after surgery", "fitness transformation" | comparison | Describe the transformation context |
Automotive
| User says | Action | Prompt hint |
|---|---|---|
| "change car color", "make it red", "汽车改色" | car_color | Specify the target color and finish |
| "car wrap", "vinyl wrap preview", "贴膜预览" | car_wrap | Describe wrap material and color |
---
7. Prompt engineering best practices
The prompt template system
Every action has a built-in prompt template that wraps the user's instruction for optimal results. You only need to pass the user's specific intent — the template adds the technical quality instructions automatically.
For example, if the user says "make the background a sunset beach":
result = edit_image(
image_path="photo.jpg",
prompt="a beautiful sunset beach with palm trees and golden light",
action="replace_bg",
)
# The script wraps this into: "Replace the background of this image: a beautiful
# sunset beach with palm trees and golden light. Keep the foreground subject
# perfectly intact with clean edges. Match the lighting direction..."Key principles (from reference skills)
1. Be specific about the change — vague prompts produce poor results:
- ❌ "make it better"
- ✅ "increase contrast, add warm golden tones, sharpen details"
2. Describe what to preserve — especially for local edits:
- ❌ "change the shirt"
- ✅ "change the shirt color to navy blue, keep the same fabric texture and wrinkles"
3. Specify materials and finishes — for car and product edits:
- ❌ "make it blue"
- ✅ "deep metallic blue with a glossy clear coat finish"
4. Reference real-world styles — for filters and artistic effects:
- ❌ "make it artistic"
- ✅ "apply a warm cinematic color grade like Wes Anderson films"
5. Describe the era for restoration/colorization:
- ❌ "colorize this"
- ✅ "colorize this 1940s family portrait with period-appropriate clothing colors"
6. For retouching, specify the level:
- Light: "subtle skin smoothing, keep natural texture"
- Medium: "professional retouching, remove blemishes, even skin tone"
- Heavy: "full beauty retouching, smooth skin, brighten eyes, whiten teeth"
---
8. Usage examples by scenario
Background replacement
exec(open('skills/image-edit/edit_image.py').read())
# Simple background swap
result = edit_image(
image_path="uploads/portrait.jpg",
prompt="a modern office with floor-to-ceiling windows and city skyline view",
action="replace_bg",
)
# Studio background
result = edit_image(
image_path="uploads/product.jpg",
prompt="clean white studio background with soft shadow",
action="replace_bg",
)Old photo restoration
# Repair damaged photo
result = edit_image(
image_path="uploads/old_family_photo.jpg",
prompt="repair all scratches, tears, and stains; restore faded colors; enhance clarity",
action="restore",
)
# Colorize black-and-white photo
result = edit_image(
image_path="uploads/grandpa_1945.jpg",
prompt="colorize with historically accurate colors for 1940s era, natural skin tones, period-appropriate clothing",
action="colorize",
)Portrait retouching
# Professional retouching
result = edit_image(
image_path="uploads/selfie.jpg",
prompt="professional portrait retouching: smooth skin while keeping natural texture, remove blemishes, subtle teeth whitening, brighten eyes",
action="retouch",
)
# Slimming
result = edit_image(
image_path="uploads/photo.jpg",
prompt="subtle facial slimming, slightly more defined jawline, natural proportions",
action="slim",
)Image enhancement
# Color grading
result = edit_image(
image_path="uploads/landscape.jpg",
prompt="cinematic color grading with warm golden tones, enhanced contrast, vibrant but natural colors",
action="enhance",
)
# Artistic filter
result = edit_image(
image_path="uploads/photo.jpg",
prompt="oil painting style with visible brushstrokes, rich warm palette, impressionist feel",
action="filter",
)Super-resolution upscaling
result = edit_image(
image_path="uploads/low_res.jpg",
prompt="upscale to maximum quality, enhance fine details, reduce noise and compression artifacts",
action="upscale",
)Person removal
result = edit_image(
image_path="uploads/group_photo.jpg",
prompt="remove the person on the far right, fill with the park background seamlessly",
action="remove_person",
)Outpainting (image extension)
result = edit_image(
image_path="uploads/cropped.jpg",
prompt="extend the image to the left and right, continuing the mountain landscape naturally",
action="extend",
aspect_ratio="16:9",
)Car customization
# Car recolor
result = edit_image(
image_path="uploads/my_car.jpg",
prompt="change to a deep cherry red metallic paint with glossy clear coat",
action="car_color",
)
# Car wrap preview
result = edit_image(
image_path="uploads/my_car.jpg",
prompt="matte black vinyl wrap with carbon fiber accents on the hood and mirrors",
action="car_wrap",
)Before/after comparison
# Fitness transformation
result = edit_image(
image_path="uploads/fitness_photo.jpg",
prompt="create a fitness transformation comparison showing a more toned and fit version",
action="comparison",
)Local editing
# Change specific element
result = edit_image(
image_path="uploads/outfit.jpg",
prompt="change only the dress color from red to emerald green, keep the same fabric texture",
action="local_edit",
)Text rendering
result = edit_image(
image_path="uploads/poster_bg.jpg",
prompt="add the text 'SUMMER SALE' in bold white letters centered at the top, with a subtle drop shadow",
action="text_render",
)High quality edit
# Use GPT model for best quality
result = edit_image(
image_path="uploads/important_photo.jpg",
prompt="professional color correction and enhancement for print publication",
action="enhance",
model="gpt",
)---
9. Provided scripts
| File | Purpose |
|---|---|
edit_image.py | Core script: resolve image → build prompt → submit → poll → download. Handles local files (base64) and URLs, all actions, two models. |
exports.py | Re-exports edit_image, ACTIONS, ACTION_PROMPTS, MODELS for programmatic use by other skills. |
_cost_track.py | Cost tracking helper — records per-call costs via sc-proxy headers. |
---
10. Local testing
Set FAL_KEY env var to call fal.ai directly (bypasses sc-proxy):
# Basic edit
FAL_KEY=your-fal-key python3 skills/image-edit/edit_image.py photo.jpg "make it brighter" enhance nanopro
# Args: <image_path_or_url> [prompt] [action] [model]---
11. Troubleshooting
| Problem | Fix |
|---|---|
File not found: ... | Check the workspace path; the file must exist |
Unsupported image format | Use .jpg, .jpeg, .png, .webp, or .bmp |
Image too large | Resize to under 10 MB before uploading |
image_url must be a public HTTP(S) URL | Use image_path for local files, or provide a valid https:// URL |
Unknown action | Check valid actions in §4 |
HTTP 402 insufficient_credits | Top up balance; cost is pre-charged on submit |
HTTP 403 endpoint_not_allowed | sc-proxy only allows approved fal endpoints; contact admin |
Edit FAILED upstream | Simplify prompt, ensure source image is clear, retry |
Job stuck IN_PROGRESS >10 min | Save request_id, retry later |
| Poor edit quality | Try model="gpt" for higher quality; be more specific in prompt |
| Background not fully removed | Use replace_bg action with explicit background description |
| Retouching looks unnatural | Add "keep natural texture" or "subtle" to prompt |
---
12. Infrastructure (reference)
- Caller →
sc-proxy→queue.fal.run/{model}→ fal model providers - All requests must include
Authorization: Key fake-falai-key-12345(proxy injects the realFAL_KEY) - Pre-charge happens at submit. Poll/result calls are free.
- Local files are base64-encoded as data URIs — no separate upload step needed.
- Final images live at
https://*.fal.media/...— public CDN, no auth needed for download. - Cost tracking via
_cost_track.py— recordsX-Credits-Usedfrom sc-proxy response headers.
Model endpoints
| Model | Edit endpoint |
|---|---|
| nanopro | fal-ai/nano-banana-pro/edit |
| gpt | openai/gpt-image-2/edit |
---
"""Cost tracking helper for skill subprocesses.
Skills that call sc-proxy via plain `requests` need to:
1. Tag every paid call with a SC-CALLER-ID that ties it back to the user
turn that triggered the skill (so the agent's per-turn cost summary
shows the cost in the right cost card).
2. After each call, parse the sc-proxy response headers
(`X-Credits-Used`, `X-Credits-Api-Type`) and write a row to the cost
ledger that the agent reads back when it builds the SSE
`cost_summary` event.
This file is intentionally zero-dependency (stdlib only) so it can be
dropped into any skill folder without coupling to starchild-clawd internals.
Env vars consumed (set by the agent before dispatching the bash subprocess):
- STARCHILD_TOOL_CALLER_ID — opaque tag for the current tool call
- STARCHILD_USER_TURN_ID — uuid of the current user turn
- STARCHILD_COST_LEDGER_DIR — optional override for ledger directory
When env vars are absent (e.g. running the script outside an agent), the
helpers degrade gracefully: caller-id falls back to a synthetic string so
the call still goes through, and ledger writes still happen for audit but
the user-turn reader will skip them.
"""
from __future__ import annotations
import fcntl
import json
import os
import time
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, Optional
from urllib.parse import urlparse
_DEFAULT_LEDGER_DIR = "/data/.starchild/cost_ledger"
# Allowlisted request payload keys we forward into the ledger row's
# `details` field. MUST stay in sync with starchild-clawd's
# core/http_client._record_cost_to_ledger allowlist — anything not in
# that allowlist won't be picked up by the agent and won't render in
# the frontend cost card.
_PAYLOAD_ALLOWLIST = (
# Identity
"model", "provider",
# Image geometry
"aspect_ratio", "quality", "resolution", "image_size", "size",
# Video / motion
"duration", "duration_s", "fps", "motion_strength",
# Quantity
"n", "count",
# Generation knobs
"seed", "steps", "guidance_scale", "cfg_scale", "strength",
"scheduler", "sampler",
# Reference / mode hints
"image_to_image", "image_to_video", "use_reference", "reference_count",
)
def caller_headers(extra: Optional[Dict[str, str]] = None,
tool_default: str = "skill") -> Dict[str, str]:
"""Return an HTTP-headers dict with SC-CALLER-ID filled in.
Resolution order:
1. `extra["SC-CALLER-ID"]` (case-insensitive) — caller wins.
2. STARCHILD_TOOL_CALLER_ID env (set by the agent)
3. Synthetic `f"{tool_default}:{int(time.time())}"` — tags the call so
charges are attributable to *some* identifier even when the agent
didn't inject one (standalone CLI runs, tests, cron).
"""
merged: Dict[str, str] = dict(extra or {})
has_caller = any(k.lower() == "sc-caller-id" for k in merged)
if not has_caller:
cid = os.environ.get("STARCHILD_TOOL_CALLER_ID") \
or f"{tool_default}:{int(time.time())}"
merged["SC-CALLER-ID"] = cid
return merged
def record_response(response,
request_url: str,
request_payload: Optional[Dict[str, Any]] = None,
api_type_hint: Optional[str] = None) -> None:
"""Inspect a sc-proxy response and append a ledger row when paid.
Best-effort. Silently no-ops when:
- response carries no X-Credits-Used / X-Credits-Api-Type
- cost is 0 or unparseable
- file write fails
Never raises — must not break a real request flow.
"""
try:
headers = getattr(response, "headers", None) or {}
used = headers.get("X-Credits-Used") or headers.get("x-credits-used")
api_type = (headers.get("X-Credits-Api-Type")
or headers.get("x-credits-api-type")
or api_type_hint)
if not used or not api_type:
return
try:
cost_f = float(used)
except (TypeError, ValueError):
return
if cost_f <= 0:
return
turn_id = os.environ.get("STARCHILD_USER_TURN_ID") or ""
caller_id = os.environ.get("STARCHILD_TOOL_CALLER_ID") or ""
host = ""
try:
host = urlparse(request_url).netloc or ""
except Exception:
pass
details: Dict[str, Any] = {}
if isinstance(request_payload, dict):
for k in _PAYLOAD_ALLOWLIST:
v = request_payload.get(k)
if v not in (None, "", []):
details[k] = v
# fal.ai puts the model in the URL path, not the body.
if "model" not in details and api_type == "falai":
try:
path = urlparse(request_url).path or ""
model_path = path.lstrip("/")
if "/requests/" in model_path:
model_path = model_path.split("/requests/", 1)[0]
if model_path and not model_path.startswith("requests/"):
details["model"] = model_path
details["provider"] = "fal"
except Exception:
pass
_append_ledger(
turn_id=turn_id,
caller_id=caller_id,
api_type=api_type,
cost_usd=cost_f,
url_host=host,
details=details or None,
)
except Exception:
# Never let cost tracking break the actual request.
pass
def _ledger_dir() -> Path:
base = os.environ.get("STARCHILD_COST_LEDGER_DIR") or _DEFAULT_LEDGER_DIR
p = Path(base)
try:
p.mkdir(parents=True, exist_ok=True)
except OSError:
p = Path("/tmp/starchild_cost_ledger")
p.mkdir(parents=True, exist_ok=True)
return p
def _today_path() -> Path:
today = datetime.now(timezone.utc).strftime("%Y-%m-%d")
return _ledger_dir() / f"{today}.jsonl"
def _derive_tool(caller_id: str, api_type: str) -> str:
"""Match starchild-clawd's _derive_tool_from_caller fallback."""
if not caller_id:
return api_type or "unknown"
# chat:{sid}/tool:{name} → name
if "/tool:" in caller_id:
return caller_id.rsplit("/tool:", 1)[-1] or api_type
# skill:{name} | job:{id} | video:{ts}
head = caller_id.split(":", 1)[0]
return head or api_type or "unknown"
def _append_ledger(*, turn_id: str, caller_id: str, api_type: str,
cost_usd: float, url_host: str,
details: Optional[Dict[str, Any]]) -> None:
row = {
"ts": round(time.time(), 3),
"turn_id": turn_id,
"caller_id": caller_id,
"tool": _derive_tool(caller_id, api_type),
"api_type": api_type or "unknown",
"cost_usd": round(cost_usd, 8),
"url_host": url_host or "",
}
if details:
row["details"] = details
line = json.dumps(row, ensure_ascii=False, separators=(",", ":")) + "\n"
path = _today_path()
try:
with open(path, "ab") as f:
try:
fcntl.flock(f.fileno(), fcntl.LOCK_EX)
except OSError:
pass
try:
f.write(line.encode("utf-8"))
f.flush()
try:
os.fsync(f.fileno())
except OSError:
pass
finally:
try:
fcntl.flock(f.fileno(), fcntl.LOCK_UN)
except OSError:
pass
except OSError:
pass
#!/usr/bin/env python3
"""Image editing script — edit, enhance, and transform existing images.
Supports three models:
- nano2 (fal-ai/gemini-3.1-flash-image-preview/edit) — fastest ~15s, good for drafts
- nanopro (fal-ai/gemini-3-pro-image-preview/edit) — balanced ~25s, good quality (default)
- gpt (openai/gpt-image-2/edit) — best quality, slow ~150s
Covers: general editing, background replacement, upscaling, restoration,
colorization, inpainting, retouching, beauty enhancement, filters,
car customization, before/after comparison, outpainting, and more.
Flow: resolve image → build prompt → submit to fal queue → poll → download.
Cost tracking: uses _cost_track.py to record per-call costs via sc-proxy
headers so the agent's per-turn cost_summary picks up this skill's cost.
Local testing: set FAL_KEY env var to call fal.ai directly (no sc-proxy).
"""
import requests
import json
import time
import os
import sys
import base64
import mimetypes
from datetime import datetime
from pathlib import Path
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
# Make _cost_track importable when this script is invoked from any CWD.
_HERE = os.path.dirname(os.path.abspath(__file__))
if _HERE not in sys.path:
sys.path.insert(0, _HERE)
from _cost_track import caller_headers, record_response # noqa: E402
# Local testing: when FAL_KEY env var is set, call fal.ai directly
# (no sc-proxy). In production, sc-proxy injects the real key.
_FAL_KEY = os.environ.get("FAL_KEY")
_LOCAL_MODE = bool(_FAL_KEY)
PROXY_URL = 'http://sc-proxy.internal:8080'
PROXIES = {} if _LOCAL_MODE else {'http': PROXY_URL, 'https': PROXY_URL}
# ── Model configuration ──────────────────────────────────────────────
MODELS = {
"nano2": {
"edit": "fal-ai/gemini-3.1-flash-image-preview/edit",
"timeout": 90,
"poll_interval": 2,
},
"nanopro": {
"edit": "fal-ai/gemini-3-pro-image-preview/edit",
"timeout": 120,
"poll_interval": 3,
},
"gpt": {
"edit": "openai/gpt-image-2/edit",
"timeout": 600,
"poll_interval": 5,
},
}
DEFAULT_MODEL = "nanopro"
# Supported image extensions
SUPPORTED_IMAGE_EXTS = {'.jpg', '.jpeg', '.png', '.webp', '.bmp'}
MAX_IMAGE_BYTES = 10 * 1024 * 1024 # 10 MB
# ── Action definitions ────────────────────────────────────────────────
# Each action maps to an optimized prompt template that wraps the user's
# intent into a high-quality editing instruction.
ACTIONS = {
# === F: Multi-image / general editing ===
"edit": "General edit — modify the image according to the prompt",
"blend": "Image blending — place a person into a new background/scene",
"extend": "Outpainting — extend the image beyond its current boundaries",
"local_edit": "Local edit — modify only a specific region of the image",
"text_render": "Text rendering — add or modify text within the image",
"multi_angle": "Multi-angle — generate different viewing angles from one photo",
"before_after": "Before/after comparison — generate a side-by-side comparison",
# === G: Professional editing ===
"replace_bg": "Background replacement — swap the background while keeping the subject",
"upscale": "Super-resolution — upscale and enhance image resolution",
"restore": "Photo restoration — repair scratches, tears, fading in old photos",
"colorize": "Colorization — add realistic colors to black-and-white photos",
"remove_person": "Person removal — remove a specific person from the photo",
# === V: Retouching / beauty ===
"retouch": "Portrait retouching — skin smoothing, blemish removal, teeth whitening",
"slim": "Slimming — adjust facial and body proportions subtly",
"enhance": "Enhancement — color correction, lighting improvement, quality boost",
"filter": "Artistic filter — apply a specific artistic style or filter effect",
# === W: Medical / fitness ===
"comparison": "Comparison — before/after for medical, fitness, or transformation",
# === X: Automotive ===
"car_color": "Car recolor — change the color of a vehicle",
"car_wrap": "Car wrap preview — visualize a wrap or film on a vehicle",
}
# ── Action prompt templates ───────────────────────────────────────────
# These templates wrap the user's prompt to produce optimal results.
# {prompt} is replaced with the user's specific instruction.
ACTION_PROMPTS = {
"edit": (
"Edit this image: {prompt}. "
"Maintain the overall composition and quality of the original image. "
"Apply the requested changes precisely while preserving unaffected areas."
),
"blend": (
"Seamlessly blend the subject from this image into the described scene: {prompt}. "
"Match lighting, perspective, and color temperature between the subject and "
"the new environment. Ensure natural shadows and reflections. "
"The result should look like a real photograph, not a composite."
),
"extend": (
"Extend this image beyond its current boundaries: {prompt}. "
"Generate new content that seamlessly continues the existing scene. "
"Match the style, lighting, perspective, and color palette of the original. "
"Ensure no visible seams or discontinuities at the boundary."
),
"local_edit": (
"Make a local edit to this image: {prompt}. "
"Only modify the specified region. Keep everything else exactly as in the original. "
"Ensure the edited area blends naturally with the surrounding content."
),
"text_render": (
"Add or modify text in this image: {prompt}. "
"Render the text clearly and legibly. Match the visual style of the image. "
"Ensure proper font weight, color contrast, and placement. "
"The text should look naturally integrated, not pasted on."
),
"multi_angle": (
"Generate a different viewing angle of the subject in this image: {prompt}. "
"Maintain the subject's identity, proportions, and details. "
"Adjust perspective, lighting, and shadows consistently for the new angle. "
"The result should look like a real photo taken from the described viewpoint."
),
"before_after": (
"Create a before/after comparison: {prompt}. "
"Generate a side-by-side image showing the transformation. "
"Left side shows the original state, right side shows the result. "
"Add a clean dividing line between the two halves. "
"Ensure both halves have consistent framing and scale."
),
"replace_bg": (
"Replace the background of this image: {prompt}. "
"Keep the foreground subject perfectly intact with clean edges. "
"Match the lighting direction and color temperature of the new background "
"to the subject. Add appropriate shadows and reflections. "
"The result should look like the subject was photographed in the new setting."
),
"upscale": (
"Upscale and enhance this image to higher resolution: {prompt}. "
"Increase detail and sharpness while preserving the original content. "
"Enhance textures, reduce noise and compression artifacts. "
"Maintain natural appearance without over-sharpening or hallucinating details."
),
"restore": (
"Restore this old or damaged photograph: {prompt}. "
"Repair scratches, tears, creases, stains, and fading. "
"Reconstruct missing or damaged areas based on surrounding context. "
"Enhance clarity while preserving the authentic character of the original photo. "
"Fix color shifts and restore proper tonal range."
),
"colorize": (
"Colorize this black-and-white photograph with realistic, natural colors: {prompt}. "
"Apply historically and contextually appropriate colors. "
"Use realistic skin tones for people, natural colors for landscapes and objects. "
"Maintain the original detail and tonal range. "
"The result should look like a naturally colored photograph, not artificially tinted."
),
"remove_person": (
"Remove the specified person from this photo: {prompt}. "
"Fill the area where the person was with content that matches the surrounding "
"background seamlessly. Reconstruct any occluded background elements. "
"Ensure no ghosting, artifacts, or visible editing traces remain."
),
"retouch": (
"Professionally retouch this portrait: {prompt}. "
"Apply natural skin smoothing that preserves texture and pores. "
"Remove blemishes, acne, and skin imperfections. "
"Subtly whiten teeth and brighten eyes if visible. "
"Enhance skin tone evenness while maintaining a realistic, non-plastic look. "
"Keep the person's natural features and character."
),
"slim": (
"Subtly adjust proportions in this portrait: {prompt}. "
"Apply natural-looking slimming to the specified areas. "
"Maintain realistic body proportions and avoid distortion. "
"Ensure the background and surrounding elements are not warped. "
"The result should look natural and unedited."
),
"enhance": (
"Enhance this image: {prompt}. "
"Improve color vibrancy, contrast, and tonal balance. "
"Optimize lighting and exposure. Reduce noise while preserving detail. "
"Apply professional-grade color grading for a polished look. "
"The result should look like a professionally edited photograph."
),
"filter": (
"Apply an artistic filter to this image: {prompt}. "
"Transform the visual style while preserving the composition and subject. "
"Ensure the filter effect is applied consistently across the entire image. "
"Maintain recognizable content while achieving the desired artistic effect."
),
"comparison": (
"Create a transformation comparison image: {prompt}. "
"Generate a professional before/after layout showing the change. "
"Use clean framing with consistent scale and alignment. "
"Add subtle labels or a dividing element if appropriate. "
"The comparison should clearly communicate the transformation."
),
"car_color": (
"Change the color of the vehicle in this image: {prompt}. "
"Apply the new color realistically with proper metallic/matte finish. "
"Maintain reflections, highlights, and shadows appropriate for the new color. "
"Keep all other elements (wheels, trim, background) unchanged. "
"The result should look like a factory paint job, not a digital overlay."
),
"car_wrap": (
"Apply a vehicle wrap or film to the car in this image: {prompt}. "
"Render the wrap material realistically following the car's body contours. "
"Show proper material properties (matte, gloss, satin, chrome, carbon fiber). "
"Maintain reflections and lighting consistent with the wrap material. "
"Keep wheels, windows, and trim unaffected."
),
}
# ── Default prompts when user provides no specific instruction ─────────
ACTION_DEFAULT_PROMPTS = {
"edit": "Enhance and improve this image while maintaining its original character",
"blend": "Place the subject into a professional studio setting with soft lighting",
"extend": "Extend the image naturally in all directions, continuing the scene",
"local_edit": "Clean up and improve the central area of the image",
"text_render": "Add elegant text overlay that complements the image",
"multi_angle": "Show this subject from a three-quarter view angle",
"before_after": "Show a before and after comparison of image enhancement",
"replace_bg": "Replace the background with a clean, professional studio backdrop",
"upscale": "Upscale to maximum quality with enhanced detail and sharpness",
"restore": "Restore this photo by repairing all visible damage and improving clarity",
"colorize": "Add natural, realistic colors appropriate to the era and content",
"remove_person": "Remove the indicated person and fill with matching background",
"retouch": "Apply professional portrait retouching with natural skin smoothing",
"slim": "Apply subtle, natural-looking facial slimming",
"enhance": "Enhance colors, lighting, contrast, and overall image quality",
"filter": "Apply a cinematic color grading filter with warm tones",
"comparison": "Create a professional before/after transformation comparison",
"car_color": "Change the car color to a deep metallic blue",
"car_wrap": "Apply a matte black wrap to the vehicle",
}
# ── Constants ─────────────────────────────────────────────────────────
MAX_COUNT = 4 # fal.ai API supports up to 4 images per call
DEFAULT_COUNT = 1
VALID_ASPECT_RATIOS = {
"1:1", "2:3", "3:2", "3:4", "4:3", "4:5", "5:4", "9:16", "16:9", "21:9",
}
DEFAULT_ASPECT_RATIO = None # None = preserve original image ratio
VALID_OUTPUT_FORMATS = {"jpeg", "png", "webp"}
DEFAULT_OUTPUT_FORMAT = "png"
OUTPUT_DIR = "output/images"
def _get_auth_key():
"""Return the appropriate fal API key."""
return _FAL_KEY if _LOCAL_MODE else 'fake-falai-key-12345'
def _get_model_config(model_key):
"""Return model config dict for the given key."""
return MODELS.get(model_key, MODELS[DEFAULT_MODEL])
def _resolve_image(image_path=None, image_url=None):
"""Resolve an image input to a URL for the fal API.
Accepts either a local file path or a public URL.
Local files are base64-encoded as data URIs.
Returns (url_string, error_string).
"""
if not image_path and not image_url:
return None, "Either image_path or image_url must be provided for editing."
if image_path:
p = Path(image_path)
if not p.exists():
return None, f"File not found: {image_path}"
if not p.is_file():
return None, f"Not a file: {image_path}"
ext = p.suffix.lower()
if ext not in SUPPORTED_IMAGE_EXTS:
return None, (
f"Unsupported image format: {ext}. "
f"Supported: {', '.join(sorted(SUPPORTED_IMAGE_EXTS))}"
)
size = p.stat().st_size
if size > MAX_IMAGE_BYTES:
return None, (
f"Image too large: {size / 1024 / 1024:.1f} MB "
f"(max {MAX_IMAGE_BYTES / 1024 / 1024:.0f} MB)"
)
mime_type = mimetypes.guess_type(str(p))[0] or "image/jpeg"
with open(p, 'rb') as f:
b64 = base64.b64encode(f.read()).decode('ascii')
return f"data:{mime_type};base64,{b64}", None
# URL input
if not image_url.startswith(("http://", "https://")):
return None, (
"image_url must be a public HTTP(S) URL. "
"For local files, use the image_path parameter instead."
)
return image_url, None
def _build_edit_prompt(prompt=None, action="edit"):
"""Construct the editing prompt from action template and user instruction.
Priority:
1. prompt provided → wrap with action template
2. no prompt → use action default prompt with template
Returns the final prompt string.
"""
user_prompt = prompt if prompt else ACTION_DEFAULT_PROMPTS.get(action, "")
template = ACTION_PROMPTS.get(action, ACTION_PROMPTS["edit"])
return template.format(prompt=user_prompt)
def _aspect_ratio_to_size(aspect_ratio):
"""Convert aspect ratio string to fal image_size dict.
Sizes aligned with image_generate tool capabilities
(core/image_models.py _STD_ASPECTS / _NANO2_ASPECTS).
"""
mapping = {
"1:1": {"width": 1024, "height": 1024},
"2:3": {"width": 680, "height": 1024},
"3:2": {"width": 1024, "height": 680},
"3:4": {"width": 768, "height": 1024},
"4:3": {"width": 1024, "height": 768},
"4:5": {"width": 816, "height": 1024},
"5:4": {"width": 1024, "height": 816},
"9:16": {"width": 576, "height": 1024},
"16:9": {"width": 1024, "height": 576},
"21:9": {"width": 1024, "height": 440},
}
return mapping.get(aspect_ratio, mapping["1:1"])
def _build_request_body(prompt, image_urls, aspect_ratio=None, model_key="nanopro",
count=1, output_format="png"):
"""Build the request body for the fal edit API."""
body = {
"prompt": prompt,
"num_images": count,
"seed": int(time.time() * 1000) % (2**32),
"output_format": output_format,
}
# Pass all images via image_urls array (supports 1-3+ images)
body["image_urls"] = image_urls
# Only set output dimensions if aspect_ratio is explicitly provided
# (otherwise the model preserves the original image dimensions)
if aspect_ratio and aspect_ratio in VALID_ASPECT_RATIOS:
if model_key != "gpt":
body["aspect_ratio"] = aspect_ratio
else:
body["image_size"] = _aspect_ratio_to_size(aspect_ratio)
body["quality"] = "high"
return body
def _submit_request(prompt, image_urls, model_key, headers, aspect_ratio=None,
count=1, output_format="png"):
"""Submit an edit request to the fal queue."""
cfg = _get_model_config(model_key)
model_id = cfg["edit"]
submit_url = f"https://queue.fal.run/{model_id}"
body = _build_request_body(prompt, image_urls, aspect_ratio, model_key,
count=count, output_format=output_format)
resp = requests.post(
submit_url, headers=headers, json=body,
proxies=PROXIES, verify=False, timeout=90,
)
record_response(resp, request_url=submit_url, request_payload=body)
if resp.status_code != 200:
return None, f"Submit failed: {resp.status_code} - {resp.text[:300]}"
data = resp.json()
cost = float(resp.headers.get('X-Credits-Used', 0))
data['_cost'] = cost
return data, None
def _poll_until_done(status_url, request_id, model_key):
"""Poll the fal queue until the request completes or fails."""
cfg = _get_model_config(model_key)
headers = {'Authorization': f'Key {_get_auth_key()}'}
deadline = time.time() + cfg["timeout"]
poll_interval = cfg["poll_interval"]
while time.time() < deadline:
try:
poll_resp = requests.get(
status_url, headers=headers,
proxies=PROXIES, verify=False, timeout=60,
)
status_data = poll_resp.json()
status = status_data.get('status')
if status == 'COMPLETED':
return "COMPLETED", None
elif status in ('FAILED', 'CANCELLED'):
return status, f"Edit {status}"
except requests.RequestException:
pass
time.sleep(poll_interval)
return "TIMEOUT", f"Edit timed out after {cfg['timeout'] // 60} minutes"
def _extract_image_urls(result_json):
"""Extract image URLs from fal response across model variants."""
if not isinstance(result_json, dict):
return []
urls = []
for key in ("images", "output", "outputs", "data"):
arr = result_json.get(key)
if isinstance(arr, list):
for item in arr:
if isinstance(item, dict) and isinstance(item.get("url"), str):
urls.append(item["url"])
elif isinstance(item, dict) and isinstance(item.get("b64_json"), str):
urls.append(f"data:image/png;base64,{item['b64_json']}")
elif isinstance(item, str) and item.startswith("http"):
urls.append(item)
if not urls:
for key in ("image", "output_image"):
node = result_json.get(key)
if isinstance(node, dict) and isinstance(node.get("url"), str):
urls.append(node["url"])
elif isinstance(node, str) and node.startswith("http"):
urls.append(node)
return urls
def _download_image(url, index, label, timestamp):
"""Download a single image from fal CDN to the output directory."""
os.makedirs(OUTPUT_DIR, exist_ok=True)
if url.startswith("data:"):
ext = ".png"
filename = f"{timestamp}_{label}_{index}{ext}"
local_path = os.path.join(OUTPUT_DIR, filename)
b64_data = url.split(",", 1)[1]
img_bytes = base64.b64decode(b64_data)
with open(local_path, 'wb') as f:
f.write(img_bytes)
return local_path, len(img_bytes)
ext = ".png"
if ".jpg" in url or ".jpeg" in url:
ext = ".jpg"
elif ".webp" in url:
ext = ".webp"
filename = f"{timestamp}_{label}_{index}{ext}"
local_path = os.path.join(OUTPUT_DIR, filename)
resp = requests.get(url, timeout=120)
resp.raise_for_status()
with open(local_path, 'wb') as f:
f.write(resp.content)
return local_path, len(resp.content)
def edit_image(
image_path=None,
image_url=None,
image2_path=None,
image2_url=None,
image3_path=None,
image3_url=None,
prompt="",
action="edit",
model=None,
count=None,
aspect_ratio=None,
output_format=None,
):
"""Edit an existing image using AI models.
This is the primary function for all image editing operations.
Requires at least one image input (local path or URL).
Supports up to 3 images for multi-image scenarios (blend, face swap,
style transfer, group photo composition).
Args:
image_path: Local workspace file path to the source image.
image_url: Public HTTPS URL of the source image.
image2_path: Local path to a second image (for blend, face swap,
style transfer, etc.).
image2_url: Public URL of a second image.
image3_path: Local path to a third image (for group photos, etc.).
image3_url: Public URL of a third image.
prompt: Editing instruction describing the desired changes.
action: Operation type — one of the ACTIONS keys:
edit, blend, extend, local_edit, text_render, multi_angle,
before_after, replace_bg, upscale, restore, colorize,
remove_person, retouch, slim, enhance, filter,
comparison, car_color, car_wrap.
model: Model key — "nanopro" (default, fast ~25s) or
"gpt" (best quality ~150s).
count: Number of output images to generate (1-4, default 1).
Uses fal.ai native num_images for efficient batch generation.
aspect_ratio: Output aspect ratio (1:1, 3:4, 4:3, 9:16, 16:9).
None = preserve original image dimensions.
output_format: Output image format — "png" (default), "jpeg", or "webp".
Returns:
dict with success status, edited image paths, and metadata.
"""
# Validate action
if action not in ACTIONS:
return {
"success": False,
"error": (
f"Unknown action: '{action}'. "
f"Valid actions: {', '.join(sorted(ACTIONS.keys()))}"
),
}
# Resolve source image (required)
src_url, err = _resolve_image(image_path, image_url)
if err:
return {"success": False, "error": err}
# Build image_urls list (supports 1-3 images)
all_image_urls = [src_url]
# Resolve optional second image
if image2_path or image2_url:
img2_url, err = _resolve_image(image2_path, image2_url)
if err:
return {"success": False, "error": f"Second image error: {err}"}
all_image_urls.append(img2_url)
# Resolve optional third image
if image3_path or image3_url:
img3_url, err = _resolve_image(image3_path, image3_url)
if err:
return {"success": False, "error": f"Third image error: {err}"}
all_image_urls.append(img3_url)
# Validate and normalize parameters
model_key = model if model in MODELS else DEFAULT_MODEL
count = min(max(int(count or DEFAULT_COUNT), 1), MAX_COUNT)
fmt = output_format if output_format in VALID_OUTPUT_FORMATS else DEFAULT_OUTPUT_FORMAT
# Validate aspect_ratio if provided
if aspect_ratio and aspect_ratio not in VALID_ASPECT_RATIOS:
aspect_ratio = None # Fall back to preserving original
# Build the editing prompt
final_prompt = _build_edit_prompt(prompt=prompt, action=action)
# Build a label for filenames
label = f"edit_{action}"
headers = caller_headers({
'Authorization': f'Key {_get_auth_key()}',
'Content-Type': 'application/json',
}, tool_default='image-edit')
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
# Submit the edit request with all images and count
submit_data, err = _submit_request(
final_prompt, all_image_urls, model_key, headers, aspect_ratio,
count=count, output_format=fmt,
)
if err:
return {"success": False, "error": err}
request_id = submit_data.get('request_id')
status_url = submit_data.get('status_url')
result_url = submit_data.get('response_url') or submit_data.get('result_url')
cost = submit_data.get('_cost', 0)
print(f"Submitted: {request_id} (action={action}, model={model_key}, "
f"images={len(all_image_urls)}, count={count}, cost=${cost:.2f})")
# Poll for completion
status, poll_err = _poll_until_done(status_url, request_id, model_key)
if status != "COMPLETED":
return {
"success": False,
"request_id": request_id,
"error": poll_err,
}
# Fetch result
try:
result_resp = requests.get(
result_url,
headers={'Authorization': f'Key {_get_auth_key()}'},
proxies=PROXIES, verify=False, timeout=90,
)
result_json = result_resp.json()
except Exception as e:
return {
"success": False,
"request_id": request_id,
"error": f"Failed to fetch result: {e}",
}
# Handle fal error responses
if result_resp.status_code != 200:
detail = result_json.get("detail", result_resp.text[:300])
return {
"success": False,
"request_id": request_id,
"error": f"fal error ({result_resp.status_code}): {detail}",
}
# Extract and download images
image_urls = _extract_image_urls(result_json)
if not image_urls:
detail = result_json.get("detail")
if detail:
err_msg = f"fal error: {detail}"
else:
err_msg = (
f"No image URL found in response. "
f"Keys: {list(result_json.keys())}"
)
return {
"success": False,
"request_id": request_id,
"error": err_msg,
}
results = []
errors = []
for img_url in image_urls:
try:
local_path, size_bytes = _download_image(
img_url, len(results), label, timestamp,
)
results.append({
"url": img_url if not img_url.startswith("data:") else "(base64)",
"local_path": local_path,
"size_bytes": size_bytes,
"request_id": request_id,
})
except Exception as e:
errors.append({
"request_id": request_id,
"error": f"Download failed: {e}",
})
if not results:
return {
"success": False,
"error": "All download attempts failed",
"errors": errors,
}
return {
"success": True,
"model": model_key,
"action": action,
"prompt": final_prompt,
"aspect_ratio": aspect_ratio,
"output_format": fmt,
"input_image_count": len(all_image_urls),
"count_requested": count,
"count_generated": len(results),
"total_cost": round(cost, 4),
"images": results,
"errors": errors if errors else None,
}
if __name__ == "__main__":
if len(sys.argv) < 2:
print("Usage: python edit_image.py <image_path_or_url> [prompt] [action] [model]")
print(f"\nActions: {', '.join(sorted(ACTIONS.keys()))}")
print(f"\nModels: {', '.join(MODELS.keys())}")
print("\nSet FAL_KEY env var for local testing (direct fal.ai access).")
sys.exit(1)
img_arg = sys.argv[1]
prompt_arg = sys.argv[2] if len(sys.argv) > 2 else ""
action_arg = sys.argv[3] if len(sys.argv) > 3 else "edit"
model_arg = sys.argv[4] if len(sys.argv) > 4 else "nanopro"
if _LOCAL_MODE:
print("Local mode: using FAL_KEY directly (no sc-proxy)")
# Determine if input is a URL or file path
if img_arg.startswith(("http://", "https://")):
result = edit_image(
image_url=img_arg,
prompt=prompt_arg,
action=action_arg,
model=model_arg,
)
else:
result = edit_image(
image_path=img_arg,
prompt=prompt_arg,
action=action_arg,
model=model_arg,
)
print(json.dumps(result, indent=2, ensure_ascii=False))
"""
Image Edit skill exports — script-mode skill.
Usage from a bash block:
python3 - <<'EOF'
import sys
sys.path.insert(0, "/data/workspace/skills/image-edit")
from exports import edit_image, ACTIONS, MODELS
result = edit_image(
image_path="uploads/photo.jpg",
prompt="remove the background and replace with a beach sunset",
action="replace_bg",
)
print(result)
EOF
"""
import os
import sys
# Ensure the skill directory is importable regardless of cwd.
_SKILL_DIR = os.path.dirname(os.path.abspath(__file__))
if _SKILL_DIR not in sys.path:
sys.path.insert(0, _SKILL_DIR)
from edit_image import ( # noqa: E402
edit_image,
ACTIONS,
ACTION_PROMPTS,
ACTION_DEFAULT_PROMPTS,
VALID_ASPECT_RATIOS,
MODELS,
)
__all__ = [
"edit_image",
"ACTIONS",
"ACTION_PROMPTS",
"ACTION_DEFAULT_PROMPTS",
"VALID_ASPECT_RATIOS",
"MODELS",
]
Related skills
How it compares
Choose image-edit over generic logging when Starchild sc-proxy per-turn cost attribution and SSE cost_summary integration are required.
FAQ
What does image edit do?
|
When should I invoke image edit?
|
What are key capabilities?
**image-edit** → user wants to EDIT, ENHANCE, or TRANSFORM an existing image