AI Newsletter Digest improvements: fixed QP soft line break decoding, URL extraction, and content cleaning

This commit is contained in:
Krilly
2026-03-04 13:29:22 +00:00
parent 29a98137a7
commit 57dd294675
13706 changed files with 2114953 additions and 237629 deletions
@@ -0,0 +1,128 @@
# Wyoming-Clawdbot
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
Wyoming Protocol server that bridges [Home Assistant Assist](https://www.home-assistant.io/voice_control/) to [Clawdbot](https://clawd.bot) — enabling voice control of your AI assistant.
## Features
- 🎤 Voice commands through Home Assistant Assist
- 🤖 Powered by Clawdbot AI (Claude, GPT, etc.)
- 🏠 Full Home Assistant integration
- 🌍 Multilingual support (English, Russian, German, French, and more)
- 💬 Persistent conversation context
## How It Works
```
Voice → Home Assistant → STT → Wyoming-Clawdbot → Clawdbot → Response → TTS → Speaker
```
1. You speak to your Home Assistant voice satellite (ESPHome, etc.)
2. Speech-to-Text converts your voice to text
3. Wyoming-Clawdbot sends the text to Clawdbot
4. Clawdbot processes and returns a response
5. Text-to-Speech speaks the response
## Requirements
- [Clawdbot](https://clawd.bot) installed and running
- Home Assistant with Wyoming integration
- Python 3.11+ (or Docker)
## Installation
### Docker Compose (recommended)
```bash
git clone https://github.com/vglafirov/wyoming-clawdbot.git
cd wyoming-clawdbot
docker-compose up -d
```
### Manual
```bash
# Clone the repository
git clone https://github.com/vglafirov/wyoming-clawdbot.git
cd wyoming-clawdbot
# Create virtual environment
python3 -m venv venv
source venv/bin/activate
# Install dependencies
pip install -r requirements.txt
```
## Usage
### Basic
```bash
python wyoming_clawdbot.py --port 10600
```
### With persistent session (recommended)
```bash
python wyoming_clawdbot.py --port 10600 --session-id voice-assistant
```
### Options
| Option | Description | Default |
|--------|-------------|---------|
| `--host` | Host to bind to | `0.0.0.0` |
| `--port` | Port to listen on | `10400` |
| `--session-id` | Clawdbot session ID for context persistence | random |
| `--agent` | Clawdbot agent ID | default |
| `--debug` | Enable debug logging | false |
## Systemd Service
Create `/etc/systemd/system/wyoming-clawdbot.service`:
```ini
[Unit]
Description=Wyoming Clawdbot Bridge
After=network.target
[Service]
Type=simple
User=your-user
WorkingDirectory=/path/to/wyoming-clawdbot
Environment="PATH=/usr/local/bin:/usr/bin:/bin"
ExecStart=/path/to/wyoming-clawdbot/venv/bin/python wyoming_clawdbot.py --port 10600 --session-id voice-assistant
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
```
Then:
```bash
sudo systemctl daemon-reload
sudo systemctl enable wyoming-clawdbot
sudo systemctl start wyoming-clawdbot
```
## Home Assistant Configuration
1. Go to **Settings → Devices & Services → Add Integration**
2. Search for **Wyoming Protocol**
3. Enter the host and port (e.g., `192.168.1.100:10600`)
4. The "clawdbot" conversation agent will appear
5. Configure your Voice Assistant pipeline to use "clawdbot" as the Conversation Agent
## License
MIT License - see [LICENSE](LICENSE) for details.
## Credits
- [Clawdbot](https://clawd.bot) - AI assistant platform
- [Wyoming Protocol](https://github.com/rhasspy/wyoming) - Voice assistant protocol
- [Home Assistant](https://www.home-assistant.io/) - Home automation platform
@@ -0,0 +1,40 @@
---
name: wyoming-clawdbot
description: Wyoming Protocol bridge for Home Assistant voice assistant integration with Clawdbot.
---
# Wyoming-Clawdbot
Bridge Home Assistant Assist voice commands to Clawdbot via Wyoming Protocol.
## What it does
- Receives voice commands from Home Assistant Assist
- Forwards them to Clawdbot for processing
- Returns AI responses to be spoken by Home Assistant TTS
## Setup
1. Clone and run the server:
```bash
git clone https://github.com/vglafirov/wyoming-clawdbot.git
cd wyoming-clawdbot
docker compose up -d
```
2. Add Wyoming integration in Home Assistant:
- Settings → Devices & Services → Add Integration
- Search "Wyoming Protocol"
- Enter host:port (e.g., `192.168.1.100:10600`)
3. Configure Voice Assistant pipeline to use "clawdbot" as Conversation Agent
## Requirements
- Clawdbot running on the same host
- Home Assistant with Wyoming integration
- Docker (recommended) or Python 3.11+
## Links
- GitHub: https://github.com/vglafirov/wyoming-clawdbot
@@ -0,0 +1,6 @@
{
"ownerId": "kn7f3y4340xpmh2nz9h7y9mf6s7zzja2",
"slug": "wyoming-clawdbot",
"version": "1.0.2",
"publishedAt": 1769456289891
}
@@ -0,0 +1,12 @@
version: "3.8"
services:
wyoming-clawdbot:
build: .
container_name: wyoming-clawdbot
restart: unless-stopped
network_mode: host
volumes:
# Share Clawdbot config for gateway connection
- ${HOME}/.clawdbot:/root/.clawdbot
command: ["--host", "0.0.0.0", "--port", "10600", "--session-id", "voice-assistant"]
@@ -0,0 +1 @@
wyoming>=1.5.0
@@ -0,0 +1,174 @@
#!/usr/bin/env python3
"""Wyoming protocol server for Clawdbot integration."""
import argparse
import asyncio
import json
import logging
from wyoming.asr import Transcript
from wyoming.event import Event, async_read_event, async_write_event
from wyoming.info import Attribution, Describe, Info, HandleProgram, HandleModel
from wyoming.handle import Handled, NotHandled
_LOGGER = logging.getLogger(__name__)
class ClawdbotHandler:
"""Handle Wyoming events for Clawdbot."""
def __init__(
self,
reader: asyncio.StreamReader,
writer: asyncio.StreamWriter,
clawdbot_args: list[str],
) -> None:
self.reader = reader
self.writer = writer
self.clawdbot_args = clawdbot_args
async def handle_event(self, event: Event) -> bool:
"""Handle incoming Wyoming event."""
_LOGGER.debug("Received event type: %s", event.type)
if Describe.is_type(event.type):
# Return service info - expose as handle (conversation) service
info = Info(
handle=[
HandleProgram(
name="clawdbot",
description="Clawdbot AI Assistant",
attribution=Attribution(
name="Clawdbot",
url="https://clawd.bot",
),
installed=True,
version="1.0.0",
models=[
HandleModel(
name="clawdbot",
description="Clawdbot multilingual assistant",
attribution=Attribution(
name="Clawdbot",
url="https://clawd.bot",
),
installed=True,
version="1.0.0",
languages=["en", "ru", "de", "fr", "es", "it", "pt", "nl", "pl", "uk"],
)
],
)
]
)
await async_write_event(info.event(), self.writer)
_LOGGER.debug("Sent info response")
return True
# Handle Transcript events from Home Assistant
if Transcript.is_type(event.type):
transcript = Transcript.from_event(event)
_LOGGER.info("Received transcript: %s", transcript.text)
try:
# Call clawdbot agent
response_text = await self._call_clawdbot(transcript.text)
_LOGGER.info("Clawdbot response: %s", response_text)
# Return handled response
handled = Handled(text=response_text)
await async_write_event(handled.event(), self.writer)
except Exception as e:
_LOGGER.error("Error calling Clawdbot: %s", e)
not_handled = NotHandled(text=f"Ошибка: {e}")
await async_write_event(not_handled.event(), self.writer)
return True
_LOGGER.warning("Unexpected event type: %s", event.type)
return True
async def _call_clawdbot(self, text: str) -> str:
"""Call Clawdbot CLI and return response."""
cmd = ["clawdbot", "agent", "--message", text, "--json"] + self.clawdbot_args
proc = await asyncio.create_subprocess_exec(
*cmd,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await proc.communicate()
if proc.returncode != 0:
error_msg = stderr.decode() if stderr else "Unknown error"
raise RuntimeError(f"Clawdbot failed: {error_msg}")
# Parse JSON response
try:
result = json.loads(stdout.decode())
# Extract the assistant's reply from nested structure
if isinstance(result, dict):
# Try result.payloads[0].text first
payloads = result.get("result", {}).get("payloads", [])
if payloads and isinstance(payloads[0], dict):
text = payloads[0].get("text")
if text:
return text
# Fallback to other common fields
return result.get("reply", result.get("text", str(result)))
return str(result)
except json.JSONDecodeError:
# Return raw output if not JSON
return stdout.decode().strip()
async def run(self) -> None:
"""Run the handler loop."""
try:
while True:
event = await async_read_event(self.reader)
if event is None:
break
if not await self.handle_event(event):
break
finally:
self.writer.close()
async def main() -> None:
"""Main entry point."""
parser = argparse.ArgumentParser(description="Wyoming server for Clawdbot")
parser.add_argument("--host", default="0.0.0.0", help="Host to bind to")
parser.add_argument("--port", type=int, default=10400, help="Port to listen on")
parser.add_argument("--agent", help="Clawdbot agent id")
parser.add_argument("--session-id", help="Clawdbot session id for context")
parser.add_argument("--debug", action="store_true", help="Enable debug logging")
args = parser.parse_args()
logging.basicConfig(
level=logging.DEBUG if args.debug else logging.INFO,
format="%(asctime)s %(levelname)s %(name)s: %(message)s",
)
# Build extra clawdbot args
clawdbot_args = []
if args.agent:
clawdbot_args.extend(["--agent", args.agent])
if args.session_id:
clawdbot_args.extend(["--session-id", args.session_id])
_LOGGER.info("Starting Wyoming-Clawdbot server on %s:%d", args.host, args.port)
async def handle_client(
reader: asyncio.StreamReader, writer: asyncio.StreamWriter
) -> None:
handler = ClawdbotHandler(reader, writer, clawdbot_args)
await handler.run()
server = await asyncio.start_server(handle_client, args.host, args.port)
async with server:
await server.serve_forever()
if __name__ == "__main__":
asyncio.run(main())