mindroot 9.3.0__py3-none-any.whl → 9.5.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 (183) hide show
  1. mindroot/coreplugins/admin/__init__.py +3 -1
  2. mindroot/coreplugins/admin/agent_router.py +250 -7
  3. mindroot/coreplugins/admin/asset_manager.py +164 -0
  4. mindroot/coreplugins/admin/command_router.py +236 -1
  5. mindroot/coreplugins/admin/mcp_catalog_routes.py +156 -0
  6. mindroot/coreplugins/admin/mcp_publish_routes.py +450 -0
  7. mindroot/coreplugins/admin/mcp_registry_routes.py +495 -0
  8. mindroot/coreplugins/admin/mcp_routes.py +216 -0
  9. mindroot/coreplugins/admin/mod.py +62 -0
  10. mindroot/coreplugins/admin/oauth_callback_router.py +84 -0
  11. mindroot/coreplugins/admin/persona_handler.py +15 -6
  12. mindroot/coreplugins/admin/persona_router.py +158 -2
  13. mindroot/coreplugins/admin/plugin_manager.py +63 -0
  14. mindroot/coreplugins/admin/plugin_router_fixed.py +23 -0
  15. mindroot/coreplugins/admin/plugin_router_new_not_working.py +145 -0
  16. mindroot/coreplugins/admin/plugin_routes.py +114 -0
  17. mindroot/coreplugins/admin/registry_settings_routes.py +140 -0
  18. mindroot/coreplugins/admin/router.py +116 -15
  19. mindroot/coreplugins/admin/service_models.py +1 -1
  20. mindroot/coreplugins/admin/settings_router.py +1 -0
  21. mindroot/coreplugins/admin/static/css/admin-custom.css +357 -2
  22. mindroot/coreplugins/admin/static/css/dark.css +1 -0
  23. mindroot/coreplugins/admin/static/css/default.css +4 -0
  24. mindroot/coreplugins/admin/static/js/about-info.js +367 -0
  25. mindroot/coreplugins/admin/static/js/agent-form.js +83 -3
  26. mindroot/coreplugins/admin/static/js/api-key-script.js +307 -0
  27. mindroot/coreplugins/admin/static/js/mcp-manager.js +348 -0
  28. mindroot/coreplugins/admin/static/js/mcp-publisher.js +780 -0
  29. mindroot/coreplugins/admin/static/js/persona-editor.js +34 -5
  30. mindroot/coreplugins/admin/static/js/plugin-toggle.js +1 -1
  31. mindroot/coreplugins/admin/static/js/recommended-plugin-install.js +63 -0
  32. mindroot/coreplugins/admin/static/js/registry-auth-section.js +132 -0
  33. mindroot/coreplugins/admin/static/js/registry-manager-base.js +613 -0
  34. mindroot/coreplugins/admin/static/js/registry-manager-old.js +385 -0
  35. mindroot/coreplugins/admin/static/js/registry-manager-publish-old-delete.js +166 -0
  36. mindroot/coreplugins/admin/static/js/registry-manager.js +351 -0
  37. mindroot/coreplugins/admin/static/js/registry-publish-section.js +377 -0
  38. mindroot/coreplugins/admin/static/js/registry-search-section.js +400 -0
  39. mindroot/coreplugins/admin/static/js/registry-search-section.js.bak +3 -0
  40. mindroot/coreplugins/admin/static/js/registry-settings.js +69 -0
  41. mindroot/coreplugins/admin/static/js/registry-shared-services.js +857 -0
  42. mindroot/coreplugins/admin/static/js/registry-simple-sections.js +85 -0
  43. mindroot/coreplugins/admin/static/js/secure-widget-manager.js +438 -0
  44. mindroot/coreplugins/admin/static/logo.png +0 -0
  45. mindroot/coreplugins/admin/templates/admin.jinja2 +275 -110
  46. mindroot/coreplugins/agent/Assistant/agent.json +27 -11
  47. mindroot/coreplugins/agent/agent.py +2 -2
  48. mindroot/coreplugins/agent/command_parser.py +25 -10
  49. mindroot/coreplugins/agent/templates/system.jinja2 +0 -12
  50. mindroot/coreplugins/chat/__init__.py +4 -1
  51. mindroot/coreplugins/chat/router.py +132 -20
  52. mindroot/coreplugins/chat/router_dedup_patch.py +20 -0
  53. mindroot/coreplugins/chat/services.py +31 -1
  54. mindroot/coreplugins/chat/static/css/action-fix.css +32 -0
  55. mindroot/coreplugins/chat/static/css/admin-custom.css +5 -3
  56. mindroot/coreplugins/chat/static/css/dark.css +24 -3
  57. mindroot/coreplugins/chat/static/css/default.css +24 -3
  58. mindroot/coreplugins/chat/static/css/main.css +1 -0
  59. mindroot/coreplugins/chat/static/js/action.js +137 -60
  60. mindroot/coreplugins/chat/static/js/chat-history.js +3 -0
  61. mindroot/coreplugins/chat/static/js/chat.js +59 -16
  62. mindroot/coreplugins/chat/static/js/chat.js.diff +221 -0
  63. mindroot/coreplugins/chat/static/js/chatform.js +2 -2
  64. mindroot/coreplugins/chat/static/site.webmanifest +1 -1
  65. mindroot/coreplugins/chat/templates/chat.jinja2 +3 -3
  66. mindroot/coreplugins/chat/widget_manager.py +139 -0
  67. mindroot/coreplugins/chat/widget_routes.py +287 -0
  68. mindroot/coreplugins/check_list/inject/admin.jinja2 +1 -1
  69. mindroot/coreplugins/email/__init__.py +2 -0
  70. mindroot/coreplugins/email/email_provider.py +2 -2
  71. mindroot/coreplugins/email/mod.py +100 -0
  72. mindroot/coreplugins/email/services.py +5 -3
  73. mindroot/coreplugins/email/smtp_handler.py +9 -3
  74. mindroot/coreplugins/email/test_email_service.py +75 -0
  75. mindroot/coreplugins/env_manager/mod.py +61 -25
  76. mindroot/coreplugins/home/router.py +37 -2
  77. mindroot/coreplugins/home/static/imgs/logo.png +0 -0
  78. mindroot/coreplugins/home/static/imgs/logo.png.bak +0 -0
  79. mindroot/coreplugins/home/static/imgs/logo_teal.png +0 -0
  80. mindroot/coreplugins/home/static/imgs/logo_teal2.png +0 -0
  81. mindroot/coreplugins/home/static/imgs/logo_teal_detailed.png +0 -0
  82. mindroot/coreplugins/home/static/imgs/logo_teal_python.png +0 -0
  83. mindroot/coreplugins/home/templates/home.jinja2 +15 -6
  84. mindroot/coreplugins/index/indices/default/index.json +6 -6
  85. mindroot/coreplugins/jwt_auth/middleware.py +47 -2
  86. mindroot/coreplugins/jwt_auth/mod.py +40 -17
  87. mindroot/coreplugins/l8n/__init__.py +6 -0
  88. mindroot/coreplugins/l8n/debug_loader.py +85 -0
  89. mindroot/coreplugins/l8n/debug_middleware.py +74 -0
  90. mindroot/coreplugins/l8n/l8n_constants.py +19 -0
  91. mindroot/coreplugins/l8n/language_detection.py +183 -0
  92. mindroot/coreplugins/l8n/middleware.py +151 -0
  93. mindroot/coreplugins/l8n/mod.py +277 -0
  94. mindroot/coreplugins/l8n/monkey_patch_to_delete.py +186 -0
  95. mindroot/coreplugins/l8n/test_enhanced.py +298 -0
  96. mindroot/coreplugins/l8n/test_l8n.py +95 -0
  97. mindroot/coreplugins/l8n/test_l8n_standalone.py +251 -0
  98. mindroot/coreplugins/l8n/test_middleware.py +272 -0
  99. mindroot/coreplugins/l8n/utils.py +232 -0
  100. mindroot/coreplugins/mcp_/__init__.py +14 -0
  101. mindroot/coreplugins/mcp_/catalog_commands.py +328 -0
  102. mindroot/coreplugins/mcp_/catalog_manager.py +263 -0
  103. mindroot/coreplugins/mcp_/dynamic_commands.py +154 -0
  104. mindroot/coreplugins/mcp_/mcp_manager.py +1031 -0
  105. mindroot/coreplugins/mcp_/mod.py +367 -0
  106. mindroot/coreplugins/mcp_/oauth_storage.py +144 -0
  107. mindroot/coreplugins/mcp_/server_installer.py +79 -0
  108. mindroot/coreplugins/mcp_/setup.py +26 -0
  109. mindroot/coreplugins/mcp_/test_dynamic_commands.py +134 -0
  110. mindroot/coreplugins/mcp_/testmcpclient.py +92 -0
  111. mindroot/coreplugins/persona/mod.py +12 -7
  112. mindroot/coreplugins/signup/templates/signup.jinja2 +1 -1
  113. mindroot/coreplugins/subscriptions/__init__.py +1 -0
  114. mindroot/coreplugins/subscriptions/mod.py +14 -3
  115. mindroot/coreplugins/subscriptions/router.py +3 -0
  116. mindroot/coreplugins/user_service/__init__.py +1 -2
  117. mindroot/coreplugins/user_service/admin_init.py +1 -0
  118. mindroot/coreplugins/user_service/email_service.py +72 -17
  119. mindroot/coreplugins/user_service/mod.py +10 -2
  120. mindroot/coreplugins/user_service/router.py +2 -0
  121. mindroot/lib/auth/api_key.py +28 -0
  122. mindroot/lib/cli/plugins.py +94 -0
  123. mindroot/lib/plugins/default_plugin_manifest.json +20 -0
  124. mindroot/lib/plugins/installation.py +5 -5
  125. mindroot/lib/plugins/l8n_static_handler.py +225 -0
  126. mindroot/lib/plugins/loader.py +33 -3
  127. mindroot/lib/plugins/loader_with_l8n.py +281 -0
  128. mindroot/lib/plugins/manifest.py +236 -24
  129. mindroot/lib/providers/commands.py +3 -1
  130. mindroot/lib/route_decorators.py +5 -5
  131. mindroot/lib/templates.py +183 -11
  132. mindroot/lib/utils/merge_arrays.py +1 -1
  133. mindroot/migrate.py +39 -20
  134. mindroot/registry/data_access.py +1 -1
  135. mindroot/server.py +42 -13
  136. mindroot/server_missing_normal_args.py +197 -0
  137. mindroot/server_prev.py +173 -0
  138. {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/METADATA +7 -2
  139. {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/RECORD +144 -112
  140. mindroot/coreplugins/admin/static/favicon/about.txt +0 -6
  141. mindroot/coreplugins/admin/static/favicon/android-chrome-512x512.png +0 -0
  142. mindroot/coreplugins/admin/static/favicon/apple-touch-icon.png +0 -0
  143. mindroot/coreplugins/admin/static/favicon/favicon-16x16.png +0 -0
  144. mindroot/coreplugins/admin/static/favicon/favicon-32x32.png +0 -0
  145. mindroot/coreplugins/admin/static/favicon/favicon.ico +0 -0
  146. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/about.txt +0 -6
  147. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/android-chrome-192x192.png +0 -0
  148. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/android-chrome-512x512.png +0 -0
  149. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/apple-touch-icon.png +0 -0
  150. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon-16x16.png +0 -0
  151. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon-32x32.png +0 -0
  152. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon.ico +0 -0
  153. mindroot/coreplugins/admin/static/favicon/favicon_io (1)/site.webmanifest +0 -1
  154. mindroot/coreplugins/admin/static/favicon/logo.png +0 -0
  155. mindroot/coreplugins/admin/static/favicon/site.webmanifest +0 -1
  156. mindroot/coreplugins/admin/static/js/backup/agent-editor.js +0 -186
  157. mindroot/coreplugins/admin/static/js/backup/agent-form.js +0 -1133
  158. mindroot/coreplugins/admin/static/js/backup/agent-list.js +0 -94
  159. mindroot/coreplugins/chat/static/favicon/about.txt +0 -6
  160. mindroot/coreplugins/chat/static/favicon/android-chrome-192x192.png +0 -0
  161. mindroot/coreplugins/chat/static/favicon/android-chrome-512x512.png +0 -0
  162. mindroot/coreplugins/chat/static/favicon/apple-touch-icon.png +0 -0
  163. mindroot/coreplugins/chat/static/favicon/favicon-16x16.png +0 -0
  164. mindroot/coreplugins/chat/static/favicon/favicon-32x32.png +0 -0
  165. mindroot/coreplugins/chat/static/favicon/favicon.ico +0 -0
  166. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/about.txt +0 -6
  167. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/android-chrome-192x192.png +0 -0
  168. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/android-chrome-512x512.png +0 -0
  169. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/apple-touch-icon.png +0 -0
  170. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon-16x16.png +0 -0
  171. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon-32x32.png +0 -0
  172. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon.ico +0 -0
  173. mindroot/coreplugins/chat/static/favicon/favicon_io (1)/site.webmanifest +0 -1
  174. mindroot/coreplugins/chat/static/favicon/logo.png +0 -0
  175. mindroot/coreplugins/chat/static/favicon/site.webmanifest +0 -1
  176. mindroot/coreplugins/index/default.json +0 -76
  177. mindroot/coreplugins/user_service/file_trigger_service.py +0 -12
  178. mindroot/coreplugins/user_service/hooks.py +0 -23
  179. /mindroot/coreplugins/{admin/static/favicon/android-chrome-192x192.png → home/static/imgs/backuplogo.png} +0 -0
  180. {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/WHEEL +0 -0
  181. {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/entry_points.txt +0 -0
  182. {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/licenses/LICENSE +0 -0
  183. {mindroot-9.3.0.dist-info → mindroot-9.5.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Test script to verify dynamic command registration for both uvx and npx MCP servers
4
+ """
5
+
6
+ import asyncio
7
+ import sys
8
+ import os
9
+
10
+ # Add the mindroot lib to path
11
+ sys.path.insert(0, '/files/mindroot/src/mindroot')
12
+
13
+ from .mod import mcp_manager, MCPServer
14
+ from src.mr_mcp.dynamic_commands import MCPDynamicCommands
15
+ from lib.providers.commands import command_manager
16
+
17
+ async def test_dynamic_command_registration():
18
+ print("Testing dynamic command registration for MCP servers...")
19
+
20
+ # Check current registered commands
21
+ print(f"\nCurrently registered commands: {len(command_manager.functions)}")
22
+ mcp_commands = [name for name in command_manager.functions.keys() if name.startswith('mcp_')]
23
+ print(f"Current MCP commands: {mcp_commands}")
24
+
25
+ # Test creating both uvx and npx servers
26
+ print("\n=== Testing UVX Server (Calculator) ===")
27
+
28
+ uvx_server = EnhancedMCPServer(
29
+ name="test_calculator",
30
+ description="Test calculator server via uvx",
31
+ command="uvx",
32
+ args=["mcp-server-calculator"],
33
+ install_method="uvx",
34
+ install_package="mcp-server-calculator",
35
+ auto_install=True
36
+ )
37
+
38
+ enhanced_mcp_manager.add_server("test_calculator", uvx_server)
39
+ print(f"Added uvx server: {uvx_server.name}")
40
+ print(f"Command: {uvx_server.command} {' '.join(uvx_server.args)}")
41
+
42
+ print("\n=== Testing NPX Server (GitHub) ===")
43
+
44
+ npx_server = EnhancedMCPServer(
45
+ name="test_github",
46
+ description="Test GitHub server via npx",
47
+ command="npx",
48
+ args=["-y", "@modelcontextprotocol/server-github"],
49
+ install_method="npx",
50
+ install_package="@modelcontextprotocol/server-github",
51
+ auto_install=True
52
+ )
53
+
54
+ enhanced_mcp_manager.add_server("test_github", npx_server)
55
+ print(f"Added npx server: {npx_server.name}")
56
+ print(f"Command: {npx_server.command} {' '.join(npx_server.args)}")
57
+
58
+ # Check if dynamic commands object is properly initialized
59
+ print(f"\n=== Dynamic Commands System ===")
60
+ print(f"Dynamic commands object: {enhanced_mcp_manager.dynamic_commands}")
61
+ print(f"Sessions reference set: {bool(enhanced_mcp_manager.dynamic_commands.sessions)}")
62
+ print(f"Registered dynamic commands: {enhanced_mcp_manager.dynamic_commands.get_registered_commands()}")
63
+
64
+ # Test the registration process manually
65
+ print("\n=== Manual Tool Registration Test ===")
66
+
67
+ # Create a mock tool object
68
+ class MockTool:
69
+ def __init__(self, name, description):
70
+ self.name = name
71
+ self.description = description
72
+ self.inputSchema = {
73
+ "type": "object",
74
+ "properties": {
75
+ "expression": {
76
+ "type": "string",
77
+ "description": "Mathematical expression to evaluate"
78
+ }
79
+ },
80
+ "required": ["expression"]
81
+ }
82
+
83
+ mock_tools = [MockTool("calculate", "Perform mathematical calculations")]
84
+
85
+ print("Testing tool registration for mock calculator...")
86
+ await enhanced_mcp_manager.dynamic_commands.register_tools("test_calculator", mock_tools)
87
+
88
+ # Check if command was registered
89
+ expected_cmd = "mcp_test_calculator_calculate"
90
+ if expected_cmd in command_manager.functions:
91
+ print(f"✅ SUCCESS: Command {expected_cmd} was registered!")
92
+ cmd_info = command_manager.functions[expected_cmd]
93
+ if hasattr(cmd_info, 'docstring'):
94
+ print(f" Docstring: {cmd_info.docstring[:100]}...")
95
+ else:
96
+ print(f" Command info: {type(cmd_info)}")
97
+ else:
98
+ print(f"❌ FAILED: Command {expected_cmd} was not registered")
99
+ print(f" Available commands: {list(command_manager.functions.keys())[-5:]}")
100
+
101
+ # Test with npx server
102
+ print("\nTesting tool registration for mock GitHub server...")
103
+
104
+ github_tools = [MockTool("create_repository", "Create a new GitHub repository")]
105
+ await enhanced_mcp_manager.dynamic_commands.register_tools("test_github", github_tools)
106
+
107
+ expected_cmd2 = "mcp_test_github_create_repository"
108
+ if expected_cmd2 in command_manager.functions:
109
+ print(f"✅ SUCCESS: Command {expected_cmd2} was registered!")
110
+ else:
111
+ print(f"❌ FAILED: Command {expected_cmd2} was not registered")
112
+
113
+ # Final summary
114
+ print(f"\n=== Final Summary ===")
115
+ final_mcp_commands = [name for name in command_manager.functions.keys() if name.startswith('mcp_')]
116
+ print(f"Total MCP commands now: {len(final_mcp_commands)}")
117
+ print(f"Dynamic commands: {[cmd for cmd in final_mcp_commands if 'test_' in cmd]}")
118
+
119
+ print("\n=== Conclusion ===")
120
+ print("The dynamic command registration system works the same for both uvx and npx servers.")
121
+ print("The issue you're experiencing might be:")
122
+ print("1. The MCP server isn't actually connecting successfully")
123
+ print("2. The server doesn't expose tools (some servers only have resources/prompts)")
124
+ print("3. There's an error during the connection process")
125
+ print("4. The admin interface cache needs to be refreshed")
126
+
127
+ print("\nTo debug further:")
128
+ print("1. Check the server logs when connecting")
129
+ print("2. Verify the server actually starts and responds")
130
+ print("3. Check if the server exposes tools vs just resources")
131
+ print("4. Try refreshing the admin interface after connection")
132
+
133
+ if __name__ == "__main__":
134
+ asyncio.run(test_dynamic_command_registration())
@@ -0,0 +1,92 @@
1
+ """
2
+ Before running, specify running MCP RS server URL.
3
+ To spin up RS server locally, see
4
+ examples/servers/simple-auth/README.md
5
+
6
+ cd to the `examples/snippets` directory and run:
7
+ uv run oauth-client
8
+ """
9
+
10
+ import asyncio
11
+ from urllib.parse import parse_qs, urlparse
12
+
13
+ from pydantic import AnyUrl
14
+
15
+ from mcp import ClientSession
16
+ from mcp.client.auth import OAuthClientProvider, TokenStorage
17
+ from mcp.client.streamable_http import streamablehttp_client
18
+ from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken
19
+
20
+
21
+ class InMemoryTokenStorage(TokenStorage):
22
+ """Demo In-memory token storage implementation."""
23
+
24
+ def __init__(self):
25
+ self.tokens: OAuthToken | None = None
26
+ self.client_info: OAuthClientInformationFull | None = None
27
+
28
+ async def get_tokens(self) -> OAuthToken | None:
29
+ """Get stored tokens."""
30
+ return self.tokens
31
+
32
+ async def set_tokens(self, tokens: OAuthToken) -> None:
33
+ """Store tokens."""
34
+ self.tokens = tokens
35
+
36
+ async def get_client_info(self) -> OAuthClientInformationFull | None:
37
+ """Get stored client information."""
38
+ return self.client_info
39
+
40
+ async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
41
+ """Store client information."""
42
+ self.client_info = client_info
43
+
44
+
45
+ async def handle_redirect(auth_url: str) -> None:
46
+ print(f"Visit: {auth_url}")
47
+
48
+
49
+ async def handle_callback() -> tuple[str, str | None]:
50
+ callback_url = input("Paste callback URL: ")
51
+ params = parse_qs(urlparse(callback_url).query)
52
+ print("Callback URL parameters:")
53
+ print(params)
54
+ return params["code"][0], params.get("state", [None])[0]
55
+
56
+
57
+ async def main():
58
+ """Run the OAuth client example."""
59
+ oauth_auth = OAuthClientProvider(
60
+ server_url="https://mcp.notion.com",
61
+ client_metadata=OAuthClientMetadata(
62
+ client_name="Example MCP Client",
63
+ redirect_uris=[AnyUrl("http://localhost:3000/callback")],
64
+ grant_types=["authorization_code", "refresh_token"],
65
+ response_types=["code"],
66
+ scope="user",
67
+ ),
68
+ storage=InMemoryTokenStorage(),
69
+ redirect_handler=handle_redirect,
70
+ callback_handler=handle_callback,
71
+ )
72
+
73
+ print("Starting.")
74
+ async with streamablehttp_client("https://mcp.notion.com/sse", auth=oauth_auth) as (read, write, _):
75
+ print("1")
76
+ async with ClientSession(read, write) as session:
77
+ print("2")
78
+ await session.initialize()
79
+ print("3")
80
+ tools = await session.list_tools()
81
+ print(f"Available tools: {[tool.name for tool in tools.tools]}")
82
+
83
+ resources = await session.list_resources()
84
+ print(f"Available resources: {[r.uri for r in resources.resources]}")
85
+
86
+
87
+ def run():
88
+ asyncio.run(main())
89
+
90
+
91
+ if __name__ == "__main__":
92
+ run()
@@ -12,15 +12,20 @@ from .init_persona import *
12
12
  @service()
13
13
  async def get_persona_data(persona_name, context=None):
14
14
  print("persona name is", persona_name, file=sys.stderr)
15
- # use pwd as base dir
16
- # current working dir of process
17
15
  pwd = os.getcwd()
18
- persona_path = os.path.join(pwd, 'personas', 'local', persona_name)
19
- if not os.path.exists(persona_path):
20
- persona_path = os.path.join(pwd, 'personas', 'shared', persona_name)
16
+
17
+ # Handle registry personas: registry/owner/name
18
+ if persona_name.startswith('registry/'):
19
+ persona_path = os.path.join(pwd, 'personas', persona_name)
20
+ else:
21
+ # Legacy support: check local first, then shared
22
+ persona_path = os.path.join(pwd, 'personas', 'local', persona_name)
21
23
  if not os.path.exists(persona_path):
22
- # need to raise an error here
23
- raise Exception(f"Persona {persona_name} not found in {persona_path}")
24
+ persona_path = os.path.join(pwd, 'personas', 'shared', persona_name)
25
+
26
+ if not os.path.exists(persona_path):
27
+ # need to raise an error here
28
+ raise Exception(f"Persona {persona_name} not found in {persona_path}")
24
29
 
25
30
  # read the persona data
26
31
  # use blue background and yellow text
@@ -130,7 +130,7 @@
130
130
  <body>
131
131
  <div class="signup-container">
132
132
  <div class="logo-container">
133
- <img src="/home/static/imgs/logo.png" alt="MindRoot Logo" class="logo">
133
+ <img src="/imgs/logo.png" alt="MindRoot Logo" class="logo">
134
134
  <h1>Create Your MindRoot Account</h1>
135
135
  </div>
136
136
 
@@ -3,3 +3,4 @@ import asyncio
3
3
 
4
4
  # Import all services and commands
5
5
  from .mod import *
6
+
@@ -266,7 +266,6 @@ async def get_available_features(active_only: bool = True, context=None) -> List
266
266
 
267
267
  features = await _subscription_manager.storage.get_all_features(active_only)
268
268
  return [feature.to_dict() for feature in features]
269
-
270
269
  @service()
271
270
  async def create_plan_feature(feature_data: Dict[str, Any], context=None) -> Dict[str, Any]:
272
271
  """Create a new plan feature
@@ -549,6 +548,19 @@ async def process_subscription_event(event_data: Dict[str, Any], context=None) -
549
548
  }
550
549
  )
551
550
 
551
+ # Notify any plugins that handle subscription creation
552
+ try:
553
+ from lib.providers.services import service_manager
554
+ await service_manager.handle_subscription_created(
555
+ user_id=username,
556
+ subscription_id=subscription.subscription_id,
557
+ plan_id=plan_id,
558
+ metadata=metadata
559
+ )
560
+ logger.info(f"Notified subscription handlers for {username}")
561
+ except Exception as e:
562
+ logger.error(f"Failed to notify subscription handlers: {e}")
563
+
552
564
  return {
553
565
  'status': 'success',
554
566
  'event_type': event_type,
@@ -558,7 +570,6 @@ async def process_subscription_event(event_data: Dict[str, Any], context=None) -
558
570
  else:
559
571
  logger.error(f"Plan not found: {plan_id}")
560
572
  return {'status': 'error', 'message': f"Plan not found: {plan_id}"}
561
-
562
573
  elif event_type == 'subscription_renewed':
563
574
  # Handle subscription renewal
564
575
  provider_subscription_id = normalized_event.get('subscription_id')
@@ -840,4 +851,4 @@ async def get_my_subscriptions(params, context=None):
840
851
 
841
852
  result += "\n"
842
853
 
843
- return result
854
+ return result
@@ -235,7 +235,9 @@ async def api_create_checkout(plan_id: str,
235
235
  provider: str = Query('stripe')):
236
236
  """Create checkout session for subscription"""
237
237
  try:
238
+ print("checkout")
238
239
  if not request.state.user:
240
+ print("Unauthorized")
239
241
  raise UNAUTHORIZED
240
242
 
241
243
  username = request.state.user.username
@@ -243,6 +245,7 @@ async def api_create_checkout(plan_id: str,
243
245
  # Get plan details
244
246
  plan = await get_subscription_plan(plan_id, context=request)
245
247
  if not plan:
248
+ print("could not find plan",plan_id)
246
249
  raise NOT_FOUND
247
250
 
248
251
  # Call provider-specific checkout service
@@ -6,5 +6,4 @@ from .admin_init import *
6
6
 
7
7
  from .password_reset_service import *
8
8
  from .router import router
9
- from .file_trigger_service import *
10
- from .hooks import *
9
+
@@ -93,6 +93,7 @@ async def initialize_admin(user_data_root: str, app) -> Tuple[Optional[str], Opt
93
93
 
94
94
  if not (username and password):
95
95
  username, password = generate_random_credentials()
96
+ username = 'admin'
96
97
  print("\n" + "="*50)
97
98
  print("INITIAL ADMIN CREDENTIALS GENERATED:")
98
99
  print(f"Username: {username}")
@@ -1,33 +1,88 @@
1
- #from mindroot.coreplugins.email.mod import EmailMessage
2
1
  from lib.providers.services import service_manager
3
2
  from datetime import datetime, timedelta
4
3
  import secrets
5
4
  import os
5
+ import logging
6
+
7
+ logger = logging.getLogger(__name__)
6
8
 
7
9
  REQUIRE_EMAIL_VERIFY = os.environ.get('REQUIRE_EMAIL_VERIFY', '').lower() == 'true'
10
+ BASE_URL = os.environ.get('BASE_URL', 'http://localhost:8011')
8
11
 
9
12
  async def send_verification_email(email: str, verification_token: str):
10
13
  """Send email verification link to user."""
11
- verification_url = f"http://localhost:8011/verify-email?token={verification_token}"
14
+ verification_url = f"{BASE_URL}/verify-email?token={verification_token}"
15
+
12
16
  email_html = f"""
13
- <h1>Welcome to MindRoot!</h1>
14
- <p>Please verify your email address by clicking the link below:</p>
15
- <p><a href="{verification_url}">{verification_url}</a></p>
16
- <p>This link will expire in 24 hours.</p>
17
- <br>
18
- <p>If you did not create this account, please ignore this email.</p>
17
+ <html>
18
+ <body>
19
+ <h1>Welcome to MindRoot!</h1>
20
+ <p>Please verify your email address by clicking the link below:</p>
21
+ <p><a href="{verification_url}" style="background-color: #4CAF50; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px;">Verify Email</a></p>
22
+ <p>Or copy and paste this link into your browser:</p>
23
+ <p><code>{verification_url}</code></p>
24
+ <p>This link will expire in 24 hours.</p>
25
+ <br>
26
+ <p><small>If you did not create this account, please ignore this email.</small></p>
27
+ </body>
28
+ </html>
19
29
  """
20
- print("Not implemented")
21
- return False
30
+
22
31
  try:
23
- #await service_manager.send_email(EmailMessage(
24
- # to=email,
25
- # subject="Verify Your MindRoot Account",
26
- # body=email_html
27
- #))
28
- return True
32
+ result = await service_manager.send_email(
33
+ to=email,
34
+ subject="Verify Your MindRoot Account",
35
+ body=email_html # HTML content will be auto-detected
36
+ )
37
+
38
+ if result.get('success'):
39
+ logger.info(f"Verification email sent successfully to {email}")
40
+ return True
41
+ else:
42
+ logger.error(f"Failed to send verification email to {email}: {result.get('error')}")
43
+ return False
44
+
45
+ except Exception as e:
46
+ logger.error(f"Exception sending verification email to {email}: {e}")
47
+ return False
48
+
49
+ async def send_password_reset_email(email: str, username: str, reset_token: str):
50
+ """Send password reset email to user."""
51
+ reset_url = f"{BASE_URL}/reset-password?token={reset_token}"
52
+
53
+ email_html = f"""
54
+ <html>
55
+ <body>
56
+ <h1>Password Reset Request</h1>
57
+ <p>Hello {username},</p>
58
+ <p>You have requested to reset your password for your MindRoot account.</p>
59
+ <p>Click the link below to reset your password:</p>
60
+ <p><a href="{reset_url}" style="background-color: #f44336; color: white; padding: 10px 20px; text-decoration: none; border-radius: 4px;">Reset Password</a></p>
61
+ <p>Or copy and paste this link into your browser:</p>
62
+ <p><code>{reset_url}</code></p>
63
+ <p>This link will expire in 1 hour.</p>
64
+ <br>
65
+ <p><small>If you did not request this password reset, please ignore this email.</small></p>
66
+ </body>
67
+ </html>
68
+ """
69
+
70
+ try:
71
+ result = await service_manager.send_email(
72
+ to=email,
73
+ subject="Password Reset Request - MindRoot",
74
+ body=email_html # HTML content will be auto-detected
75
+ )
76
+
77
+ if result.get('success'):
78
+ logger.info(f"Password reset email sent successfully to {email}")
79
+ return True
80
+ else:
81
+ logger.error(f"Failed to send password reset email to {email}: {result.get('error')}")
82
+ return False
83
+
29
84
  except Exception as e:
30
- print(f"Warning: Could not send verification email: {e}")
85
+ logger.error(f"Exception sending password reset email to {email}: {e}")
31
86
  return False
32
87
 
33
88
  def setup_verification() -> tuple[str, str, bool]:
@@ -70,20 +70,28 @@ async def create_user(user_data: UserCreate, roles: List[str] = None, skip_verif
70
70
  async def verify_user(username: str, password: str, context=None) -> bool:
71
71
  """Verify user credentials and update last login"""
72
72
  auth_file = os.path.join(USER_DATA_ROOT, username, "auth.json")
73
-
73
+
74
74
  if not os.path.exists(auth_file):
75
+ print("user not found")
76
+ print("path", auth_file)
77
+ print("working dir", os.getcwd())
75
78
  return False
76
79
 
77
80
  with open(auth_file, 'r') as f:
78
81
  auth_data = UserAuth(**json.load(f))
79
82
 
80
83
  if bcrypt.checkpw(password.encode(), auth_data.password_hash.encode()):
84
+ print("check pw passed")
81
85
  # Update last login
82
86
  auth_data.last_login = datetime.utcnow().isoformat()
83
87
  with open(auth_file, 'w') as f:
84
88
  json.dump(auth_data.dict(), f, indent=2, default=str)
85
89
  return True
86
- return False
90
+ else:
91
+ print("checkpw failed,")
92
+ print("auth_data", auth_data)
93
+ print("password:", password.encode())
94
+ return False
87
95
 
88
96
 
89
97
  @service()
@@ -17,6 +17,8 @@ router = APIRouter()
17
17
  @router.get("/reset-password/{filename}")
18
18
  async def get_reset_password_form_by_file(request: Request, filename: str):
19
19
  """Show password reset form if trigger file exists"""
20
+ # print the current working directory
21
+ print("Current working directory:", os.getcwd())
20
22
  trigger_dir = "data/password_resets"
21
23
  file_path = os.path.join(trigger_dir, f"{filename}")
22
24
  print("file path", file_path)
@@ -0,0 +1,28 @@
1
+ from typing import Optional, Dict
2
+
3
+ async def verify_api_key(api_key: str) -> Optional[Dict]:
4
+ """
5
+ Verify an API key and return user data if valid.
6
+
7
+ Args:
8
+ api_key: The API key to verify
9
+
10
+ Returns:
11
+ Dict containing user data if valid, None otherwise
12
+ """
13
+ try:
14
+ # Import here to avoid circular imports
15
+ from mindroot.coreplugins.api_keys.api_key_manager import api_key_manager
16
+
17
+ key_data = api_key_manager.validate_key(api_key)
18
+ if key_data:
19
+ return {
20
+ 'username': key_data['username'],
21
+ 'api_key': api_key,
22
+ 'created_at': key_data['created_at'],
23
+ 'description': key_data.get('description', '')
24
+ }
25
+ return None
26
+ except Exception as e:
27
+ print(f"Error verifying API key: {e}")
28
+ return None
@@ -0,0 +1,94 @@
1
+ import sys
2
+ import os
3
+ import asyncio
4
+ from termcolor import colored
5
+ from ..plugins.installation import download_github_files
6
+ from ..plugins.manifest import update_plugin_manifest
7
+
8
+ async def _stream_subprocess(cmd):
9
+ process = await asyncio.create_subprocess_exec(
10
+ *cmd,
11
+ stdout=asyncio.subprocess.PIPE,
12
+ stderr=asyncio.subprocess.PIPE
13
+ )
14
+
15
+ async def read_stream(stream, prefix):
16
+ while True:
17
+ line = await stream.readline()
18
+ if line:
19
+ print(f"{prefix}{line.decode().strip()}")
20
+ else:
21
+ break
22
+
23
+ await asyncio.gather(
24
+ read_stream(process.stdout, ''),
25
+ read_stream(process.stderr, colored('ERR: ', 'red'))
26
+ )
27
+
28
+ await process.wait()
29
+ return process.returncode
30
+
31
+ async def install_plugins_from_cli(plugin_sources: list, reinstall: bool = False):
32
+ """
33
+ Install plugins from the command line, streaming output.
34
+ """
35
+ print(colored(f"Attempting to install {len(plugin_sources)} plugins...", "cyan"))
36
+ results = []
37
+
38
+ for plugin_source in plugin_sources:
39
+ plugin_name = plugin_source.split('/')[-1]
40
+ print(colored(f"\n=== Installing {plugin_name} ===", "yellow"))
41
+
42
+ try:
43
+ if not reinstall:
44
+ import pkg_resources
45
+ try:
46
+ pkg_resources.get_distribution(plugin_name)
47
+ print(colored(f"{plugin_name} is already installed. Use --reinstall to update or force.", "green"))
48
+ results.append({"plugin": plugin_name, "status": "already_installed"})
49
+ continue
50
+ except pkg_resources.DistributionNotFound:
51
+ pass # Not installed, proceed
52
+
53
+ if '/' in plugin_source: # GitHub source
54
+ print(f"Installing from GitHub: {plugin_source}")
55
+ plugin_dir, _, plugin_info = download_github_files(plugin_source)
56
+ cmd = [sys.executable, '-m', 'pip', 'install', '-e', plugin_dir]
57
+ if reinstall:
58
+ cmd.append('--force-reinstall')
59
+ return_code = await _stream_subprocess(cmd)
60
+
61
+ if return_code == 0:
62
+ print(colored(f"Successfully installed {plugin_name} from {plugin_source}", "green"))
63
+ update_plugin_manifest(
64
+ plugin_info['name'],
65
+ 'github',
66
+ os.path.abspath(plugin_dir),
67
+ remote_source=plugin_source,
68
+ version=plugin_info.get('version', '0.0.1'),
69
+ metadata=plugin_info
70
+ )
71
+ results.append({"plugin": plugin_name, "status": "success", "source": "github"})
72
+ else:
73
+ raise Exception(f"pip install failed with exit code {return_code}")
74
+
75
+ else: # PyPI source
76
+ print(f"Installing from PyPI: {plugin_name}")
77
+ cmd = [sys.executable, '-m', 'pip', 'install', plugin_name]
78
+ if reinstall:
79
+ cmd.extend(['--upgrade', '--force-reinstall'])
80
+ return_code = await _stream_subprocess(cmd)
81
+
82
+ if return_code == 0:
83
+ print(colored(f"Successfully installed {plugin_name} from PyPI", "green"))
84
+ update_plugin_manifest(plugin_name, 'pypi', None)
85
+ results.append({"plugin": plugin_name, "status": "success", "source": "pypi"})
86
+ else:
87
+ raise Exception(f"pip install failed with exit code {return_code}")
88
+
89
+ except Exception as e:
90
+ print(colored(f"ERROR: Failed to install {plugin_name}: {str(e)}", "red"))
91
+ results.append({"plugin": plugin_name, "status": "error", "message": str(e)})
92
+
93
+ print(colored("\nPlugin installation process finished.", "cyan"))
94
+ # You can optionally print a summary of results here
@@ -21,10 +21,18 @@
21
21
  "enabled": true,
22
22
  "source": "core"
23
23
  },
24
+ "l8n": {
25
+ "enabled": true,
26
+ "source": "core"
27
+ },
24
28
  "jwt_auth": {
25
29
  "enabled": true,
26
30
  "source": "core"
27
31
  },
32
+ "mcp_": {
33
+ "enabled": true,
34
+ "source": "core"
35
+ },
28
36
  "api_keys": {
29
37
  "enabled": true,
30
38
  "source": "core"
@@ -45,6 +53,14 @@
45
53
  "enabled": true,
46
54
  "source": "core"
47
55
  },
56
+ "signup": {
57
+ "enabled": true,
58
+ "source": "core"
59
+ },
60
+ "email": {
61
+ "enabled": true,
62
+ "source": "core"
63
+ },
48
64
  "user_service": {
49
65
  "enabled": true,
50
66
  "source": "core"
@@ -65,6 +81,10 @@
65
81
  "enabled": false,
66
82
  "source": "core"
67
83
  },
84
+ "subscriptions": {
85
+ "enabled": false,
86
+ "source": "core"
87
+ },
68
88
  "startup": {
69
89
  "enabled": true,
70
90
  "source": "core"