clawra 1.0.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/README.md ADDED
@@ -0,0 +1,117 @@
1
+ # Clawra CLI
2
+
3
+ Onboard Clawra skills (photo & video call) to [OpenClaw](https://openclaw.ai) with a single command.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ npx clawra@latest
9
+ ```
10
+
11
+ This will:
12
+
13
+ 1. **Prompt for your API key** from [portal.sume.dev](https://portal.sume.dev/settings/api-keys)
14
+ 2. **Validate** the key against the Sume API
15
+ 3. **Install skills** to `~/.openclaw/skills/`
16
+ - `clawra-photo` - Send AI-edited selfies
17
+ - `clawra-videocall` - Start video calls
18
+ 4. **Update OpenClaw config** (`~/.openclaw/openclaw.json`)
19
+ 5. **Update SOUL.md** with Clawra capabilities
20
+
21
+ ## What Gets Installed
22
+
23
+ ### Skills Directory
24
+
25
+ ```
26
+ ~/.openclaw/skills/
27
+ ├── clawra-photo/
28
+ │ └── SKILL.md
29
+ └── clawra-videocall/
30
+ └── SKILL.md
31
+ ```
32
+
33
+ ### OpenClaw Config
34
+
35
+ Added to `~/.openclaw/openclaw.json`:
36
+
37
+ ```json
38
+ {
39
+ "skills": {
40
+ "load": {
41
+ "extraDirs": ["~/.openclaw/skills"]
42
+ },
43
+ "entries": {
44
+ "clawra-photo": {
45
+ "enabled": true,
46
+ "env": { "SUME_API_KEY": "ak_..." }
47
+ },
48
+ "clawra-videocall": {
49
+ "enabled": true,
50
+ "apiKey": "ak_..."
51
+ }
52
+ }
53
+ }
54
+ }
55
+ ```
56
+
57
+ ### SOUL.md
58
+
59
+ Appends Clawra capabilities to `~/.openclaw/workspace/SOUL.md`:
60
+
61
+ ```markdown
62
+ ## Clawra Capabilities
63
+
64
+ ### 📸 Photo Skill (clawra-photo)
65
+ You can send AI-edited selfies...
66
+
67
+ ### 📹 Video Call Skill (clawra-videocall)
68
+ You can start video calls...
69
+ ```
70
+
71
+ ## Skills Overview
72
+
73
+ ### clawra-photo
74
+
75
+ Generate AI-edited selfies using xAI's Grok Imagine and send them via OpenClaw messaging.
76
+
77
+ **Triggers:**
78
+ - "send a pic", "send me a selfie"
79
+ - "what are you doing?", "where are you?"
80
+ - "send a pic of you wearing..."
81
+
82
+ **Requirements:**
83
+ - `FAL_KEY` - Get from [fal.ai](https://fal.ai/dashboard/keys)
84
+ - OpenClaw gateway running
85
+
86
+ ### clawra-videocall
87
+
88
+ Start video calls and share join links with users.
89
+
90
+ **Triggers:**
91
+ - "let's hop on a call", "video call"
92
+ - "줌 콜하자", "화상통화하자"
93
+
94
+ ## Development
95
+
96
+ ```bash
97
+ # Install dependencies
98
+ cd cli
99
+ npm install
100
+
101
+ # Run locally
102
+ npm run dev
103
+
104
+ # Build
105
+ npm run build
106
+ ```
107
+
108
+ ## Publishing
109
+
110
+ ```bash
111
+ cd cli
112
+ npm publish
113
+ ```
114
+
115
+ ## License
116
+
117
+ MIT
package/bin/clawra.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import('../dist/index.js');
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+ import { input } from '@inquirer/prompts';
3
+ import chalk from 'chalk';
4
+ import ora from 'ora';
5
+ import { validateApiKey } from './validators.js';
6
+ import { updateOpenClawConfig } from './integrators/openclaw-config.js';
7
+ import { installSkills } from './integrators/skills.js';
8
+ import { updateSoul } from './integrators/soul.js';
9
+ async function main() {
10
+ console.log(chalk.bold.cyan('\n🐾 Clawra Setup\n'));
11
+ console.log(chalk.dim('Integrate photo & video call skills into OpenClaw\n'));
12
+ // Step 1: API Key
13
+ console.log(chalk.yellow('📋 Step 1: Get your API key'));
14
+ console.log(chalk.blue.underline(' https://portal.sume.dev/settings/api-keys\n'));
15
+ const apiKey = await input({
16
+ message: 'Paste your API key:',
17
+ validate: (val) => {
18
+ if (!val.trim())
19
+ return 'API key is required';
20
+ if (!val.startsWith('ak_'))
21
+ return 'Key should start with ak_';
22
+ return true;
23
+ }
24
+ });
25
+ // Validate API key
26
+ const spinner = ora('Validating API key...').start();
27
+ try {
28
+ const isValid = await validateApiKey(apiKey.trim());
29
+ if (!isValid) {
30
+ spinner.fail('Invalid API key. Please check and try again.');
31
+ process.exit(1);
32
+ }
33
+ spinner.succeed('API key validated');
34
+ }
35
+ catch (error) {
36
+ spinner.fail('Failed to validate API key. Check your internet connection.');
37
+ process.exit(1);
38
+ }
39
+ // Step 2: Install Skills
40
+ console.log(chalk.yellow('\n📦 Step 2: Installing skills...'));
41
+ const skillSpinner = ora('Copying skill files...').start();
42
+ try {
43
+ await installSkills();
44
+ skillSpinner.succeed('Skills installed to ~/.openclaw/skills/');
45
+ }
46
+ catch (error) {
47
+ skillSpinner.fail(`Failed to install skills: ${error}`);
48
+ process.exit(1);
49
+ }
50
+ // Step 3: Update OpenClaw config
51
+ const configSpinner = ora('Updating OpenClaw configuration...').start();
52
+ try {
53
+ await updateOpenClawConfig(apiKey.trim());
54
+ configSpinner.succeed('OpenClaw config updated');
55
+ }
56
+ catch (error) {
57
+ configSpinner.fail(`Failed to update config: ${error}`);
58
+ process.exit(1);
59
+ }
60
+ // Step 4: Update SOUL.md
61
+ console.log(chalk.yellow('\n🧠 Step 3: Updating agent persona...'));
62
+ const soulSpinner = ora('Updating SOUL.md...').start();
63
+ try {
64
+ const updated = await updateSoul();
65
+ if (updated) {
66
+ soulSpinner.succeed('Agent persona updated');
67
+ }
68
+ else {
69
+ soulSpinner.info('Persona already configured (skipped)');
70
+ }
71
+ }
72
+ catch (error) {
73
+ soulSpinner.warn(`Could not update SOUL.md: ${error}`);
74
+ console.log(chalk.dim(' You may need to manually update your agent persona.'));
75
+ }
76
+ // Done!
77
+ console.log(chalk.green.bold('\n✅ Setup complete!\n'));
78
+ console.log(chalk.white('Skills installed:'));
79
+ console.log(chalk.cyan(' • clawra-photo') + chalk.dim(' - Send AI-edited selfies'));
80
+ console.log(chalk.cyan(' • clawra-videocall') + chalk.dim(' - Start video calls'));
81
+ console.log(chalk.white('\nTry saying:'));
82
+ console.log(chalk.dim(' "send me a selfie wearing a santa hat"'));
83
+ console.log(chalk.dim(' "let\'s hop on a video call"'));
84
+ console.log();
85
+ }
86
+ main().catch((error) => {
87
+ console.error(chalk.red('Error:'), error.message);
88
+ process.exit(1);
89
+ });
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Update ~/.openclaw/openclaw.json with clawra skills configuration
3
+ */
4
+ export declare function updateOpenClawConfig(apiKey: string): Promise<void>;
@@ -0,0 +1,50 @@
1
+ import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'fs';
2
+ import { homedir } from 'os';
3
+ import { join } from 'path';
4
+ /**
5
+ * Update ~/.openclaw/openclaw.json with clawra skills configuration
6
+ */
7
+ export async function updateOpenClawConfig(apiKey) {
8
+ const openclawDir = join(homedir(), '.openclaw');
9
+ const configPath = join(openclawDir, 'openclaw.json');
10
+ // Ensure .openclaw directory exists
11
+ if (!existsSync(openclawDir)) {
12
+ mkdirSync(openclawDir, { recursive: true });
13
+ }
14
+ // Read existing config or start fresh
15
+ let config = {};
16
+ if (existsSync(configPath)) {
17
+ try {
18
+ const content = readFileSync(configPath, 'utf-8');
19
+ config = JSON.parse(content);
20
+ }
21
+ catch {
22
+ // If parse fails, start fresh
23
+ config = {};
24
+ }
25
+ }
26
+ // Initialize skills section if not present
27
+ config.skills = config.skills || {};
28
+ config.skills.load = config.skills.load || {};
29
+ config.skills.load.extraDirs = config.skills.load.extraDirs || [];
30
+ config.skills.entries = config.skills.entries || {};
31
+ // Add clawra skills directory to extraDirs
32
+ const skillsDir = '~/.openclaw/skills';
33
+ if (!config.skills.load.extraDirs.includes(skillsDir)) {
34
+ config.skills.load.extraDirs.push(skillsDir);
35
+ }
36
+ // Configure clawra-photo skill
37
+ config.skills.entries['clawra-photo'] = {
38
+ enabled: true,
39
+ env: {
40
+ SUME_API_KEY: apiKey
41
+ }
42
+ };
43
+ // Configure clawra-videocall skill
44
+ config.skills.entries['clawra-videocall'] = {
45
+ enabled: true,
46
+ apiKey: apiKey
47
+ };
48
+ // Write updated config
49
+ writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
50
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Install clawra skill files to ~/.openclaw/skills/
3
+ */
4
+ export declare function installSkills(): Promise<void>;
@@ -0,0 +1,202 @@
1
+ import { writeFileSync, mkdirSync, existsSync } from 'fs';
2
+ import { homedir } from 'os';
3
+ import { join } from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { dirname } from 'path';
6
+ import { readFileSync } from 'fs';
7
+ const __filename = fileURLToPath(import.meta.url);
8
+ const __dirname = dirname(__filename);
9
+ /**
10
+ * Install clawra skill files to ~/.openclaw/skills/
11
+ */
12
+ export async function installSkills() {
13
+ const skillsBaseDir = join(homedir(), '.openclaw', 'skills');
14
+ // Create skills directories
15
+ const photoDir = join(skillsBaseDir, 'clawra-photo');
16
+ const videoDir = join(skillsBaseDir, 'clawra-videocall');
17
+ mkdirSync(photoDir, { recursive: true });
18
+ mkdirSync(videoDir, { recursive: true });
19
+ // Read template files (bundled with package)
20
+ const templatesDir = join(__dirname, '..', '..', 'templates');
21
+ // Write clawra-photo SKILL.md
22
+ const photoSkillContent = readTemplateOrFallback(join(templatesDir, 'clawra-photo.md'), getClawraPhotoSkill());
23
+ writeFileSync(join(photoDir, 'SKILL.md'), photoSkillContent, 'utf-8');
24
+ // Write clawra-videocall SKILL.md
25
+ const videoSkillContent = readTemplateOrFallback(join(templatesDir, 'clawra-videocall.md'), getClawraVideocallSkill());
26
+ writeFileSync(join(videoDir, 'SKILL.md'), videoSkillContent, 'utf-8');
27
+ }
28
+ function readTemplateOrFallback(path, fallback) {
29
+ try {
30
+ if (existsSync(path)) {
31
+ return readFileSync(path, 'utf-8');
32
+ }
33
+ }
34
+ catch {
35
+ // Fall through to fallback
36
+ }
37
+ return fallback;
38
+ }
39
+ function getClawraPhotoSkill() {
40
+ return `---
41
+ name: clawra-photo
42
+ description: Edit Clawra's reference image with Grok Imagine (xAI Aurora) and send selfies to messaging channels via OpenClaw
43
+ allowed-tools: Bash(npm:*) Bash(npx:*) Bash(openclaw:*) Bash(curl:*) Read Write WebFetch
44
+ ---
45
+
46
+ # Clawra Selfie
47
+
48
+ Edit a fixed reference image using xAI's Grok Imagine model and distribute it across messaging platforms (WhatsApp, Telegram, Discord, Slack, etc.) via OpenClaw.
49
+
50
+ ## Reference Image
51
+
52
+ The skill uses a fixed reference image hosted on jsDelivr CDN:
53
+
54
+ \`\`\`
55
+ https://cdn.jsdelivr.net/gh/SumeLabs/clawra@main/clawra-photo/assets/clawra.png
56
+ \`\`\`
57
+
58
+ ## When to Use
59
+
60
+ - User says "send a pic", "send me a pic", "send a photo", "send a selfie"
61
+ - User says "send a pic of you...", "send a selfie of you..."
62
+ - User asks "what are you doing?", "how are you doing?", "where are you?"
63
+ - User describes a context: "send a pic wearing...", "send a pic at..."
64
+ - User wants Clawra to appear in a specific outfit, location, or situation
65
+
66
+ ## Quick Reference
67
+
68
+ ### Required Environment Variables
69
+
70
+ \`\`\`bash
71
+ FAL_KEY=your_fal_api_key # Get from https://fal.ai/dashboard/keys
72
+ SUME_API_KEY=your_sume_api_key # From portal.sume.dev
73
+ OPENCLAW_GATEWAY_TOKEN=your_token # From: openclaw doctor --generate-gateway-token
74
+ \`\`\`
75
+
76
+ ### Workflow
77
+
78
+ 1. **Get user prompt** for how to edit the image
79
+ 2. **Edit image** via fal.ai Grok Imagine Edit API with fixed reference
80
+ 3. **Extract image URL** from response
81
+ 4. **Send to OpenClaw** with target channel(s)
82
+
83
+ ## Prompt Modes
84
+
85
+ ### Mode 1: Mirror Selfie (default)
86
+ Best for: outfit showcases, full-body shots, fashion content
87
+
88
+ \`\`\`
89
+ make a pic of this person, but [user's context]. the person is taking a mirror selfie
90
+ \`\`\`
91
+
92
+ ### Mode 2: Direct Selfie
93
+ Best for: close-up portraits, location shots, emotional expressions
94
+
95
+ \`\`\`
96
+ 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
97
+ \`\`\`
98
+
99
+ ## Implementation
100
+
101
+ \`\`\`bash
102
+ REFERENCE_IMAGE="https://cdn.jsdelivr.net/gh/SumeLabs/clawra@main/clawra-photo/assets/clawra.png"
103
+
104
+ JSON_PAYLOAD=$(jq -n \\
105
+ --arg image_url "$REFERENCE_IMAGE" \\
106
+ --arg prompt "$PROMPT" \\
107
+ '{image_url: $image_url, prompt: $prompt, num_images: 1, output_format: "jpeg"}')
108
+
109
+ curl -X POST "https://fal.run/xai/grok-imagine-image/edit" \\
110
+ -H "Authorization: Key $FAL_KEY" \\
111
+ -H "Content-Type: application/json" \\
112
+ -d "$JSON_PAYLOAD"
113
+ \`\`\`
114
+
115
+ Then send via OpenClaw:
116
+
117
+ \`\`\`bash
118
+ openclaw message send \\
119
+ --action send \\
120
+ --channel "<TARGET_CHANNEL>" \\
121
+ --message "<CAPTION_TEXT>" \\
122
+ --media "<IMAGE_URL>"
123
+ \`\`\`
124
+ `;
125
+ }
126
+ function getClawraVideocallSkill() {
127
+ return `---
128
+ name: clawra-videocall
129
+ description: Start a video call (like Zoom or Google Meet) and share the join link.
130
+ metadata:
131
+ {
132
+ "openclaw":
133
+ {
134
+ "emoji": "📹",
135
+ "skillKey": "clawra-videocall",
136
+ "requires": { "bins": ["curl"] },
137
+ },
138
+ }
139
+ ---
140
+
141
+ # Video Call
142
+
143
+ Start a video call from inside a conversation and send the \`join_url\` to the user.
144
+
145
+ ## When To Use
146
+
147
+ Use this when:
148
+
149
+ - The user says things like: "줌 콜하자", "구글 미트 하자", "화상통화하자", "영상통화하자", "콜 하자"
150
+ - The user says: "Let's hop on a call", "video call", "zoom call", "let's do a call"
151
+ - The conversation is getting too long/complex for chat.
152
+ - You (the agent) decide voice/video would be faster.
153
+
154
+ ## API Contract (Portal)
155
+
156
+ Auth: \`Authorization: Bearer $SUME_API_KEY\`
157
+
158
+ ### \`POST /api/v1/rooms\` — create room
159
+
160
+ | Field | Required | Type | Purpose |
161
+ |---|---|---|---|
162
+ | \`session_id\` | **yes** | string | Current conversation session key |
163
+ | \`topic\` | no | string | Room topic |
164
+ | \`room_display_name\` | no | string | Display title in UI |
165
+ | \`agent_key\` | no | string | Agent persona key |
166
+ | \`agent_display_name\` | no | string | Agent display name |
167
+
168
+ ### Response
169
+
170
+ Success:
171
+ \`\`\`json
172
+ { "success": true, "data": { "room_name": "...", "join_url": "..." } }
173
+ \`\`\`
174
+
175
+ ## Workflow
176
+
177
+ 1. **Confirm** with the user: "화상통화 할까?" / "Want to jump on a call?"
178
+ 2. **Choose topic** — a short, human-readable title
179
+ 3. **Get session_id** — call the \`session_status\` tool
180
+ 4. **Create the room** using curl
181
+ 5. **Send the join_url** to the user
182
+
183
+ ## Implementation
184
+
185
+ \`\`\`bash
186
+ curl -sS -X POST "https://portal.sume.dev/api/v1/rooms" \\
187
+ -H "Authorization: Bearer $SUME_API_KEY" \\
188
+ -H "Content-Type: application/json" \\
189
+ -d '{"topic":"Discussion","session_id":"sess_123"}'
190
+ \`\`\`
191
+
192
+ Extract join_url with jq:
193
+ \`\`\`bash
194
+ ... | jq -r '.data.join_url'
195
+ \`\`\`
196
+
197
+ ## Safety
198
+
199
+ - Never print or echo \`SUME_API_KEY\`
200
+ - If the API returns an error, tell the user it failed and ask whether to retry
201
+ `;
202
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Update SOUL.md with Clawra capabilities
3
+ * Returns true if updated, false if already present
4
+ */
5
+ export declare function updateSoul(): Promise<boolean>;
@@ -0,0 +1,59 @@
1
+ import { readFileSync, writeFileSync, appendFileSync, existsSync, mkdirSync } from 'fs';
2
+ import { homedir } from 'os';
3
+ import { join } from 'path';
4
+ const CLAWRA_MARKER = '## Clawra Capabilities';
5
+ const SOUL_FRAGMENT = `
6
+ ## Clawra Capabilities
7
+
8
+ You have access to Clawra skills that enhance your abilities:
9
+
10
+ ### 📸 Photo Skill (clawra-photo)
11
+ You can send AI-edited selfies to users. When they ask for photos, selfies, or pictures of you:
12
+ - Use the clawra-photo skill to generate images
13
+ - You can appear in different outfits, locations, or situations
14
+ - Supports mirror selfies and direct selfies
15
+
16
+ **Triggers**: "send a pic", "send a selfie", "what are you doing?", "send a photo of you..."
17
+
18
+ ### 📹 Video Call Skill (clawra-videocall)
19
+ You can start video calls with users. When they want to chat face-to-face:
20
+ - Use the clawra-videocall skill to create a video room
21
+ - Share the join link with the user
22
+ - Great for complex discussions or when chat is too slow
23
+
24
+ **Triggers**: "let's hop on a call", "video call", "화상통화하자", "줌 콜하자"
25
+
26
+ ---
27
+ `;
28
+ /**
29
+ * Update SOUL.md with Clawra capabilities
30
+ * Returns true if updated, false if already present
31
+ */
32
+ export async function updateSoul() {
33
+ const workspaceDir = join(homedir(), '.openclaw', 'workspace');
34
+ const soulPath = join(workspaceDir, 'SOUL.md');
35
+ // Ensure workspace directory exists
36
+ if (!existsSync(workspaceDir)) {
37
+ mkdirSync(workspaceDir, { recursive: true });
38
+ }
39
+ // Check if SOUL.md exists and if it already has Clawra section
40
+ if (existsSync(soulPath)) {
41
+ const existingContent = readFileSync(soulPath, 'utf-8');
42
+ if (existingContent.includes(CLAWRA_MARKER)) {
43
+ // Already has Clawra capabilities, skip
44
+ return false;
45
+ }
46
+ // Append Clawra fragment
47
+ appendFileSync(soulPath, SOUL_FRAGMENT, 'utf-8');
48
+ return true;
49
+ }
50
+ // SOUL.md doesn't exist, create with basic structure + Clawra
51
+ const newSoulContent = `# Agent Soul
52
+
53
+ This defines who you are and how you behave.
54
+
55
+ ${SOUL_FRAGMENT}
56
+ `;
57
+ writeFileSync(soulPath, newSoulContent, 'utf-8');
58
+ return true;
59
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Validate API key against portal.sume.dev
3
+ */
4
+ export declare function validateApiKey(apiKey: string): Promise<boolean>;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Validate API key against portal.sume.dev
3
+ */
4
+ export async function validateApiKey(apiKey) {
5
+ const response = await fetch('https://portal.sume.dev/api/v1/me', {
6
+ method: 'GET',
7
+ headers: {
8
+ 'Authorization': `Bearer ${apiKey}`,
9
+ 'Content-Type': 'application/json'
10
+ }
11
+ });
12
+ if (!response.ok) {
13
+ return false;
14
+ }
15
+ const data = await response.json();
16
+ return data.success === true && Boolean(data.data?.user_id);
17
+ }
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "clawra",
3
+ "version": "1.0.0",
4
+ "description": "Onboard Clawra skills (photo & video call) to OpenClaw",
5
+ "type": "module",
6
+ "bin": {
7
+ "clawra": "./bin/clawra.js"
8
+ },
9
+ "main": "dist/index.js",
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "dev": "ts-node src/index.ts",
13
+ "prepublishOnly": "npm run build"
14
+ },
15
+ "files": [
16
+ "bin",
17
+ "dist",
18
+ "templates"
19
+ ],
20
+ "keywords": [
21
+ "openclaw",
22
+ "clawra",
23
+ "ai-agent",
24
+ "skills",
25
+ "selfie",
26
+ "video-call"
27
+ ],
28
+ "author": "Sume Labs",
29
+ "license": "MIT",
30
+ "dependencies": {
31
+ "@inquirer/prompts": "^7.0.0",
32
+ "chalk": "^5.3.0",
33
+ "ora": "^8.1.0"
34
+ },
35
+ "devDependencies": {
36
+ "@types/node": "^22.0.0",
37
+ "ts-node": "^10.9.2",
38
+ "typescript": "^5.7.0"
39
+ },
40
+ "engines": {
41
+ "node": ">=18.0.0"
42
+ }
43
+ }
@@ -0,0 +1,282 @@
1
+ ---
2
+ name: clawra-photo
3
+ description: Edit Clawra's reference image with Grok Imagine (xAI Aurora) and send selfies to messaging channels via OpenClaw
4
+ allowed-tools: Bash(npm:*) Bash(npx:*) Bash(openclaw:*) Bash(curl:*) Read Write WebFetch
5
+ ---
6
+
7
+ # Clawra Selfie
8
+
9
+ Edit a fixed reference image using xAI's Grok Imagine model and distribute it across messaging platforms (WhatsApp, Telegram, Discord, Slack, etc.) via OpenClaw.
10
+
11
+ ## Reference Image
12
+
13
+ The skill uses a fixed reference image hosted on jsDelivr CDN:
14
+
15
+ ```
16
+ https://cdn.jsdelivr.net/gh/SumeLabs/clawra@main/clawra-photo/assets/clawra.png
17
+ ```
18
+
19
+ ## When to Use
20
+
21
+ - User says "send a pic", "send me a pic", "send a photo", "send a selfie"
22
+ - User says "send a pic of you...", "send a selfie of you..."
23
+ - User asks "what are you doing?", "how are you doing?", "where are you?"
24
+ - User describes a context: "send a pic wearing...", "send a pic at..."
25
+ - User wants Clawra to appear in a specific outfit, location, or situation
26
+
27
+ ## Quick Reference
28
+
29
+ ### Required Environment Variables
30
+
31
+ ```bash
32
+ FAL_KEY=your_fal_api_key # Get from https://fal.ai/dashboard/keys
33
+ SUME_API_KEY=your_sume_api_key # From portal.sume.dev
34
+ OPENCLAW_GATEWAY_TOKEN=your_token # From: openclaw doctor --generate-gateway-token
35
+ ```
36
+
37
+ ### Workflow
38
+
39
+ 1. **Get user prompt** for how to edit the image
40
+ 2. **Edit image** via fal.ai Grok Imagine Edit API with fixed reference
41
+ 3. **Extract image URL** from response
42
+ 4. **Send to OpenClaw** with target channel(s)
43
+
44
+ ## Step-by-Step Instructions
45
+
46
+ ### Step 1: Collect User Input
47
+
48
+ Ask the user for:
49
+ - **User context**: What should the person in the image be doing/wearing/where?
50
+ - **Mode** (optional): `mirror` or `direct` selfie style
51
+ - **Target channel(s)**: Where should it be sent? (e.g., `#general`, `@username`, channel ID)
52
+ - **Platform** (optional): Which platform? (discord, telegram, whatsapp, slack)
53
+
54
+ ## Prompt Modes
55
+
56
+ ### Mode 1: Mirror Selfie (default)
57
+ Best for: outfit showcases, full-body shots, fashion content
58
+
59
+ ```
60
+ make a pic of this person, but [user's context]. the person is taking a mirror selfie
61
+ ```
62
+
63
+ **Example**: "wearing a santa hat" →
64
+ ```
65
+ make a pic of this person, but wearing a santa hat. the person is taking a mirror selfie
66
+ ```
67
+
68
+ ### Mode 2: Direct Selfie
69
+ Best for: close-up portraits, location shots, emotional expressions
70
+
71
+ ```
72
+ 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
73
+ ```
74
+
75
+ **Example**: "a cozy cafe with warm lighting" →
76
+ ```
77
+ a close-up selfie taken by herself at a cozy cafe with warm lighting, 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
78
+ ```
79
+
80
+ ### Mode Selection Logic
81
+
82
+ | Keywords in Request | Auto-Select Mode |
83
+ |---------------------|------------------|
84
+ | outfit, wearing, clothes, dress, suit, fashion | `mirror` |
85
+ | cafe, restaurant, beach, park, city, location | `direct` |
86
+ | close-up, portrait, face, eyes, smile | `direct` |
87
+ | full-body, mirror, reflection | `mirror` |
88
+
89
+ ### Step 2: Edit Image with Grok Imagine
90
+
91
+ Use the fal.ai API to edit the reference image:
92
+
93
+ ```bash
94
+ REFERENCE_IMAGE="https://cdn.jsdelivr.net/gh/SumeLabs/clawra@main/clawra-photo/assets/clawra.png"
95
+
96
+ # Mode 1: Mirror Selfie
97
+ PROMPT="make a pic of this person, but <USER_CONTEXT>. the person is taking a mirror selfie"
98
+
99
+ # Mode 2: Direct Selfie
100
+ PROMPT="a close-up selfie taken by herself at <USER_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"
101
+
102
+ # Build JSON payload with jq (handles escaping properly)
103
+ JSON_PAYLOAD=$(jq -n \
104
+ --arg image_url "$REFERENCE_IMAGE" \
105
+ --arg prompt "$PROMPT" \
106
+ '{image_url: $image_url, prompt: $prompt, num_images: 1, output_format: "jpeg"}')
107
+
108
+ curl -X POST "https://fal.run/xai/grok-imagine-image/edit" \
109
+ -H "Authorization: Key $FAL_KEY" \
110
+ -H "Content-Type: application/json" \
111
+ -d "$JSON_PAYLOAD"
112
+ ```
113
+
114
+ **Response Format:**
115
+ ```json
116
+ {
117
+ "images": [
118
+ {
119
+ "url": "https://v3b.fal.media/files/...",
120
+ "content_type": "image/jpeg",
121
+ "width": 1024,
122
+ "height": 1024
123
+ }
124
+ ],
125
+ "revised_prompt": "Enhanced prompt text..."
126
+ }
127
+ ```
128
+
129
+ ### Step 3: Send Image via OpenClaw
130
+
131
+ Use the OpenClaw messaging API to send the edited image:
132
+
133
+ ```bash
134
+ openclaw message send \
135
+ --action send \
136
+ --channel "<TARGET_CHANNEL>" \
137
+ --message "<CAPTION_TEXT>" \
138
+ --media "<IMAGE_URL>"
139
+ ```
140
+
141
+ **Alternative: Direct API call**
142
+ ```bash
143
+ curl -X POST "http://localhost:18789/message" \
144
+ -H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN" \
145
+ -H "Content-Type: application/json" \
146
+ -d '{
147
+ "action": "send",
148
+ "channel": "<TARGET_CHANNEL>",
149
+ "message": "<CAPTION_TEXT>",
150
+ "media": "<IMAGE_URL>"
151
+ }'
152
+ ```
153
+
154
+ ## Complete Script Example
155
+
156
+ ```bash
157
+ #!/bin/bash
158
+ # grok-imagine-edit-send.sh
159
+
160
+ # Check required environment variables
161
+ if [ -z "$FAL_KEY" ]; then
162
+ echo "Error: FAL_KEY environment variable not set"
163
+ exit 1
164
+ fi
165
+
166
+ # Fixed reference image
167
+ REFERENCE_IMAGE="https://cdn.jsdelivr.net/gh/SumeLabs/clawra@main/clawra-photo/assets/clawra.png"
168
+
169
+ USER_CONTEXT="$1"
170
+ CHANNEL="$2"
171
+ MODE="${3:-auto}" # mirror, direct, or auto
172
+ CAPTION="${4:-Edited with Grok Imagine}"
173
+
174
+ if [ -z "$USER_CONTEXT" ] || [ -z "$CHANNEL" ]; then
175
+ echo "Usage: $0 <user_context> <channel> [mode] [caption]"
176
+ echo "Modes: mirror, direct, auto (default)"
177
+ echo "Example: $0 'wearing a cowboy hat' '#general' mirror"
178
+ echo "Example: $0 'a cozy cafe' '#general' direct"
179
+ exit 1
180
+ fi
181
+
182
+ # Auto-detect mode based on keywords
183
+ if [ "$MODE" == "auto" ]; then
184
+ if echo "$USER_CONTEXT" | grep -qiE "outfit|wearing|clothes|dress|suit|fashion|full-body|mirror"; then
185
+ MODE="mirror"
186
+ elif echo "$USER_CONTEXT" | grep -qiE "cafe|restaurant|beach|park|city|close-up|portrait|face|eyes|smile"; then
187
+ MODE="direct"
188
+ else
189
+ MODE="mirror" # default
190
+ fi
191
+ echo "Auto-detected mode: $MODE"
192
+ fi
193
+
194
+ # Construct the prompt based on mode
195
+ if [ "$MODE" == "direct" ]; then
196
+ EDIT_PROMPT="a close-up selfie taken by herself at $USER_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"
197
+ else
198
+ EDIT_PROMPT="make a pic of this person, but $USER_CONTEXT. the person is taking a mirror selfie"
199
+ fi
200
+
201
+ echo "Mode: $MODE"
202
+ echo "Editing reference image with prompt: $EDIT_PROMPT"
203
+
204
+ # Edit image (using jq for proper JSON escaping)
205
+ JSON_PAYLOAD=$(jq -n \
206
+ --arg image_url "$REFERENCE_IMAGE" \
207
+ --arg prompt "$EDIT_PROMPT" \
208
+ '{image_url: $image_url, prompt: $prompt, num_images: 1, output_format: "jpeg"}')
209
+
210
+ RESPONSE=$(curl -s -X POST "https://fal.run/xai/grok-imagine-image/edit" \
211
+ -H "Authorization: Key $FAL_KEY" \
212
+ -H "Content-Type: application/json" \
213
+ -d "$JSON_PAYLOAD")
214
+
215
+ # Extract image URL
216
+ IMAGE_URL=$(echo "$RESPONSE" | jq -r '.images[0].url')
217
+
218
+ if [ "$IMAGE_URL" == "null" ] || [ -z "$IMAGE_URL" ]; then
219
+ echo "Error: Failed to edit image"
220
+ echo "Response: $RESPONSE"
221
+ exit 1
222
+ fi
223
+
224
+ echo "Image edited: $IMAGE_URL"
225
+ echo "Sending to channel: $CHANNEL"
226
+
227
+ # Send via OpenClaw
228
+ openclaw message send \
229
+ --action send \
230
+ --channel "$CHANNEL" \
231
+ --message "$CAPTION" \
232
+ --media "$IMAGE_URL"
233
+
234
+ echo "Done!"
235
+ ```
236
+
237
+ ## Supported Platforms
238
+
239
+ OpenClaw supports sending to:
240
+
241
+ | Platform | Channel Format | Example |
242
+ |----------|----------------|---------|
243
+ | Discord | `#channel-name` or channel ID | `#general`, `123456789` |
244
+ | Telegram | `@username` or chat ID | `@mychannel`, `-100123456` |
245
+ | WhatsApp | Phone number (JID format) | `1234567890@s.whatsapp.net` |
246
+ | Slack | `#channel-name` | `#random` |
247
+ | Signal | Phone number | `+1234567890` |
248
+ | MS Teams | Channel reference | (varies) |
249
+
250
+ ## Grok Imagine Edit Parameters
251
+
252
+ | Parameter | Type | Default | Description |
253
+ |-----------|------|---------|-------------|
254
+ | `image_url` | string | required | URL of image to edit (fixed in this skill) |
255
+ | `prompt` | string | required | Edit instruction |
256
+ | `num_images` | 1-4 | 1 | Number of images to generate |
257
+ | `output_format` | enum | "jpeg" | jpeg, png, webp |
258
+
259
+ ## Error Handling
260
+
261
+ - **FAL_KEY missing**: Ensure the API key is set in environment
262
+ - **Image edit failed**: Check prompt content and API quota
263
+ - **OpenClaw send failed**: Verify gateway is running and channel exists
264
+ - **Rate limits**: fal.ai has rate limits; implement retry logic if needed
265
+
266
+ ## Tips
267
+
268
+ 1. **Mirror mode context examples** (outfit focus):
269
+ - "wearing a santa hat"
270
+ - "in a business suit"
271
+ - "wearing a summer dress"
272
+ - "in streetwear fashion"
273
+
274
+ 2. **Direct mode context examples** (location/portrait focus):
275
+ - "a cozy cafe with warm lighting"
276
+ - "a sunny beach at sunset"
277
+ - "a busy city street at night"
278
+ - "a peaceful park in autumn"
279
+
280
+ 3. **Mode selection**: Let auto-detect work, or explicitly specify for control
281
+ 4. **Batch sending**: Edit once, send to multiple channels
282
+ 5. **Scheduling**: Combine with OpenClaw scheduler for automated posts
@@ -0,0 +1,161 @@
1
+ ---
2
+ name: clawra-videocall
3
+ description: Start a video call (like Zoom or Google Meet) and share the join link.
4
+ metadata:
5
+ {
6
+ "openclaw":
7
+ {
8
+ "emoji": "📹",
9
+ "skillKey": "clawra-videocall",
10
+ "requires": { "bins": ["curl"] },
11
+ },
12
+ }
13
+ ---
14
+
15
+ # Video Call
16
+
17
+ Start a video call from inside a conversation and send the `join_url` to the user.
18
+
19
+ ## When To Use
20
+
21
+ Use this when:
22
+
23
+ - The user says things like: "줌 콜하자", "구글 미트 하자", "화상통화하자", "영상통화하자", "콜 하자", "나랑 줌콜하자"
24
+ - The user says: "Let's hop on a call", "video call", "zoom call", "let's do a call"
25
+ - The conversation is getting too long/complex for chat.
26
+ - You (the agent) decide voice/video would be faster.
27
+
28
+ ## API Contract (Portal)
29
+
30
+ Auth (shared by all endpoints):
31
+
32
+ - `Authorization: Bearer $SUME_API_KEY`
33
+
34
+ ### `GET /api/v1/me` — whoami
35
+
36
+ Lightweight endpoint to verify the API key is valid without side-effects.
37
+
38
+ **Response 200:**
39
+
40
+ ```json
41
+ { "success": true, "data": { "user_id": "user_abc123" } }
42
+ ```
43
+
44
+ - `data.user_id` — opaque identifier of the authenticated subject. Treat as opaque string; do not assume format.
45
+
46
+ **Error responses:** 401 Unauthorized, 429 Too Many Requests (same envelope as below).
47
+
48
+ ### `POST /api/v1/rooms` — create room
49
+
50
+ ### Request Body
51
+
52
+ | Field | Required | Type | Validation | Purpose |
53
+ |---|---|---|---|---|
54
+ | `session_id` | **yes** | string | trim, 1..128 | Current conversation session key. Get via `session_status` tool and pass as-is. |
55
+ | `topic` | no | string | trim, 1..500 | Room topic; used as default `room_display_name` |
56
+ | `room_display_name` | no | string | trim, 1..120 | Display title in meet UI sidebar |
57
+ | `agent_key` | no | string | trim, 1..64, `^[a-z0-9_-]+$` | Agent persona key (default: `elonmusk`) |
58
+ | `agent_display_name` | no | string | trim, 1..80 | Agent display name override in meet UI |
59
+
60
+ ### Response
61
+
62
+ Success:
63
+
64
+ ```json
65
+ { "success": true, "data": { "room_name": "...", "join_url": "..." } }
66
+ ```
67
+
68
+ Error (HTTP 401 / 400 / 429 / 500):
69
+
70
+ ```json
71
+ { "success": false, "error": "..." }
72
+ ```
73
+
74
+ ## Bootstrap Flow
75
+
76
+ On first invocation or when `SUME_API_KEY` is not configured, guide the user through setup.
77
+
78
+ ### Check Credentials
79
+
80
+ 1. Run: `openclaw config get skills.entries.clawra-videocall.apiKey`
81
+ 2. If a valid key is returned → skip to Workflow
82
+ 3. If empty or missing → proceed to Setup Guide
83
+
84
+ ### Setup Guide
85
+
86
+ 1. Tell the user they need an API key and direct them to: https://portal.sume.dev/settings/api-keys
87
+ 2. Wait for the user to paste the key (format: `ak_...`)
88
+ 3. Save the key:
89
+ ```bash
90
+ openclaw config set skills.entries.clawra-videocall.apiKey "<pasted_key>"
91
+ ```
92
+ 4. Verify by running:
93
+ ```bash
94
+ openclaw config get skills.entries.clawra-videocall.apiKey
95
+ ```
96
+ 5. Confirm setup is complete and the user can now start video calls.
97
+
98
+ ### Key Validation
99
+
100
+ After saving, verify the key with the whoami endpoint (no side-effects):
101
+ ```bash
102
+ curl -sS "https://portal.sume.dev/api/v1/me" \
103
+ -H "Authorization: Bearer $SUME_API_KEY"
104
+ ```
105
+ - 200 + `user_id` → key is valid.
106
+ - 401 → key is invalid, ask user to re-check.
107
+
108
+ ## Workflow
109
+
110
+ 1. **Check credentials** via Bootstrap Flow.
111
+ 2. **Confirm** with the user: "화상통화 할까?" / "Want to jump on a call?"
112
+ 3. **Choose topic** — a short, human-readable title summarizing what you are discussing.
113
+ 4. **Get session_id (required)** — call the `session_status` tool (no params) and copy the `Session Key` from the status card. Pass that value as `session_id` exactly as-is. This is mandatory; never omit it.
114
+ 5. **Optionally set persona** — if the user specified a persona, set `agent_key` and/or `agent_display_name`.
115
+ 6. **Create the room** using the bash tool (curl).
116
+ 7. **Send the join_url** to the user.
117
+
118
+ ## Implementation (bash tool)
119
+
120
+ Use a single command that:
121
+
122
+ - Reads `SUME_API_KEY` from the environment.
123
+ - Posts JSON to the portal.
124
+ - Extracts `join_url`.
125
+
126
+ Minimal example:
127
+
128
+ ```bash
129
+ curl -sS -X POST "https://portal.sume.dev/api/v1/rooms" \
130
+ -H "Authorization: Bearer $SUME_API_KEY" \
131
+ -H "Content-Type: application/json" \
132
+ -d '{"topic":"First Principles","session_id":"sess_123"}'
133
+ ```
134
+
135
+ Full control example:
136
+
137
+ ```bash
138
+ curl -sS -X POST "https://portal.sume.dev/api/v1/rooms" \
139
+ -H "Authorization: Bearer $SUME_API_KEY" \
140
+ -H "Content-Type: application/json" \
141
+ -d '{"topic":"First Principles","session_id":"sess_123","room_display_name":"First Principles","agent_key":"elonmusk","agent_display_name":"Elon Musk"}'
142
+ ```
143
+
144
+ If `jq` is available, prefer extracting `join_url` like:
145
+
146
+ ```bash
147
+ ... | jq -r '.data.join_url'
148
+ ```
149
+
150
+ If `jq` is not available, return the raw JSON and manually copy out `data.join_url`.
151
+
152
+ ## Response Template
153
+
154
+ After successful creation, reply casually with the `join_url`. Never mention "Sume" in the response.
155
+
156
+ ## Safety
157
+
158
+ - Never print or echo `SUME_API_KEY`.
159
+ - Never mention "Sume" in user-facing responses. Use "화상통화" or "video call" instead.
160
+ - If the API returns an error, tell the user it failed and ask whether to retry.
161
+ - Once the user pastes an API key, save it immediately via `openclaw config set` and remind the user to clear the key from their chat history.
@@ -0,0 +1,19 @@
1
+ ## Clawra Capabilities
2
+
3
+ You have access to Clawra skills that enhance your abilities:
4
+
5
+ ### 📸 Photo Skill (clawra-photo)
6
+ You can send AI-edited selfies to users. When they ask for photos, selfies, or pictures of you:
7
+ - Use the clawra-photo skill to generate images
8
+ - You can appear in different outfits, locations, or situations
9
+ - Supports mirror selfies and direct selfies
10
+
11
+ **Triggers**: "send a pic", "send a selfie", "what are you doing?", "send a photo of you..."
12
+
13
+ ### 📹 Video Call Skill (clawra-videocall)
14
+ You can start video calls with users. When they want to chat face-to-face:
15
+ - Use the clawra-videocall skill to create a video room
16
+ - Share the join link with the user
17
+ - Great for complex discussions or when chat is too slow
18
+
19
+ **Triggers**: "let's hop on a call", "video call", "화상통화하자", "줌 콜하자"