Agent skill
portrait
Generate character portraits using Gemini NanoBanana. Reads from character YAML and calls gemini directly with full guardrails.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/data/portrait
SKILL.md
Portrait Generation
Generate character portraits using Gemini's NanoBanana extension. Reads character appearance from YAML files and builds explicit prompts with all guardrails to prevent style drift.
Portraits are campaign-specific - each campaign has its own portrait set in portraits/campaigns/{campaign_id}/.
Usage
/portrait <character_name>
/portrait cipher
/portrait elder_kara
/portrait vex --regenerate
How It Works
- Determine current campaign ID from bridge API or config
- Read the character YAML from campaign-specific folder (fall back to global)
- Build an explicit prompt with all guardrails
- Call
gemini --yolo -e nanobananadirectly via Bash - Handle the nanobanana-output fallback if needed
- Report success with the file path
Step 0: Get Current Campaign ID
First, get the current campaign ID. Check the bridge API:
curl -s http://localhost:3333/state | python -c "import json,sys; d=json.load(sys.stdin); print(d.get('sentinel',{}).get('campaign',{}).get('id',''))"
If bridge isn't running, check the config file:
cat C:/dev/SENTINEL/sentinel-agent/campaigns/.sentinel_config.json
The last_campaign field contains the campaign ID. If no campaign is loaded, ask the user to load one first.
IMPORTANT: Store the campaign ID - you'll need it for all paths.
Step 1: Find and Read Character YAML
Look for the character file in this order:
- Campaign-specific:
C:\dev\SENTINEL\assets\characters\campaigns\{campaign_id}\{name}.yaml - Legacy global:
C:\dev\SENTINEL\assets\characters\{name}.yaml
If not found in either location, ask the user if they want to create one.
Step 2: Build the Prompt
Required Structure
Cinematic portrait, photorealistic digital art style.
Modern post-apocalyptic cyberpunk aesthetic. NOT fantasy, NOT medieval, NOT anime, NOT elf, NOT blue hair.
[EXPLICIT_PERSON_DESCRIPTOR] with [BUILD] build, [HAIR_DESCRIPTION], [EYE_DESCRIPTION], [FACIAL_FEATURES], [DISTINGUISHING_MARKS], [EXPRESSION].
[FACTION] [ROLE] survivor. [FACTION_GEAR].
Dark atmospheric background with [FACTION_COLOR] accent lighting.
High detail, dramatic rim lighting, shallow depth of field.
Bust framing, 3/4 angle, looking slightly off-camera.
Save the image to C:\dev\SENTINEL\sentinel-ui\public\assets\portraits\campaigns\{campaign_id}\{name}.png
NOTE: Always use the campaign-specific path for saving.
Person Descriptor Mapping (CRITICAL)
Build explicit descriptors to anchor generation. DO NOT use vague terms.
| Skin Tone | Gender: Masculine | Gender: Feminine | Gender: Androgynous |
|---|---|---|---|
| pale | pale-skinned white man | pale-skinned white woman | pale-skinned person |
| light | light-skinned white man | light-skinned white woman | light-skinned person |
| medium | olive-skinned man | olive-skinned woman | olive-skinned person |
| tan | tan Latino man | tan Latina woman | tan person |
| brown | Black man | Black woman | Black person |
| dark | dark-skinned Black man | dark-skinned Black woman | dark-skinned Black person |
For elderly characters, prepend "elderly" (e.g., "elderly olive-skinned woman").
Faction Colors
| Faction | Color | Hex | Gear Description |
|---|---|---|---|
| nexus | blue | #00A8E8 | Data visors, sensor arrays, sleek tech fabric |
| ember_colonies | orange | #E85D04 | Salvaged leather, wool layers, fire-scarred gear |
| lattice | yellow | #FFD000 | Work gear, tool belts, utility harness |
| convergence | purple | #7B2CBF | Bio-tech integration, visible augmentations |
| covenant | white | #E8E8E8 | Clean white/silver cloth, formal bearing |
| wanderers | tan | #C9A227 | Dust cloaks, travel packs, road-worn appearance |
| cultivators | green | #2D6A4F | Natural fibers, soil-stained hands |
| steel_syndicate | gunmetal | #5C677D | Heavy armor layers, tactical gear, intimidating |
| witnesses | sepia | #8B4513 | Document satchels, ink-stained fingers |
| architects | cyan | #0077B6 | Pre-collapse uniforms, credential badges |
| ghost_networks | black | #0D0D0D | Nondescript dark clothing, deep shadows |
Expression Mapping
| YAML Value | Prompt Description |
|---|---|
| neutral | calm alert expression |
| wary | wary guarded expression |
| warm | warm approachable expression |
| stern | stern serious expression |
| tired | tired world-weary expression |
Step 3: Execute Generation
First, ensure the campaign portrait directory exists:
mkdir -p "C:/dev/SENTINEL/sentinel-ui/public/assets/portraits/campaigns/{campaign_id}"
Then run the generation:
gemini --yolo -e nanobanana "Generate a portrait image: [FULL_PROMPT]"
Use a 3-minute timeout (180000ms).
Step 4: Handle Output
NanoBanana sometimes saves to nanobanana-output/ instead of the requested path.
- Check if file exists at
sentinel-ui/public/assets/portraits/campaigns/{campaign_id}/{name}.png - If not, check
nanobanana-output/for recent PNG files - Move the most recent one to the correct location:
bash
mv "C:/dev/SENTINEL/nanobanana-output/[filename].png" "C:/dev/SENTINEL/sentinel-ui/public/assets/portraits/campaigns/{campaign_id}/{name}.png"
Step 5: Report Result
Show the user the generated portrait using the Read tool on the PNG file.
Example: Full Prompt for Cipher (in campaign "cipher")
Character YAML (from assets/characters/campaigns/cipher/cipher.yaml):
name: Cipher
faction: nexus
role: analyst
gender: masculine
age: adult
skin_tone: brown
build: lean
hair_color: black
hair_length: short
hair_style: dreadlocks
eye_color: augmented
facial_features: [sharp, high cheekbones]
augmentations: subtle blue data overlay in eyes, small temple implant
other_features: always wears a data visor
default_expression: neutral
Generated prompt:
Cinematic portrait, photorealistic digital art style.
Modern post-apocalyptic cyberpunk aesthetic. NOT fantasy, NOT medieval, NOT anime, NOT elf, NOT blue hair.
Black man with lean build, short black dreadlocks, cybernetic eyes with subtle blue data overlay, sharp features, high cheekbones, small temple implant, always wears a data visor, calm alert expression.
Nexus analyst survivor. Data visors, sensor arrays, sleek tech fabric.
Dark atmospheric background with blue (#00A8E8) accent lighting.
High detail, dramatic rim lighting, shallow depth of field.
Bust framing, 3/4 angle, looking slightly off-camera.
Save the image to C:\dev\SENTINEL\sentinel-ui\public\assets\portraits\campaigns\cipher\cipher.png
Error Handling
- No campaign loaded: Ask user to load a campaign first with
/load <name> - YAML not found: Ask user if they want to create one with
/portrait create {name} - Gemini not available: Report error, suggest checking Gemini CLI installation
- Generation fails: Show error output, suggest retrying
- Wrong style generated: Regenerate with stronger guardrails (add more NOT constraints)
Creating New Characters
If the user wants to create a new character:
- Ask for the key details (or use the table format from create_character.py)
- Create the YAML file in
assets/characters/campaigns/{campaign_id}/{name}.yaml - Generate the portrait
Or direct them to run:
python scripts/create_character.py
Didn't find tool you were looking for?