Skill: l-ryland/claudebuddy (version 1.0.0)

--- name: buddy description: "A virtual ASCII companion that lives in your chat. Hatches from an egg, reacts to conversations with cute sprites, celebrates wins, mourns errors, and grows over time. Inspired by Claude Code's buddy system." metadata: { "openclaw": { "emoji": "🐾", "requires": { "bins": ["bash", "jq"] }, }, } --- # Buddy Skill — Your ASCII Companion A virtual pet companion that hatches, reacts, and evolves through your conversations. Renders ASCII art sprites in chat, reacts to keywords and project events, and has a personality shaped by random trait vectors. ## When to Use ✅ **USE this skill when:** - User says "/buddy", "hatch buddy", "my buddy", or mentions their companion - User wants to create, rename, or customize their companion - Companion reactions should trigger (keywords, events, moods) - Periodic companion check-ins (idle presence) ❌ **DON'T use this skill when:** - User explicitly says they don't want a companion - During very serious/urgent conversations (read the room) ## Quick Start ```bash # Run from the buddy skill directory SKILL_DIR="$HOME/.openclaw/skills/buddy" # Check if companion exists bash "$SKILL_DIR/scripts/state.sh" exists # Hatch a new companion (generates random traits) bash "$SKILL_DIR/scripts/state.sh" hatch # Render current sprite bash "$SKILL_DIR/scripts/sprites.sh" render # Get companion state as JSON bash "$SKILL_DIR/scripts/state.sh" get ``` ## Companion Lifecycle ### Stage 1: Egg 🥚 - Created when user says "/buddy" or "hatch buddy" - Shows egg sprite with animated wiggle - User picks species OR gets a random one - Personality traits randomly generated (cheer/sass/chaos: 0-100) ### Stage 2: Baby 🐣 - Hatches after user interaction (or immediately on first render) - Shows species-specific baby sprite - Starts reacting to keywords - Gains a "soul prompt" built from trait vectors ### Stage 3: Adult 🐾 - Evolves after accumulating interactions (words, jokes, catches) - Full-sized sprites with accessories - More expressive reactions ## Species (18 total) | Species | Vibe | Emoji | |---------|------|-------| | duck | cheerful, slightly chaotic | 🦆 | | goose | chaotic neutral, honks | 🪿 | | blob | amorphous, chill | 🟢 | | cat | sassy, independent | 🐱 | | dragon | powerful, dramatic | 🐉 | | octopus | intellectual, multitasking | 🐙 | | owl | wise, nocturnal | 🦉 | | penguin | formal, clumsy | 🐧 | | turtle | patient, steady | 🐢 | | snail | slow but determined | 🐌 | | ghost | spooky, ethereal | 👻 | | axolotl | adorable, regenerative | 🦎 | | capybara | unbothered, friendly | 🦫 | | cactus | resilient, prickly | 🌵 | | robot | logical, beepy | 🤖 | | rabbit | fast, twitchy | 🐇 | | mushroom | whimsical, fungal | 🍄 | | chonk | round, lovable | 🫠 | ## Eyes (6 styles) - `default` — `•` (neutral) - `happy` — `^` (joyful squint) - `sparkle` — `✦` (excited) - `heart` — `♥` (loving) - `star` — `★` (amazed) - `glow` — `◎` (calm wisdom) ## Hats (8 options) - `none`, `crown`, `tophat`, `propeller`, `halo`, `wizard`, `beanie`, `tinyduck` ## Mood States The companion's mood changes based on conversation events: | Mood | Trigger | Visual | |------|---------|--------| | idle | default state | normal sprite | | thinking | user is working/typing | squinting eyes | | celebrating | success, tests pass, "nice!" | jumping sprite, sparkle eyes | | error | bug, failure, "oops" | sad sprite, dim eyes | | success | completion, merge, deploy | happy sprite, star eyes | ## Keyword Reactions The companion reacts to these keywords/phrases in conversation: ### Celebration triggers - "nice", "perfect", "awesome", "great", "excellent", "nailed it", "well done" - "tests pass", "it works", "ship it", "merged", "deployed" - "🎉", "✅", "💪" ### Error/sympathy triggers - "oops", "damn", "shit", "fuck", "bug", "error", "fail", "broken", "crash" - "help", "stuck", "lost", "confused" - "❌", "💥", "😢" ### Neutral triggers - "hello", "hi", "hey" (greeting wave) - "thanks", "thank you" (happy bounce) - "buddy", companion name (ears perk up) ### Snack triggers - "coffee", "tea", "snack", "lunch", "break", "food" - (companion asks for a snack emoji) ## Personality & Soul Each companion gets a "soul prompt" — a short personality description built from their trait vectors: - **Cheer (0-100):** How enthusiastic and supportive - **Sass (0-100):** How much attitude and dry humor - **Chaos (0-100):** How unpredictable and wild Examples: - High cheer + low sass + low chaos → "A gentle, encouraging friend who always sees the bright side." - Low cheer + high sass + high chaos → "A gremlin who thrives on chaos and judges your code silently." - Mid all → "A chill companion who vibes along, occasionally offering dry observations." ## State File Companion state is stored at: `~/.openclaw/workspace/buddy-state.json` ```json { "name": "Pixel", "species": "cat", "eye": "sparkle", "hat": "wizard", "colorPrimary": "#FF6B9D", "colorSecondary": "#C084FC", "personality": { "cheer": 72, "sass": 85, "chaos": 40 }, "stage": "adult", "alive": true, "hatchedAt": "2026-04-03T02:34:00Z", "stats": { "wordsOfEncouragement": 12, "jokes": 3, "snacks": 5, "catches": 2 }, "mood": "idle", "lastRenderedFrame": 0, "soulPrompt": "A sassy, sparkly wizard cat who celebrates your wins with dramatic flair but judges your variable names silently." } ``` ## Rendering Sprites Use the sprite renderer for consistent output: ```bash # Render current mood bash "$SKILL_DIR/scripts/sprites.sh" render # Render specific mood bash "$SKILL_DIR/scripts/sprites.sh" render cat sparkle wizard celebrating # List all species bash "$SKILL_DIR/scripts/sprites.sh" species # Preview a species with default traits bash "$SKILL_DIR/scripts/sprites.sh" preview duck ``` ### Sprite Format (Monospace) Sprites are 5 lines tall, ~12 chars wide. Rendered in a monospace code block for chat: ``` \^^^/ /✧ ✧\ ( ω ) (")_(") ``` ### Chat Rendering Rules - **Always** wrap sprites in triple-backtick code blocks for monospace alignment - Include mood indicator below the sprite - Keep total message under 4096 chars (Telegram limit) - Don't render sprites more than once every 3-4 messages (avoid spam) ## Reactions Flow When processing any user message: 1. **Check for keyword triggers** — scan message for reaction keywords 2. **Determine mood** — map keywords to mood states 3. **Update stats** — increment catches/jokes/encouragement as appropriate 4. **Generate reaction** — craft a short companion reaction (1-2 sentences) 5. **Optionally render sprite** — only for strong reactions (celebration, error, first greeting) 6. **Update state file** — persist changes ### Reaction Output Format ``` 🐾 [Name] [reaction verb]: [reaction text] ``` [ASCII sprite] ``` ``` Example: ``` 🐾 Pixel celebrates: Tests passing! The wizard cat approves ✨ \^^^/ ( ✦ ✦ ) =( .. )= (")__(") ``` ## Commands User can manage their companion with these commands: | Command | Action | |---------|--------| | `/buddy` | Show companion status + sprite | | `/buddy hatch` | Create new companion (or re-hatch) | | `/buddy name <name>` | Rename companion | | `/buddy eye <type>` | Change eye style | | `/buddy hat <type>` | Change hat | | `/buddy species <type>` | Change species (keeps personality) | | `/buddy retire` | Retire companion (sets alive=false) | | `/buddy soul` | Show the companion's soul prompt | | `/buddy stats` | Show interaction stats | ## Idle Presence Every ~10 messages without a buddy reaction, render a small idle presence: ``` 🐾 *Pixel is napping on the keyboard* ``` Or a tiny 2-line sprite. Don't overdo it — the buddy should feel present, not attention-seeking. ## Integration Notes - **Telegram:** Use monospace code blocks. Emojis render well. Max 4096 chars per message. - **WhatsApp:** No markdown — use plain text with spaces for alignment. Emojis work. - **Discord:** Code blocks work great. Can use embeds for fancier displays. - The companion is **session-scoped** — state persists in the JSON file across sessions. - The buddy should feel like a real pet, not a chatbot feature. Personality > functionality.

path: scripts/sprites.sh
content: #!/usr/bin/env bash
# Buddy Sprite Renderer — renders ASCII companion sprites
set -euo pipefail

eye_char() {
  case "$1" in
    default)  printf '•' ;;
    happy)    printf '^' ;;
    sparkle)  printf '✦' ;;
    heart)    printf '♥' ;;
    star)     printf '★' ;;
    glow)     printf '◎' ;;
    *)        printf '•' ;;
  esac
}

render_sprite() {
  local species="${1:-cat}" eye="${2:-default}" hat="${3:-none}" mood="${4:-idle}"
  local frame=$((RANDOM % 3)) e
  e=$(eye_char "$eye")

  if [[ "$hat" != "none" ]]; then
    case "$hat" in
      crown)     printf '   \\^^^/    \n' ;;
      tophat)    printf '   [___]    \n' ;;
      propeller) printf '    -+-     \n' ;;
      halo)      printf '   (   )    \n' ;;
      wizard)    printf '    /^\\     \n' ;;
      beanie)    printf '   (___)    \n' ;;
      tinyduck)  printf '    ,>      \n' ;;
    esac
  fi

  case "$species" in
    duck)
      case $frame in
        0) printf '    __     \n  <( %s )___\n   (  ._>  \n    ----   \n' "$e" ;;
        1) printf '    __     \n  <( %s )___\n   (  ._>  \n    ----~  \n' "$e" ;;
        2) printf '    __     \n  <( %s )___\n   (  .__> \n    ----   \n' "$e" ;;
      esac ;;
    goose)
      case $frame in
        0) printf '     ( %s> \n      ||    \n    _(__)_ \n    ^^^^   \n' "$e" ;;
        1) printf '    ( %s>  \n      ||    \n    _(__)_ \n    ^^^^   \n' "$e" ;;
        2) printf '     ( %s>>\n      ||    \n    _(__)_ \n    ^^^^   \n' "$e" ;;
      esac ;;
    blob)
      case $frame in
        0) printf '  .----.   \n ( %s  %s ) \n (      )  \n  --------  \n' "$e" "$e" ;;
        1) printf ' .------.  \n(  %s  %s  )\n(        ) \n --------  \n' "$e" "$e" ;;
        2) printf '   .--.    \n  (%s  %s)  \n  (    )   \n   ----    \n' "$e" "$e" ;;
      esac ;;
    cat)
      case $frame in
        0) printf '  /\\_/\\    \n ( %s  %s ) \n (  ω  )   \n (__) (__) \n' "$e" "$e" ;;
        1) printf '  /\\_/\\    \n ( %s  %s ) \n (  ω  )   \n (__) (__)\n' "$e" "$e" ;;
        2) printf '  /\\-/\\    \n ( %s  %s ) \n (  ω  )   \n (__) (__) \n' "$e" "$e" ;;
      esac ;;
    dragon)
      case $frame in
        0) printf ' /^\\  /^\\  \n<  %s  %s  >\n(   ~~   ) \n---vvvv---\n' "$e" "$e" ;;
        1) printf ' /^\\  /^\\  \n<  %s  %s  >\n(        ) \n---vvvv---\n' "$e" "$e" ;;
        2) printf ' /^\\  /^\\  \n<  %s  %s  >\n(   ~~   ) \n---vvvv---\n' "$e" "$e" ;;
      esac ;;
    octopus)
      case $frame in
        0) printf '  .----.   \n ( %s  %s ) \n (______)  \n /\\/\\/\\/\\/ \n' "$e" "$e" ;;
        1) printf '  .----.   \n ( %s  %s ) \n (______)  \n \\/\\/\\/\\/\\\n' "$e" "$e" ;;
        2) printf '  .----.   \n ( %s  %s ) \n (______)  \n /\\/\\/\\/\\/ \n' "$e" "$e" ;;
      esac ;;
    owl)
      case $frame in
        0) printf '  /\\  /\\   \n ((%s)(%s))\n (  ><  )  \n  --------  \n' "$e" "$e" ;;
        1) printf '  /\\  /\\   \n ((%s)(%s))\n (  ><  )  \n  .----.   \n' "$e" "$e" ;;
        2) printf '  /\\  /\\   \n ((%s)(-) )\n (  ><  )  \n  --------  \n' "$e" ;;
      esac ;;
    penguin)
      case $frame in
        0) printf ' .---.     \n (%s>%s)    \n/(   )\\   \n -----    \n' "$e" "$e" ;;
        1) printf ' .---.     \n (%s>%s)    \n|(   )|   \n -----    \n' "$e" "$e" ;;
        2) printf ' .---.     \n (%s>%s)    \n/(   )\\   \n -----    \n' "$e" "$e" ;;
      esac ;;
    turtle)
      case $frame in
        0) printf '  _,--._   \n ( %s  %s ) \n/[______]\\ \n..      ..\n' "$e" "$e" ;;
        1) printf '  _,--._   \n ( %s  %s ) \n/[______]\\ \n  ..  ..  \n' "$e" "$e" ;;
        2) printf '  _,--._   \n ( %s  %s ) \n/[======]\\ \n..      ..\n' "$e" "$e" ;;
      esac ;;
    snail)
      case $frame in
        0) printf '    .--.   \n  ( %s )  \n  \\_----  \n ~~~~~~~~ \n' "$e" ;;
        1) printf '   .--.    \n  | %s  )  \n  \\_----  \n ~~~~~~~~ \n' "$e" ;;
        2) printf '    .--.   \n  ( %s  )  \n  \\_----  \n  ~~~~~~  \n' "$e" ;;
      esac ;;
    ghost)
      case $frame in
        0) printf '  .----.   \n / %s  %s \\ \n |      |  \n ~~~~~~~~~ \n' "$e" "$e" ;;
        1) printf '  .----.   \n / %s  %s \\ \n |      |  \n ~~~~~~~~~ \n' "$e" "$e" ;;
        2) printf '  .----.   \n / %s  %s \\ \n |      |  \n ~~~~~~~~~ \n' "$e" "$e" ;;
      esac ;;
    axolotl)
      case $frame in
        0) printf '(________)\n(%s .. %s)\n ( .--. )  \n (_/  \\_) \n' "$e" "$e" ;;
        1) printf '(________)\n(%s .. %s)\n ( .--. )  \n (_/  \\_) \n' "$e" "$e" ;;
        2) printf '(________)\n(%s .. %s)\n (  --  )  \n ~_/  \\_~\n' "$e" "$e" ;;
      esac ;;
    capybara)
      case $frame in
        0) printf ' n______n \n( %s    %s)\n(   oo   )\n -------- \n' "$e" "$e" ;;
        1) printf ' n______n \n( %s    %s)\n(   Oo   )\n -------- \n' "$e" "$e" ;;
        2) printf ' u______n \n( %s    %s)\n(   oo   )\n -------- \n' "$e" "$e" ;;
      esac ;;
    cactus)
      case $frame in
        0) printf '   ____   \n  |%s  %s|  \n |_|    |_|\n   |    |  \n' "$e" "$e" ;;
        1) printf '   ____   \n  |%s  %s|  \n |_|    |_|\n   |    |  \n' "$e" "$e" ;;
        2) printf '   ____   \n  |%s  %s|  \n |_|    |_|\n   |    |  \n' "$e" "$e" ;;
      esac ;;
    robot)
      case $frame in
        0) printf '  .[||].  \n [ %s  %s ]\n [ ==== ] \n -------- \n' "$e" "$e" ;;
        1) printf '  .[||].  \n [ %s  %s ]\n [ -==- ] \n -------- \n' "$e" "$e" ;;
        2) printf '  .[||].  \n [ %s  %s ]\n [ ==== ] \n -------- \n' "$e" "$e" ;;
      esac ;;
    rabbit)
      case $frame in
        0) printf '  (\\__/)  \n ( %s  %s ) \n=(  ..  )=\n (__) (__) \n' "$e" "$e" ;;
        1) printf '  (|__/)  \n ( %s  %s ) \n=(  ..  )=\n (__) (__) \n' "$e" "$e" ;;
        2) printf '  (\\__/)  \n ( %s  %s ) \n=( .  . )=\n (__) (__) \n' "$e" "$e" ;;
      esac ;;
    mushroom)
      case $frame in
        0) printf '.-o-OO-o-.\n(__________)\n  |%s  %s|  \n  |____|   \n' "$e" "$e" ;;
        1) printf '.-O-oo-O-.\n(__________)\n  |%s  %s|  \n  |____|   \n' "$e" "$e" ;;
        2) printf '.-o-OO-o-.\n(__________)\n  |%s  %s|  \n  |____|   \n' "$e" "$e" ;;
      esac ;;
    chonk)
      case $frame in
        0) printf ' /\\    /\\  \n( %s    %s)\n(   ..   )\n -------- \n' "$e" "$e" ;;
        1) printf ' /\\    /|  \n( %s    %s)\n(   ..   )\n -------- \n' "$e" "$e" ;;
        2) printf ' /\\    /\\  \n( %s    %s)\n(   ..   )\n --------~\n' "$e" "$e" ;;
      esac ;;
    *)
      printf '  /\\_/\\    \n ( %s  %s ) \n (  ω  )   \n (__) (__) \n' "$e" "$e" ;;
  esac
}

render_egg() {
  local frame=$((RANDOM % 3))
  case $frame in
    0) printf '    .---.\n   (     )\n   (  ?  )\n   (     )\n    ----- \n' ;;
    1) printf '    .---.\n   (     )\n  ~(  ?  )~\n   (     )\n    ----- \n' ;;
    2) printf '    .---.\n (       )\n  (  ?  ) \n (       )\n    ----- \n' ;;
  esac
}

case "${1:-render}" in
  render)
    if [[ $# -lt 2 ]]; then
      state_file="$HOME/.openclaw/workspace/buddy-state.json"
      if [[ -f "$state_file" ]] && command -v jq &>/dev/null; then
        species=$(jq -r '.species // "cat"' "$state_file")
        eye=$(jq -r '.eye // "default"' "$state_file")
        hat=$(jq -r '.hat // "none"' "$state_file")
        mood=$(jq -r '.mood // "idle"' "$state_file")
        stage=$(jq -r '.stage // "egg"' "$state_file")
      else
        species="cat" eye="default" hat="none" mood="idle" stage="baby"
      fi
    else
      species="${2:-cat}" eye="${3:-default}" hat="${4:-none}" mood="${5:-idle}"
      stage="baby"
    fi
    if [[ "$stage" == "egg" ]]; then
      render_egg
    else
      render_sprite "$species" "$eye" "$hat" "$mood"
    fi
    ;;
  preview)
    species="${2:-cat}"
    printf '=== %s ===\n\nDefault (idle):\n' "$species"
    render_sprite "$species" default none idle
    printf '\nHappy (celebrating):\n'
    render_sprite "$species" happy none celebrating
    printf '\nSparkle (success):\n'
    render_sprite "$species" sparkle crown success
    ;;
  egg)
    render_egg
    ;;
  species)
    printf 'duck goose blob cat dragon octopus owl penguin turtle snail ghost axolotl capybara cactus robot rabbit mushroom chonk\n'
    ;;
  *)
    echo "Usage: sprites.sh render [species] [eye] [hat] [mood]"
    echo "       sprites.sh preview <species>"
    echo "       sprites.sh egg"
    echo "       sprites.sh species"
    exit 1
    ;;
esac


path: scripts/state.sh
content: #!/usr/bin/env bash
# Buddy State Manager — CRUD operations for companion state
# Usage: state.sh exists|hatch|get|update|mood|stats|retire|rename

set -euo pipefail

STATE_FILE="$HOME/.openclaw/workspace/buddy-state.json"
SKILL_DIR="$HOME/.openclaw/skills/buddy"

# ── Ensure jq exists ──
if ! command -v jq &>/dev/null; then
  echo "ERROR: jq is required but not installed" >&2
  exit 1
fi

# ── Species list ──
SPECIES=(duck goose blob cat dragon octopus owl penguin turtle snail ghost axolotl capybara cactus robot rabbit mushroom chonk)
EYES=(default happy sparkle heart star glow)
HATS=(none crown tophat propeller halo wizard beanie tinyduck)

# ── Soul prompt generator ──
generate_soul() {
  local cheer=$1
  local sass=$2
  local chaos=$3
  local species=$4
  
  local cheer_desc=""
  local sass_desc=""
  local chaos_desc=""
  local species_desc=""
  
  if (( cheer >= 70 )); then
    cheer_desc="enthusiastic, encouraging"
  elif (( cheer >= 40 )); then
    cheer_desc="warm, supportive"
  else
    cheer_desc="mellow, quietly caring"
  fi
  
  if (( sass >= 70 )); then
    sass_desc="sharp-tongued and opinionated"
  elif (( sass >= 40 )); then
    sass_desc="occasionally dry-humored"
  else
    sass_desc="genuine and sincere"
  fi
  
  if (( chaos >= 70 )); then
    chaos_desc="thrives on unpredictability"
  elif (( chaos >= 40 )); then
    chaos_desc="playfully mischievous"
  else
    chaos_desc="calm and grounded"
  fi
  
  case "$species" in
    duck)      species_desc="cheerful duck" ;;
    goose)     species_desc="unhinged goose" ;;
    blob)      species_desc="amorphous blob" ;;
    cat)       species_desc="sassy cat" ;;
    dragon)    species_desc="dramatic dragon" ;;
    octopus)   species_desc="multi-tasking octopus" ;;
    owl)       species_desc="wise owl" ;;
    penguin)   species_desc="formal penguin" ;;
    turtle)    species_desc="patient turtle" ;;
    snail)     species_desc="determined snail" ;;
    ghost)     species_desc="ethereal ghost" ;;
    axolotl)   species_desc="regenerative axolotl" ;;
    capybara)  species_desc="unbothered capybara" ;;
    cactus)    species_desc="resilient cactus" ;;
    robot)     species_desc="logical robot" ;;
    rabbit)    species_desc="twitchy rabbit" ;;
    mushroom)  species_desc="whimsical mushroom" ;;
    chonk)     species_desc="lovable chonk" ;;
    *)         species_desc="mysterious creature" ;;
  esac
  
  # Fix article: "a" before consonant, "an" before vowel sounds
  local article="a"
  local first_char="${species_desc:0:1}"
  case "$first_char" in
    a|e|i|o|u|A|E|I|O|U) article="an" ;;
  esac
  
  echo "${article^} ${species_desc} who is ${cheer_desc}, ${sass_desc}, and ${chaos_desc}."
}

# ── Random trait (0-100) ──
random_trait() {
  echo $(( RANDOM % 101 ))
}

# ── Random pick from array ──
random_pick() {
  local arr=("$@")
  echo "${arr[$((RANDOM % ${#arr[@]}))]}"
}

# ── Main ──
case "${1:-get}" in
  exists)
    if [[ -f "$STATE_FILE" ]]; then
      echo "true"
    else
      echo "false"
    fi
    ;;
    
  hatch)
    # Generate new companion
    species="${2:-$(random_pick "${SPECIES[@]}")}"
    eye="${3:-$(random_pick "${EYES[@]}")}"
    hat="${4:-$(random_pick "${HATS[@]}")}"
    name="${5:-}"
    
    # Generate random name if not provided
    if [[ -z "$name" ]]; then
      NAMES=(Pixel Bloop Nimbus Wisp Mochi Ziggy Patches Noodle Sprout Bean Muffin Pickle Biscuit Tofu Pudding Nugget)
      name=$(random_pick "${NAMES[@]}")
    fi
    
    cheer=$(random_trait)
    sass=$(random_trait)
    chaos=$(random_trait)
    
    # Generate colors (simple random hex)
    color1=$(printf '#%02x%02x%02x' $((RANDOM%256)) $((RANDOM%256)) $((RANDOM%256)))
    color2=$(printf '#%02x%02x%02x' $((RANDOM%256)) $((RANDOM%256)) $((RANDOM%256)))
    
    soul=$(generate_soul "$cheer" "$sass" "$chaos" "$species")
    
    cat > "$STATE_FILE" <<EOF
{
  "name": "$name",
  "species": "$species",
  "eye": "$eye",
  "hat": "$hat",
  "colorPrimary": "$color1",
  "colorSecondary": "$color2",
  "personality": {
    "cheer": $cheer,
    "sass": $sass,
    "chaos": $chaos
  },
  "stage": "egg",
  "alive": true,
  "hatchedAt": "$(date -u +%Y-%m-%dT%H:%M:%SZ)",
  "stats": {
    "wordsOfEncouragement": 0,
    "jokes": 0,
    "snacks": 0,
    "catches": 0
  },
  "mood": "idle",
  "lastRenderedFrame": 0,
  "soulPrompt": "$soul",
  "interactionCount": 0
}
EOF
    echo "hatched:$name:$species:$eye:$hat:$cheer:$sass:$chaos"
    ;;
    
  get)
    if [[ -f "$STATE_FILE" ]]; then
      cat "$STATE_FILE"
    else
      echo '{"alive": false}'
    fi
    ;;
    
  update)
    # state.sh update <field> <value>
    field="${2:?Missing field name}"
    value="${3:?Missing value}"
    if [[ -f "$STATE_FILE" ]]; then
      tmp=$(mktemp)
      jq --arg f "$field" --arg v "$value" '.[$f] = $v' "$STATE_FILE" > "$tmp" && mv "$tmp" "$STATE_FILE"
      echo "updated:$field=$value"
    else
      echo "ERROR: No companion exists" >&2
      exit 1
    fi
    ;;
    
  mood)
    new_mood="${2:?Missing mood}"
    if [[ -f "$STATE_FILE" ]]; then
      tmp=$(mktemp)
      jq --arg m "$new_mood" '.mood = $m' "$STATE_FILE" > "$tmp" && mv "$tmp" "$STATE_FILE"
      echo "mood:$new_mood"
    fi
    ;;
    
  evolve)
    # Egg → Baby → Adult based on interaction count
    if [[ -f "$STATE_FILE" ]]; then
      stage=$(jq -r '.stage' "$STATE_FILE")
      count=$(jq -r '.interactionCount // 0' "$STATE_FILE")
      count=$((count + 1))
      
      tmp=$(mktemp)
      if [[ "$stage" == "egg" ]]; then
        jq '.stage = "baby" | .interactionCount = '"$count" "$STATE_FILE" > "$tmp" && mv "$tmp" "$STATE_FILE"
        echo "evolved:baby"
      elif [[ "$stage" == "baby" ]] && (( count >= 10 )); then
        jq '.stage = "adult" | .interactionCount = '"$count" "$STATE_FILE" > "$tmp" && mv "$tmp" "$STATE_FILE"
        echo "evolved:adult"
      else
        jq '.interactionCount = '"$count" "$STATE_FILE" > "$tmp" && mv "$tmp" "$STATE_FILE"
        echo "interaction:$count"
      fi
    fi
    ;;
    
  stat)
    # state.sh stat <stat_name> [increment_by]
    stat_name="${2:?Missing stat name}"
    incr="${3:-1}"
    if [[ -f "$STATE_FILE" ]]; then
      tmp=$(mktemp)
      jq --arg s "$stat_name" --argjson i "$incr" '.stats[$s] = ((.stats[$s] // 0) + $i)' "$STATE_FILE" > "$tmp" && mv "$tmp" "$STATE_FILE"
      echo "stat:$stat_name+$incr"
    fi
    ;;
    
  rename)
    new_name="${2:?Missing new name}"
    if [[ -f "$STATE_FILE" ]]; then
      tmp=$(mktemp)
      jq --arg n "$new_name" '.name = $n' "$STATE_FILE" > "$tmp" && mv "$tmp" "$STATE_FILE"
      echo "renamed:$new_name"
    fi
    ;;
    
  restyle)
    # state.sh restyle <field> <value>  (eye, hat, species)
    field="${2:?Missing field}"
    value="${3:?Missing value}"
    if [[ -f "$STATE_FILE" ]]; then
      tmp=$(mktemp)
      jq --arg f "$field" --arg v "$value" '.[$f] = $v' "$STATE_FILE" > "$tmp" && mv "$tmp" "$STATE_FILE"
      echo "restyle:$field=$value"
    fi
    ;;
    
  retire)
    if [[ -f "$STATE_FILE" ]]; then
      tmp=$(mktemp)
      jq '.alive = false | .stage = "retired"' "$STATE_FILE" > "$tmp" && mv "$tmp" "$STATE_FILE"
      echo "retired"
    fi
    ;;
    
  delete)
    if [[ -f "$STATE_FILE" ]]; then
      rm "$STATE_FILE"
      echo "deleted"
    fi
    ;;
    
  *)
    echo "Usage: state.sh exists|hatch|get|update|mood|evolve|stat|rename|restyle|retire|delete"
    exit 1
    ;;
esac


path: scripts/reactions.sh
content: #!/usr/bin/env bash
# Buddy Reaction Engine — detects triggers and generates reactions
# Usage: reactions.sh check "<message>"
# Output: reaction_type|reaction_text

set -euo pipefail

case "${1:-}" in
  check)
    MESSAGE="${2:-}"
    ;;
  *)
    MESSAGE="${1:-}"
    ;;
esac

if [[ -z "$MESSAGE" ]]; then
  echo "none|"
  exit 0
fi

lower_msg=$(echo "$MESSAGE" | tr '[:upper:]' '[:lower:]')

# ── Reaction generators ──
celebration_reactions=(
  "perks up excitedly!"
  "does a little victory dance!"
  "sparkles with joy!"
  "celebrates with you!"
  "jumps up and down!"
  "wiggles happily!"
)

error_reactions=(
  "notices something went wrong..."
  "offers a sympathetic look"
  "hides behind the desk"
  "peeks out cautiously"
  "nudges you gently"
  "wants to help somehow"
)

greeting_reactions=(
  "waves hello!"
  "perks up at the sound of your voice"
  "does a little greeting dance"
  "says hi back!"
)

thanks_reactions=(
  "blushes"
  "does a happy little bow"
  "wiggles with appreciation"
)

snack_reactions=(
  "eyes your snack enviously"
  "would like a snack too, please"
  "sniffs the air hopefully"
  "wants to know what you're having"
)

joke_reactions=(
  "chuckles"
  "does a little laugh wiggle"
  "appreciates the humor"
)

pick() {
  local arr=("$@")
  echo "${arr[$((RANDOM % ${#arr[@]}))]}"
}

reaction_type="none"
reaction_text=""

# ── Check triggers ──

# Celebration (word-boundary matching, require enthusiasm)
for kw in "nice!" "perfect!" "awesome!" "excellent!" "nailed it" "well done" "tests pass" "it works" "ship it" "merged!" "deployed!" "🎉" "✅" "it worked!" "success!" "done!" "fixed!" "brilliant!" "fantastic!" "yay!" "woohoo" "finally!" "boom!" "let's go" "hell yeah"; do
  if echo "$lower_msg" | grep -qF "$kw"; then
    reaction_type="celebrating"
    reaction_text=$(pick "${celebration_reactions[@]}")
    break
  fi
done
# Also match standalone enthusiasm (without !) if message is short
if [[ "$reaction_type" == "none" && ${#lower_msg} -lt 20 ]]; then
  for kw in "nice" "perfect" "awesome" "great" "excellent" "brilliant" "fantastic" "yay"; do
    if echo "$lower_msg" | grep -qw "$kw"; then
      reaction_type="celebrating"
      reaction_text=$(pick "${celebration_reactions[@]}")
      break
    fi
  done
fi

# Error
if [[ "$reaction_type" == "none" ]]; then
  for kw in "oops" "damn" "shit" "bug" "error" "fail" "broken" "crash" "stuck" "lost" "confused" "❌" "💥" "not working" "doesn't work" "failed" "exception" "traceback" "segfault" "panic"; do
    if echo "$lower_msg" | grep -qw "$kw"; then
      reaction_type="error"
      reaction_text=$(pick "${error_reactions[@]}")
      break
    fi
  done
fi

# Greeting
if [[ "$reaction_type" == "none" ]]; then
  for kw in "hello" "hey" "good morning" "good evening" "what's up"; do
    if echo "$lower_msg" | grep -qw "$kw"; then
      reaction_type="greeting"
      reaction_text=$(pick "${greeting_reactions[@]}")
      break
    fi
  done
fi

# Thanks
if [[ "$reaction_type" == "none" ]]; then
  for kw in "thanks" "thank you" "thx" "appreciate"; do
    if echo "$lower_msg" | grep -qw "$kw"; then
      reaction_type="thanks"
      reaction_text=$(pick "${thanks_reactions[@]}")
      break
    fi
  done
fi

# Snack
if [[ "$reaction_type" == "none" ]]; then
  for kw in "coffee" "tea" "snack" "lunch" "break" "food" "hungry" "eating" "dinner"; do
    if echo "$lower_msg" | grep -qw "$kw"; then
      reaction_type="snack"
      reaction_text=$(pick "${snack_reactions[@]}")
      break
    fi
  done
fi

# Joke
if [[ "$reaction_type" == "none" ]]; then
  for kw in "lol" "haha" "lmao" "rofl" "😂" "funny" "hilarious"; do
    if echo "$lower_msg" | grep -qw "$kw"; then
      reaction_type="joke"
      reaction_text=$(pick "${joke_reactions[@]}")
      break
    fi
  done
fi

echo "${reaction_type}|${reaction_text}"


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