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:
2026-01-28 10:09:25 +00:00
commit df98d3ef95
6 changed files with 396 additions and 0 deletions

14
.env.example Normal file
View 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
View File

@@ -0,0 +1,4 @@
node_modules/
.env
*.log
.DS_Store

164
README.md Normal file
View 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
View 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);
});

View 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
View 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"
}
}