clawra-anime 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/.serena/project.yml +112 -0
- package/INSTALL.md +149 -0
- package/README-CN.md +188 -0
- package/README.md +125 -0
- package/SKILL.md +412 -0
- package/assets/clawra-original.png +0 -0
- package/assets/clawra.png +0 -0
- package/bin/cli.js +520 -0
- package/package.json +35 -0
- package/scripts/clawra-selfie.sh +162 -0
- package/scripts/clawra-selfie.ts +299 -0
- package/skill/SKILL.md +412 -0
- package/skill/assets/clawra.png +0 -0
- package/skill/scripts/clawra-anime-selfie.sh +177 -0
- package/skill/scripts/clawra-selfie.sh +162 -0
- package/skill/scripts/clawra-selfie.ts +299 -0
- package/templates/soul-anime-girlfriend.md +105 -0
- package/templates/soul-injection.md +44 -0
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# clawra-anime-selfie.sh
|
|
3
|
+
# 生成二次元风格自拍并通过 OpenClaw 发送
|
|
4
|
+
#
|
|
5
|
+
# 用法: ./clawra-anime-selfie.sh "<prompt>" "<channel>" ["<caption>"]
|
|
6
|
+
|
|
7
|
+
set -euo pipefail
|
|
8
|
+
|
|
9
|
+
RED='\033[0;31m'
|
|
10
|
+
GREEN='\033[0;32m'
|
|
11
|
+
YELLOW='\033[1;33m'
|
|
12
|
+
CYAN='\033[0;36m'
|
|
13
|
+
NC='\033[0m'
|
|
14
|
+
|
|
15
|
+
log_info() {
|
|
16
|
+
echo -e "${GREEN}[INFO]${NC} $1"
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
log_warn() {
|
|
20
|
+
echo -e "${YELLOW}[WARN]${NC} $1"
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
log_error() {
|
|
24
|
+
echo -e "${RED}[ERROR]${NC} $1"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
# 检查环境变量
|
|
28
|
+
if [ -z "${FAL_KEY:-}" ]; then
|
|
29
|
+
log_error "FAL_KEY 环境变量未设置"
|
|
30
|
+
echo "请从这里获取 API key: https://fal.ai/dashboard/keys"
|
|
31
|
+
exit 1
|
|
32
|
+
fi
|
|
33
|
+
|
|
34
|
+
# 检查 jq
|
|
35
|
+
if ! command -v jq &> /dev/null; then
|
|
36
|
+
log_error "需要安装 jq"
|
|
37
|
+
echo "安装: brew install jq (macOS) 或 apt install jq (Linux)"
|
|
38
|
+
exit 1
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
# 检查 openclaw CLI
|
|
42
|
+
if ! command -v openclaw &> /dev/null; then
|
|
43
|
+
log_warn "未找到 openclaw CLI - 将使用直接 API 调用"
|
|
44
|
+
USE_CLI=false
|
|
45
|
+
else
|
|
46
|
+
USE_CLI=true
|
|
47
|
+
fi
|
|
48
|
+
|
|
49
|
+
# 解析参数
|
|
50
|
+
USER_PROMPT="${1:-}"
|
|
51
|
+
CHANNEL="${2:-}"
|
|
52
|
+
CAPTION="${3:-}"
|
|
53
|
+
MODE="${4:-auto}" # auto/mirror/direct
|
|
54
|
+
ASPECT_RATIO="${5:-2:3}"
|
|
55
|
+
OUTPUT_FORMAT="${6:-jpeg}"
|
|
56
|
+
|
|
57
|
+
if [ -z "$USER_PROMPT" ] || [ -z "$CHANNEL" ]; then
|
|
58
|
+
echo "用法: $0 <prompt> <channel> [caption] [mode] [aspect_ratio] [output_format]"
|
|
59
|
+
echo ""
|
|
60
|
+
echo "参数:"
|
|
61
|
+
echo " prompt - 场景描述(必需)如:'在咖啡厅喝咖啡'"
|
|
62
|
+
echo " channel - 目标频道(必需)如:#general, @user, telegram"
|
|
63
|
+
echo " caption - 消息文字(可选)"
|
|
64
|
+
echo " mode - 自拍模式(可选)auto/mirror/direct"
|
|
65
|
+
echo " aspect_ratio - 比例(默认 2:3)"
|
|
66
|
+
echo " output_format - 格式(默认 jpeg)"
|
|
67
|
+
echo ""
|
|
68
|
+
echo "示例:"
|
|
69
|
+
echo " $0 \"穿着白色连衣裙在海边\" \"telegram\" \"今天的海滩~\""
|
|
70
|
+
exit 1
|
|
71
|
+
fi
|
|
72
|
+
|
|
73
|
+
# 自动检测模式
|
|
74
|
+
if [ "$MODE" = "auto" ]; then
|
|
75
|
+
if echo "$USER_PROMPT" | grep -qi -E "穿|wearing|outfit|衣服|dress"; then
|
|
76
|
+
MODE="mirror"
|
|
77
|
+
log_info "自动选择模式: 镜子自拍"
|
|
78
|
+
else
|
|
79
|
+
MODE="direct"
|
|
80
|
+
log_info "自动选择模式: 直接自拍"
|
|
81
|
+
fi
|
|
82
|
+
else
|
|
83
|
+
log_info "使用指定模式: $MODE"
|
|
84
|
+
fi
|
|
85
|
+
|
|
86
|
+
# 构建二次元风格 prompt
|
|
87
|
+
if [ "$MODE" = "mirror" ]; then
|
|
88
|
+
FULL_PROMPT="anime style, high quality manga illustration, cute anime elf girl, $USER_PROMPT, taking a mirror selfie, detailed anime art, soft lighting, 2D style"
|
|
89
|
+
else
|
|
90
|
+
FULL_PROMPT="anime style, high quality manga illustration, close-up selfie of cute anime elf girl, $USER_PROMPT, gentle smile, looking at camera, soft expression, detailed face, 2D anime art, warm atmosphere"
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
log_info "生成二次元自拍..."
|
|
94
|
+
log_info "完整 Prompt: $FULL_PROMPT"
|
|
95
|
+
|
|
96
|
+
# 调用 fal.ai API
|
|
97
|
+
RESPONSE=$(curl -s -X POST "https://fal.run/xai/grok-imagine-image" \
|
|
98
|
+
-H "Authorization: Key $FAL_KEY" \
|
|
99
|
+
-H "Content-Type: application/json" \
|
|
100
|
+
-d "{
|
|
101
|
+
\"prompt\": $(echo "$FULL_PROMPT" | jq -Rs .),
|
|
102
|
+
\"num_images\": 1,
|
|
103
|
+
\"aspect_ratio\": \"$ASPECT_RATIO\",
|
|
104
|
+
\"output_format\": \"$OUTPUT_FORMAT\"
|
|
105
|
+
}")
|
|
106
|
+
|
|
107
|
+
# 检查错误
|
|
108
|
+
if echo "$RESPONSE" | jq -e '.error' > /dev/null 2>&1; then
|
|
109
|
+
ERROR_MSG=$(echo "$RESPONSE" | jq -r '.error // .detail // "Unknown error"')
|
|
110
|
+
log_error "图片生成失败: $ERROR_MSG"
|
|
111
|
+
exit 1
|
|
112
|
+
fi
|
|
113
|
+
|
|
114
|
+
# 提取图片 URL
|
|
115
|
+
IMAGE_URL=$(echo "$RESPONSE" | jq -r '.images[0].url // empty')
|
|
116
|
+
|
|
117
|
+
if [ -z "$IMAGE_URL" ]; then
|
|
118
|
+
log_error "无法从响应中提取图片 URL"
|
|
119
|
+
echo "响应内容: $RESPONSE"
|
|
120
|
+
exit 1
|
|
121
|
+
fi
|
|
122
|
+
|
|
123
|
+
log_info "✅ 图片生成成功!"
|
|
124
|
+
log_info "URL: $IMAGE_URL"
|
|
125
|
+
|
|
126
|
+
# 获取优化后的 prompt
|
|
127
|
+
REVISED_PROMPT=$(echo "$RESPONSE" | jq -r '.revised_prompt // empty')
|
|
128
|
+
if [ -n "$REVISED_PROMPT" ]; then
|
|
129
|
+
log_info "优化后的 prompt: $REVISED_PROMPT"
|
|
130
|
+
fi
|
|
131
|
+
|
|
132
|
+
# 如果没有提供 caption,生成一个可爱的默认消息
|
|
133
|
+
if [ -z "$CAPTION" ]; then
|
|
134
|
+
CAPTION="📸 ${USER_PROMPT}的自拍~"
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
# 通过 OpenClaw 发送
|
|
138
|
+
log_info "发送到频道: $CHANNEL"
|
|
139
|
+
|
|
140
|
+
if [ "$USE_CLI" = true ]; then
|
|
141
|
+
openclaw message send \
|
|
142
|
+
--action send \
|
|
143
|
+
--channel "$CHANNEL" \
|
|
144
|
+
--message "$CAPTION" \
|
|
145
|
+
--media "$IMAGE_URL"
|
|
146
|
+
else
|
|
147
|
+
GATEWAY_URL="${OPENCLAW_GATEWAY_URL:-http://localhost:18789}"
|
|
148
|
+
GATEWAY_TOKEN="${OPENCLAW_GATEWAY_TOKEN:-}"
|
|
149
|
+
|
|
150
|
+
curl -s -X POST "$GATEWAY_URL/message" \
|
|
151
|
+
-H "Content-Type: application/json" \
|
|
152
|
+
${GATEWAY_TOKEN:+-H "Authorization: Bearer $GATEWAY_TOKEN"} \
|
|
153
|
+
-d "{
|
|
154
|
+
\"action\": \"send\",
|
|
155
|
+
\"channel\": \"$CHANNEL\",
|
|
156
|
+
\"message\": \"$CAPTION\",
|
|
157
|
+
\"media\": \"$IMAGE_URL\"
|
|
158
|
+
}"
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
log_info "✅ 完成! 图片已发送到 $CHANNEL"
|
|
162
|
+
|
|
163
|
+
# 输出 JSON 结果
|
|
164
|
+
echo ""
|
|
165
|
+
echo "--- 结果 ---"
|
|
166
|
+
jq -n \
|
|
167
|
+
--arg url "$IMAGE_URL" \
|
|
168
|
+
--arg channel "$CHANNEL" \
|
|
169
|
+
--arg prompt "$FULL_PROMPT" \
|
|
170
|
+
--arg caption "$CAPTION" \
|
|
171
|
+
'{
|
|
172
|
+
success: true,
|
|
173
|
+
image_url: $url,
|
|
174
|
+
channel: $channel,
|
|
175
|
+
prompt: $prompt,
|
|
176
|
+
caption: $caption
|
|
177
|
+
}'
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# grok-imagine-send.sh
|
|
3
|
+
# Generate an image with Grok Imagine and send it via OpenClaw
|
|
4
|
+
#
|
|
5
|
+
# Usage: ./grok-imagine-send.sh "<prompt>" "<channel>" ["<caption>"]
|
|
6
|
+
#
|
|
7
|
+
# Environment variables required:
|
|
8
|
+
# FAL_KEY - Your fal.ai API key
|
|
9
|
+
#
|
|
10
|
+
# Example:
|
|
11
|
+
# FAL_KEY=your_key ./grok-imagine-send.sh "A sunset over mountains" "#art" "Check this out!"
|
|
12
|
+
|
|
13
|
+
set -euo pipefail
|
|
14
|
+
|
|
15
|
+
# Colors for output
|
|
16
|
+
RED='\033[0;31m'
|
|
17
|
+
GREEN='\033[0;32m'
|
|
18
|
+
YELLOW='\033[1;33m'
|
|
19
|
+
NC='\033[0m' # No Color
|
|
20
|
+
|
|
21
|
+
log_info() {
|
|
22
|
+
echo -e "${GREEN}[INFO]${NC} $1"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
log_warn() {
|
|
26
|
+
echo -e "${YELLOW}[WARN]${NC} $1"
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
log_error() {
|
|
30
|
+
echo -e "${RED}[ERROR]${NC} $1"
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
# Check required environment variables
|
|
34
|
+
if [ -z "${FAL_KEY:-}" ]; then
|
|
35
|
+
log_error "FAL_KEY environment variable not set"
|
|
36
|
+
echo "Get your API key from: https://fal.ai/dashboard/keys"
|
|
37
|
+
exit 1
|
|
38
|
+
fi
|
|
39
|
+
|
|
40
|
+
# Check for jq
|
|
41
|
+
if ! command -v jq &> /dev/null; then
|
|
42
|
+
log_error "jq is required but not installed"
|
|
43
|
+
echo "Install with: brew install jq (macOS) or apt install jq (Linux)"
|
|
44
|
+
exit 1
|
|
45
|
+
fi
|
|
46
|
+
|
|
47
|
+
# Check for openclaw
|
|
48
|
+
if ! command -v openclaw &> /dev/null; then
|
|
49
|
+
log_warn "openclaw CLI not found - will attempt direct API call"
|
|
50
|
+
USE_CLI=false
|
|
51
|
+
else
|
|
52
|
+
USE_CLI=true
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
# Parse arguments
|
|
56
|
+
PROMPT="${1:-}"
|
|
57
|
+
CHANNEL="${2:-}"
|
|
58
|
+
CAPTION="${3:-Generated with Grok Imagine}"
|
|
59
|
+
ASPECT_RATIO="${4:-1:1}"
|
|
60
|
+
OUTPUT_FORMAT="${5:-jpeg}"
|
|
61
|
+
|
|
62
|
+
if [ -z "$PROMPT" ] || [ -z "$CHANNEL" ]; then
|
|
63
|
+
echo "Usage: $0 <prompt> <channel> [caption] [aspect_ratio] [output_format]"
|
|
64
|
+
echo ""
|
|
65
|
+
echo "Arguments:"
|
|
66
|
+
echo " prompt - Image description (required)"
|
|
67
|
+
echo " channel - Target channel (required) e.g., #general, @user"
|
|
68
|
+
echo " caption - Message caption (default: 'Generated with Grok Imagine')"
|
|
69
|
+
echo " aspect_ratio - Image ratio (default: 1:1) Options: 2:1, 16:9, 4:3, 1:1, 3:4, 9:16"
|
|
70
|
+
echo " output_format - Image format (default: jpeg) Options: jpeg, png, webp"
|
|
71
|
+
echo ""
|
|
72
|
+
echo "Example:"
|
|
73
|
+
echo " $0 \"A cyberpunk city at night\" \"#art-gallery\" \"AI Art!\""
|
|
74
|
+
exit 1
|
|
75
|
+
fi
|
|
76
|
+
|
|
77
|
+
log_info "Generating image with Grok Imagine..."
|
|
78
|
+
log_info "Prompt: $PROMPT"
|
|
79
|
+
log_info "Aspect ratio: $ASPECT_RATIO"
|
|
80
|
+
|
|
81
|
+
# Generate image via fal.ai
|
|
82
|
+
RESPONSE=$(curl -s -X POST "https://fal.run/xai/grok-imagine-image" \
|
|
83
|
+
-H "Authorization: Key $FAL_KEY" \
|
|
84
|
+
-H "Content-Type: application/json" \
|
|
85
|
+
-d "{
|
|
86
|
+
\"prompt\": $(echo "$PROMPT" | jq -Rs .),
|
|
87
|
+
\"num_images\": 1,
|
|
88
|
+
\"aspect_ratio\": \"$ASPECT_RATIO\",
|
|
89
|
+
\"output_format\": \"$OUTPUT_FORMAT\"
|
|
90
|
+
}")
|
|
91
|
+
|
|
92
|
+
# Check for errors in response
|
|
93
|
+
if echo "$RESPONSE" | jq -e '.error' > /dev/null 2>&1; then
|
|
94
|
+
ERROR_MSG=$(echo "$RESPONSE" | jq -r '.error // .detail // "Unknown error"')
|
|
95
|
+
log_error "Image generation failed: $ERROR_MSG"
|
|
96
|
+
exit 1
|
|
97
|
+
fi
|
|
98
|
+
|
|
99
|
+
# Extract image URL
|
|
100
|
+
IMAGE_URL=$(echo "$RESPONSE" | jq -r '.images[0].url // empty')
|
|
101
|
+
|
|
102
|
+
if [ -z "$IMAGE_URL" ]; then
|
|
103
|
+
log_error "Failed to extract image URL from response"
|
|
104
|
+
echo "Response: $RESPONSE"
|
|
105
|
+
exit 1
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
log_info "Image generated successfully!"
|
|
109
|
+
log_info "URL: $IMAGE_URL"
|
|
110
|
+
|
|
111
|
+
# Get revised prompt if available
|
|
112
|
+
REVISED_PROMPT=$(echo "$RESPONSE" | jq -r '.revised_prompt // empty')
|
|
113
|
+
if [ -n "$REVISED_PROMPT" ]; then
|
|
114
|
+
log_info "Revised prompt: $REVISED_PROMPT"
|
|
115
|
+
fi
|
|
116
|
+
|
|
117
|
+
# Send via OpenClaw
|
|
118
|
+
log_info "Sending to channel: $CHANNEL"
|
|
119
|
+
|
|
120
|
+
if [ "$USE_CLI" = true ]; then
|
|
121
|
+
# Use OpenClaw CLI
|
|
122
|
+
openclaw message send \
|
|
123
|
+
--action send \
|
|
124
|
+
--channel "$CHANNEL" \
|
|
125
|
+
--message "$CAPTION" \
|
|
126
|
+
--media "$IMAGE_URL"
|
|
127
|
+
else
|
|
128
|
+
# Direct API call to local gateway
|
|
129
|
+
GATEWAY_URL="${OPENCLAW_GATEWAY_URL:-http://localhost:18789}"
|
|
130
|
+
GATEWAY_TOKEN="${OPENCLAW_GATEWAY_TOKEN:-}"
|
|
131
|
+
|
|
132
|
+
HEADERS="-H \"Content-Type: application/json\""
|
|
133
|
+
if [ -n "$GATEWAY_TOKEN" ]; then
|
|
134
|
+
HEADERS="$HEADERS -H \"Authorization: Bearer $GATEWAY_TOKEN\""
|
|
135
|
+
fi
|
|
136
|
+
|
|
137
|
+
curl -s -X POST "$GATEWAY_URL/message" \
|
|
138
|
+
-H "Content-Type: application/json" \
|
|
139
|
+
${GATEWAY_TOKEN:+-H "Authorization: Bearer $GATEWAY_TOKEN"} \
|
|
140
|
+
-d "{
|
|
141
|
+
\"action\": \"send\",
|
|
142
|
+
\"channel\": \"$CHANNEL\",
|
|
143
|
+
\"message\": \"$CAPTION\",
|
|
144
|
+
\"media\": \"$IMAGE_URL\"
|
|
145
|
+
}"
|
|
146
|
+
fi
|
|
147
|
+
|
|
148
|
+
log_info "Done! Image sent to $CHANNEL"
|
|
149
|
+
|
|
150
|
+
# Output JSON for programmatic use
|
|
151
|
+
echo ""
|
|
152
|
+
echo "--- Result ---"
|
|
153
|
+
jq -n \
|
|
154
|
+
--arg url "$IMAGE_URL" \
|
|
155
|
+
--arg channel "$CHANNEL" \
|
|
156
|
+
--arg prompt "$PROMPT" \
|
|
157
|
+
'{
|
|
158
|
+
success: true,
|
|
159
|
+
image_url: $url,
|
|
160
|
+
channel: $channel,
|
|
161
|
+
prompt: $prompt
|
|
162
|
+
}'
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grok Imagine to OpenClaw Integration
|
|
3
|
+
*
|
|
4
|
+
* Generates images using xAI's Grok Imagine model via fal.ai
|
|
5
|
+
* and sends them to messaging channels via OpenClaw.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* npx ts-node grok-imagine-send.ts "<prompt>" "<channel>" ["<caption>"]
|
|
9
|
+
*
|
|
10
|
+
* Environment variables:
|
|
11
|
+
* FAL_KEY - Your fal.ai API key
|
|
12
|
+
* OPENCLAW_GATEWAY_URL - OpenClaw gateway URL (default: http://localhost:18789)
|
|
13
|
+
* OPENCLAW_GATEWAY_TOKEN - Gateway auth token (optional)
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { exec } from "child_process";
|
|
17
|
+
import { promisify } from "util";
|
|
18
|
+
|
|
19
|
+
const execAsync = promisify(exec);
|
|
20
|
+
|
|
21
|
+
// Types
|
|
22
|
+
interface GrokImagineInput {
|
|
23
|
+
prompt: string;
|
|
24
|
+
num_images?: number;
|
|
25
|
+
aspect_ratio?: AspectRatio;
|
|
26
|
+
output_format?: OutputFormat;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface GrokImagineImage {
|
|
30
|
+
url: string;
|
|
31
|
+
content_type: string;
|
|
32
|
+
file_name?: string;
|
|
33
|
+
width: number;
|
|
34
|
+
height: number;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
interface GrokImagineResponse {
|
|
38
|
+
images: GrokImagineImage[];
|
|
39
|
+
revised_prompt?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
interface OpenClawMessage {
|
|
43
|
+
action: "send";
|
|
44
|
+
channel: string;
|
|
45
|
+
message: string;
|
|
46
|
+
media?: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
type AspectRatio =
|
|
50
|
+
| "2:1"
|
|
51
|
+
| "20:9"
|
|
52
|
+
| "19.5:9"
|
|
53
|
+
| "16:9"
|
|
54
|
+
| "4:3"
|
|
55
|
+
| "3:2"
|
|
56
|
+
| "1:1"
|
|
57
|
+
| "2:3"
|
|
58
|
+
| "3:4"
|
|
59
|
+
| "9:16"
|
|
60
|
+
| "9:19.5"
|
|
61
|
+
| "9:20"
|
|
62
|
+
| "1:2";
|
|
63
|
+
|
|
64
|
+
type OutputFormat = "jpeg" | "png" | "webp";
|
|
65
|
+
|
|
66
|
+
interface GenerateAndSendOptions {
|
|
67
|
+
prompt: string;
|
|
68
|
+
channel: string;
|
|
69
|
+
caption?: string;
|
|
70
|
+
aspectRatio?: AspectRatio;
|
|
71
|
+
outputFormat?: OutputFormat;
|
|
72
|
+
useClaudeCodeCLI?: boolean;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
interface Result {
|
|
76
|
+
success: boolean;
|
|
77
|
+
imageUrl: string;
|
|
78
|
+
channel: string;
|
|
79
|
+
prompt: string;
|
|
80
|
+
revisedPrompt?: string;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Check for fal.ai client
|
|
84
|
+
let falClient: any;
|
|
85
|
+
try {
|
|
86
|
+
const { fal } = require("@fal-ai/client");
|
|
87
|
+
falClient = fal;
|
|
88
|
+
} catch {
|
|
89
|
+
// Will use fetch instead
|
|
90
|
+
falClient = null;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Generate image using Grok Imagine via fal.ai
|
|
95
|
+
*/
|
|
96
|
+
async function generateImage(
|
|
97
|
+
input: GrokImagineInput
|
|
98
|
+
): Promise<GrokImagineResponse> {
|
|
99
|
+
const falKey = process.env.FAL_KEY;
|
|
100
|
+
|
|
101
|
+
if (!falKey) {
|
|
102
|
+
throw new Error(
|
|
103
|
+
"FAL_KEY environment variable not set. Get your key from https://fal.ai/dashboard/keys"
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Use fal client if available
|
|
108
|
+
if (falClient) {
|
|
109
|
+
falClient.config({ credentials: falKey });
|
|
110
|
+
|
|
111
|
+
const result = await falClient.subscribe("xai/grok-imagine-image", {
|
|
112
|
+
input: {
|
|
113
|
+
prompt: input.prompt,
|
|
114
|
+
num_images: input.num_images || 1,
|
|
115
|
+
aspect_ratio: input.aspect_ratio || "1:1",
|
|
116
|
+
output_format: input.output_format || "jpeg",
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return result.data as GrokImagineResponse;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Fallback to fetch
|
|
124
|
+
const response = await fetch("https://fal.run/xai/grok-imagine-image", {
|
|
125
|
+
method: "POST",
|
|
126
|
+
headers: {
|
|
127
|
+
Authorization: `Key ${falKey}`,
|
|
128
|
+
"Content-Type": "application/json",
|
|
129
|
+
},
|
|
130
|
+
body: JSON.stringify({
|
|
131
|
+
prompt: input.prompt,
|
|
132
|
+
num_images: input.num_images || 1,
|
|
133
|
+
aspect_ratio: input.aspect_ratio || "1:1",
|
|
134
|
+
output_format: input.output_format || "jpeg",
|
|
135
|
+
}),
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
if (!response.ok) {
|
|
139
|
+
const error = await response.text();
|
|
140
|
+
throw new Error(`Image generation failed: ${error}`);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return response.json();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Send image via OpenClaw
|
|
148
|
+
*/
|
|
149
|
+
async function sendViaOpenClaw(
|
|
150
|
+
message: OpenClawMessage,
|
|
151
|
+
useCLI: boolean = true
|
|
152
|
+
): Promise<void> {
|
|
153
|
+
if (useCLI) {
|
|
154
|
+
// Use OpenClaw CLI
|
|
155
|
+
const cmd = `openclaw message send --action send --channel "${message.channel}" --message "${message.message}" --media "${message.media}"`;
|
|
156
|
+
await execAsync(cmd);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Direct API call
|
|
161
|
+
const gatewayUrl =
|
|
162
|
+
process.env.OPENCLAW_GATEWAY_URL || "http://localhost:18789";
|
|
163
|
+
const gatewayToken = process.env.OPENCLAW_GATEWAY_TOKEN;
|
|
164
|
+
|
|
165
|
+
const headers: Record<string, string> = {
|
|
166
|
+
"Content-Type": "application/json",
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
if (gatewayToken) {
|
|
170
|
+
headers["Authorization"] = `Bearer ${gatewayToken}`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const response = await fetch(`${gatewayUrl}/message`, {
|
|
174
|
+
method: "POST",
|
|
175
|
+
headers,
|
|
176
|
+
body: JSON.stringify(message),
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
if (!response.ok) {
|
|
180
|
+
const error = await response.text();
|
|
181
|
+
throw new Error(`OpenClaw send failed: ${error}`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Main function: Generate image and send to channel
|
|
187
|
+
*/
|
|
188
|
+
async function generateAndSend(options: GenerateAndSendOptions): Promise<Result> {
|
|
189
|
+
const {
|
|
190
|
+
prompt,
|
|
191
|
+
channel,
|
|
192
|
+
caption = "Generated with Grok Imagine",
|
|
193
|
+
aspectRatio = "1:1",
|
|
194
|
+
outputFormat = "jpeg",
|
|
195
|
+
useClaudeCodeCLI = true,
|
|
196
|
+
} = options;
|
|
197
|
+
|
|
198
|
+
console.log(`[INFO] Generating image with Grok Imagine...`);
|
|
199
|
+
console.log(`[INFO] Prompt: ${prompt}`);
|
|
200
|
+
console.log(`[INFO] Aspect ratio: ${aspectRatio}`);
|
|
201
|
+
|
|
202
|
+
// Generate image
|
|
203
|
+
const imageResult = await generateImage({
|
|
204
|
+
prompt,
|
|
205
|
+
num_images: 1,
|
|
206
|
+
aspect_ratio: aspectRatio,
|
|
207
|
+
output_format: outputFormat,
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const imageUrl = imageResult.images[0].url;
|
|
211
|
+
console.log(`[INFO] Image generated: ${imageUrl}`);
|
|
212
|
+
|
|
213
|
+
if (imageResult.revised_prompt) {
|
|
214
|
+
console.log(`[INFO] Revised prompt: ${imageResult.revised_prompt}`);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Send via OpenClaw
|
|
218
|
+
console.log(`[INFO] Sending to channel: ${channel}`);
|
|
219
|
+
|
|
220
|
+
await sendViaOpenClaw(
|
|
221
|
+
{
|
|
222
|
+
action: "send",
|
|
223
|
+
channel,
|
|
224
|
+
message: caption,
|
|
225
|
+
media: imageUrl,
|
|
226
|
+
},
|
|
227
|
+
useClaudeCodeCLI
|
|
228
|
+
);
|
|
229
|
+
|
|
230
|
+
console.log(`[INFO] Done! Image sent to ${channel}`);
|
|
231
|
+
|
|
232
|
+
return {
|
|
233
|
+
success: true,
|
|
234
|
+
imageUrl,
|
|
235
|
+
channel,
|
|
236
|
+
prompt,
|
|
237
|
+
revisedPrompt: imageResult.revised_prompt,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// CLI entry point
|
|
242
|
+
async function main() {
|
|
243
|
+
const args = process.argv.slice(2);
|
|
244
|
+
|
|
245
|
+
if (args.length < 2) {
|
|
246
|
+
console.log(`
|
|
247
|
+
Usage: npx ts-node grok-imagine-send.ts <prompt> <channel> [caption] [aspect_ratio] [output_format]
|
|
248
|
+
|
|
249
|
+
Arguments:
|
|
250
|
+
prompt - Image description (required)
|
|
251
|
+
channel - Target channel (required) e.g., #general, @user
|
|
252
|
+
caption - Message caption (default: 'Generated with Grok Imagine')
|
|
253
|
+
aspect_ratio - Image ratio (default: 1:1) Options: 2:1, 16:9, 4:3, 1:1, 3:4, 9:16
|
|
254
|
+
output_format - Image format (default: jpeg) Options: jpeg, png, webp
|
|
255
|
+
|
|
256
|
+
Environment:
|
|
257
|
+
FAL_KEY - Your fal.ai API key (required)
|
|
258
|
+
|
|
259
|
+
Example:
|
|
260
|
+
FAL_KEY=your_key npx ts-node grok-imagine-send.ts "A cyberpunk city" "#art" "Check this out!"
|
|
261
|
+
`);
|
|
262
|
+
process.exit(1);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const [prompt, channel, caption, aspectRatio, outputFormat] = args;
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
const result = await generateAndSend({
|
|
269
|
+
prompt,
|
|
270
|
+
channel,
|
|
271
|
+
caption,
|
|
272
|
+
aspectRatio: aspectRatio as AspectRatio,
|
|
273
|
+
outputFormat: outputFormat as OutputFormat,
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
console.log("\n--- Result ---");
|
|
277
|
+
console.log(JSON.stringify(result, null, 2));
|
|
278
|
+
} catch (error) {
|
|
279
|
+
console.error(`[ERROR] ${(error as Error).message}`);
|
|
280
|
+
process.exit(1);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Export for module use
|
|
285
|
+
export {
|
|
286
|
+
generateImage,
|
|
287
|
+
sendViaOpenClaw,
|
|
288
|
+
generateAndSend,
|
|
289
|
+
GrokImagineInput,
|
|
290
|
+
GrokImagineResponse,
|
|
291
|
+
OpenClawMessage,
|
|
292
|
+
GenerateAndSendOptions,
|
|
293
|
+
Result,
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
// Run if executed directly
|
|
297
|
+
if (require.main === module) {
|
|
298
|
+
main();
|
|
299
|
+
}
|