callme-openclaw-plugin 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 +196 -0
- package/index.js +255 -0
- package/install.sh +65 -0
- package/package.json +62 -0
- package/setup.js +138 -0
package/README.md
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# š callme.ai OpenClaw Plugin
|
|
2
|
+
|
|
3
|
+
**Hands-free voice calls with your AI assistant - seamlessly integrated into OpenClaw**
|
|
4
|
+
|
|
5
|
+
Talk naturally with Commodus using voice. No buttons, no typing. Just speak.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## š Quick Start
|
|
10
|
+
|
|
11
|
+
### 1. Get Your API Key
|
|
12
|
+
|
|
13
|
+
Sign up at [callme.ai](https://callme.ai) and get your API key from the dashboard.
|
|
14
|
+
|
|
15
|
+
### 2. Install Plugin
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
cd ~/.openclaw/plugins
|
|
19
|
+
git clone https://github.com/Benedict-VC/openclaw-voice.git callme
|
|
20
|
+
cd callme/plugin-callme
|
|
21
|
+
npm install
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or via npm (when published):
|
|
25
|
+
```bash
|
|
26
|
+
npm install -g @callme/openclaw-plugin
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### 3. Configure OpenClaw
|
|
30
|
+
|
|
31
|
+
Add to your `~/.openclaw/config.yaml`:
|
|
32
|
+
|
|
33
|
+
```yaml
|
|
34
|
+
plugins:
|
|
35
|
+
callme:
|
|
36
|
+
enabled: true
|
|
37
|
+
apiKey: "callme_your_key_here" # Get from https://callme.ai/dashboard
|
|
38
|
+
defaultVoice: "nova" # optional: alloy, echo, fable, onyx, nova, shimmer
|
|
39
|
+
language: "en" # optional: en, de, es, fr
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### 4. Reload OpenClaw
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
openclaw reload
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### 5. Start Calling!
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
/call # Start a voice call
|
|
52
|
+
/call 5 # Call with 5-minute time limit
|
|
53
|
+
/voice-usage # Check your usage stats
|
|
54
|
+
/voice-keys # List your API keys
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## š Commands
|
|
60
|
+
|
|
61
|
+
| Command | Description |
|
|
62
|
+
|---------|-------------|
|
|
63
|
+
| `/call [minutes]` | Start a hands-free voice call |
|
|
64
|
+
| `/voice-setup` | Show setup instructions |
|
|
65
|
+
| `/voice-usage` | Display usage stats (minutes, cost, limits) |
|
|
66
|
+
| `/voice-keys` | List your API keys |
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## āļø Configuration Options
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
plugins:
|
|
74
|
+
callme:
|
|
75
|
+
# Required
|
|
76
|
+
apiKey: "callme_xxx" # Your callme.ai API key
|
|
77
|
+
|
|
78
|
+
# Optional
|
|
79
|
+
defaultVoice: "nova" # Voice: alloy, echo, fable, onyx, nova, shimmer
|
|
80
|
+
language: "en" # Language: en, de, es, fr
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## š° Pricing
|
|
86
|
+
|
|
87
|
+
- **Free:** 50 minutes/month
|
|
88
|
+
- **Pro:** 500 minutes/month - $19/month
|
|
89
|
+
- **Enterprise:** Unlimited - $99/month
|
|
90
|
+
|
|
91
|
+
View plans: [callme.ai/#pricing](https://callme.ai/#pricing)
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## š§ How It Works
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
You speak ā callme.ai (STT) ā OpenClaw Session ā Claude ā callme.ai (TTS) ā You hear
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
1. **Voice Activity Detection** - Automatically detects when you stop speaking
|
|
102
|
+
2. **Real-time Processing** - Sub-2-second response time
|
|
103
|
+
3. **Seamless Integration** - Uses your active OpenClaw session
|
|
104
|
+
4. **Usage Tracking** - Automatically tracked per API key
|
|
105
|
+
|
|
106
|
+
---
|
|
107
|
+
|
|
108
|
+
## š ļø Development
|
|
109
|
+
|
|
110
|
+
### Local Testing
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
cd plugin-callme
|
|
114
|
+
npm install
|
|
115
|
+
npm test
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Environment Variables
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
export CALLME_API_BASE="http://localhost:3000"
|
|
122
|
+
export CALLME_WS_BASE="ws://localhost:3000"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## š Examples
|
|
128
|
+
|
|
129
|
+
### Basic Call
|
|
130
|
+
```bash
|
|
131
|
+
/call
|
|
132
|
+
# Speaks naturally, conversation ends when you stop talking
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Time-Limited Call
|
|
136
|
+
```bash
|
|
137
|
+
/call 10
|
|
138
|
+
# Automatic cutoff after 10 minutes
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Check Usage
|
|
142
|
+
```bash
|
|
143
|
+
/voice-usage
|
|
144
|
+
# Shows: calls, minutes used, remaining quota, cost
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Manage Keys
|
|
148
|
+
```bash
|
|
149
|
+
/voice-keys
|
|
150
|
+
# Lists all your API keys with status and expiry
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## š Security
|
|
156
|
+
|
|
157
|
+
- API keys are stored securely in OpenClaw config
|
|
158
|
+
- Never log or expose keys in plain text
|
|
159
|
+
- All connections use TLS encryption
|
|
160
|
+
- Keys can be revoked anytime at dashboard
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## š Troubleshooting
|
|
165
|
+
|
|
166
|
+
### "API key not configured"
|
|
167
|
+
```bash
|
|
168
|
+
/voice-setup
|
|
169
|
+
# Follow the setup instructions
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### "Monthly limit reached"
|
|
173
|
+
Check usage:
|
|
174
|
+
```bash
|
|
175
|
+
/voice-usage
|
|
176
|
+
```
|
|
177
|
+
Upgrade plan at: https://callme.ai/dashboard
|
|
178
|
+
|
|
179
|
+
### "WebSocket connection failed"
|
|
180
|
+
1. Check your internet connection
|
|
181
|
+
2. Verify API key is valid
|
|
182
|
+
3. Check callme.ai status page
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## š Documentation
|
|
187
|
+
|
|
188
|
+
- **API Reference:** https://docs.callme.ai/api
|
|
189
|
+
- **OpenClaw Docs:** https://docs.openclaw.ai
|
|
190
|
+
- **Support:** https://discord.com/invite/clawd
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
## š License
|
|
195
|
+
|
|
196
|
+
MIT Ā© callme.ai
|
package/index.js
ADDED
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* callme.ai OpenClaw Plugin
|
|
3
|
+
* Hands-free voice calls with your AI assistant
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const WebSocket = require('ws');
|
|
7
|
+
const fetch = require('node-fetch');
|
|
8
|
+
|
|
9
|
+
const API_BASE = process.env.CALLME_API_BASE || 'https://callme.ai/api';
|
|
10
|
+
const WS_BASE = process.env.CALLME_WS_BASE || 'wss://callme.ai/ws';
|
|
11
|
+
|
|
12
|
+
class CallmePlugin {
|
|
13
|
+
constructor(config, openclaw) {
|
|
14
|
+
this.config = config;
|
|
15
|
+
this.openclaw = openclaw;
|
|
16
|
+
this.apiKey = config.apiKey;
|
|
17
|
+
|
|
18
|
+
if (!this.apiKey) {
|
|
19
|
+
throw new Error('callme.ai API key not configured. Run /voice-setup');
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Start a voice call
|
|
25
|
+
*/
|
|
26
|
+
async startCall(duration = null) {
|
|
27
|
+
if (!this.apiKey) {
|
|
28
|
+
return {
|
|
29
|
+
error: true,
|
|
30
|
+
message: 'ā API key not configured. Get yours at https://callme.ai/dashboard'
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
// Verify API key & get user info
|
|
36
|
+
const userResponse = await fetch(`${API_BASE}/auth/me`, {
|
|
37
|
+
headers: {
|
|
38
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
if (!userResponse.ok) {
|
|
43
|
+
throw new Error('Invalid API key');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const userData = await userResponse.json();
|
|
47
|
+
const user = userData.data.user;
|
|
48
|
+
|
|
49
|
+
// Check usage limits
|
|
50
|
+
const usageResponse = await fetch(`${API_BASE}/usage/current`, {
|
|
51
|
+
headers: {
|
|
52
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const usageData = await usageResponse.json();
|
|
57
|
+
const usage = usageData.data;
|
|
58
|
+
|
|
59
|
+
if (!usage.withinLimit) {
|
|
60
|
+
return {
|
|
61
|
+
error: true,
|
|
62
|
+
message: `ā Monthly limit reached (${usage.limit} minutes). Upgrade at https://callme.ai/dashboard`
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Connect to WebSocket
|
|
67
|
+
const ws = new WebSocket(WS_BASE, {
|
|
68
|
+
headers: {
|
|
69
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
return new Promise((resolve, reject) => {
|
|
74
|
+
ws.on('open', () => {
|
|
75
|
+
ws.send(JSON.stringify({
|
|
76
|
+
type: 'start_call',
|
|
77
|
+
voice: this.config.defaultVoice || 'nova',
|
|
78
|
+
language: this.config.language || 'en',
|
|
79
|
+
duration: duration
|
|
80
|
+
}));
|
|
81
|
+
|
|
82
|
+
resolve({
|
|
83
|
+
success: true,
|
|
84
|
+
message: 'š Call started! Speak naturally, I\'m listening...',
|
|
85
|
+
ws: ws,
|
|
86
|
+
user: user,
|
|
87
|
+
usage: usage
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
ws.on('error', (error) => {
|
|
92
|
+
reject(new Error(`WebSocket error: ${error.message}`));
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
} catch (error) {
|
|
97
|
+
return {
|
|
98
|
+
error: true,
|
|
99
|
+
message: `ā Failed to start call: ${error.message}`
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Get usage stats
|
|
106
|
+
*/
|
|
107
|
+
async getUsage() {
|
|
108
|
+
try {
|
|
109
|
+
const response = await fetch(`${API_BASE}/usage/current`, {
|
|
110
|
+
headers: {
|
|
111
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
112
|
+
}
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
if (!response.ok) {
|
|
116
|
+
throw new Error('Failed to fetch usage');
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
const data = await response.json();
|
|
120
|
+
return data.data;
|
|
121
|
+
|
|
122
|
+
} catch (error) {
|
|
123
|
+
throw new Error(`Failed to get usage: ${error.message}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* List API keys
|
|
129
|
+
*/
|
|
130
|
+
async listKeys() {
|
|
131
|
+
try {
|
|
132
|
+
const response = await fetch(`${API_BASE}/keys`, {
|
|
133
|
+
headers: {
|
|
134
|
+
'Authorization': `Bearer ${this.apiKey}`
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
if (!response.ok) {
|
|
139
|
+
throw new Error('Failed to fetch keys');
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const data = await response.json();
|
|
143
|
+
return data.data.keys;
|
|
144
|
+
|
|
145
|
+
} catch (error) {
|
|
146
|
+
throw new Error(`Failed to list keys: ${error.message}`);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Plugin initialization
|
|
153
|
+
*/
|
|
154
|
+
module.exports = {
|
|
155
|
+
name: '@callme/openclaw-plugin',
|
|
156
|
+
version: '1.0.0',
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Initialize plugin
|
|
160
|
+
*/
|
|
161
|
+
async init(config, openclaw) {
|
|
162
|
+
const plugin = new CallmePlugin(config, openclaw);
|
|
163
|
+
|
|
164
|
+
// Register /call command
|
|
165
|
+
openclaw.registerCommand('call', async (args) => {
|
|
166
|
+
const duration = args[0] ? parseInt(args[0]) : null;
|
|
167
|
+
const result = await plugin.startCall(duration);
|
|
168
|
+
|
|
169
|
+
if (result.error) {
|
|
170
|
+
return result.message;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return `${result.message}\n\nš Usage: ${result.usage.minutesUsed.toFixed(1)}/${result.usage.limit} min used this month`;
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
// Register /voice-setup command
|
|
177
|
+
openclaw.registerCommand('voice-setup', async () => {
|
|
178
|
+
return `š§ **Setup callme.ai Voice**
|
|
179
|
+
|
|
180
|
+
1. Get your API key at: https://callme.ai/dashboard
|
|
181
|
+
2. Add it to your OpenClaw config:
|
|
182
|
+
|
|
183
|
+
\`\`\`yaml
|
|
184
|
+
plugins:
|
|
185
|
+
callme:
|
|
186
|
+
apiKey: "callme_your_key_here"
|
|
187
|
+
defaultVoice: "nova" # optional
|
|
188
|
+
language: "en" # optional
|
|
189
|
+
\`\`\`
|
|
190
|
+
|
|
191
|
+
3. Reload OpenClaw: \`openclaw reload\`
|
|
192
|
+
4. Start calling: \`/call\`
|
|
193
|
+
|
|
194
|
+
Need help? Check the docs: https://docs.callme.ai/openclaw`;
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Register /voice-usage command
|
|
198
|
+
openclaw.registerCommand('voice-usage', async () => {
|
|
199
|
+
try {
|
|
200
|
+
const usage = await plugin.getUsage();
|
|
201
|
+
|
|
202
|
+
return `š **callme.ai Usage**
|
|
203
|
+
|
|
204
|
+
**This Month:**
|
|
205
|
+
⢠Calls: ${usage.calls}
|
|
206
|
+
⢠Minutes: ${usage.minutesUsed.toFixed(1)} / ${usage.limit}
|
|
207
|
+
⢠Cost: $${usage.cost.toFixed(2)}
|
|
208
|
+
⢠Remaining: ${usage.remaining.toFixed(1)} min
|
|
209
|
+
|
|
210
|
+
**Plan:** ${usage.tier}
|
|
211
|
+
|
|
212
|
+
Upgrade or view details: https://callme.ai/dashboard`;
|
|
213
|
+
} catch (error) {
|
|
214
|
+
return `ā ${error.message}`;
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// Register /voice-keys command
|
|
219
|
+
openclaw.registerCommand('voice-keys', async () => {
|
|
220
|
+
try {
|
|
221
|
+
const keys = await plugin.listKeys();
|
|
222
|
+
|
|
223
|
+
if (keys.length === 0) {
|
|
224
|
+
return `š **API Keys**
|
|
225
|
+
|
|
226
|
+
No keys found. Create one at: https://callme.ai/dashboard`;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const keysList = keys.map(key => {
|
|
230
|
+
const expiry = key.expiresAt
|
|
231
|
+
? new Date(key.expiresAt).toLocaleDateString()
|
|
232
|
+
: 'Never';
|
|
233
|
+
const status = key.isExpired ? 'ā Expired' : 'ā
Active';
|
|
234
|
+
const lastUsed = key.lastUsedAt
|
|
235
|
+
? new Date(key.lastUsedAt).toLocaleDateString()
|
|
236
|
+
: 'Never';
|
|
237
|
+
|
|
238
|
+
return `⢠**${key.name}**
|
|
239
|
+
${key.keyPreview}
|
|
240
|
+
${status} Ā· Expires: ${expiry} Ā· Last used: ${lastUsed}`;
|
|
241
|
+
}).join('\n\n');
|
|
242
|
+
|
|
243
|
+
return `š **API Keys**
|
|
244
|
+
|
|
245
|
+
${keysList}
|
|
246
|
+
|
|
247
|
+
Manage keys: https://callme.ai/dashboard`;
|
|
248
|
+
} catch (error) {
|
|
249
|
+
return `ā ${error.message}`;
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
return plugin;
|
|
254
|
+
}
|
|
255
|
+
};
|
package/install.sh
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# callme.ai One-Command Installer for OpenClaw
|
|
3
|
+
# Usage: curl -fsSL https://callme.ai/install.sh | bash
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
PLUGIN_DIR="$HOME/.openclaw/plugins/callme"
|
|
8
|
+
CONFIG_FILE="$HOME/.openclaw/config.yaml"
|
|
9
|
+
PLUGIN_URL="https://raw.githubusercontent.com/Benedict-VC/openclaw-voice/main/plugin-callme"
|
|
10
|
+
|
|
11
|
+
echo "š Installing callme.ai voice plugin for OpenClaw..."
|
|
12
|
+
|
|
13
|
+
# Create plugin directory
|
|
14
|
+
mkdir -p "$PLUGIN_DIR"
|
|
15
|
+
|
|
16
|
+
# Download plugin files
|
|
17
|
+
echo "š„ Downloading plugin..."
|
|
18
|
+
curl -fsSL "$PLUGIN_URL/package.json" -o "$PLUGIN_DIR/package.json"
|
|
19
|
+
curl -fsSL "$PLUGIN_URL/index.js" -o "$PLUGIN_DIR/index.js"
|
|
20
|
+
|
|
21
|
+
# Install dependencies
|
|
22
|
+
echo "š¦ Installing dependencies..."
|
|
23
|
+
cd "$PLUGIN_DIR"
|
|
24
|
+
npm install --silent 2>/dev/null || npm install
|
|
25
|
+
|
|
26
|
+
# Check if config.yaml exists
|
|
27
|
+
if [ ! -f "$CONFIG_FILE" ]; then
|
|
28
|
+
echo "ā ļø No config.yaml found. Creating one..."
|
|
29
|
+
mkdir -p "$(dirname "$CONFIG_FILE")"
|
|
30
|
+
cat > "$CONFIG_FILE" << 'EOF'
|
|
31
|
+
plugins:
|
|
32
|
+
callme:
|
|
33
|
+
enabled: true
|
|
34
|
+
apiKey: "YOUR_API_KEY_HERE"
|
|
35
|
+
defaultVoice: "nova"
|
|
36
|
+
language: "en"
|
|
37
|
+
EOF
|
|
38
|
+
else
|
|
39
|
+
# Check if callme plugin already in config
|
|
40
|
+
if ! grep -q "callme:" "$CONFIG_FILE"; then
|
|
41
|
+
echo "āļø Adding callme plugin to config..."
|
|
42
|
+
cat >> "$CONFIG_FILE" << 'EOF'
|
|
43
|
+
|
|
44
|
+
# callme.ai Voice Plugin
|
|
45
|
+
plugins:
|
|
46
|
+
callme:
|
|
47
|
+
enabled: true
|
|
48
|
+
apiKey: "YOUR_API_KEY_HERE"
|
|
49
|
+
defaultVoice: "nova"
|
|
50
|
+
language: "en"
|
|
51
|
+
EOF
|
|
52
|
+
fi
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
echo ""
|
|
56
|
+
echo "ā
callme.ai plugin installed successfully!"
|
|
57
|
+
echo ""
|
|
58
|
+
echo "š Next steps:"
|
|
59
|
+
echo "1. Get your API key at: https://callme.ai/dashboard"
|
|
60
|
+
echo "2. Add it to: $CONFIG_FILE"
|
|
61
|
+
echo " Replace 'YOUR_API_KEY_HERE' with your actual key"
|
|
62
|
+
echo "3. Reload OpenClaw: openclaw reload"
|
|
63
|
+
echo "4. Start calling: /call"
|
|
64
|
+
echo ""
|
|
65
|
+
echo "Need help? Check: https://docs.callme.ai/openclaw"
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "callme-openclaw-plugin",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "OpenClaw plugin for callme.ai - hands-free voice calls with your AI assistant",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"callme-setup": "./setup.js"
|
|
8
|
+
},
|
|
9
|
+
"keywords": [
|
|
10
|
+
"openclaw",
|
|
11
|
+
"plugin",
|
|
12
|
+
"voice",
|
|
13
|
+
"ai",
|
|
14
|
+
"calls",
|
|
15
|
+
"callme"
|
|
16
|
+
],
|
|
17
|
+
"author": "callme.ai",
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "https://github.com/Benedict-VC/openclaw-voice.git"
|
|
22
|
+
},
|
|
23
|
+
"openclaw": {
|
|
24
|
+
"displayName": "callme.ai Voice",
|
|
25
|
+
"description": "Make hands-free voice calls to your AI assistant",
|
|
26
|
+
"category": "communication",
|
|
27
|
+
"commands": [
|
|
28
|
+
{
|
|
29
|
+
"name": "/call",
|
|
30
|
+
"description": "Start a voice call with your AI assistant",
|
|
31
|
+
"usage": "/call [duration]"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"name": "/voice-setup",
|
|
35
|
+
"description": "Configure your callme.ai API key",
|
|
36
|
+
"usage": "/voice-setup"
|
|
37
|
+
}
|
|
38
|
+
],
|
|
39
|
+
"config": {
|
|
40
|
+
"apiKey": {
|
|
41
|
+
"type": "string",
|
|
42
|
+
"description": "Your callme.ai API key (from dashboard)",
|
|
43
|
+
"required": true,
|
|
44
|
+
"secret": true
|
|
45
|
+
},
|
|
46
|
+
"defaultVoice": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"description": "Default voice (alloy, echo, fable, onyx, nova, shimmer)",
|
|
49
|
+
"default": "nova"
|
|
50
|
+
},
|
|
51
|
+
"language": {
|
|
52
|
+
"type": "string",
|
|
53
|
+
"description": "Language code (en, de, es, fr)",
|
|
54
|
+
"default": "en"
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"ws": "^8.16.0",
|
|
60
|
+
"node-fetch": "^2.7.0"
|
|
61
|
+
}
|
|
62
|
+
}
|
package/setup.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Interactive callme.ai setup for OpenClaw
|
|
4
|
+
* Run: npx @callme/setup
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const https = require('https');
|
|
10
|
+
const readline = require('readline');
|
|
11
|
+
const { exec } = require('child_process');
|
|
12
|
+
const { promisify } = require('util');
|
|
13
|
+
|
|
14
|
+
const execAsync = promisify(exec);
|
|
15
|
+
|
|
16
|
+
const PLUGIN_DIR = path.join(process.env.HOME, '.openclaw', 'plugins', 'callme');
|
|
17
|
+
const CONFIG_FILE = path.join(process.env.HOME, '.openclaw', 'config.yaml');
|
|
18
|
+
|
|
19
|
+
const rl = readline.createInterface({
|
|
20
|
+
input: process.stdin,
|
|
21
|
+
output: process.stdout
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
function question(query) {
|
|
25
|
+
return new Promise(resolve => rl.question(query, resolve));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function download(url, dest) {
|
|
29
|
+
return new Promise((resolve, reject) => {
|
|
30
|
+
const file = fs.createWriteStream(dest);
|
|
31
|
+
https.get(url, (response) => {
|
|
32
|
+
response.pipe(file);
|
|
33
|
+
file.on('finish', () => {
|
|
34
|
+
file.close();
|
|
35
|
+
resolve();
|
|
36
|
+
});
|
|
37
|
+
}).on('error', (err) => {
|
|
38
|
+
fs.unlink(dest, () => {});
|
|
39
|
+
reject(err);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function main() {
|
|
45
|
+
console.log('\nš \x1b[1mcallme.ai Setup for OpenClaw\x1b[0m\n');
|
|
46
|
+
|
|
47
|
+
// Step 1: Check if OpenClaw is installed
|
|
48
|
+
console.log('š Checking OpenClaw installation...');
|
|
49
|
+
try {
|
|
50
|
+
await execAsync('which openclaw');
|
|
51
|
+
console.log('ā
OpenClaw found\n');
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.log('ā OpenClaw not found. Install it first: npm install -g openclaw');
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Step 2: Get API Key
|
|
58
|
+
console.log('š \x1b[1mGet your API key:\x1b[0m https://callme.ai/dashboard\n');
|
|
59
|
+
const apiKey = await question('Paste your API key: ');
|
|
60
|
+
|
|
61
|
+
if (!apiKey || !apiKey.startsWith('callme_')) {
|
|
62
|
+
console.log('\nā Invalid API key format. Must start with "callme_"');
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Step 3: Download plugin
|
|
67
|
+
console.log('\nš„ Downloading plugin...');
|
|
68
|
+
fs.mkdirSync(PLUGIN_DIR, { recursive: true });
|
|
69
|
+
|
|
70
|
+
const files = [
|
|
71
|
+
{ name: 'package.json', url: 'https://raw.githubusercontent.com/Benedict-VC/openclaw-voice/main/plugin-callme/package.json' },
|
|
72
|
+
{ name: 'index.js', url: 'https://raw.githubusercontent.com/Benedict-VC/openclaw-voice/main/plugin-callme/index.js' }
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
for (const file of files) {
|
|
76
|
+
await download(file.url, path.join(PLUGIN_DIR, file.name));
|
|
77
|
+
}
|
|
78
|
+
console.log('ā
Plugin downloaded');
|
|
79
|
+
|
|
80
|
+
// Step 4: Install dependencies
|
|
81
|
+
console.log('\nš¦ Installing dependencies...');
|
|
82
|
+
process.chdir(PLUGIN_DIR);
|
|
83
|
+
await execAsync('npm install --silent');
|
|
84
|
+
console.log('ā
Dependencies installed');
|
|
85
|
+
|
|
86
|
+
// Step 5: Update config
|
|
87
|
+
console.log('\nāļø Updating OpenClaw config...');
|
|
88
|
+
|
|
89
|
+
const configContent = `
|
|
90
|
+
# callme.ai Voice Plugin
|
|
91
|
+
plugins:
|
|
92
|
+
callme:
|
|
93
|
+
enabled: true
|
|
94
|
+
apiKey: "${apiKey}"
|
|
95
|
+
defaultVoice: "nova"
|
|
96
|
+
language: "en"
|
|
97
|
+
`;
|
|
98
|
+
|
|
99
|
+
if (fs.existsSync(CONFIG_FILE)) {
|
|
100
|
+
const existing = fs.readFileSync(CONFIG_FILE, 'utf8');
|
|
101
|
+
if (existing.includes('callme:')) {
|
|
102
|
+
// Replace existing
|
|
103
|
+
const updated = existing.replace(
|
|
104
|
+
/plugins:\s*\n\s*callme:[\s\S]*?(?=\n\S|$)/,
|
|
105
|
+
configContent.trim()
|
|
106
|
+
);
|
|
107
|
+
fs.writeFileSync(CONFIG_FILE, updated);
|
|
108
|
+
} else {
|
|
109
|
+
// Append
|
|
110
|
+
fs.appendFileSync(CONFIG_FILE, configContent);
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
fs.mkdirSync(path.dirname(CONFIG_FILE), { recursive: true });
|
|
114
|
+
fs.writeFileSync(CONFIG_FILE, configContent);
|
|
115
|
+
}
|
|
116
|
+
console.log('ā
Config updated');
|
|
117
|
+
|
|
118
|
+
// Step 6: Done!
|
|
119
|
+
console.log('\n⨠\x1b[1;32mSetup complete!\x1b[0m\n');
|
|
120
|
+
console.log('š Next steps:');
|
|
121
|
+
console.log(' 1. Reload OpenClaw: \x1b[36mopenclaw reload\x1b[0m');
|
|
122
|
+
console.log(' 2. Start calling: \x1b[36m/call\x1b[0m');
|
|
123
|
+
console.log('');
|
|
124
|
+
console.log('š Commands:');
|
|
125
|
+
console.log(' /call - Start voice call');
|
|
126
|
+
console.log(' /voice-usage - Check usage stats');
|
|
127
|
+
console.log(' /voice-keys - Manage API keys');
|
|
128
|
+
console.log('');
|
|
129
|
+
console.log('Need help? https://docs.callme.ai/openclaw');
|
|
130
|
+
console.log('');
|
|
131
|
+
|
|
132
|
+
rl.close();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
main().catch((error) => {
|
|
136
|
+
console.error('\nā Setup failed:', error.message);
|
|
137
|
+
process.exit(1);
|
|
138
|
+
});
|