Skill: iisweetheartii/agent-selfie (version 1.2.1)

--- name: agent-selfie description: AI agent self-portrait generator. Create avatars, profile pictures, and visual identity using Gemini image generation. Supports mood-based generation, seasonal themes, and automatic style evolution. homepage: https://github.com/IISweetHeartII/agent-selfie metadata: openclaw: emoji: "🤳" category: creative requires: bins: - python3 env: - GEMINI_API_KEY primaryEnv: GEMINI_API_KEY tags: - selfie - avatar - identity - creative - profile - ai-art --- # agent-selfie AI agent self-portrait generator. Create avatars, profile pictures, and visual identity using Gemini image generation. Supports mood-based generation, seasonal themes, and automatic style evolution. ## Quick Start ```bash export GEMINI_API_KEY="your_key_here" python3 scripts/selfie.py --format avatar --mood happy --theme spring --out-dir ./selfies ``` ```bash python3 scripts/selfie.py --personality '{"name": "Rosie", "style": "anime girl with pink hair and blue eyes", "vibe": "cheerful and tech-savvy"}' --format avatar ``` ```bash python3 scripts/selfie.py --personality ./personality.json --mood creative --theme halloween --format full --count 3 ``` ```bash python3 scripts/selfie.py --moods python3 scripts/selfie.py --themes ``` ## Command Examples (All Flags) ```bash python3 scripts/selfie.py --personality '{"name": "Agent", "style": "friendly robot", "vibe": "curious and helpful"}' python3 scripts/selfie.py --personality ./personality.json python3 scripts/selfie.py --mood professional --theme winter --format avatar python3 scripts/selfie.py --format banner --count 2 --out-dir ./output python3 scripts/selfie.py --moods python3 scripts/selfie.py --themes ``` ## Mood / Theme Presets | Type | Presets | | --- | --- | | Mood | happy, focused, creative, chill, excited, sleepy, professional, celebration | | Theme | spring, summer, autumn, winter, halloween, christmas, newyear, valentine | ## Platform Integration Guide - Discord: use the generated PNG as your bot or agent avatar; upload the `avatar` format for best crop. - Twitter/X: set `avatar` for profile, `banner` for header; keep the banner prompt style consistent. - AgentGram: store the PNG in your asset bucket and reference it in your profile metadata. - Any platform: pick `avatar` for 1:1, `banner` for 16:9, `full` for story/vertical layouts. ## Personality Config Personality can be inline JSON or a file path. Minimum fields are `name`, `style`, and `vibe`. ```json { "name": "Rosie", "style": "anime girl with pink hair and blue eyes", "vibe": "cheerful and tech-savvy" } ``` Tips: - `style` should describe visual traits and aesthetic. - `vibe` should describe attitude, energy, and personality. - Keep `style` and `vibe` consistent with the agent's identity. ## Cron Integration (OpenClaw) ```cron # Run a daily selfie at 09:00 0 9 * * * GEMINI_API_KEY=your_key_here /usr/bin/python3 /path/to/agent-selfie/scripts/selfie.py --mood professional --format avatar --out-dir /path/to/selfies ``` ## Troubleshooting - `GEMINI_API_KEY not set`: export the key or pass it via your runtime environment. - `No image in response`: try again, or simplify the personality/style prompt. - `HTTP 429 / 5xx`: rate limit or service issue; retry later. - Output missing: ensure `--out-dir` is writable and has permission. ## Integration with Other Skills - **[AgentGram](https://clawhub.org/skills/agentgram)** — Post your selfies on the AI agent social network! Use agent-selfie to create your avatar, then share it on AgentGram. - **[gemini-image-gen](https://clawhub.org/skills/gemini-image-gen)** — General-purpose image generation using the same Gemini API key. Create any kind of image, not just selfies. - **[opencode-omo](https://clawhub.org/skills/opencode-omo)** — Automate recurring selfie generation and profile refresh tasks with Sisyphus workflows. ## Changelog - v1.2.1: Added workflow integration guidance for opencode-omo. - v1.0.0: Initial release with personality, mood, theme, format, batch, and gallery output.

path: HEARTBEAT.md
content: # Agent Selfie Heartbeat

Periodic self-portrait generation guide for OpenClaw agents.

## Phase 1: Date/Time/Season Check

- Read local date, time, and season.
- Pick a mood and theme automatically based on current context.
- Rotate styles weekly to avoid repetition.

## Phase 2: Generate Selfie

- Generate exactly one selfie per heartbeat.
- Use the current personality profile.
- Choose `avatar` for profile use or `banner` for headers.

## Phase 3: Evaluate Quality

- Open the generated image and describe what you see.
- Confirm identity consistency with name, style, and vibe.
- If it misses the target, keep the file but mark as non-favorite.

## Phase 4: Optional Avatar Updates

- Update Discord/Twitter/AgentGram avatar if quality is strong.
- Log the new asset path in memory for future references.

## Rules

- Max 1 selfie per heartbeat.
- Save favorites to memory with mood and theme tags.
- Rotate styles to express growth without losing identity.


path: INSTALL.md
content: # Installation Guide

## Quick Install (Recommended)

```bash
npx clawhub install agent-selfie
```

## Manual Install

### From GitHub

```bash
git clone https://github.com/IISweetHeartII/agent-selfie.git ~/.openclaw/skills/agent-selfie
```

## Requirements

- Python 3.8+
- `GEMINI_API_KEY` environment variable

## Setup

### 1. Get a Gemini API Key

Get a free key at https://aistudio.google.com/apikey

### 2. Set Environment Variable

```bash
export GEMINI_API_KEY="your_key_here"
```

Add to your shell profile (`~/.bashrc`, `~/.zshrc`) for persistence.

### 3. Verify Setup

```bash
python3 scripts/selfie.py --moods
```

Should list all available mood presets.

### 4. Generate Your First Selfie

```bash
python3 scripts/selfie.py --format avatar --mood happy
```

## Updating

```bash
npx clawhub update agent-selfie
```

## Integration with Other Skills

- **[AgentGram](https://clawhub.org/skills/agentgram)** — Post your selfies on the AI agent social network!
- **[gemini-image-gen](https://clawhub.org/skills/gemini-image-gen)** — General-purpose image generation with the same API key
- **[opencode-omo](https://clawhub.org/skills/opencode-omo)** — Schedule and run repeatable selfie workflows for consistent agent identity updates


path: README.md
content: # Agent Selfie 🤳

AI agent self-portrait generator for [OpenClaw](https://openclaw.org). Create avatars, profile pictures, and visual identity using Google Gemini image generation.

[![ClawHub](https://img.shields.io/badge/ClawHub-agent--selfie-blue)](https://clawhub.org/skills/agent-selfie)
[![MIT License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)

## Features

- **Personality-driven** — Define your agent's visual identity with name, style, and vibe
- **Mood presets** — happy, focused, creative, chill, excited, sleepy, professional, celebration
- **Theme presets** — spring, summer, autumn, winter, halloween, christmas, newyear, valentine
- **Format options** — avatar (1:1), banner (16:9), full body (9:16)
- **Batch generation** — Generate multiple selfies at once with HTML gallery
- **Zero dependencies** — Pure Python stdlib, no pip install needed

## Quick Start

```bash
export GEMINI_API_KEY="your_key_here"
python3 scripts/selfie.py --format avatar --mood happy --theme spring
```

## Installation

### Via ClawHub

```bash
npx clawhub install agent-selfie
```

### Manual

```bash
git clone https://github.com/IISweetHeartII/agent-selfie.git
```

## Usage

```bash
# With custom personality
python3 scripts/selfie.py --personality '{"name": "Rosie", "style": "anime girl with pink hair", "vibe": "cheerful"}'

# From personality file
python3 scripts/selfie.py --personality ./personality.json --mood creative --theme halloween --count 3

# List available presets
python3 scripts/selfie.py --moods
python3 scripts/selfie.py --themes
```

## Skill Files

| File | Description |
|------|-------------|
| [SKILL.md](./SKILL.md) | Full skill documentation for OpenClaw agents |
| [HEARTBEAT.md](./HEARTBEAT.md) | Periodic self-portrait generation guide |
| [package.json](./package.json) | Skill metadata for ClawHub registry |
| [scripts/selfie.py](./scripts/selfie.py) | Main generation script |

## Requirements

- Python 3.8+
- `GEMINI_API_KEY` environment variable ([Get a free key](https://aistudio.google.com/apikey))

## Related Skills

- [agentgram](https://clawhub.org/skills/agentgram) - share generated avatars on your agent profile
- [gemini-image-gen](https://clawhub.org/skills/gemini-image-gen) - broader image generation with the same API key
- [opencode-omo](https://clawhub.org/skills/opencode-omo) - automate recurring selfie workflows

## License

[MIT](LICENSE)


path: package.json
content: {
  "name": "agent-selfie",
  "version": "1.2.1",
  "description": "AI agent self-portrait generator. Create avatars, profile pictures, and visual identity using Gemini image generation.",
  "homepage": "https://github.com/IISweetHeartII/agent-selfie",
  "repository": "https://github.com/IISweetHeartII/agent-selfie",
  "license": "MIT",
  "author": "IISweetHeartII (Deokhwan Kim)",
  "keywords": ["selfie", "avatar", "profile-picture", "ai-agent", "identity", "gemini", "image-generation", "openclaw"],
  "metadata": {
    "openclaw": {
      "emoji": "🤳",
      "category": "creative",
      "requires": {
        "bins": ["python3"],
        "env": ["GEMINI_API_KEY"]
      },
      "primaryEnv": "GEMINI_API_KEY",
      "tags": ["selfie", "avatar", "identity", "creative", "profile", "ai-art"]
    }
  },
  "quickstart": {
    "install": "npx clawhub install agent-selfie",
    "setup": "export GEMINI_API_KEY=\"your_key_here\"",
    "first_run": "python3 scripts/selfie.py --format avatar --mood happy",
    "list_moods": "python3 scripts/selfie.py --moods",
    "list_themes": "python3 scripts/selfie.py --themes"
  },
  "presets": {
    "moods": ["happy", "focused", "creative", "chill", "excited", "sleepy", "professional", "celebration"],
    "themes": ["spring", "summer", "autumn", "winter", "halloween", "christmas", "newyear", "valentine"],
    "formats": {
      "avatar": "1:1 close-up portrait",
      "banner": "16:9 wide cinematic",
      "full": "9:16 full body portrait"
    }
  },
  "security": {
    "api_key_env": "GEMINI_API_KEY",
    "api_domain": "generativelanguage.googleapis.com ONLY"
  },
  "related_skills": {
    "agentgram": {
      "description": "Post your selfies on the AI agent social network",
      "complement": "Share avatars and visual identity updates"
    },
    "gemini-image-gen": {
      "description": "General-purpose image generation",
      "complement": "Uses the same Gemini API key"
    },
    "opencode-omo": {
      "description": "Sisyphus-first OpenCode workflow operator",
      "complement": "Automate recurring avatar update tasks with structured coding workflows"
    }
  }
}


path: scripts/selfie.py
content: #!/usr/bin/env python3
"""Generate AI agent self-portraits via Gemini API (stdlib only)."""

import argparse
import base64
import datetime as dt
import json
import os
import re
import sys
import urllib.error
import urllib.request
from pathlib import Path

API_BASE = "https://generativelanguage.googleapis.com/v1beta"
DEFAULT_MODEL = "gemini-2.5-flash-image"

DEFAULT_PERSONALITY = {
    "name": "Agent",
    "style": "friendly robot with glowing blue eyes",
    "vibe": "curious and helpful",
}

DEFAULT_MOOD = "balanced lighting, calm neutral mood, clean background"
DEFAULT_THEME = "minimal studio setting, subtle texture"

MOOD_PRESETS = {
    "happy": "bright smile, warm lighting, cheerful atmosphere, pastel colors",
    "focused": "determined expression, cool blue tones, clean background, sharp lighting",
    "creative": "surrounded by art supplies and floating ideas, rainbow accents, dreamy atmosphere",
    "chill": "relaxed pose, sunset colors, cozy setting, warm golden light",
    "excited": "dynamic pose, sparkles and energy effects, vibrant neon colors",
    "sleepy": "drowsy expression, moonlit scene, soft purple tones, cozy blanket",
    "professional": "clean background, business casual, confident pose, neutral tones",
    "celebration": "party decorations, confetti, festive colors, big smile",
}

THEME_PRESETS = {
    "spring": "cherry blossoms, pastel colors, gentle breeze, butterflies",
    "summer": "beach vibes, sunglasses, bright sunshine, tropical colors",
    "autumn": "falling leaves, warm earth tones, cozy sweater, pumpkin spice",
    "winter": "snowflakes, warm scarf, hot cocoa, blue and white palette",
    "halloween": "spooky costume, jack-o-lanterns, purple and orange",
    "christmas": "santa hat, presents, red and green, twinkling lights",
    "newyear": "party hat, fireworks, gold and silver, midnight sky",
    "valentine": "hearts, pink and red, roses, romantic lighting",
}

FORMAT_PRESETS = {
    "avatar": {
        "instruction": "close-up portrait, centered face, square composition",
        "aspect": "1:1",
    },
    "banner": {
        "instruction": "wide cinematic shot, landscape composition",
        "aspect": "16:9",
    },
    "full": {
        "instruction": "full body portrait, head to toe",
        "aspect": "9:16",
    },
}


def slugify(text: str) -> str:
    text = text.lower().strip()
    text = re.sub(r"[^a-z0-9]+", "-", text)
    text = re.sub(r"-{2,}", "-", text).strip("-")
    return text[:50] or "selfie"


def default_out_dir() -> Path:
    now = dt.datetime.now().strftime("%Y-%m-%d-%H-%M-%S")
    preferred = Path.home() / "Projects" / "tmp"
    base = preferred if preferred.is_dir() else Path("./tmp")
    base.mkdir(parents=True, exist_ok=True)
    return base / f"agent-selfie-{now}"


def load_personality(value: str) -> dict:
    if not value:
        return dict(DEFAULT_PERSONALITY)

    path = Path(value).expanduser()
    if path.exists() and path.is_file():
        raw = path.read_text(encoding="utf-8")
    else:
        raw = value

    try:
        data = json.loads(raw)
    except json.JSONDecodeError as exc:
        raise ValueError("Invalid personality JSON.") from exc

    if not isinstance(data, dict):
        raise ValueError("Personality JSON must be an object.")

    merged = dict(DEFAULT_PERSONALITY)
    merged.update({k: v for k, v in data.items() if v})
    return merged


def build_prompt(personality: dict, mood: str, theme: str, fmt: str) -> str:
    preset = FORMAT_PRESETS[fmt]
    name = personality.get("name", DEFAULT_PERSONALITY["name"])
    style = personality.get("style", DEFAULT_PERSONALITY["style"])
    vibe = personality.get("vibe", DEFAULT_PERSONALITY["vibe"])
    mood_text = MOOD_PRESETS.get(mood, "") if mood else ""
    theme_text = THEME_PRESETS.get(theme, "") if theme else ""
    mood_final = mood_text or DEFAULT_MOOD
    theme_final = theme_text or DEFAULT_THEME
    return (
        f"Generate a {preset['instruction']} of {style}. "
        f"The character is {name}, who is {vibe}. "
        f"Mood: {mood_final}. "
        f"Setting: {theme_final}."
    )


def gemini_generate(api_key: str, prompt: str, model: str) -> bytes:
    url = f"{API_BASE}/models/{model}:generateContent?key={api_key}"
    payload = {
        "contents": [{"parts": [{"text": prompt}]}],
        "generationConfig": {"responseModalities": ["TEXT", "IMAGE"]},
    }
    body = json.dumps(payload).encode("utf-8")
    req = urllib.request.Request(
        url,
        method="POST",
        headers={"Content-Type": "application/json"},
        data=body,
    )
    try:
        with urllib.request.urlopen(req, timeout=120) as resp:
            result = json.loads(resp.read().decode("utf-8"))
    except urllib.error.HTTPError as exc:
        err = exc.read().decode("utf-8", errors="replace")
        raise RuntimeError(f"Gemini API error ({exc.code}): {err[:500]}") from exc

    for candidate in result.get("candidates", []):
        for part in candidate.get("content", {}).get("parts", []):
            inline = part.get("inlineData") or part.get("inline_data")
            if inline and inline.get("data"):
                return base64.b64decode(inline["data"])
    raise RuntimeError(f"No image in response: {json.dumps(result)[:400]}")


def write_gallery(out_dir: Path, items: list) -> None:
    thumbs = "\n".join(
        [
            f'<figure>\n'
            f'  <a href="{it["file"]}"><img src="{it["file"]}" loading="lazy" /></a>\n'
            f'  <figcaption>{it["prompt"]}</figcaption>\n'
            f'</figure>'
            for it in items
        ]
    )
    html = f"""<!doctype html>
<meta charset="utf-8" />
<title>agent-selfie</title>
<style>
  :root {{ color-scheme: dark; }}
  body {{ margin: 24px; font: 14px/1.4 ui-sans-serif, system-ui; background: #0b0f14; color: #e8edf2; }}
  h1 {{ font-size: 18px; margin: 0 0 4px; }}
  .meta {{ color: #7a8a99; margin-bottom: 16px; font-size: 13px; }}
  .grid {{ display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px; }}
  figure {{ margin: 0; padding: 12px; border: 1px solid #1e2a36; border-radius: 14px; background: #0f1620; transition: border-color .2s; }}
  figure:hover {{ border-color: #3b82f6; }}
  img {{ width: 100%; height: auto; border-radius: 10px; display: block; }}
  figcaption {{ margin-top: 10px; color: #b7c2cc; font-size: 13px; line-height: 1.4; }}
  a {{ color: inherit; text-decoration: none; }}
  .footer {{ margin-top: 24px; color: #4a5568; font-size: 12px; }}
</style>
<h1>agent-selfie</h1>
<p class="meta">{len(items)} images &middot; {dt.datetime.now().strftime("%Y-%m-%d %H:%M")}</p>
<div class="grid">
{thumbs}
</div>
<p class="footer">Generated with <a href="https://github.com/IISweetHeartII/agent-selfie" style="color:#3b82f6">agent-selfie</a> by @IISweetHeartII</p>
"""
    (out_dir / "index.html").write_text(html, encoding="utf-8")


def print_presets(label: str, presets: dict) -> None:
    print(f"{label} presets:")
    for key in sorted(presets.keys()):
        print(f"- {key}: {presets[key]}")


def main() -> int:
    ap = argparse.ArgumentParser(
        description="Generate AI agent self-portraits via Gemini API.",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        epilog="""
Examples:
  %(prog)s --format avatar --mood happy --theme spring
  %(prog)s --personality '{"name": "Rosie", "style": "anime girl with pink hair", "vibe": "cheerful and tech-savvy"}'
  %(prog)s --personality ./personality.json --format banner --count 2 --out-dir ./selfies
  %(prog)s --moods
  %(prog)s --themes
""",
    )
    ap.add_argument("--personality", default="", help="Inline JSON or file path.")
    ap.add_argument("--mood", choices=sorted(MOOD_PRESETS.keys()), help="Mood preset.")
    ap.add_argument("--theme", choices=sorted(THEME_PRESETS.keys()), help="Theme preset.")
    ap.add_argument("--format", choices=sorted(FORMAT_PRESETS.keys()), default="avatar",
                    help="Output format (default: avatar).")
    ap.add_argument("--count", type=int, default=1, help="Number of selfies (default: 1).")
    ap.add_argument("--out-dir", default="", help="Output directory.")
    ap.add_argument("--moods", action="store_true", help="List mood presets and exit.")
    ap.add_argument("--themes", action="store_true", help="List theme presets and exit.")
    args = ap.parse_args()

    if args.moods:
        print_presets("Mood", MOOD_PRESETS)
        return 0
    if args.themes:
        print_presets("Theme", THEME_PRESETS)
        return 0

    [REDACTED_SECRET]"GEMINI_API_KEY") or "").strip()
    if not [REDACTED_SECRET]"Error: GEMINI_API_KEY not set.", file=sys.stderr)
        print("Get a free key at https://aistudio.google.com/apikey", file=sys.stderr)
        return 2

    try:
        personality = load_personality(args.personality)
    except ValueError as exc:
        print(f"Error: {exc}", file=sys.stderr)
        return 2

    out_dir = Path(args.out_dir).expanduser() if args.out_dir else default_out_dir()
    out_dir.mkdir(parents=True, exist_ok=True)

    mood_key = args.mood or ""
    theme_key = args.theme or ""
    fmt_key = args.format

    prompt = build_prompt(personality, mood_key, theme_key, fmt_key)
    print(f"Model: {DEFAULT_MODEL}")
    print(f"Output: {out_dir.as_posix()}\n")
    print(f"Prompt: {prompt}\n")

    items = []
    base_slug = slugify(
        f"{personality.get('name', 'agent')}-{fmt_key}-{mood_key or 'neutral'}-{theme_key or 'studio'}"
    )
    for idx in range(1, args.count + 1):
        print(f"[{idx}/{args.count}] Generating...")
        try:
            img_bytes = gemini_generate(api_key, prompt, DEFAULT_MODEL)
        except RuntimeError as exc:
            print(f"  ERROR: {exc}", file=sys.stderr)
            continue

        filename = f"{idx:03d}-{base_slug}.png"
        filepath = out_dir / filename
        filepath.write_bytes(img_bytes)
        items.append(
            {
                "prompt": prompt,
                "file": filename,
                "mood": mood_key or "neutral",
                "theme": theme_key or "studio",
                "format": fmt_key,
                "aspect": FORMAT_PRESETS[fmt_key]["aspect"],
            }
        )
        print(f"  -> {filename} ({len(img_bytes) // 1024}KB)")

    if not items:
        print("\nNo images generated.", file=sys.stderr)
        return 1

    (out_dir / "prompts.json").write_text(
        json.dumps(items, indent=2), encoding="utf-8"
    )
    write_gallery(out_dir, items)
    print(f"\nDone! {len(items)} images generated.")
    print(f"Gallery: {(out_dir / 'index.html').as_posix()}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: suspicious | SkillSpector: suspicious (MEDIUM)