@jimiford/webex 0.1.1

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Jimi Ford
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,314 @@
1
+ # @jimiford/webex
2
+
3
+ OpenClaw channel plugin for Cisco Webex messaging. Enables your OpenClaw gateway to send and receive messages via Webex bots.
4
+
5
+ ## Features
6
+
7
+ - **Direct Messages (1:1)**: Send and receive private messages with users
8
+ - **Space/Room Messages**: Communicate in Webex spaces and rooms
9
+ - **Attachments**: Send files via public URLs
10
+ - **Adaptive Cards**: Rich interactive message cards
11
+ - **Threaded Replies**: Support for message threading
12
+ - **Webhook Integration**: Real-time message reception
13
+ - **Automatic Retries**: Configurable retry logic with exponential backoff
14
+ - **Message Normalization**: Converts Webex messages to OpenClaw's envelope format
15
+
16
+ ## Installation
17
+
18
+ ```bash
19
+ npm install @jimiford/webex
20
+ ```
21
+
22
+ ## Prerequisites
23
+
24
+ ### 1. Create a Webex Bot
25
+
26
+ 1. Go to [Webex Developer Portal](https://developer.webex.com)
27
+ 2. Sign in with your Webex account
28
+ 3. Navigate to **My Webex Apps** → **Create a New App**
29
+ 4. Select **Create a Bot**
30
+ 5. Fill in the bot details:
31
+ - Bot Name: Your bot's display name
32
+ - Bot Username: Unique identifier (e.g., `mybot@webex.bot`)
33
+ - Icon: Upload or select an icon
34
+ - Description: Brief description of your bot
35
+ 6. Click **Add Bot**
36
+ 7. **Important**: Copy the **Bot Access Token** - you'll only see it once!
37
+
38
+ ### 2. Set Up a Public Webhook URL
39
+
40
+ Your webhook endpoint must be publicly accessible. Options:
41
+
42
+ - **Production**: Deploy to a cloud provider with HTTPS
43
+ - **Development**: Use [ngrok](https://ngrok.com) to expose localhost:
44
+ ```bash
45
+ ngrok http 3000
46
+ ```
47
+
48
+ ## Configuration
49
+
50
+ ```typescript
51
+ import { createWebexChannel, WebexChannelConfig } from '@jimiford/webex';
52
+
53
+ const config: WebexChannelConfig = {
54
+ // Required: Your Webex bot access token
55
+ token: 'YOUR_BOT_ACCESS_TOKEN',
56
+
57
+ // Required: Public URL for receiving webhooks
58
+ webhookUrl: 'https://your-domain.com/webhooks/webex',
59
+
60
+ // Required: Policy for handling direct messages
61
+ // - 'allow': Accept DMs from anyone
62
+ // - 'deny': Reject all DMs
63
+ // - 'allowlisted': Only accept from specified users
64
+ dmPolicy: 'allow',
65
+
66
+ // Optional: List of allowed person IDs or emails (when dmPolicy is 'allowlisted')
67
+ allowFrom: ['user@example.com', 'Y2lzY29zcGFyazov...'],
68
+
69
+ // Optional: Secret for webhook signature verification
70
+ webhookSecret: 'your-webhook-secret',
71
+
72
+ // Optional: Custom API base URL (default: https://webexapis.com/v1)
73
+ apiBaseUrl: 'https://webexapis.com/v1',
74
+
75
+ // Optional: Maximum retry attempts (default: 3)
76
+ maxRetries: 3,
77
+
78
+ // Optional: Retry delay in ms (default: 1000)
79
+ retryDelayMs: 1000,
80
+ };
81
+ ```
82
+
83
+ ## Usage
84
+
85
+ ### Basic Setup
86
+
87
+ ```typescript
88
+ import { createWebexChannel } from '@jimiford/webex';
89
+
90
+ async function main() {
91
+ // Create and initialize the channel
92
+ const channel = createWebexChannel();
93
+ await channel.initialize({
94
+ token: process.env.WEBEX_BOT_TOKEN!,
95
+ webhookUrl: process.env.WEBHOOK_URL!,
96
+ dmPolicy: 'allow',
97
+ });
98
+
99
+ // Register webhooks with Webex
100
+ await channel.registerWebhooks();
101
+
102
+ // Register a message handler
103
+ channel.onMessage(async (envelope) => {
104
+ console.log('Received message:', envelope);
105
+
106
+ // Echo the message back
107
+ await channel.send({
108
+ to: envelope.conversationId,
109
+ content: { text: `You said: ${envelope.content.text}` },
110
+ });
111
+ });
112
+
113
+ console.log('Webex channel ready!');
114
+ }
115
+
116
+ main().catch(console.error);
117
+ ```
118
+
119
+ ### Sending Messages
120
+
121
+ ```typescript
122
+ // Send to a room
123
+ await channel.sendText('roomId', 'Hello, room!');
124
+
125
+ // Send markdown
126
+ await channel.sendMarkdown('roomId', '**Bold** and _italic_');
127
+
128
+ // Send direct message
129
+ await channel.sendDirect('user@example.com', 'Hello!');
130
+
131
+ // Reply in a thread
132
+ await channel.reply('roomId', 'parentMessageId', 'This is a reply');
133
+
134
+ // Send with full options
135
+ await channel.send({
136
+ to: 'roomId',
137
+ content: {
138
+ text: 'Plain text fallback',
139
+ markdown: '**Rich** content',
140
+ files: ['https://example.com/image.png'],
141
+ },
142
+ parentId: 'threadParentId',
143
+ });
144
+ ```
145
+
146
+ ### Handling Webhooks
147
+
148
+ Set up an HTTP endpoint to receive webhooks:
149
+
150
+ ```typescript
151
+ import express from 'express';
152
+ import { createWebexChannel } from '@jimiford/webex';
153
+
154
+ const app = express();
155
+ app.use(express.json());
156
+
157
+ const channel = createWebexChannel();
158
+
159
+ // Initialize channel (do this on startup)
160
+ await channel.initialize({
161
+ token: process.env.WEBEX_BOT_TOKEN!,
162
+ webhookUrl: 'https://your-domain.com/webhooks/webex',
163
+ dmPolicy: 'allow',
164
+ webhookSecret: process.env.WEBHOOK_SECRET,
165
+ });
166
+
167
+ // Webhook endpoint
168
+ app.post('/webhooks/webex', async (req, res) => {
169
+ try {
170
+ const signature = req.headers['x-spark-signature'] as string;
171
+ const envelope = await channel.handleWebhook(req.body, signature);
172
+
173
+ if (envelope) {
174
+ // Process the message
175
+ console.log('Message from:', envelope.author.email);
176
+ console.log('Content:', envelope.content.text);
177
+ }
178
+
179
+ res.status(200).send('OK');
180
+ } catch (error) {
181
+ console.error('Webhook error:', error);
182
+ res.status(500).send('Error');
183
+ }
184
+ });
185
+
186
+ app.listen(3000);
187
+ ```
188
+
189
+ ### OpenClaw Envelope Format
190
+
191
+ Incoming messages are normalized to this format:
192
+
193
+ ```typescript
194
+ interface OpenClawEnvelope {
195
+ id: string; // Webex message ID
196
+ channel: 'webex'; // Channel identifier
197
+ conversationId: string; // Room ID
198
+ author: {
199
+ id: string; // Person ID
200
+ email?: string; // Email address
201
+ displayName?: string; // Display name
202
+ isBot: boolean; // Always false (bot messages filtered)
203
+ };
204
+ content: {
205
+ text?: string; // Plain text content
206
+ markdown?: string; // Markdown content
207
+ attachments?: Array<{
208
+ type: 'file' | 'card';
209
+ url?: string; // File URL
210
+ content?: unknown; // Card content
211
+ }>;
212
+ };
213
+ metadata: {
214
+ roomType: 'direct' | 'group';
215
+ roomId: string;
216
+ timestamp: string; // ISO 8601
217
+ mentions?: string[]; // Mentioned person IDs
218
+ parentId?: string; // Thread parent message ID
219
+ raw: WebexMessage; // Original Webex message
220
+ };
221
+ }
222
+ ```
223
+
224
+ ## Advanced Usage
225
+
226
+ ### Direct Sender Access
227
+
228
+ ```typescript
229
+ const sender = channel.getSender();
230
+
231
+ // Get message details
232
+ const message = await sender.getMessage('messageId');
233
+
234
+ // Delete a message
235
+ await sender.deleteMessage('messageId');
236
+ ```
237
+
238
+ ### Direct Webhook Handler Access
239
+
240
+ ```typescript
241
+ const webhookHandler = channel.getWebhookHandler();
242
+
243
+ // List existing webhooks
244
+ const webhooks = await webhookHandler.listWebhooks();
245
+
246
+ // Delete a webhook
247
+ await webhookHandler.deleteWebhook('webhookId');
248
+ ```
249
+
250
+ ### Error Handling
251
+
252
+ ```typescript
253
+ import { WebexApiRequestError } from '@jimiford/webex';
254
+
255
+ try {
256
+ await channel.send({ to: 'invalid', content: { text: 'test' } });
257
+ } catch (error) {
258
+ if (error instanceof WebexApiRequestError) {
259
+ console.error('API Error:', error.message);
260
+ console.error('Status:', error.statusCode);
261
+ console.error('Tracking ID:', error.trackingId);
262
+ }
263
+ }
264
+ ```
265
+
266
+ ## API Reference
267
+
268
+ ### WebexChannel
269
+
270
+ | Method | Description |
271
+ |--------|-------------|
272
+ | `initialize(config)` | Initialize with configuration |
273
+ | `send(message)` | Send a message |
274
+ | `sendText(roomId, text)` | Send plain text to a room |
275
+ | `sendMarkdown(roomId, md)` | Send markdown to a room |
276
+ | `sendDirect(to, text)` | Send direct message |
277
+ | `reply(roomId, parentId, text)` | Send threaded reply |
278
+ | `handleWebhook(payload, sig?)` | Process incoming webhook |
279
+ | `onMessage(handler)` | Register message handler |
280
+ | `offMessage(handler)` | Remove message handler |
281
+ | `registerWebhooks()` | Register webhooks with Webex |
282
+ | `shutdown()` | Cleanup and shutdown |
283
+
284
+ ## Environment Variables
285
+
286
+ Recommended environment variables:
287
+
288
+ ```bash
289
+ WEBEX_BOT_TOKEN=your_bot_access_token
290
+ WEBHOOK_URL=https://your-domain.com/webhooks/webex
291
+ WEBHOOK_SECRET=your_webhook_secret
292
+ ```
293
+
294
+ ## Troubleshooting
295
+
296
+ ### Bot not receiving messages
297
+
298
+ 1. Ensure webhooks are registered: `await channel.registerWebhooks()`
299
+ 2. Verify your webhook URL is publicly accessible
300
+ 3. Check that the bot is added to the room/space
301
+ 4. For DMs, the user must message the bot first
302
+
303
+ ### "Invalid webhook signature" errors
304
+
305
+ 1. Ensure `webhookSecret` matches the secret used when creating webhooks
306
+ 2. Verify the signature header name: `x-spark-signature`
307
+
308
+ ### Rate limiting
309
+
310
+ The plugin includes automatic retry with exponential backoff for rate-limited requests. Adjust `maxRetries` and `retryDelayMs` in config if needed.
311
+
312
+ ## License
313
+
314
+ MIT
@@ -0,0 +1,18 @@
1
+ /**
2
+ * OpenClaw Channel Plugin for Webex
3
+ *
4
+ * Implements the ChannelPlugin interface for OpenClaw's plugin system.
5
+ */
6
+ import type { ChannelPlugin } from "openclaw/plugin-sdk";
7
+ import type { WebexChannelConfig } from "./types";
8
+ /** Resolved account configuration */
9
+ export interface ResolvedWebexAccount {
10
+ accountId: string;
11
+ name?: string;
12
+ enabled: boolean;
13
+ configured: boolean;
14
+ config: WebexChannelConfig;
15
+ token?: string;
16
+ webhookUrl?: string;
17
+ }
18
+ export declare const webexPlugin: ChannelPlugin<ResolvedWebexAccount>;