mindbot 0.1.0__tar.gz
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.
- mindbot-0.1.0/LICENSE +21 -0
- mindbot-0.1.0/PKG-INFO +71 -0
- mindbot-0.1.0/README.md +43 -0
- mindbot-0.1.0/pyproject.toml +44 -0
- mindbot-0.1.0/setup.cfg +4 -0
- mindbot-0.1.0/src/mindbot/__init__.py +26 -0
- mindbot-0.1.0/src/mindbot/__main__.py +6 -0
- mindbot-0.1.0/src/mindbot/bus/__init__.py +10 -0
- mindbot-0.1.0/src/mindbot/bus/events.py +35 -0
- mindbot-0.1.0/src/mindbot/bus/queue.py +80 -0
- mindbot-0.1.0/src/mindbot/channels/__init__.py +11 -0
- mindbot-0.1.0/src/mindbot/channels/base.py +121 -0
- mindbot-0.1.0/src/mindbot/channels/cli.py +71 -0
- mindbot-0.1.0/src/mindbot/channels/feishu.py +442 -0
- mindbot-0.1.0/src/mindbot/channels/http.py +241 -0
- mindbot-0.1.0/src/mindbot/channels/manager.py +169 -0
- mindbot-0.1.0/src/mindbot/cli/__init__.py +329 -0
- mindbot-0.1.0/src/mindbot/core/__init__.py +9 -0
- mindbot-0.1.0/src/mindbot/core/bot.py +275 -0
- mindbot-0.1.0/src/mindbot/core/config.py +185 -0
- mindbot-0.1.0/src/mindbot/cron/__init__.py +19 -0
- mindbot-0.1.0/src/mindbot/cron/service.py +340 -0
- mindbot-0.1.0/src/mindbot/cron/types.py +64 -0
- mindbot-0.1.0/src/mindbot.egg-info/PKG-INFO +71 -0
- mindbot-0.1.0/src/mindbot.egg-info/SOURCES.txt +28 -0
- mindbot-0.1.0/src/mindbot.egg-info/dependency_links.txt +1 -0
- mindbot-0.1.0/src/mindbot.egg-info/entry_points.txt +2 -0
- mindbot-0.1.0/src/mindbot.egg-info/requires.txt +10 -0
- mindbot-0.1.0/src/mindbot.egg-info/top_level.txt +1 -0
- mindbot-0.1.0/tests/test_http_channel.py +330 -0
mindbot-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 runkezhong
|
|
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.
|
mindbot-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mindbot
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MindBot - AI Assistant powered by Thryve
|
|
5
|
+
Author: MindBot Team
|
|
6
|
+
License: MIT
|
|
7
|
+
Classifier: Development Status :: 3 - Alpha
|
|
8
|
+
Classifier: Intended Audience :: Developers
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: thryve>=0.1.0
|
|
18
|
+
Requires-Dist: pydantic>=2.0
|
|
19
|
+
Requires-Dist: pydantic-settings>=2.0
|
|
20
|
+
Requires-Dist: pyyaml>=6.0
|
|
21
|
+
Requires-Dist: loguru>=0.7
|
|
22
|
+
Requires-Dist: typer>=0.12
|
|
23
|
+
Requires-Dist: prompt-toolkit>=3.0
|
|
24
|
+
Requires-Dist: rich>=13.0
|
|
25
|
+
Requires-Dist: croniter>=1.4
|
|
26
|
+
Requires-Dist: aiohttp>=3.9
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# MindBot 开发计划
|
|
30
|
+
|
|
31
|
+
> 基于 Thryve 的多通道 AI 助手
|
|
32
|
+
|
|
33
|
+
## 项目愿景
|
|
34
|
+
|
|
35
|
+
MindBot 是一个开箱即用的多通道 AI 助手。
|
|
36
|
+
|
|
37
|
+
## 阶段划分
|
|
38
|
+
|
|
39
|
+
| 阶段 | 内容 | 优先级 |
|
|
40
|
+
|------|------|--------|
|
|
41
|
+
| [阶段一](phase1_core.md) | 项目初始化与核心类 | P0 |
|
|
42
|
+
| [阶段二](phase2_cli.md) | CLI 实现 | P0 |
|
|
43
|
+
| [阶段三](phase3_channels.md) | 通道实现 | P1 |
|
|
44
|
+
| [阶段四](phase4_tools.md) | 工具系统 | P1 |
|
|
45
|
+
| [阶段五](phase5_integration.md) | 完善与集成 | P2 |
|
|
46
|
+
|
|
47
|
+
## 技术栈
|
|
48
|
+
|
|
49
|
+
- **核心框架**: Thryve v0.2.0
|
|
50
|
+
- **CLI**: typer + prompt_toolkit + rich
|
|
51
|
+
- **通道**: aiohttp/FastAPI
|
|
52
|
+
- **工具**: 内置 + MCP
|
|
53
|
+
|
|
54
|
+
## 快速开始
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# 安装
|
|
58
|
+
pip install mindbot
|
|
59
|
+
|
|
60
|
+
# 初始化
|
|
61
|
+
mindbot onboard
|
|
62
|
+
|
|
63
|
+
# 对话
|
|
64
|
+
mindbot chat -m "你好"
|
|
65
|
+
|
|
66
|
+
# 交互式
|
|
67
|
+
mindbot shell
|
|
68
|
+
|
|
69
|
+
# 启动服务
|
|
70
|
+
mindbot serve --port 8080
|
|
71
|
+
```
|
mindbot-0.1.0/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# MindBot 开发计划
|
|
2
|
+
|
|
3
|
+
> 基于 Thryve 的多通道 AI 助手
|
|
4
|
+
|
|
5
|
+
## 项目愿景
|
|
6
|
+
|
|
7
|
+
MindBot 是一个开箱即用的多通道 AI 助手。
|
|
8
|
+
|
|
9
|
+
## 阶段划分
|
|
10
|
+
|
|
11
|
+
| 阶段 | 内容 | 优先级 |
|
|
12
|
+
|------|------|--------|
|
|
13
|
+
| [阶段一](phase1_core.md) | 项目初始化与核心类 | P0 |
|
|
14
|
+
| [阶段二](phase2_cli.md) | CLI 实现 | P0 |
|
|
15
|
+
| [阶段三](phase3_channels.md) | 通道实现 | P1 |
|
|
16
|
+
| [阶段四](phase4_tools.md) | 工具系统 | P1 |
|
|
17
|
+
| [阶段五](phase5_integration.md) | 完善与集成 | P2 |
|
|
18
|
+
|
|
19
|
+
## 技术栈
|
|
20
|
+
|
|
21
|
+
- **核心框架**: Thryve v0.2.0
|
|
22
|
+
- **CLI**: typer + prompt_toolkit + rich
|
|
23
|
+
- **通道**: aiohttp/FastAPI
|
|
24
|
+
- **工具**: 内置 + MCP
|
|
25
|
+
|
|
26
|
+
## 快速开始
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
# 安装
|
|
30
|
+
pip install mindbot
|
|
31
|
+
|
|
32
|
+
# 初始化
|
|
33
|
+
mindbot onboard
|
|
34
|
+
|
|
35
|
+
# 对话
|
|
36
|
+
mindbot chat -m "你好"
|
|
37
|
+
|
|
38
|
+
# 交互式
|
|
39
|
+
mindbot shell
|
|
40
|
+
|
|
41
|
+
# 启动服务
|
|
42
|
+
mindbot serve --port 8080
|
|
43
|
+
```
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "mindbot"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "MindBot - AI Assistant powered by Thryve"
|
|
5
|
+
authors = [
|
|
6
|
+
{name = "MindBot Team"}
|
|
7
|
+
]
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
license = {text = "MIT"}
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
classifiers = [
|
|
12
|
+
"Development Status :: 3 - Alpha",
|
|
13
|
+
"Intended Audience :: Developers",
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Programming Language :: Python :: 3.10",
|
|
16
|
+
"Programming Language :: Python :: 3.11",
|
|
17
|
+
"Programming Language :: Python :: 3.12",
|
|
18
|
+
"Programming Language :: Python :: 3.13",
|
|
19
|
+
]
|
|
20
|
+
dependencies = [
|
|
21
|
+
"thryve>=0.1.0",
|
|
22
|
+
"pydantic>=2.0",
|
|
23
|
+
"pydantic-settings>=2.0",
|
|
24
|
+
"pyyaml>=6.0",
|
|
25
|
+
"loguru>=0.7",
|
|
26
|
+
"typer>=0.12",
|
|
27
|
+
"prompt-toolkit>=3.0",
|
|
28
|
+
"rich>=13.0",
|
|
29
|
+
"croniter>=1.4",
|
|
30
|
+
"aiohttp>=3.9",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.scripts]
|
|
34
|
+
mindbot = "mindbot.cli:app"
|
|
35
|
+
|
|
36
|
+
[build-system]
|
|
37
|
+
requires = ["setuptools>=61.0"]
|
|
38
|
+
build-backend = "setuptools.build_meta"
|
|
39
|
+
|
|
40
|
+
[tool.setuptools.packages.find]
|
|
41
|
+
where = ["src"]
|
|
42
|
+
|
|
43
|
+
[tool.pytest.ini_options]
|
|
44
|
+
asyncio_mode = "auto"
|
mindbot-0.1.0/setup.cfg
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""MindBot - AI Assistant powered by Thryve."""
|
|
2
|
+
|
|
3
|
+
__version__ = "0.1.0"
|
|
4
|
+
|
|
5
|
+
__logo__ = """
|
|
6
|
+
╔════════════════════════════════════╗
|
|
7
|
+
║ MindBot ║
|
|
8
|
+
╚════════════════════════════════════╝
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from mindbot.core.bot import MindBot
|
|
12
|
+
from mindbot.core.config import MindConfig
|
|
13
|
+
from mindbot.bus import MessageBus, InboundMessage, OutboundMessage
|
|
14
|
+
from mindbot.channels import BaseChannel, ChannelManager
|
|
15
|
+
|
|
16
|
+
__all__ = [
|
|
17
|
+
"MindBot",
|
|
18
|
+
"MindConfig",
|
|
19
|
+
"MessageBus",
|
|
20
|
+
"InboundMessage",
|
|
21
|
+
"OutboundMessage",
|
|
22
|
+
"BaseChannel",
|
|
23
|
+
"ChannelManager",
|
|
24
|
+
"__version__",
|
|
25
|
+
"__logo__",
|
|
26
|
+
]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""Event types for the message bus."""
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class InboundMessage:
|
|
10
|
+
"""Message received from a chat channel."""
|
|
11
|
+
|
|
12
|
+
channel: str # telegram, discord, http, cli
|
|
13
|
+
sender_id: str # User identifier
|
|
14
|
+
chat_id: str # Chat/channel identifier
|
|
15
|
+
content: str # Message text
|
|
16
|
+
timestamp: datetime = field(default_factory=datetime.now)
|
|
17
|
+
media: list[str] = field(default_factory=list) # Media URLs
|
|
18
|
+
metadata: dict[str, Any] = field(default_factory=dict) # Channel-specific data
|
|
19
|
+
|
|
20
|
+
@property
|
|
21
|
+
def session_key(self) -> str:
|
|
22
|
+
"""Unique key for session identification."""
|
|
23
|
+
return f"{self.channel}:{self.chat_id}"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@dataclass
|
|
27
|
+
class OutboundMessage:
|
|
28
|
+
"""Message to send to a chat channel."""
|
|
29
|
+
|
|
30
|
+
channel: str
|
|
31
|
+
chat_id: str
|
|
32
|
+
content: str
|
|
33
|
+
reply_to: str | None = None
|
|
34
|
+
media: list[str] = field(default_factory=list)
|
|
35
|
+
metadata: dict[str, Any] = field(default_factory=dict)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"""Async message queue for decoupled channel-agent communication."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Callable, Awaitable
|
|
5
|
+
|
|
6
|
+
from loguru import logger
|
|
7
|
+
|
|
8
|
+
from mindbot.bus.events import InboundMessage, OutboundMessage
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MessageBus:
|
|
12
|
+
"""Async message bus that decouples chat channels from the agent core.
|
|
13
|
+
|
|
14
|
+
Channels push messages to the inbound queue, and the agent processes
|
|
15
|
+
them and pushes responses to the outbound queue.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
def __init__(self):
|
|
19
|
+
self.inbound: asyncio.Queue[InboundMessage] = asyncio.Queue()
|
|
20
|
+
self.outbound: asyncio.Queue[OutboundMessage] = asyncio.Queue()
|
|
21
|
+
self._outbound_subscribers: dict[str, list[Callable[[OutboundMessage], Awaitable[None]]]] = {}
|
|
22
|
+
self._running = False
|
|
23
|
+
|
|
24
|
+
async def publish_inbound(self, msg: InboundMessage) -> None:
|
|
25
|
+
"""Publish a message from a channel to the agent."""
|
|
26
|
+
await self.inbound.put(msg)
|
|
27
|
+
|
|
28
|
+
async def consume_inbound(self) -> InboundMessage:
|
|
29
|
+
"""Consume the next inbound message (blocks until available)."""
|
|
30
|
+
return await self.inbound.get()
|
|
31
|
+
|
|
32
|
+
async def publish_outbound(self, msg: OutboundMessage) -> None:
|
|
33
|
+
"""Publish a response from the agent to channels."""
|
|
34
|
+
await self.outbound.put(msg)
|
|
35
|
+
|
|
36
|
+
async def consume_outbound(self) -> OutboundMessage:
|
|
37
|
+
"""Consume the next outbound message (blocks until available)."""
|
|
38
|
+
return await self.outbound.get()
|
|
39
|
+
|
|
40
|
+
def subscribe_outbound(
|
|
41
|
+
self,
|
|
42
|
+
channel: str,
|
|
43
|
+
callback: Callable[[OutboundMessage], Awaitable[None]]
|
|
44
|
+
) -> None:
|
|
45
|
+
"""Subscribe to outbound messages for a specific channel."""
|
|
46
|
+
if channel not in self._outbound_subscribers:
|
|
47
|
+
self._outbound_subscribers[channel] = []
|
|
48
|
+
self._outbound_subscribers[channel].append(callback)
|
|
49
|
+
|
|
50
|
+
async def dispatch_outbound(self) -> None:
|
|
51
|
+
"""Dispatch outbound messages to subscribed channels.
|
|
52
|
+
|
|
53
|
+
Run this as a background task.
|
|
54
|
+
"""
|
|
55
|
+
self._running = True
|
|
56
|
+
while self._running:
|
|
57
|
+
try:
|
|
58
|
+
msg = await asyncio.wait_for(self.outbound.get(), timeout=1.0)
|
|
59
|
+
subscribers = self._outbound_subscribers.get(msg.channel, [])
|
|
60
|
+
for callback in subscribers:
|
|
61
|
+
try:
|
|
62
|
+
await callback(msg)
|
|
63
|
+
except Exception as e:
|
|
64
|
+
logger.error(f"Error dispatching to {msg.channel}: {e}")
|
|
65
|
+
except asyncio.TimeoutError:
|
|
66
|
+
continue
|
|
67
|
+
|
|
68
|
+
def stop(self) -> None:
|
|
69
|
+
"""Stop the dispatcher loop."""
|
|
70
|
+
self._running = False
|
|
71
|
+
|
|
72
|
+
@property
|
|
73
|
+
def inbound_size(self) -> int:
|
|
74
|
+
"""Number of pending inbound messages."""
|
|
75
|
+
return self.inbound.qsize()
|
|
76
|
+
|
|
77
|
+
@property
|
|
78
|
+
def outbound_size(self) -> int:
|
|
79
|
+
"""Number of pending outbound messages."""
|
|
80
|
+
return self.outbound.qsize()
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Chat channels for MindBot."""
|
|
2
|
+
|
|
3
|
+
from mindbot.channels.base import BaseChannel
|
|
4
|
+
from mindbot.channels.manager import ChannelManager
|
|
5
|
+
from mindbot.channels.feishu import FeishuChannel
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"BaseChannel",
|
|
9
|
+
"ChannelManager",
|
|
10
|
+
"FeishuChannel",
|
|
11
|
+
]
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
"""Base channel interface for chat platforms."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from loguru import logger
|
|
7
|
+
|
|
8
|
+
from mindbot.bus.events import InboundMessage, OutboundMessage
|
|
9
|
+
from mindbot.bus.queue import MessageBus
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class BaseChannel(ABC):
|
|
13
|
+
"""Abstract base class for chat channel implementations.
|
|
14
|
+
|
|
15
|
+
Each channel (Telegram, Discord, etc.) should implement this interface
|
|
16
|
+
to integrate with the mindbot message bus.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
name: str = "base"
|
|
20
|
+
|
|
21
|
+
def __init__(self, config: Any, bus: MessageBus):
|
|
22
|
+
"""Initialize the channel.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
config: Channel-specific configuration.
|
|
26
|
+
bus: The message bus for communication.
|
|
27
|
+
"""
|
|
28
|
+
self.config = config
|
|
29
|
+
self.bus = bus
|
|
30
|
+
self._running = False
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
async def start(self) -> None:
|
|
34
|
+
"""Start the channel and begin listening for messages.
|
|
35
|
+
|
|
36
|
+
This should be a long-running async task that:
|
|
37
|
+
1. Connects to the chat platform
|
|
38
|
+
2. Listens for incoming messages
|
|
39
|
+
3. Forwards messages to the bus via _handle_message()
|
|
40
|
+
"""
|
|
41
|
+
pass
|
|
42
|
+
|
|
43
|
+
@abstractmethod
|
|
44
|
+
async def stop(self) -> None:
|
|
45
|
+
"""Stop the channel and clean up resources."""
|
|
46
|
+
pass
|
|
47
|
+
|
|
48
|
+
@abstractmethod
|
|
49
|
+
async def send(self, msg: OutboundMessage) -> None:
|
|
50
|
+
"""Send a message through this channel.
|
|
51
|
+
|
|
52
|
+
Args:
|
|
53
|
+
msg: The message to send.
|
|
54
|
+
"""
|
|
55
|
+
pass
|
|
56
|
+
|
|
57
|
+
def is_allowed(self, sender_id: str) -> bool:
|
|
58
|
+
"""Check if a sender is allowed to use this bot.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
sender_id: The sender's identifier.
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
True if allowed, False otherwise.
|
|
65
|
+
"""
|
|
66
|
+
allow_list = getattr(self.config, "allow_from", [])
|
|
67
|
+
|
|
68
|
+
# If no allow list, allow everyone
|
|
69
|
+
if not allow_list:
|
|
70
|
+
return True
|
|
71
|
+
|
|
72
|
+
sender_str = str(sender_id)
|
|
73
|
+
if sender_str in allow_list:
|
|
74
|
+
return True
|
|
75
|
+
if "|" in sender_str:
|
|
76
|
+
for part in sender_str.split("|"):
|
|
77
|
+
if part and part in allow_list:
|
|
78
|
+
return True
|
|
79
|
+
return False
|
|
80
|
+
|
|
81
|
+
async def _handle_message(
|
|
82
|
+
self,
|
|
83
|
+
sender_id: str,
|
|
84
|
+
chat_id: str,
|
|
85
|
+
content: str,
|
|
86
|
+
media: list[str] | None = None,
|
|
87
|
+
metadata: dict[str, Any] | None = None
|
|
88
|
+
) -> None:
|
|
89
|
+
"""Handle an incoming message from the chat platform.
|
|
90
|
+
|
|
91
|
+
This method checks permissions and forwards to the bus.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
sender_id: The sender's identifier.
|
|
95
|
+
chat_id: The chat/channel identifier.
|
|
96
|
+
content: Message text content.
|
|
97
|
+
media: Optional list of media URLs.
|
|
98
|
+
metadata: Optional channel-specific metadata.
|
|
99
|
+
"""
|
|
100
|
+
if not self.is_allowed(sender_id):
|
|
101
|
+
logger.warning(
|
|
102
|
+
f"Access denied for sender {sender_id} on channel {self.name}. "
|
|
103
|
+
f"Add them to allow_from list in config to grant access."
|
|
104
|
+
)
|
|
105
|
+
return
|
|
106
|
+
|
|
107
|
+
msg = InboundMessage(
|
|
108
|
+
channel=self.name,
|
|
109
|
+
sender_id=str(sender_id),
|
|
110
|
+
chat_id=str(chat_id),
|
|
111
|
+
content=content,
|
|
112
|
+
media=media or [],
|
|
113
|
+
metadata=metadata or {}
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
await self.bus.publish_inbound(msg)
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def is_running(self) -> bool:
|
|
120
|
+
"""Check if the channel is running."""
|
|
121
|
+
return self._running
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"""CLI channel for MindBot."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from loguru import logger
|
|
7
|
+
|
|
8
|
+
from mindbot.bus.events import OutboundMessage
|
|
9
|
+
from mindbot.bus.queue import MessageBus
|
|
10
|
+
from mindbot.channels.base import BaseChannel
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class CLIChannel(BaseChannel):
|
|
14
|
+
"""CLI (stdin/stdout) channel for interactive terminal use.
|
|
15
|
+
|
|
16
|
+
This channel handles interactive command-line conversations.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
name: str = "cli"
|
|
20
|
+
|
|
21
|
+
def __init__(self, config: Any, bus: MessageBus):
|
|
22
|
+
super().__init__(config, bus)
|
|
23
|
+
self._input_task: asyncio.Task | None = None
|
|
24
|
+
|
|
25
|
+
async def start(self) -> None:
|
|
26
|
+
"""Start the CLI channel."""
|
|
27
|
+
self._running = True
|
|
28
|
+
self._input_task = asyncio.create_task(self._read_input())
|
|
29
|
+
logger.info("CLI channel started")
|
|
30
|
+
|
|
31
|
+
async def stop(self) -> None:
|
|
32
|
+
"""Stop the CLI channel."""
|
|
33
|
+
self._running = False
|
|
34
|
+
if self._input_task:
|
|
35
|
+
self._input_task.cancel()
|
|
36
|
+
try:
|
|
37
|
+
await self._input_task
|
|
38
|
+
except asyncio.CancelledError:
|
|
39
|
+
pass
|
|
40
|
+
logger.info("CLI channel stopped")
|
|
41
|
+
|
|
42
|
+
async def _read_input(self) -> None:
|
|
43
|
+
"""Read input from stdin and send to message bus."""
|
|
44
|
+
loop = asyncio.get_event_loop()
|
|
45
|
+
|
|
46
|
+
while self._running:
|
|
47
|
+
try:
|
|
48
|
+
# Read line from stdin
|
|
49
|
+
line = await loop.run_in_executor(None, input, ">>> ")
|
|
50
|
+
|
|
51
|
+
if not line.strip():
|
|
52
|
+
continue
|
|
53
|
+
|
|
54
|
+
if line.strip().lower() in ["exit", "quit", "bye"]:
|
|
55
|
+
break
|
|
56
|
+
|
|
57
|
+
await self._handle_message(
|
|
58
|
+
sender_id="cli_user",
|
|
59
|
+
chat_id="cli",
|
|
60
|
+
content=line.strip(),
|
|
61
|
+
metadata={"session_id": "default"}
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
except EOFError:
|
|
65
|
+
break
|
|
66
|
+
except Exception as e:
|
|
67
|
+
logger.error(f"Error reading CLI input: {e}")
|
|
68
|
+
|
|
69
|
+
async def send(self, msg: OutboundMessage) -> None:
|
|
70
|
+
"""Send a message to stdout."""
|
|
71
|
+
print(f"\n{msg.content}\n>>> ", end="")
|