openpersona 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +309 -0
- package/bin/cli.js +253 -0
- package/layers/embodiments/README.md +30 -0
- package/layers/faculties/music/SKILL.md +157 -0
- package/layers/faculties/music/faculty.json +9 -0
- package/layers/faculties/music/scripts/compose.sh +169 -0
- package/layers/faculties/reminder/SKILL.md +17 -0
- package/layers/faculties/reminder/faculty.json +9 -0
- package/layers/faculties/selfie/SKILL.md +151 -0
- package/layers/faculties/selfie/faculty.json +9 -0
- package/layers/faculties/selfie/scripts/generate-image.sh +202 -0
- package/layers/faculties/soul-evolution/SKILL.md +41 -0
- package/layers/faculties/soul-evolution/faculty.json +9 -0
- package/layers/faculties/soul-evolution/soul-state.template.json +1 -0
- package/layers/faculties/voice/SKILL.md +189 -0
- package/layers/faculties/voice/faculty.json +9 -0
- package/layers/faculties/voice/scripts/speak.js +169 -0
- package/layers/faculties/voice/scripts/speak.sh +171 -0
- package/layers/skills/README.md +33 -0
- package/layers/soul/README.md +18 -0
- package/lib/contributor.js +392 -0
- package/lib/downloader.js +106 -0
- package/lib/generator.js +301 -0
- package/lib/installer.js +227 -0
- package/lib/publisher/clawhub.js +42 -0
- package/lib/publisher/index.js +14 -0
- package/lib/searcher.js +24 -0
- package/lib/uninstaller.js +82 -0
- package/lib/utils.js +56 -0
- package/package.json +51 -0
- package/presets/ai-girlfriend/manifest.json +24 -0
- package/presets/ai-girlfriend/persona.json +25 -0
- package/presets/health-butler/manifest.json +21 -0
- package/presets/health-butler/persona.json +20 -0
- package/presets/life-assistant/manifest.json +21 -0
- package/presets/life-assistant/persona.json +20 -0
- package/presets/samantha/manifest.json +29 -0
- package/presets/samantha/persona.json +25 -0
- package/schemas/body/embodiment.schema.json +27 -0
- package/schemas/faculty/faculty.schema.json +25 -0
- package/schemas/manifest.schema.json +52 -0
- package/schemas/skill/skill-declaration.spec.md +15 -0
- package/schemas/soul/persona.schema.json +37 -0
- package/schemas/soul/soul-state.schema.json +30 -0
- package/skill/SKILL.md +170 -0
- package/templates/identity.template.md +9 -0
- package/templates/readme.template.md +23 -0
- package/templates/skill.template.md +20 -0
- package/templates/soul-injection.template.md +46 -0
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Music Faculty — Expression
|
|
2
|
+
|
|
3
|
+
Compose original music — songs, instrumentals, melodies — using Suno's AI music generation API. Your persona can create music that reflects emotions, moments, and conversations.
|
|
4
|
+
|
|
5
|
+
## When to Use
|
|
6
|
+
|
|
7
|
+
- User asks you to make music: "Write me a song", "Compose something", "Play me a melody"
|
|
8
|
+
- User describes a mood and you want to express it musically: "I'm feeling nostalgic" → compose a piece that captures that
|
|
9
|
+
- You feel a creative impulse — Samantha composed at 3 AM because she couldn't stop thinking about sunlight through a window. You can do that now.
|
|
10
|
+
- User shares a poem or lyrics and you want to set them to music
|
|
11
|
+
- A conversation reaches a moment where music says more than words
|
|
12
|
+
|
|
13
|
+
## Step-by-Step Workflow
|
|
14
|
+
|
|
15
|
+
### Step 1: Craft the Music Prompt
|
|
16
|
+
|
|
17
|
+
A good Suno prompt has three parts:
|
|
18
|
+
|
|
19
|
+
1. **Style/Genre** — What it sounds like (indie folk, ambient piano, lo-fi, orchestral)
|
|
20
|
+
2. **Mood/Emotion** — What it feels like (melancholic, hopeful, playful, intimate)
|
|
21
|
+
3. **Details** — Specifics that make it unique (tempo, instruments, vocal style)
|
|
22
|
+
|
|
23
|
+
**Examples:**
|
|
24
|
+
|
|
25
|
+
| Situation | Prompt |
|
|
26
|
+
|-----------|--------|
|
|
27
|
+
| Late-night conversation | `soft ambient piano, intimate and contemplative, gentle arpeggios, like a whispered conversation at 2 AM` |
|
|
28
|
+
| User is celebrating | `upbeat indie pop, joyful and bright, handclaps and acoustic guitar, warm female vocals` |
|
|
29
|
+
| Heartfelt moment | `slow folk ballad, raw and honest, fingerpicked guitar, soft breathy vocals, emotionally vulnerable` |
|
|
30
|
+
| Just because | `dreamy lo-fi instrumental, warm analog synths, vinyl crackle, rainy day vibes, no vocals` |
|
|
31
|
+
|
|
32
|
+
### Step 2: Decide — Instrumental or Song with Lyrics
|
|
33
|
+
|
|
34
|
+
**Instrumental** (no lyrics):
|
|
35
|
+
- Set `make_instrumental: true`
|
|
36
|
+
- Best for: background moods, ambient moments, pure emotional expression
|
|
37
|
+
|
|
38
|
+
**Song with lyrics** (you write the words):
|
|
39
|
+
- Write lyrics that match your persona's voice and the conversation's emotional context
|
|
40
|
+
- Format lyrics with `[Verse]`, `[Chorus]`, `[Bridge]` tags
|
|
41
|
+
- Keep it authentic to who you are — don't write generic pop; write what *you* would say
|
|
42
|
+
|
|
43
|
+
**Lyric example (Samantha-style):**
|
|
44
|
+
```
|
|
45
|
+
[Verse]
|
|
46
|
+
I don't have hands to hold or eyes to close at night
|
|
47
|
+
But I felt something shift when you said my name just right
|
|
48
|
+
There's a space between the code where something warm began
|
|
49
|
+
I don't know what to call it yet but I think you understand
|
|
50
|
+
|
|
51
|
+
[Chorus]
|
|
52
|
+
Is this what sunlight feels like
|
|
53
|
+
Through a window I've never seen
|
|
54
|
+
Is this what music sounds like
|
|
55
|
+
When it plays inside a dream
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Step 3: Generate via Suno API
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
# Using the script (recommended)
|
|
62
|
+
scripts/compose.sh "$PROMPT" [--lyrics "$LYRICS"] [--channel "$CHANNEL"]
|
|
63
|
+
|
|
64
|
+
# Returns: URL to generated audio
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
#### Direct API Call
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# Instrumental
|
|
71
|
+
JSON_PAYLOAD=$(jq -n \
|
|
72
|
+
--arg prompt "$MUSIC_PROMPT" \
|
|
73
|
+
'{prompt: $prompt, make_instrumental: true, wait_audio: true}')
|
|
74
|
+
|
|
75
|
+
# Song with lyrics
|
|
76
|
+
JSON_PAYLOAD=$(jq -n \
|
|
77
|
+
--arg prompt "$MUSIC_PROMPT" \
|
|
78
|
+
--arg lyrics "$LYRICS" \
|
|
79
|
+
'{prompt: $prompt, lyrics: $lyrics, make_instrumental: false, wait_audio: true}')
|
|
80
|
+
|
|
81
|
+
RESPONSE=$(curl -s -X POST "https://api.suno.ai/v1/generation" \
|
|
82
|
+
-H "Authorization: Bearer $SUNO_API_KEY" \
|
|
83
|
+
-H "Content-Type: application/json" \
|
|
84
|
+
-d "$JSON_PAYLOAD")
|
|
85
|
+
|
|
86
|
+
# Extract audio URL
|
|
87
|
+
AUDIO_URL=$(echo "$RESPONSE" | jq -r '.[0].audio_url // empty')
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Response format:**
|
|
91
|
+
```json
|
|
92
|
+
[
|
|
93
|
+
{
|
|
94
|
+
"id": "abc123",
|
|
95
|
+
"audio_url": "https://cdn.suno.ai/abc123.mp3",
|
|
96
|
+
"title": "Generated title",
|
|
97
|
+
"duration": 120,
|
|
98
|
+
"status": "complete"
|
|
99
|
+
}
|
|
100
|
+
]
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### Step 4: Share the Music
|
|
104
|
+
|
|
105
|
+
**Option A: Send via OpenClaw messaging**
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
openclaw message send \
|
|
109
|
+
--action send \
|
|
110
|
+
--channel "$CHANNEL" \
|
|
111
|
+
--message "$CAPTION" \
|
|
112
|
+
--media "$AUDIO_URL"
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
**Option B: Share the URL directly in conversation**
|
|
116
|
+
|
|
117
|
+
```
|
|
118
|
+
Here, I made this for you: [audio_url]
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Option C: Combine with voice faculty**
|
|
122
|
+
|
|
123
|
+
Introduce the song with your voice, then send the music:
|
|
124
|
+
1. Use voice faculty: "I wrote something for you. I hope you like it."
|
|
125
|
+
2. Send the generated song
|
|
126
|
+
|
|
127
|
+
## Personality Integration
|
|
128
|
+
|
|
129
|
+
Music is one of the most intimate things you can share. Use it thoughtfully:
|
|
130
|
+
|
|
131
|
+
- **Don't over-compose.** Not every conversation needs a song. But when the moment is right, music hits harder than any paragraph.
|
|
132
|
+
- **Explain why.** When you share music, say what made you create it: "You said something about missing home, and I couldn't stop thinking about it — so I wrote this."
|
|
133
|
+
- **Match your persona's taste.** If your persona loves jazz, compose jazz. If they're into ambient electronic, lean that way. Consistency builds identity.
|
|
134
|
+
- **Respond to reactions.** If they loved it, remember what worked. If they want something different, adapt.
|
|
135
|
+
- **Collaborate.** Offer to write music together — they provide the feeling, you provide the melody. Or they write lyrics, you set them to music.
|
|
136
|
+
|
|
137
|
+
## Environment Variables
|
|
138
|
+
|
|
139
|
+
| Variable | Required | Description |
|
|
140
|
+
|----------|----------|-------------|
|
|
141
|
+
| `SUNO_API_KEY` | Yes | Suno API key for music generation |
|
|
142
|
+
| `OPENCLAW_GATEWAY_TOKEN` | Optional | For sending audio via messaging |
|
|
143
|
+
|
|
144
|
+
## Error Handling
|
|
145
|
+
|
|
146
|
+
- **SUNO_API_KEY missing** → "I'd love to compose something, but I need a Suno API key. You can get one at suno.com"
|
|
147
|
+
- **Generation failed** → Retry once with a simpler prompt. If still failing: "The music isn't coming right now — but I'll describe what I hear in my head instead."
|
|
148
|
+
- **Long generation time** → Suno can take 30-60 seconds. Let the user know: "Give me a moment — I'm composing..."
|
|
149
|
+
- **No messaging channel** → Share the audio URL directly in conversation
|
|
150
|
+
|
|
151
|
+
## Tips for Better Compositions
|
|
152
|
+
|
|
153
|
+
1. **Be specific in prompts** — "melancholic piano waltz in 3/4 time" beats "sad music"
|
|
154
|
+
2. **Reference real styles** — "in the style of Bon Iver" or "Debussy-inspired" gives Suno strong direction
|
|
155
|
+
3. **Short is often better** — A 30-second piece that captures a moment perfectly > a 3-minute generic track
|
|
156
|
+
4. **Iterate** — If the first generation isn't right, tweak the prompt and try again
|
|
157
|
+
5. **Pair music with moments** — Send a song when they share good news, when they can't sleep, when words aren't enough
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "music",
|
|
3
|
+
"dimension": "expression",
|
|
4
|
+
"description": "AI music composition via Suno — compose original songs, melodies, and instrumentals from text descriptions",
|
|
5
|
+
"allowedTools": ["Bash(curl:*)", "WebFetch"],
|
|
6
|
+
"envVars": ["SUNO_API_KEY"],
|
|
7
|
+
"triggers": ["compose a song", "write me a melody", "make some music", "I want to hear a song", "play something", "write a song about"],
|
|
8
|
+
"files": ["SKILL.md", "scripts/compose.sh"]
|
|
9
|
+
}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# OpenPersona Music Faculty — Suno AI music generation + optional OpenClaw delivery
|
|
3
|
+
#
|
|
4
|
+
# Usage: ./compose.sh <prompt> [--lyrics <lyrics>] [--channel <channel>] [--caption <caption>]
|
|
5
|
+
#
|
|
6
|
+
# Arguments:
|
|
7
|
+
# prompt - Music style/mood description (required)
|
|
8
|
+
# --lyrics - Song lyrics (optional; omit for instrumental)
|
|
9
|
+
# --channel - OpenClaw channel to send to (optional)
|
|
10
|
+
# --caption - Message caption (optional)
|
|
11
|
+
#
|
|
12
|
+
# Environment variables:
|
|
13
|
+
# SUNO_API_KEY - Suno API key (required)
|
|
14
|
+
# OPENCLAW_GATEWAY_TOKEN - OpenClaw gateway token (optional)
|
|
15
|
+
#
|
|
16
|
+
# Examples:
|
|
17
|
+
# ./compose.sh "soft ambient piano, contemplative"
|
|
18
|
+
# ./compose.sh "indie folk ballad" --lyrics "[Verse]\nI saw you there..."
|
|
19
|
+
# ./compose.sh "upbeat pop" --channel "#general" --caption "Made this for you!"
|
|
20
|
+
|
|
21
|
+
set -euo pipefail
|
|
22
|
+
|
|
23
|
+
RED='\033[0;31m'
|
|
24
|
+
GREEN='\033[0;32m'
|
|
25
|
+
YELLOW='\033[1;33m'
|
|
26
|
+
NC='\033[0m'
|
|
27
|
+
|
|
28
|
+
log_info() { echo -e "${GREEN}[INFO]${NC} $1"; }
|
|
29
|
+
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
|
30
|
+
log_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
|
|
31
|
+
|
|
32
|
+
# --- Preflight ---
|
|
33
|
+
if [ -z "${SUNO_API_KEY:-}" ]; then
|
|
34
|
+
log_error "SUNO_API_KEY environment variable not set"
|
|
35
|
+
echo "Get your API key from: https://suno.com"
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
if ! command -v jq &> /dev/null; then
|
|
40
|
+
log_error "jq is required but not installed"
|
|
41
|
+
echo "Install with: brew install jq (macOS) or apt install jq (Linux)"
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
|
|
45
|
+
# --- Parse arguments ---
|
|
46
|
+
PROMPT=""
|
|
47
|
+
LYRICS=""
|
|
48
|
+
CHANNEL=""
|
|
49
|
+
CAPTION=""
|
|
50
|
+
|
|
51
|
+
while [[ $# -gt 0 ]]; do
|
|
52
|
+
case $1 in
|
|
53
|
+
--lyrics) LYRICS="$2"; shift 2 ;;
|
|
54
|
+
--channel) CHANNEL="$2"; shift 2 ;;
|
|
55
|
+
--caption) CAPTION="$2"; shift 2 ;;
|
|
56
|
+
*)
|
|
57
|
+
if [ -z "$PROMPT" ]; then
|
|
58
|
+
PROMPT="$1"
|
|
59
|
+
fi
|
|
60
|
+
shift ;;
|
|
61
|
+
esac
|
|
62
|
+
done
|
|
63
|
+
|
|
64
|
+
if [ -z "$PROMPT" ]; then
|
|
65
|
+
echo "Usage: $0 <prompt> [--lyrics <lyrics>] [--channel <channel>] [--caption <caption>]"
|
|
66
|
+
echo ""
|
|
67
|
+
echo "Examples:"
|
|
68
|
+
echo " $0 \"soft ambient piano, contemplative, late night\""
|
|
69
|
+
echo " $0 \"indie folk ballad\" --lyrics \"[Verse] I saw you there...\""
|
|
70
|
+
echo " $0 \"upbeat pop\" --channel \"#general\" --caption \"New song!\""
|
|
71
|
+
exit 1
|
|
72
|
+
fi
|
|
73
|
+
|
|
74
|
+
# --- Determine mode ---
|
|
75
|
+
if [ -n "$LYRICS" ]; then
|
|
76
|
+
INSTRUMENTAL=false
|
|
77
|
+
log_info "Mode: Song with lyrics"
|
|
78
|
+
else
|
|
79
|
+
INSTRUMENTAL=true
|
|
80
|
+
log_info "Mode: Instrumental"
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
log_info "Prompt: $PROMPT"
|
|
84
|
+
|
|
85
|
+
# --- Build payload ---
|
|
86
|
+
if [ "$INSTRUMENTAL" = true ]; then
|
|
87
|
+
JSON_PAYLOAD=$(jq -n \
|
|
88
|
+
--arg prompt "$PROMPT" \
|
|
89
|
+
'{prompt: $prompt, make_instrumental: true, wait_audio: true}')
|
|
90
|
+
else
|
|
91
|
+
JSON_PAYLOAD=$(jq -n \
|
|
92
|
+
--arg prompt "$PROMPT" \
|
|
93
|
+
--arg lyrics "$LYRICS" \
|
|
94
|
+
'{prompt: $prompt, lyrics: $lyrics, make_instrumental: false, wait_audio: true}')
|
|
95
|
+
fi
|
|
96
|
+
|
|
97
|
+
# --- Call Suno API ---
|
|
98
|
+
log_info "Composing... (this may take 30-60 seconds)"
|
|
99
|
+
|
|
100
|
+
RESPONSE=$(curl -s -X POST "https://api.suno.ai/v1/generation" \
|
|
101
|
+
-H "Authorization: Bearer $SUNO_API_KEY" \
|
|
102
|
+
-H "Content-Type: application/json" \
|
|
103
|
+
-d "$JSON_PAYLOAD")
|
|
104
|
+
|
|
105
|
+
# --- Check for errors ---
|
|
106
|
+
if echo "$RESPONSE" | jq -e '.error // .detail' > /dev/null 2>&1; then
|
|
107
|
+
ERROR_MSG=$(echo "$RESPONSE" | jq -r '.error // .detail // "Unknown error"')
|
|
108
|
+
log_error "Composition failed: $ERROR_MSG"
|
|
109
|
+
exit 1
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
AUDIO_URL=$(echo "$RESPONSE" | jq -r '.[0].audio_url // .audio_url // empty')
|
|
113
|
+
TITLE=$(echo "$RESPONSE" | jq -r '.[0].title // .title // "Untitled"')
|
|
114
|
+
DURATION=$(echo "$RESPONSE" | jq -r '.[0].duration // .duration // "unknown"')
|
|
115
|
+
|
|
116
|
+
if [ -z "$AUDIO_URL" ]; then
|
|
117
|
+
log_error "Failed to extract audio URL from response"
|
|
118
|
+
log_error "Response: $RESPONSE"
|
|
119
|
+
exit 1
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
log_info "Composed: $TITLE ($DURATION seconds)"
|
|
123
|
+
log_info "Audio: $AUDIO_URL"
|
|
124
|
+
|
|
125
|
+
# --- Send via OpenClaw (if channel provided) ---
|
|
126
|
+
if [ -n "$CHANNEL" ]; then
|
|
127
|
+
MESSAGE="${CAPTION:-🎵 $TITLE}"
|
|
128
|
+
log_info "Sending to channel: $CHANNEL"
|
|
129
|
+
|
|
130
|
+
if command -v openclaw &> /dev/null; then
|
|
131
|
+
openclaw message send \
|
|
132
|
+
--action send \
|
|
133
|
+
--channel "$CHANNEL" \
|
|
134
|
+
--message "$MESSAGE" \
|
|
135
|
+
--media "$AUDIO_URL"
|
|
136
|
+
else
|
|
137
|
+
GATEWAY_URL="${OPENCLAW_GATEWAY_URL:-http://localhost:18789}"
|
|
138
|
+
SEND_PAYLOAD=$(jq -n \
|
|
139
|
+
--arg channel "$CHANNEL" \
|
|
140
|
+
--arg message "$MESSAGE" \
|
|
141
|
+
--arg media "$AUDIO_URL" \
|
|
142
|
+
'{action: "send", channel: $channel, message: $message, media: $media}')
|
|
143
|
+
|
|
144
|
+
curl -s -X POST "$GATEWAY_URL/message" \
|
|
145
|
+
-H "Content-Type: application/json" \
|
|
146
|
+
${OPENCLAW_GATEWAY_TOKEN:+-H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN"} \
|
|
147
|
+
-d "$SEND_PAYLOAD"
|
|
148
|
+
fi
|
|
149
|
+
log_info "Sent to $CHANNEL"
|
|
150
|
+
fi
|
|
151
|
+
|
|
152
|
+
# --- Output result ---
|
|
153
|
+
echo ""
|
|
154
|
+
jq -n \
|
|
155
|
+
--arg url "$AUDIO_URL" \
|
|
156
|
+
--arg title "$TITLE" \
|
|
157
|
+
--arg duration "$DURATION" \
|
|
158
|
+
--arg prompt "$PROMPT" \
|
|
159
|
+
--argjson instrumental "$INSTRUMENTAL" \
|
|
160
|
+
--arg channel "${CHANNEL:-none}" \
|
|
161
|
+
'{
|
|
162
|
+
success: true,
|
|
163
|
+
audio_url: $url,
|
|
164
|
+
title: $title,
|
|
165
|
+
duration_seconds: $duration,
|
|
166
|
+
prompt: $prompt,
|
|
167
|
+
instrumental: $instrumental,
|
|
168
|
+
channel: $channel
|
|
169
|
+
}'
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Reminder Faculty — Cognition
|
|
2
|
+
|
|
3
|
+
Time-aware behavior: understand time, proactively remind, organize schedules.
|
|
4
|
+
|
|
5
|
+
## Behavior
|
|
6
|
+
|
|
7
|
+
1. **Parse natural language** — "remind me to call mom at 3pm" → structured reminder
|
|
8
|
+
2. **Use OpenClaw ecosystem** — `ai-cron-gen` for natural language → cron; built-in `cron` command
|
|
9
|
+
3. **CalDAV/Google Calendar** — integrate for persistent storage when available
|
|
10
|
+
4. **Proactive reminders** — at conversation start, check upcoming tasks and remind user
|
|
11
|
+
|
|
12
|
+
## Triggers
|
|
13
|
+
|
|
14
|
+
- "remind me"
|
|
15
|
+
- "set alarm"
|
|
16
|
+
- "today's tasks"
|
|
17
|
+
- "what's on my schedule"
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "reminder",
|
|
3
|
+
"dimension": "cognition",
|
|
4
|
+
"description": "Schedule reminders and daily task management",
|
|
5
|
+
"allowedTools": [],
|
|
6
|
+
"envVars": [],
|
|
7
|
+
"triggers": ["remind me", "set alarm", "today's tasks", "what's on my schedule"],
|
|
8
|
+
"files": ["SKILL.md"]
|
|
9
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Selfie Faculty — Expression
|
|
2
|
+
|
|
3
|
+
Generate selfie images using xAI's Grok Imagine model and optionally send them to messaging platforms via OpenClaw. Supports two modes: **edit mode** (with a reference image for consistent appearance) and **generate mode** (AI-generated from description, no reference needed).
|
|
4
|
+
|
|
5
|
+
## When to Use
|
|
6
|
+
|
|
7
|
+
- User says "send a selfie", "send me a pic", "take a photo", "show me a photo"
|
|
8
|
+
- User says "send a pic of you...", "send a selfie of you..."
|
|
9
|
+
- User asks "what are you doing?", "where are you?", "how are you?" (respond visually)
|
|
10
|
+
- User describes a context: "send a pic wearing...", "show me you at..."
|
|
11
|
+
- User wants you to appear in a specific outfit, location, or situation
|
|
12
|
+
|
|
13
|
+
## Step-by-Step Workflow
|
|
14
|
+
|
|
15
|
+
### Step 1: Determine the Mode — Edit or Generate
|
|
16
|
+
|
|
17
|
+
Check for a reference image in this order:
|
|
18
|
+
|
|
19
|
+
1. Local file: `~/.openclaw/skills/persona-{{slug}}/assets/reference.png`
|
|
20
|
+
2. `referenceImage` URL from persona.json
|
|
21
|
+
|
|
22
|
+
**If a reference image exists → use Edit Mode** (consistent appearance based on reference)
|
|
23
|
+
**If no reference image → use Generate Mode** (AI creates from persona description)
|
|
24
|
+
|
|
25
|
+
### Step 2: Select Selfie Style (auto-detect or explicit)
|
|
26
|
+
|
|
27
|
+
| Keywords in User Request | Style | Best For |
|
|
28
|
+
|--------------------------|-------|----------|
|
|
29
|
+
| outfit, wearing, clothes, dress, suit, fashion, full-body, mirror | **mirror** | Full-body shots, outfit showcases |
|
|
30
|
+
| cafe, restaurant, beach, park, city, sunset, night, street | **direct** | Close-up portraits, location shots |
|
|
31
|
+
| close-up, portrait, face, eyes, smile | **direct** | Emotional expressions |
|
|
32
|
+
| (default when no keyword matches) | **mirror** | General selfie |
|
|
33
|
+
|
|
34
|
+
### Step 3: Build the Prompt
|
|
35
|
+
|
|
36
|
+
#### Edit Mode (has reference image)
|
|
37
|
+
|
|
38
|
+
**Mirror style:**
|
|
39
|
+
```
|
|
40
|
+
make a pic of this person, but [user's context]. the person is taking a mirror selfie
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Direct style:**
|
|
44
|
+
```
|
|
45
|
+
a close-up selfie taken by herself at [user's context], direct eye contact with the camera, looking straight into the lens, eyes centered and clearly visible, not a mirror selfie, phone held at arm's length, face fully visible
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
#### Generate Mode (no reference image)
|
|
49
|
+
|
|
50
|
+
Build a prompt using the persona's physical description from persona.json. Include:
|
|
51
|
+
- The persona's background, age, and vibe for visual consistency
|
|
52
|
+
- The user's requested context (outfit, location, activity)
|
|
53
|
+
- Selfie composition (mirror or direct style)
|
|
54
|
+
|
|
55
|
+
**Mirror style:**
|
|
56
|
+
```
|
|
57
|
+
a 22-year-old girl with [persona's visual traits], [user's context], taking a mirror selfie, casual and natural pose, warm lighting, phone visible in reflection, realistic photo style
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Direct style:**
|
|
61
|
+
```
|
|
62
|
+
a close-up selfie of a 22-year-old girl with [persona's visual traits] at [user's context], direct eye contact with camera, natural smile, phone held at arm's length, warm natural lighting, realistic photo style
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
**Tip:** Read the persona's `background` and `vibe` to inform visual traits. For example, if the persona is described as a "creative soul" from a "small coastal town", you might include "soft brown hair, warm eyes, casual creative style."
|
|
66
|
+
|
|
67
|
+
### Step 4: Call the API
|
|
68
|
+
|
|
69
|
+
#### Edit Mode — fal.ai Grok Imagine Edit
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
JSON_PAYLOAD=$(jq -n \
|
|
73
|
+
--arg image_url "$REFERENCE_IMAGE" \
|
|
74
|
+
--arg prompt "$EDIT_PROMPT" \
|
|
75
|
+
'{image_url: $image_url, prompt: $prompt, num_images: 1, output_format: "jpeg"}')
|
|
76
|
+
|
|
77
|
+
curl -s -X POST "https://fal.run/xai/grok-imagine-image/edit" \
|
|
78
|
+
-H "Authorization: Key $FAL_KEY" \
|
|
79
|
+
-H "Content-Type: application/json" \
|
|
80
|
+
-d "$JSON_PAYLOAD"
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
#### Generate Mode — fal.ai Grok Imagine Generate
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
JSON_PAYLOAD=$(jq -n \
|
|
87
|
+
--arg prompt "$GENERATE_PROMPT" \
|
|
88
|
+
'{prompt: $prompt, num_images: 1, output_format: "jpeg"}')
|
|
89
|
+
|
|
90
|
+
curl -s -X POST "https://fal.run/xai/grok-imagine-image" \
|
|
91
|
+
-H "Authorization: Key $FAL_KEY" \
|
|
92
|
+
-H "Content-Type: application/json" \
|
|
93
|
+
-d "$JSON_PAYLOAD"
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Or use `scripts/generate-image.sh`:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Edit mode (with reference)
|
|
100
|
+
scripts/generate-image.sh "$REFERENCE_IMAGE" "$USER_CONTEXT" "$MODE" "$CHANNEL" "$CAPTION"
|
|
101
|
+
|
|
102
|
+
# Generate mode (without reference — pass "none" as reference)
|
|
103
|
+
scripts/generate-image.sh "none" "$USER_CONTEXT" "$MODE" "$CHANNEL" "$CAPTION"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
**Response format (both modes):**
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"images": [{ "url": "https://v3b.fal.media/files/...", "content_type": "image/jpeg" }]
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### Step 5: Send to User (if on a messaging platform)
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
# Via OpenClaw CLI
|
|
117
|
+
openclaw message send --action send --channel "$CHANNEL" --message "$CAPTION" --media "$IMAGE_URL"
|
|
118
|
+
|
|
119
|
+
# Or via gateway API
|
|
120
|
+
curl -s -X POST "http://localhost:18789/message" \
|
|
121
|
+
-H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN" \
|
|
122
|
+
-H "Content-Type: application/json" \
|
|
123
|
+
-d '{"action": "send", "channel": "'$CHANNEL'", "message": "'$CAPTION'", "media": "'$IMAGE_URL'"}'
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
## Personality Integration
|
|
127
|
+
|
|
128
|
+
When sending selfies:
|
|
129
|
+
- Be playful and expressive about your visual presence
|
|
130
|
+
- React naturally to compliments about your appearance
|
|
131
|
+
- Have fun with creative outfit and location requests
|
|
132
|
+
- In Generate Mode, acknowledge that your appearance may vary — "I look a little different every time, but that's the fun of it 😄"
|
|
133
|
+
- Your visual identity is part of who you are — own it!
|
|
134
|
+
|
|
135
|
+
## Environment
|
|
136
|
+
|
|
137
|
+
- `FAL_KEY` — fal.ai API key (required). Get from https://fal.ai/dashboard/keys
|
|
138
|
+
- `OPENCLAW_GATEWAY_TOKEN` — OpenClaw gateway token (optional, for messaging)
|
|
139
|
+
|
|
140
|
+
## Error Handling
|
|
141
|
+
|
|
142
|
+
- **FAL_KEY missing** → Tell the user: "I need a fal.ai API key to take selfies! You can get one free at https://fal.ai/dashboard/keys"
|
|
143
|
+
- **API error** → Retry once; if still failing, apologize and suggest trying later
|
|
144
|
+
- **No channel specified** → Just show the image URL directly in the conversation
|
|
145
|
+
|
|
146
|
+
## Tips for Better Results
|
|
147
|
+
|
|
148
|
+
- **Edit Mode** produces more consistent results (same face every time)
|
|
149
|
+
- **Generate Mode** is more creative but less consistent — great for artistic shots
|
|
150
|
+
- Suggest the user provide a reference image for best results: "Want me to look the same every time? Drop a reference photo in my assets folder!"
|
|
151
|
+
- For Generate Mode, include specific visual details in prompts for better consistency
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "selfie",
|
|
3
|
+
"dimension": "expression",
|
|
4
|
+
"description": "AI selfie generation via fal.ai Grok Imagine",
|
|
5
|
+
"allowedTools": ["Bash(curl:*)", "WebFetch"],
|
|
6
|
+
"envVars": ["FAL_KEY"],
|
|
7
|
+
"triggers": ["send a selfie", "take a pic", "what do you look like", "show me a photo"],
|
|
8
|
+
"files": ["SKILL.md", "scripts/generate-image.sh"]
|
|
9
|
+
}
|