mindroom 0.0.0__py3-none-any.whl → 0.1.0__py3-none-any.whl

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.
Files changed (155) hide show
  1. mindroom/__init__.py +3 -0
  2. mindroom/agent_prompts.py +963 -0
  3. mindroom/agents.py +248 -0
  4. mindroom/ai.py +421 -0
  5. mindroom/api/__init__.py +1 -0
  6. mindroom/api/credentials.py +137 -0
  7. mindroom/api/google_integration.py +355 -0
  8. mindroom/api/google_tools_helper.py +40 -0
  9. mindroom/api/homeassistant_integration.py +421 -0
  10. mindroom/api/integrations.py +189 -0
  11. mindroom/api/main.py +506 -0
  12. mindroom/api/matrix_operations.py +219 -0
  13. mindroom/api/tools.py +94 -0
  14. mindroom/background_tasks.py +87 -0
  15. mindroom/bot.py +2470 -0
  16. mindroom/cli.py +86 -0
  17. mindroom/commands.py +377 -0
  18. mindroom/config.py +343 -0
  19. mindroom/config_commands.py +324 -0
  20. mindroom/config_confirmation.py +411 -0
  21. mindroom/constants.py +52 -0
  22. mindroom/credentials.py +146 -0
  23. mindroom/credentials_sync.py +134 -0
  24. mindroom/custom_tools/__init__.py +8 -0
  25. mindroom/custom_tools/config_manager.py +765 -0
  26. mindroom/custom_tools/gmail.py +92 -0
  27. mindroom/custom_tools/google_calendar.py +92 -0
  28. mindroom/custom_tools/google_sheets.py +92 -0
  29. mindroom/custom_tools/homeassistant.py +341 -0
  30. mindroom/error_handling.py +35 -0
  31. mindroom/file_watcher.py +49 -0
  32. mindroom/interactive.py +313 -0
  33. mindroom/logging_config.py +207 -0
  34. mindroom/matrix/__init__.py +1 -0
  35. mindroom/matrix/client.py +782 -0
  36. mindroom/matrix/event_info.py +173 -0
  37. mindroom/matrix/identity.py +149 -0
  38. mindroom/matrix/large_messages.py +267 -0
  39. mindroom/matrix/mentions.py +141 -0
  40. mindroom/matrix/message_builder.py +94 -0
  41. mindroom/matrix/message_content.py +209 -0
  42. mindroom/matrix/presence.py +178 -0
  43. mindroom/matrix/rooms.py +311 -0
  44. mindroom/matrix/state.py +77 -0
  45. mindroom/matrix/typing.py +91 -0
  46. mindroom/matrix/users.py +217 -0
  47. mindroom/memory/__init__.py +21 -0
  48. mindroom/memory/config.py +137 -0
  49. mindroom/memory/functions.py +396 -0
  50. mindroom/py.typed +0 -0
  51. mindroom/response_tracker.py +128 -0
  52. mindroom/room_cleanup.py +139 -0
  53. mindroom/routing.py +107 -0
  54. mindroom/scheduling.py +758 -0
  55. mindroom/stop.py +207 -0
  56. mindroom/streaming.py +203 -0
  57. mindroom/teams.py +749 -0
  58. mindroom/thread_utils.py +318 -0
  59. mindroom/tools/__init__.py +520 -0
  60. mindroom/tools/agentql.py +64 -0
  61. mindroom/tools/airflow.py +57 -0
  62. mindroom/tools/apify.py +49 -0
  63. mindroom/tools/arxiv.py +64 -0
  64. mindroom/tools/aws_lambda.py +41 -0
  65. mindroom/tools/aws_ses.py +57 -0
  66. mindroom/tools/baidusearch.py +87 -0
  67. mindroom/tools/brightdata.py +116 -0
  68. mindroom/tools/browserbase.py +62 -0
  69. mindroom/tools/cal_com.py +98 -0
  70. mindroom/tools/calculator.py +112 -0
  71. mindroom/tools/cartesia.py +84 -0
  72. mindroom/tools/composio.py +166 -0
  73. mindroom/tools/config_manager.py +44 -0
  74. mindroom/tools/confluence.py +73 -0
  75. mindroom/tools/crawl4ai.py +101 -0
  76. mindroom/tools/csv.py +104 -0
  77. mindroom/tools/custom_api.py +106 -0
  78. mindroom/tools/dalle.py +85 -0
  79. mindroom/tools/daytona.py +180 -0
  80. mindroom/tools/discord.py +81 -0
  81. mindroom/tools/docker.py +73 -0
  82. mindroom/tools/duckdb.py +124 -0
  83. mindroom/tools/duckduckgo.py +99 -0
  84. mindroom/tools/e2b.py +121 -0
  85. mindroom/tools/eleven_labs.py +77 -0
  86. mindroom/tools/email.py +74 -0
  87. mindroom/tools/exa.py +246 -0
  88. mindroom/tools/fal.py +50 -0
  89. mindroom/tools/file.py +80 -0
  90. mindroom/tools/financial_datasets_api.py +112 -0
  91. mindroom/tools/firecrawl.py +124 -0
  92. mindroom/tools/gemini.py +85 -0
  93. mindroom/tools/giphy.py +49 -0
  94. mindroom/tools/github.py +376 -0
  95. mindroom/tools/gmail.py +102 -0
  96. mindroom/tools/google_calendar.py +55 -0
  97. mindroom/tools/google_maps.py +112 -0
  98. mindroom/tools/google_sheets.py +86 -0
  99. mindroom/tools/googlesearch.py +83 -0
  100. mindroom/tools/groq.py +77 -0
  101. mindroom/tools/hackernews.py +54 -0
  102. mindroom/tools/jina.py +108 -0
  103. mindroom/tools/jira.py +70 -0
  104. mindroom/tools/linear.py +103 -0
  105. mindroom/tools/linkup.py +65 -0
  106. mindroom/tools/lumalabs.py +71 -0
  107. mindroom/tools/mem0.py +82 -0
  108. mindroom/tools/modelslabs.py +85 -0
  109. mindroom/tools/moviepy_video_tools.py +62 -0
  110. mindroom/tools/newspaper4k.py +63 -0
  111. mindroom/tools/openai.py +143 -0
  112. mindroom/tools/openweather.py +89 -0
  113. mindroom/tools/oxylabs.py +54 -0
  114. mindroom/tools/pandas.py +35 -0
  115. mindroom/tools/pubmed.py +64 -0
  116. mindroom/tools/python.py +120 -0
  117. mindroom/tools/reddit.py +155 -0
  118. mindroom/tools/replicate.py +56 -0
  119. mindroom/tools/resend.py +55 -0
  120. mindroom/tools/scrapegraph.py +87 -0
  121. mindroom/tools/searxng.py +120 -0
  122. mindroom/tools/serpapi.py +55 -0
  123. mindroom/tools/serper.py +81 -0
  124. mindroom/tools/shell.py +46 -0
  125. mindroom/tools/slack.py +80 -0
  126. mindroom/tools/sleep.py +38 -0
  127. mindroom/tools/spider.py +62 -0
  128. mindroom/tools/sql.py +138 -0
  129. mindroom/tools/tavily.py +104 -0
  130. mindroom/tools/telegram.py +54 -0
  131. mindroom/tools/todoist.py +103 -0
  132. mindroom/tools/trello.py +121 -0
  133. mindroom/tools/twilio.py +97 -0
  134. mindroom/tools/web_browser_tools.py +37 -0
  135. mindroom/tools/webex.py +63 -0
  136. mindroom/tools/website.py +45 -0
  137. mindroom/tools/whatsapp.py +81 -0
  138. mindroom/tools/wikipedia.py +45 -0
  139. mindroom/tools/x.py +97 -0
  140. mindroom/tools/yfinance.py +121 -0
  141. mindroom/tools/youtube.py +81 -0
  142. mindroom/tools/zendesk.py +62 -0
  143. mindroom/tools/zep.py +107 -0
  144. mindroom/tools/zoom.py +62 -0
  145. mindroom/tools_metadata.json +7643 -0
  146. mindroom/tools_metadata.py +220 -0
  147. mindroom/topic_generator.py +153 -0
  148. mindroom/voice_handler.py +266 -0
  149. mindroom-0.1.0.dist-info/METADATA +425 -0
  150. mindroom-0.1.0.dist-info/RECORD +152 -0
  151. {mindroom-0.0.0.dist-info → mindroom-0.1.0.dist-info}/WHEEL +1 -2
  152. mindroom-0.1.0.dist-info/entry_points.txt +2 -0
  153. mindroom-0.0.0.dist-info/METADATA +0 -24
  154. mindroom-0.0.0.dist-info/RECORD +0 -4
  155. mindroom-0.0.0.dist-info/top_level.txt +0 -1
mindroom/cli.py ADDED
@@ -0,0 +1,86 @@
1
+ """Mindroom CLI - Simplified multi-agent Matrix bot system."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import asyncio
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ import typer
10
+ from rich.console import Console
11
+
12
+ from mindroom import __version__
13
+ from mindroom.bot import main as bot_main
14
+ from mindroom.constants import STORAGE_PATH
15
+
16
+ app = typer.Typer(
17
+ help="Mindroom: Multi-agent Matrix bot system",
18
+ pretty_exceptions_enable=True,
19
+ # Disable showing locals which can be very large (also see `setup_logging`)
20
+ pretty_exceptions_show_locals=False,
21
+ )
22
+ console = Console()
23
+
24
+
25
+ @app.command()
26
+ def version() -> None:
27
+ """Show the current version of Mindroom."""
28
+ console.print(f"Mindroom version: [bold]{__version__}[/bold]")
29
+ console.print("Multi-agent Matrix bot system")
30
+
31
+
32
+ @app.command()
33
+ def run(
34
+ log_level: str = typer.Option(
35
+ "INFO",
36
+ "--log-level",
37
+ "-l",
38
+ help="Set the logging level (DEBUG, INFO, WARNING, ERROR)",
39
+ case_sensitive=False,
40
+ ),
41
+ storage_path: Path = typer.Option( # noqa: B008
42
+ Path(STORAGE_PATH),
43
+ "--storage-path",
44
+ "-s",
45
+ help="Base directory for persistent MindRoom data (state, sessions, tracking)",
46
+ ),
47
+ ) -> None:
48
+ """Run the mindroom multi-agent system.
49
+
50
+ This command starts the multi-agent bot system which automatically:
51
+ - Creates all necessary user and agent accounts
52
+ - Creates all rooms defined in config.yaml
53
+ - Manages agent room memberships
54
+ """
55
+ asyncio.run(_run(log_level=log_level.upper(), storage_path=storage_path))
56
+
57
+
58
+ async def _run(log_level: str, storage_path: Path) -> None:
59
+ """Run the multi-agent system."""
60
+ console.print(f"🚀 Starting Mindroom multi-agent system (log level: {log_level})...")
61
+ console.print("Press Ctrl+C to stop\n")
62
+
63
+ try:
64
+ await bot_main(log_level=log_level, storage_path=storage_path)
65
+ except KeyboardInterrupt:
66
+ console.print("\n✋ Stopped")
67
+
68
+
69
+ def main() -> None:
70
+ """Main entry point that shows help by default."""
71
+ # Handle -h flag by replacing with --help
72
+ for i, arg in enumerate(sys.argv):
73
+ if arg == "-h":
74
+ sys.argv[i] = "--help"
75
+ break
76
+
77
+ # If no arguments provided, show help
78
+ if len(sys.argv) == 1:
79
+ # Show help by appending --help to argv
80
+ sys.argv.append("--help")
81
+
82
+ app()
83
+
84
+
85
+ if __name__ == "__main__":
86
+ main()
mindroom/commands.py ADDED
@@ -0,0 +1,377 @@
1
+ """Command parsing and handling for user commands."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from dataclasses import dataclass
7
+ from enum import Enum
8
+ from typing import Any
9
+
10
+ import nio
11
+
12
+ from .constants import VOICE_PREFIX
13
+ from .logging_config import get_logger
14
+
15
+ logger = get_logger(__name__)
16
+
17
+
18
+ class CommandType(Enum):
19
+ """Types of commands supported."""
20
+
21
+ HELP = "help"
22
+ SCHEDULE = "schedule"
23
+ LIST_SCHEDULES = "list_schedules"
24
+ CANCEL_SCHEDULE = "cancel_schedule"
25
+ WIDGET = "widget"
26
+ CONFIG = "config" # Configuration command
27
+ HI = "hi" # Welcome message command
28
+ UNKNOWN = "unknown" # Special type for unrecognized commands
29
+
30
+
31
+ # Command documentation for each command type
32
+ COMMAND_DOCS = {
33
+ CommandType.SCHEDULE: ("!schedule <task>", "Schedule a task"),
34
+ CommandType.LIST_SCHEDULES: ("!list_schedules", "List scheduled tasks"),
35
+ CommandType.CANCEL_SCHEDULE: ("!cancel_schedule <id>", "Cancel a scheduled task"),
36
+ CommandType.HELP: ("!help [topic]", "Get help"),
37
+ CommandType.WIDGET: ("!widget [url]", "Add configuration widget"),
38
+ CommandType.CONFIG: ("!config <operation>", "Manage configuration"),
39
+ CommandType.HI: ("!hi", "Show welcome message"),
40
+ }
41
+
42
+
43
+ def _get_command_entries(format_code: bool = False) -> list[str]:
44
+ """Get command entries as a list of formatted strings.
45
+
46
+ Args:
47
+ format_code: If True, wrap commands in backticks for markdown
48
+
49
+ Returns:
50
+ List of formatted command strings
51
+
52
+ """
53
+ entries = []
54
+ for cmd_type in CommandType:
55
+ if cmd_type in COMMAND_DOCS and cmd_type != CommandType.UNKNOWN:
56
+ syntax, description = COMMAND_DOCS[cmd_type]
57
+ if format_code:
58
+ entries.append(f"- `{syntax}` - {description}")
59
+ else:
60
+ entries.append(f"- {syntax} - {description}")
61
+ return entries
62
+
63
+
64
+ def get_command_list() -> str:
65
+ """Get a formatted list of all available commands.
66
+
67
+ Returns:
68
+ Formatted string with all commands and their descriptions
69
+
70
+ """
71
+ lines = ["Available commands:", *_get_command_entries(format_code=False)]
72
+ return "\n".join(lines)
73
+
74
+
75
+ @dataclass
76
+ class Command:
77
+ """Parsed command with arguments."""
78
+
79
+ type: CommandType
80
+ args: dict[str, Any]
81
+ raw_text: str
82
+
83
+
84
+ class CommandParser:
85
+ """Parser for user commands in messages."""
86
+
87
+ # Command patterns
88
+ HELP_PATTERN = re.compile(r"^!help(?:\s+(.+))?$", re.IGNORECASE)
89
+ SCHEDULE_PATTERN = re.compile(r"^!schedule\s+(.+)$", re.IGNORECASE | re.DOTALL)
90
+ LIST_SCHEDULES_PATTERN = re.compile(r"^!list[_-]?schedules?$", re.IGNORECASE)
91
+ CANCEL_SCHEDULE_PATTERN = re.compile(r"^!cancel[_-]?schedule\s+(.+)$", re.IGNORECASE)
92
+ WIDGET_PATTERN = re.compile(r"^!widget(?:\s+(.+))?$", re.IGNORECASE)
93
+ CONFIG_PATTERN = re.compile(r"^!config(?:\s+(.+))?$", re.IGNORECASE)
94
+ HI_PATTERN = re.compile(r"^!hi$", re.IGNORECASE)
95
+
96
+ def parse(self, message: str) -> Command | None: # noqa: PLR0911
97
+ """Parse a message for commands.
98
+
99
+ Args:
100
+ message: The message text to parse
101
+
102
+ Returns:
103
+ Parsed command or None if no command found
104
+
105
+ """
106
+ message = message.strip()
107
+
108
+ # Handle voice emoji prefixe (e.g., "🎤 !schedule ...")
109
+ message = message.removeprefix(VOICE_PREFIX)
110
+ if not message.startswith("!"):
111
+ return None
112
+
113
+ # Try to match each command pattern
114
+
115
+ # !hi command (check this early as it's simple)
116
+ if self.HI_PATTERN.match(message):
117
+ return Command(
118
+ type=CommandType.HI,
119
+ args={},
120
+ raw_text=message,
121
+ )
122
+
123
+ # !help command
124
+ match = self.HELP_PATTERN.match(message)
125
+ if match:
126
+ topic = match.group(1)
127
+ return Command(
128
+ type=CommandType.HELP,
129
+ args={"topic": topic},
130
+ raw_text=message,
131
+ )
132
+
133
+ # !schedule command
134
+ match = self.SCHEDULE_PATTERN.match(message)
135
+ if match:
136
+ full_text = match.group(1).strip()
137
+ # Pass the entire text to AI - it will parse both time and message
138
+ return Command(
139
+ type=CommandType.SCHEDULE,
140
+ args={"full_text": full_text},
141
+ raw_text=message,
142
+ )
143
+
144
+ # !list_schedules command
145
+ if self.LIST_SCHEDULES_PATTERN.match(message):
146
+ return Command(
147
+ type=CommandType.LIST_SCHEDULES,
148
+ args={},
149
+ raw_text=message,
150
+ )
151
+
152
+ # !cancel_schedule command
153
+ match = self.CANCEL_SCHEDULE_PATTERN.match(message)
154
+ if match:
155
+ task_id = match.group(1).strip()
156
+ # Check if user wants to cancel all tasks
157
+ cancel_all = task_id.lower() == "all"
158
+ return Command(
159
+ type=CommandType.CANCEL_SCHEDULE,
160
+ args={"task_id": task_id, "cancel_all": cancel_all},
161
+ raw_text=message,
162
+ )
163
+
164
+ # !widget command
165
+ match = self.WIDGET_PATTERN.match(message)
166
+ if match:
167
+ url = match.group(1).strip() if match.group(1) else None
168
+ return Command(
169
+ type=CommandType.WIDGET,
170
+ args={"url": url},
171
+ raw_text=message,
172
+ )
173
+
174
+ # !config command
175
+ match = self.CONFIG_PATTERN.match(message)
176
+ if match:
177
+ args_text = match.group(1).strip() if match.group(1) else ""
178
+ return Command(
179
+ type=CommandType.CONFIG,
180
+ args={"args_text": args_text},
181
+ raw_text=message,
182
+ )
183
+
184
+ # Unknown command - return a special Command indicating it's unknown
185
+ logger.debug(f"Unknown command: {message}")
186
+ return Command(
187
+ type=CommandType.UNKNOWN,
188
+ args={"raw_command": message},
189
+ raw_text=message,
190
+ )
191
+
192
+
193
+ def get_command_help(topic: str | None = None) -> str:
194
+ """Get help text for commands.
195
+
196
+ Args:
197
+ topic: Specific topic to get help for (optional)
198
+
199
+ Returns:
200
+ Help text
201
+
202
+ """
203
+ if topic == "schedule":
204
+ return """**Schedule Command**
205
+
206
+ Usage: `!schedule <time> <message>` - Schedule tasks, reminders, or agent workflows
207
+
208
+ **Simple Reminders:**
209
+ - `!schedule in 5 minutes Check the deployment`
210
+ - `!schedule tomorrow at 3pm Send the weekly report`
211
+ - `!schedule later Ping me about the meeting`
212
+ - `ping me tomorrow about the meeting`
213
+ - `remind me in 2 hours to review PRs`
214
+
215
+ **Event-Driven Workflows (New!):**
216
+ - `!schedule If I get an email about "urgent", @phone_agent call me`
217
+ - `!schedule When Bitcoin drops below $40k, @crypto_agent notify me`
218
+ - `!schedule If server CPU > 80%, @ops_agent scale up`
219
+ - `!schedule When someone mentions our product on Reddit, @analyst summarize it`
220
+ - `!schedule Whenever I get email from boss, @notification_agent alert me immediately`
221
+
222
+ **Agent Workflows:**
223
+ - `!schedule Daily at 9am, @finance give me a market analysis`
224
+ - `!schedule Every Monday, @research AI news and @email_assistant send me a summary`
225
+ - `!schedule tomorrow at 2pm, @email_assistant check my Gmail`
226
+
227
+ **Recurring Tasks (Cron-style):**
228
+ - `!schedule Every hour, @shell check server status`
229
+ - `!schedule Daily at 9am, @finance market report`
230
+ - `!schedule Weekly on Friday, @analyst prepare weekly summary`
231
+
232
+ How it works:
233
+ - **Time-based**: Executes at specific times or intervals
234
+ - **Event-based**: Automatically converts to smart polling (e.g., "if email" → check every 1-2 min)
235
+ - Agents receive clear instructions about conditions to check
236
+ - Multiple agents collaborate when mentioned together
237
+ - Automated tasks are clearly marked so agents don't wait for follow-up"""
238
+
239
+ if topic == "list_schedules":
240
+ return """**List Schedules Command**
241
+
242
+ Usage: `!list_schedules` or `!listschedules`
243
+
244
+ Shows all pending scheduled tasks in this thread."""
245
+
246
+ if topic in {"cancel", "cancel_schedule"}:
247
+ return """**Cancel Schedule Command**
248
+
249
+ Usage: `!cancel_schedule <id>` - Cancel a scheduled task
250
+ `!cancel_schedule all` - Cancel ALL scheduled tasks in this room
251
+
252
+ Examples:
253
+ - `!cancel_schedule abc123` - Cancel the task with ID abc123
254
+ - `!cancel_schedule all` - Cancel all scheduled tasks (requires confirmation)
255
+
256
+ Use `!list_schedules` to see task IDs."""
257
+
258
+ if topic == "config":
259
+ return """**Config Command**
260
+
261
+ Usage: `!config <operation>` - View and modify MindRoom configuration
262
+
263
+ **Viewing Configuration:**
264
+ - `!config show` - Show entire configuration
265
+ - `!config get <path>` - Get a specific configuration value
266
+ - `!config get agents` - Show all agents
267
+ - `!config get models.default` - Show default model
268
+ - `!config get agents.analyst.display_name` - Show analyst's display name
269
+
270
+ **Modifying Configuration:**
271
+ - `!config set <path> <value>` - Set a configuration value
272
+ - `!config set agents.analyst.display_name "Research Expert"` - Change display name
273
+ - `!config set models.default.id gpt-4` - Change default model
274
+ - `!config set defaults.markdown false` - Disable markdown by default
275
+ - `!config set timezone America/New_York` - Set timezone
276
+
277
+ **Path Syntax:**
278
+ - Use dot notation to navigate nested config (e.g., `agents.analyst.role`)
279
+ - Arrays use indexes (e.g., `agents.analyst.tools.0` for first tool)
280
+ - String values with spaces must be quoted
281
+
282
+ **Note:** Configuration changes are immediately saved to config.yaml and affect all new agent interactions."""
283
+
284
+ if topic == "widget":
285
+ return """**Widget Command**
286
+
287
+ Usage: `!widget [url]` - Add the MindRoom configuration widget to this room
288
+
289
+ Examples:
290
+ - `!widget` - Add widget using default URL (http://localhost:3003)
291
+ - `!widget https://config.mindroom.ai` - Add widget from custom URL
292
+
293
+ The widget provides a visual interface for configuring MindRoom agents and settings.
294
+ Pin it to keep it visible in the room.
295
+
296
+ Note: Widget support requires Element Desktop or self-hosted Element Web."""
297
+
298
+ # General help - dynamically generated from COMMAND_DOCS
299
+ commands_text = "\n".join(_get_command_entries(format_code=True))
300
+
301
+ return f"""**Available Commands**
302
+
303
+ {commands_text}
304
+
305
+ **Scheduling Features:**
306
+ - Time-based and event-driven workflows
307
+ - Recurring tasks with cron-style scheduling (daily, weekly, hourly)
308
+ - Agent workflows - mention agents to have them collaborate on scheduled tasks
309
+ - Natural language time parsing - "tomorrow", "in 5 minutes", "every Monday"
310
+
311
+ Note: All commands only work within threads, not in main room messages
312
+ (except !widget which works in the main room).
313
+
314
+ For detailed help on a command, use: `!help <command>`"""
315
+
316
+
317
+ async def handle_widget_command(
318
+ client: nio.AsyncClient,
319
+ room_id: str,
320
+ url: str | None = None,
321
+ ) -> str:
322
+ """Handle the widget command to add configuration widget to room.
323
+
324
+ Args:
325
+ client: The Matrix client
326
+ room_id: The room ID to add widget to
327
+ url: Optional custom widget URL
328
+
329
+ Returns:
330
+ Response text for the user
331
+
332
+ """
333
+ # Default URL for local development
334
+ default_url = "http://localhost:3003/matrix-widget.html"
335
+ widget_url = url if url else default_url
336
+
337
+ # Create the widget state event content
338
+ widget_content = {
339
+ "type": "custom",
340
+ "url": widget_url,
341
+ "name": "MindRoom Configuration",
342
+ "data": {"title": "MindRoom Configuration", "curl": widget_url.replace("/matrix-widget.html", "")},
343
+ "creatorUserId": client.user_id,
344
+ "id": "mindroom_config",
345
+ }
346
+
347
+ try:
348
+ # Send the state event to add the widget
349
+ response = await client.room_put_state(
350
+ room_id=room_id,
351
+ event_type="im.vector.modular.widgets",
352
+ state_key="mindroom_config",
353
+ content=widget_content,
354
+ )
355
+
356
+ if isinstance(response, nio.RoomPutStateError):
357
+ logger.error(f"Failed to add widget to room {room_id}: {response.message}")
358
+ return f"❌ Failed to add widget: {response.message}"
359
+
360
+ logger.info(f"Successfully added widget to room {room_id}")
361
+ except Exception as e:
362
+ logger.exception("Error adding widget to room", room_id=room_id)
363
+ return f"❌ Error adding widget: {e!s}"
364
+ else:
365
+ return (
366
+ "✅ **MindRoom Configuration widget added!**\n\n"
367
+ "• Pin the widget to keep it visible\n"
368
+ "• All room members can access the configuration\n"
369
+ "• Changes sync in real-time with config.yaml\n\n"
370
+ f"Widget URL: {widget_url}\n\n"
371
+ "**Note:** Widgets require Element Desktop or self-hosted Element Web.\n"
372
+ "Alternatively, you can use: `/addwidget {url}` in Element."
373
+ )
374
+
375
+
376
+ # Global parser instance
377
+ command_parser = CommandParser()