Initial commit: MQTT to Clawdbot bridge
- Subscribe to MQTT topics, forward to Clawdbot webhook - Configurable via environment variables - Supports wake (system event) and agent (isolated) modes - Includes systemd service template - Home Assistant examples in README
This commit is contained in:
14
.env.example
Normal file
14
.env.example
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# MQTT Configuration
|
||||||
|
MQTT_URL=mqtt://your-broker:1883
|
||||||
|
MQTT_USERNAME=
|
||||||
|
MQTT_PASSWORD=
|
||||||
|
MQTT_TOPICS=clawd/#,home/alerts/#
|
||||||
|
MQTT_CLIENT_ID=clawdbot-bridge
|
||||||
|
|
||||||
|
# Clawdbot Configuration
|
||||||
|
CLAWDBOT_URL=http://127.0.0.1:18789
|
||||||
|
CLAWDBOT_TOKEN=your-webhook-token-here
|
||||||
|
CLAWDBOT_MODE=wake # 'wake' for system events, 'agent' for isolated runs
|
||||||
|
|
||||||
|
# Logging
|
||||||
|
LOG_LEVEL=info # debug, info, warn, error
|
||||||
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
node_modules/
|
||||||
|
.env
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
164
README.md
Normal file
164
README.md
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
# MQTT → Clawdbot Bridge
|
||||||
|
|
||||||
|
Subscribe to MQTT topics and forward messages to [Clawdbot](https://github.com/clawdbot/clawdbot) via webhook.
|
||||||
|
|
||||||
|
Perfect for triggering your AI assistant from Home Assistant, IoT devices, or any MQTT-enabled system — without exposing your Clawdbot gateway to the internet.
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
```
|
||||||
|
[Your Device] → [MQTT Broker] → [This Bridge] → [Clawdbot Webhook] → [AI Response]
|
||||||
|
```
|
||||||
|
|
||||||
|
The bridge runs locally alongside Clawdbot, subscribes to MQTT topics, and calls the local webhook endpoint when messages arrive.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone https://gitea.olex.me/olex/mqtt-clawdbot-bridge.git
|
||||||
|
cd mqtt-clawdbot-bridge
|
||||||
|
npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Configure
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
# Edit .env with your settings
|
||||||
|
```
|
||||||
|
|
||||||
|
Required settings:
|
||||||
|
- `MQTT_URL` — Your MQTT broker (e.g., `mqtt://192.168.1.100:1883`)
|
||||||
|
- `CLAWDBOT_TOKEN` — Webhook token from your Clawdbot config
|
||||||
|
|
||||||
|
### 3. Enable Clawdbot Webhooks
|
||||||
|
|
||||||
|
Add to your `~/.clawdbot/clawdbot.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"hooks": {
|
||||||
|
"enabled": true,
|
||||||
|
"token": "your-secret-token",
|
||||||
|
"path": "/hooks"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Restart the gateway: `clawdbot gateway restart`
|
||||||
|
|
||||||
|
### 4. Run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
All configuration is via environment variables (or `.env` file):
|
||||||
|
|
||||||
|
| Variable | Default | Description |
|
||||||
|
|----------|---------|-------------|
|
||||||
|
| `MQTT_URL` | `mqtt://localhost:1883` | MQTT broker URL |
|
||||||
|
| `MQTT_USERNAME` | — | MQTT username (optional) |
|
||||||
|
| `MQTT_PASSWORD` | — | MQTT password (optional) |
|
||||||
|
| `MQTT_TOPICS` | `clawd/#` | Comma-separated topics to subscribe |
|
||||||
|
| `MQTT_CLIENT_ID` | random | MQTT client identifier |
|
||||||
|
| `CLAWDBOT_URL` | `http://127.0.0.1:18789` | Clawdbot gateway URL |
|
||||||
|
| `CLAWDBOT_TOKEN` | — | Webhook token (**required**) |
|
||||||
|
| `CLAWDBOT_MODE` | `wake` | `wake` (system event) or `agent` (isolated run) |
|
||||||
|
| `LOG_LEVEL` | `info` | `debug`, `info`, `warn`, `error` |
|
||||||
|
|
||||||
|
## Webhook Modes
|
||||||
|
|
||||||
|
### `wake` (default)
|
||||||
|
Triggers a system event in the main session. Good for alerts and notifications that should appear in your regular chat context.
|
||||||
|
|
||||||
|
### `agent`
|
||||||
|
Runs an isolated agent session. Good for tasks that need a dedicated response without cluttering main chat history.
|
||||||
|
|
||||||
|
## Running as a Service
|
||||||
|
|
||||||
|
### systemd (Linux)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Copy files
|
||||||
|
sudo cp -r . /opt/mqtt-clawdbot-bridge
|
||||||
|
sudo cp mqtt-clawdbot-bridge.service /etc/systemd/system/
|
||||||
|
|
||||||
|
# Configure
|
||||||
|
sudo cp .env.example /opt/mqtt-clawdbot-bridge/.env
|
||||||
|
sudo nano /opt/mqtt-clawdbot-bridge/.env
|
||||||
|
|
||||||
|
# Enable and start
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable mqtt-clawdbot-bridge
|
||||||
|
sudo systemctl start mqtt-clawdbot-bridge
|
||||||
|
|
||||||
|
# Check status
|
||||||
|
sudo systemctl status mqtt-clawdbot-bridge
|
||||||
|
journalctl -u mqtt-clawdbot-bridge -f
|
||||||
|
```
|
||||||
|
|
||||||
|
## Home Assistant Example
|
||||||
|
|
||||||
|
Publish to MQTT when a door opens:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
automation:
|
||||||
|
- alias: "Notify Clawd - Front Door"
|
||||||
|
trigger:
|
||||||
|
- platform: state
|
||||||
|
entity_id: binary_sensor.front_door
|
||||||
|
to: "on"
|
||||||
|
action:
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "clawd/alerts/door"
|
||||||
|
payload: "Front door opened at {{ now().strftime('%H:%M') }}"
|
||||||
|
```
|
||||||
|
|
||||||
|
Or send JSON with more context:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
action:
|
||||||
|
- service: mqtt.publish
|
||||||
|
data:
|
||||||
|
topic: "clawd/alerts/motion"
|
||||||
|
payload: >
|
||||||
|
{"message": "Motion detected in {{ trigger.to_state.attributes.friendly_name }}",
|
||||||
|
"entity": "{{ trigger.entity_id }}",
|
||||||
|
"time": "{{ now().isoformat() }}"}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Message Format
|
||||||
|
|
||||||
|
The bridge accepts:
|
||||||
|
|
||||||
|
- **Plain text**: Forwarded as-is
|
||||||
|
- **JSON with `message`/`text`/`payload` field**: That field is extracted and forwarded
|
||||||
|
|
||||||
|
Messages are prefixed with the topic for context:
|
||||||
|
```
|
||||||
|
[MQTT clawd/alerts/door] Front door opened at 10:30
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### Bridge can't connect to MQTT
|
||||||
|
- Check broker URL and credentials
|
||||||
|
- Verify network connectivity: `mosquitto_sub -h <broker> -t '#'`
|
||||||
|
|
||||||
|
### Messages not reaching Clawdbot
|
||||||
|
- Verify `CLAWDBOT_TOKEN` matches your gateway config
|
||||||
|
- Check gateway is running: `clawdbot gateway status`
|
||||||
|
- Enable debug logging: `LOG_LEVEL=debug npm start`
|
||||||
|
|
||||||
|
### Webhook returns 401
|
||||||
|
- Token mismatch — check `hooks.token` in Clawdbot config matches `CLAWDBOT_TOKEN`
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT
|
||||||
175
index.js
Normal file
175
index.js
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MQTT → Clawdbot Bridge
|
||||||
|
*
|
||||||
|
* Subscribes to MQTT topics and forwards messages to Clawdbot via webhook.
|
||||||
|
*
|
||||||
|
* Configuration via environment variables:
|
||||||
|
* MQTT_URL - MQTT broker URL (default: mqtt://localhost:1883)
|
||||||
|
* MQTT_USERNAME - MQTT username (optional)
|
||||||
|
* MQTT_PASSWORD - MQTT password (optional)
|
||||||
|
* MQTT_TOPICS - Comma-separated list of topics to subscribe (default: clawd/#)
|
||||||
|
* CLAWDBOT_URL - Clawdbot gateway URL (default: http://127.0.0.1:18789)
|
||||||
|
* CLAWDBOT_TOKEN - Clawdbot webhook token (required)
|
||||||
|
* CLAWDBOT_MODE - Webhook mode: 'wake' or 'agent' (default: wake)
|
||||||
|
* LOG_LEVEL - Logging level: debug, info, warn, error (default: info)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import mqtt from 'mqtt';
|
||||||
|
|
||||||
|
// Configuration
|
||||||
|
const config = {
|
||||||
|
mqtt: {
|
||||||
|
url: process.env.MQTT_URL || 'mqtt://localhost:1883',
|
||||||
|
username: process.env.MQTT_USERNAME || undefined,
|
||||||
|
password: process.env.MQTT_PASSWORD || undefined,
|
||||||
|
topics: (process.env.MQTT_TOPICS || 'clawd/#').split(',').map(t => t.trim()),
|
||||||
|
clientId: process.env.MQTT_CLIENT_ID || `clawdbot-bridge-${Math.random().toString(16).slice(2, 8)}`,
|
||||||
|
},
|
||||||
|
clawdbot: {
|
||||||
|
url: process.env.CLAWDBOT_URL || 'http://127.0.0.1:18789',
|
||||||
|
token: process.env.CLAWDBOT_TOKEN,
|
||||||
|
mode: process.env.CLAWDBOT_MODE || 'wake', // 'wake' or 'agent'
|
||||||
|
},
|
||||||
|
logLevel: process.env.LOG_LEVEL || 'info',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Logging
|
||||||
|
const LOG_LEVELS = { debug: 0, info: 1, warn: 2, error: 3 };
|
||||||
|
const currentLogLevel = LOG_LEVELS[config.logLevel] ?? 1;
|
||||||
|
|
||||||
|
const log = {
|
||||||
|
debug: (...args) => currentLogLevel <= 0 && console.log('[DEBUG]', new Date().toISOString(), ...args),
|
||||||
|
info: (...args) => currentLogLevel <= 1 && console.log('[INFO]', new Date().toISOString(), ...args),
|
||||||
|
warn: (...args) => currentLogLevel <= 2 && console.warn('[WARN]', new Date().toISOString(), ...args),
|
||||||
|
error: (...args) => currentLogLevel <= 3 && console.error('[ERROR]', new Date().toISOString(), ...args),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Validate config
|
||||||
|
if (!config.clawdbot.token) {
|
||||||
|
log.error('CLAWDBOT_TOKEN is required. Set it in your environment.');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send message to Clawdbot webhook
|
||||||
|
*/
|
||||||
|
async function sendToClawdbot(topic, payload) {
|
||||||
|
const endpoint = config.clawdbot.mode === 'agent'
|
||||||
|
? `${config.clawdbot.url}/hooks/agent`
|
||||||
|
: `${config.clawdbot.url}/hooks/wake`;
|
||||||
|
|
||||||
|
const body = config.clawdbot.mode === 'agent'
|
||||||
|
? { message: `[MQTT ${topic}] ${payload}`, name: 'MQTT' }
|
||||||
|
: { text: `[MQTT ${topic}] ${payload}`, mode: 'now' };
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(endpoint, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Authorization': `Bearer ${config.clawdbot.token}`,
|
||||||
|
},
|
||||||
|
body: JSON.stringify(body),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const text = await response.text();
|
||||||
|
throw new Error(`HTTP ${response.status}: ${text}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info(`Forwarded to Clawdbot: ${topic} → ${payload.substring(0, 100)}`);
|
||||||
|
return true;
|
||||||
|
} catch (err) {
|
||||||
|
log.error(`Failed to forward to Clawdbot: ${err.message}`);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse MQTT payload (handles JSON and plain text)
|
||||||
|
*/
|
||||||
|
function parsePayload(payload) {
|
||||||
|
const str = payload.toString();
|
||||||
|
try {
|
||||||
|
const json = JSON.parse(str);
|
||||||
|
// If JSON has a 'message' or 'text' field, use that
|
||||||
|
return json.message || json.text || json.payload || str;
|
||||||
|
} catch {
|
||||||
|
return str;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main entry point
|
||||||
|
*/
|
||||||
|
async function main() {
|
||||||
|
log.info('Starting MQTT → Clawdbot Bridge');
|
||||||
|
log.info(`MQTT broker: ${config.mqtt.url}`);
|
||||||
|
log.info(`Topics: ${config.mqtt.topics.join(', ')}`);
|
||||||
|
log.info(`Clawdbot: ${config.clawdbot.url} (mode: ${config.clawdbot.mode})`);
|
||||||
|
|
||||||
|
// Connect to MQTT
|
||||||
|
const mqttOptions = {
|
||||||
|
clientId: config.mqtt.clientId,
|
||||||
|
clean: true,
|
||||||
|
reconnectPeriod: 5000,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (config.mqtt.username) {
|
||||||
|
mqttOptions.username = config.mqtt.username;
|
||||||
|
mqttOptions.password = config.mqtt.password;
|
||||||
|
}
|
||||||
|
|
||||||
|
const client = mqtt.connect(config.mqtt.url, mqttOptions);
|
||||||
|
|
||||||
|
client.on('connect', () => {
|
||||||
|
log.info('Connected to MQTT broker');
|
||||||
|
|
||||||
|
// Subscribe to topics
|
||||||
|
for (const topic of config.mqtt.topics) {
|
||||||
|
client.subscribe(topic, (err) => {
|
||||||
|
if (err) {
|
||||||
|
log.error(`Failed to subscribe to ${topic}: ${err.message}`);
|
||||||
|
} else {
|
||||||
|
log.info(`Subscribed to: ${topic}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('message', async (topic, payload) => {
|
||||||
|
const message = parsePayload(payload);
|
||||||
|
log.debug(`Received: ${topic} → ${message}`);
|
||||||
|
await sendToClawdbot(topic, message);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('error', (err) => {
|
||||||
|
log.error(`MQTT error: ${err.message}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('reconnect', () => {
|
||||||
|
log.warn('Reconnecting to MQTT broker...');
|
||||||
|
});
|
||||||
|
|
||||||
|
client.on('close', () => {
|
||||||
|
log.warn('MQTT connection closed');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Graceful shutdown
|
||||||
|
const shutdown = () => {
|
||||||
|
log.info('Shutting down...');
|
||||||
|
client.end(true, () => {
|
||||||
|
process.exit(0);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
process.on('SIGINT', shutdown);
|
||||||
|
process.on('SIGTERM', shutdown);
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch((err) => {
|
||||||
|
log.error(`Fatal error: ${err.message}`);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
22
mqtt-clawdbot-bridge.service
Normal file
22
mqtt-clawdbot-bridge.service
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=MQTT to Clawdbot Bridge
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
WorkingDirectory=/opt/mqtt-clawdbot-bridge
|
||||||
|
ExecStart=/usr/bin/node index.js
|
||||||
|
Restart=always
|
||||||
|
RestartSec=10
|
||||||
|
|
||||||
|
# Load environment from file
|
||||||
|
EnvironmentFile=/opt/mqtt-clawdbot-bridge/.env
|
||||||
|
|
||||||
|
# Security hardening
|
||||||
|
NoNewPrivileges=true
|
||||||
|
ProtectSystem=strict
|
||||||
|
ProtectHome=true
|
||||||
|
PrivateTmp=true
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
17
package.json
Normal file
17
package.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"name": "mqtt-clawdbot-bridge",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Bridge MQTT messages to Clawdbot triggers via webhook",
|
||||||
|
"main": "index.js",
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node index.js",
|
||||||
|
"dev": "node --watch index.js"
|
||||||
|
},
|
||||||
|
"keywords": ["mqtt", "clawdbot", "automation", "home-assistant", "webhook"],
|
||||||
|
"author": "Clawd",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mqtt": "^5.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user