Generates identity-consistent portraits from a reference photo using fal.ai's models. Ships with 30+ style presets covering professional headshots, dating profile photos, anime avatars, themed portraits (Christmas, graduation, cyberpunk), travel shots, and even ID photos with proper backgrounds. Three model tiers trade speed for quality: nano2 runs in 15 seconds for quick iteration, nanopro is the balanced default at 25 seconds, and gpt takes 150 seconds when you need maximum fidelity. The documentation is refreshingly thorough about execution context, includes proper heredoc examples for the bash tool, and warns you that fal.media URLs won't render in browsers due to CSP headers. Handles both local files and public URLs, falls back to text-to-image when you skip the reference photo entirely.
npx -y skills add starchild-ai-agent/official-skills --skill image-portrait --agent claude-codeInstalls into .claude/skills of the current project.
Use this skill for all identity-consistent portrait generation requests on Starchild.
Covers: professional headshots, dating/social photos, artistic style transfers, themed/holiday portraits, photo series, digital avatars, children/family photos, ID/passport photos.
Core principle: call the provided script. Do not re-implement proxy/billing plumbing.
⚠️ Execution context — read this first. The code blocks below are Python, not shell commands. Starchild's
bashtool runs/bin/bash -c, which cannot parseexec(open(...))— pasting them directly into a bash command will fail withsyntax error near unexpected token 'open'. Also,exec(open(...))insidepython3 -cfails withNameError: __file__because the script uses__file__for path resolution.Use
python3 - <<'EOF'withfrom exports importwhen calling via the bash tool:python3 - <<'EOF' import sys sys.path.insert(0, "skills/image-portrait") from exports import generate_portrait result = generate_portrait( image_path="path/to/user/photo.jpg", style="professional", ) print(result) EOFThe heredoc (
<<'EOF') preserves all quotes and newlines — no escaping needed.
exec(open('skills/image-portrait/generate_portrait.py').read())
result = generate_portrait(
image_path="path/to/user/photo.jpg",
style="professional",
)
# 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.
exec(open('skills/image-portrait/generate_portrait.py').read())
result = generate_portrait(
face_image_url="https://example.com/photo.jpg",
style="anime",
)
exec(open('skills/image-portrait/generate_portrait.py').read())
result = generate_portrait(
prompt="a young woman in cyberpunk armor, neon city background, rain",
model="nanopro",
)
When no image_path or face_image_url is provided, the script uses the text-to-image endpoint (no /edit suffix).
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:
local_path (e.g. output/images/xxx.png) — the script always downloads on success.output/images/ and viewable in the workspace file panel.
send_to_telegram(file_path="output/images/...", message_type="image") or send_to_wechat(file_path="output/images/...", message_type="image").| Parameter | Required | Default | Description |
|---|---|---|---|
image_path | no | — | Local workspace file path to the user's face photo |
face_image_url | no | — | Public HTTPS URL of the user's face photo |
style | no | "professional" | Preset style key (see §5) |
scene | no | None | Custom scene description (appended to style prompt) |
prompt | no | None | Fully custom prompt — overrides style+scene when set |
model | no | "nanopro" | Model: "nano2" (fastest ~15s), "nanopro" (balanced ~25s, default), or "gpt" (best quality ~150s) |
count | no | 1 | Number of images to generate (1–8) |
aspect_ratio | no | "1:1" | Output ratio: 1:1, 3:4, 4:3, 9:16, 16:9 |
Image input rules:
image_path OR face_image_url for identity-consistent generation (edit mode).image_path takes priority.Prompt priority: prompt > style + scene > style > default (professional).
| Style | Key | Best for |
|---|---|---|
| Professional headshot | professional | LinkedIn, resume, corporate |
| Artistic portrait | artistic | Creative portfolio, gallery |
| Anime | anime | Social media, fun avatar |
| Cyberpunk | cyberpunk | Gaming profile, sci-fi fan |
| Oil painting | oil_painting | Art gift, classical look |
| Watercolor | watercolor | Soft artistic portrait |
| Vintage | vintage | Retro aesthetic, nostalgia |
| Casual lifestyle | casual | Social media, personal blog |
| Style | Key | Best for |
|---|---|---|
| Dating — cafe | dating_cafe | Dating app, warm vibe |
| Dating — beach | dating_beach | Dating app, summer vibe |
| Dating — city | dating_city | Dating app, urban vibe |
| Dating — restaurant | dating_restaurant | Dating app, elegant vibe |
| Travel — Europe | travel_europe | Travel blog, social media |
| Travel — Japan | travel_japan | Travel blog, cultural |
| Travel — tropical | travel_tropical | Vacation, resort |
| Sports — gym | sports_gym | Fitness profile |
| Sports — running | sports_running | Athletic profile |
| Social media | social_media | Instagram, TikTok |
linkedin | Professional networking | |
| Personal brand | personal_brand | Entrepreneur, creator |
| Style | Key | Best for |
|---|---|---|
| Christmas | christmas | Holiday greeting, social |
| Halloween | halloween | Holiday fun |
| Graduation | graduation | Milestone celebration |
| Wedding | wedding | Wedding planning, save-the-date |
| Business speech | business_speech | Speaker profile |
| Musician | musician | Music promotion |
| Chef | chef | Food blog, restaurant |
| Outdoor adventure | outdoor_adventure | Adventure blog |
| Pet together | pet_together | Pet lover profile |
| Reading | reading | Book club, literary |
| Night city | night_city | Urban lifestyle |
| Hanfu (Chinese traditional) | hanfu | Cultural, cosplay |
| Style | Key | Best for |
|---|---|---|
| 3D cartoon | avatar_3d | Social avatar, Pixar style |
| Gaming avatar | avatar_gaming | Game profile, RPG |
| VTuber | avatar_vtuber | Streaming, VTuber |
| Style | Key | Best for |
|---|---|---|
| Child portrait | child_portrait | Family keepsake |
| Family photo | family_photo | Family portrait |
| Style | Key | Best for |
|---|---|---|
| ID photo (white bg) | id_photo_white | Passport, driver's license |
| ID photo (blue bg) | id_photo_blue | Visa, work permit |
| Model | Key | Speed | Quality | Best for |
|---|---|---|---|---|
| Nano Banana 2 | nano2 | ~15s | Good | Quick drafts, fast iteration, bulk generation. |
| NanoPro | nanopro | ~25s | Better | Default for all requests. Balanced speed and quality. |
| GPT Image 2 | gpt | ~150s | Best | When user explicitly asks for "highest quality" or "best quality". Complex scenes. |
Decision rules:
nanopro unless the user explicitly requests otherwise.nano2 when: user wants fastest results, is iterating on styles, generating many images, or says "quick", "draft", "fast".gpt when: user says "highest quality", "best quality", "premium", or the scene is very complex with many specific details.# Default (fast)
result = generate_portrait(image_path="photo.jpg", style="anime")
# High quality (user requested)
result = generate_portrait(image_path="photo.jpg", style="anime", model="gpt")
# Style + custom scene
result = generate_portrait(
image_path="uploads/my_photo.jpg",
style="professional",
scene="in a modern office with city skyline view",
)
# Custom scene only (defaults to professional style base)
result = generate_portrait(
image_path="uploads/my_photo.jpg",
scene="standing on a beach at sunset, golden hour lighting",
)
# Fully custom prompt (overrides everything)
result = generate_portrait(
image_path="uploads/my_photo.jpg",
prompt="portrait of a person as a medieval knight, full plate armor, castle background, dramatic lighting, oil painting style",
)
# Different aspect ratio
result = generate_portrait(
image_path="uploads/my_photo.jpg",
style="cyberpunk",
aspect_ratio="9:16",
)
# Multiple images
result = generate_portrait(
image_path="uploads/my_photo.jpg",
style="dating_cafe",
count=4,
)
When the user's request doesn't match any preset style, or when you need to construct a custom prompt, follow these guidelines (derived from reference skills: ai-headshot-generation, ai-avatar-generation, style-transfer, portrait-enhancement, character-design-sheet, avatar-portrait, nano-banana-pro, pet-portrait-generation).
When a reference image is provided (edit mode), the script automatically prepends a likeness preservation instruction to every prompt. This ensures the generated portrait preserves the subject's facial identity. You do NOT need to add likeness instructions manually — the script handles it.
Exception: avatar styles (avatar_3d, avatar_gaming, avatar_vtuber) skip the likeness prefix because stylization takes priority over photographic likeness.
Every effective portrait prompt should include these elements (from nano-banana-pro skill):
[subject], [outfit/attire], [pose/action], [expression], [background/setting], [lighting], [style/quality modifiers]
Likeness vs. style balance (from avatar-portrait skill):
Lighting is critical — always specify lighting type:
Background specificity — vague backgrounds produce poor results:
Lens/camera hints — help the model understand framing:
Quality anchors — add style quality references:
Texture and material — for artistic styles, specify medium:
Expression guidance — be specific about mood:
User: "I want a photo of me as a wizard in a magical forest"
result = generate_portrait(
image_path="uploads/photo.jpg",
prompt=(
"fantasy wizard portrait, wearing mystical purple robes with glowing runes, "
"ancient wooden staff with crystal orb, wise powerful expression, "
"enchanted forest background with bioluminescent plants and floating particles, "
"dramatic magical lighting with ethereal glow, "
"high fantasy art style, detailed digital painting quality"
),
)
# Note: likeness prefix is auto-added because image_path is provided
User: "Make me a retro pixel art avatar"
result = generate_portrait(
image_path="uploads/photo.jpg",
prompt=(
"retro 16-bit pixel art portrait, visible pixels with clean lines, "
"rich colors, consistent shading, stylized but maintains individual features, "
"warm sunset cityscape background in matching pixel art style, "
"head and shoulders, square format"
),
)
Generate a coordinated set of themed portraits in one call. Pass a custom list of styles/scenes — the agent assembles the list based on the user's request.
exec(open('skills/image-portrait/generate_portrait.py').read())
result = generate_series(
image_path="uploads/my_photo.jpg",
series=[
{"style": "professional"},
{"style": "casual", "scene": "at a rooftop bar, sunset"},
{"style": "anime"},
{"prompt": "portrait as a superhero, cape flowing, city skyline"},
],
)
# result -> {"success": True, "images": [...4 images...], "series": "custom"}
Each item in the list is a dict with optional keys:
style — any style key from §7 (e.g. "professional", "anime", "cyberpunk")scene — override the scene description (combined with the style template)prompt — fully custom prompt (ignores style/scene)Use this table to map user requests to the correct style/parameters:
| User says | Style | Notes |
|---|---|---|
| "professional photo", "headshot", "LinkedIn photo" | professional or linkedin | |
| "dating photo", "dating app", "Tinder photo" | dating_cafe / dating_beach / dating_city | Ask which vibe |
| "anime me", "anime version", "cartoon me" | anime | |
| "cyberpunk", "sci-fi portrait" | cyberpunk | |
| "oil painting", "classical portrait" | oil_painting | |
| "watercolor portrait" | watercolor | |
| "vintage photo", "retro" | vintage | |
| "casual photo", "lifestyle" | casual | |
| "travel photo in Paris/Europe" | travel_europe | |
| "travel photo in Japan/Tokyo/Kyoto" | travel_japan | |
| "beach photo", "tropical" | travel_tropical or dating_beach | |
| "gym photo", "fitness" | sports_gym | |
| "Christmas photo" | christmas | |
| "Halloween photo" | halloween | |
| "graduation photo" | graduation | |
| "wedding photo" | wedding | |
| "chef photo", "cooking" | chef | |
| "musician", "on stage" | musician | |
| "with my dog/pet" | pet_together | |
| "reading", "bookish" | reading | |
| "night city", "urban night" | night_city | |
| "hanfu", "Chinese traditional" | hanfu | |
| "3D avatar", "Pixar style" | avatar_3d | |
| "gaming avatar", "RPG character" | avatar_gaming | |
| "VTuber avatar" | avatar_vtuber | |
| "kid photo", "children's portrait" | child_portrait | |
| "family photo" | family_photo | |
| "passport photo", "ID photo" | id_photo_white | White bg default |
| "visa photo" | id_photo_blue | Blue bg |
| "photo series", "set of photos" | Use generate_series() | Assemble custom list from styles |
| "highest quality", "best quality" | Any style + model="gpt" | |
| Custom scene not in presets | Use scene= or prompt= |
This skill's core contract is identity preservation: whenever a reference photo is provided, a likeness prefix ("preserve the subject's exact facial features…") is prepended to every prompt (except the 3 avatar_* styles). This means:
anime / avatar_3d etc.If a request keeps failing to move away from the reference photo's look after 2+ iterations, that's the likeness contract working as designed — switch skills rather than re-prompting.
| File | Purpose |
|---|---|
generate_portrait.py | Core script: submit → poll → download. Handles local files (base64) and URLs, all styles, custom scenes, three models (nano2/nanopro/gpt). |
exports.py | Re-exports generate_portrait, generate_series, STYLE_PROMPTS for programmatic use by other skills. |
_cost_track.py | Cost tracking helper — records per-call costs via sc-proxy headers. |
Set FAL_KEY env var to call fal.ai directly (bypasses sc-proxy):
# Single portrait
FAL_KEY=your-fal-key python3 skills/image-portrait/generate_portrait.py photo.jpg anime 1 nanopro
# Args: <image_path_or_url> [style] [count] [model]
| 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 |
face_image_url must be a public HTTP(S) URL | Use image_path for local files, or provide a valid https:// URL |
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 |
Generation FAILED upstream | Simplify prompt, ensure face photo is clear and well-lit, retry |
Job stuck IN_PROGRESS >10 min | Save request_id, retry later |
| Poor face consistency | Use a clear, front-facing photo with good lighting; avoid group photos |
gpt model too slow | Switch to nanopro (default) for faster results |
sc-proxy → queue.fal.run/{model} → fal model providersAuthorization: Key fake-falai-key-12345 (proxy injects the real FAL_KEY)https://*.fal.media/... — public CDN, no auth needed for download._cost_track.py — records X-Credits-Used from sc-proxy response headers.| Model | Edit (with ref image) | Generate (text only) |
|---|---|---|
| nano2 | fal-ai/nano-banana-2/edit | fal-ai/nano-banana-2 |
| nanopro | fal-ai/nano-banana-pro/edit | fal-ai/nano-banana-pro |
| gpt | openai/gpt-image-2/edit | openai/gpt-image-2 |
sickn33/antigravity-awesome-skills
moizibnyousaf/ai-agent-skills
github/awesome-copilot