mindroot 9.3.0__py3-none-any.whl → 9.6.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.
- mindroot/coreplugins/admin/__init__.py +3 -1
- mindroot/coreplugins/admin/agent_router.py +250 -7
- mindroot/coreplugins/admin/asset_manager.py +164 -0
- mindroot/coreplugins/admin/command_router.py +236 -1
- mindroot/coreplugins/admin/mcp_catalog_routes.py +156 -0
- mindroot/coreplugins/admin/mcp_publish_routes.py +450 -0
- mindroot/coreplugins/admin/mcp_registry_routes.py +495 -0
- mindroot/coreplugins/admin/mcp_routes.py +216 -0
- mindroot/coreplugins/admin/mod.py +62 -0
- mindroot/coreplugins/admin/oauth_callback_router.py +84 -0
- mindroot/coreplugins/admin/persona_handler.py +15 -6
- mindroot/coreplugins/admin/persona_router.py +158 -2
- mindroot/coreplugins/admin/plugin_manager.py +105 -9
- mindroot/coreplugins/admin/plugin_router_fixed.py +23 -0
- mindroot/coreplugins/admin/plugin_router_new_not_working.py +145 -0
- mindroot/coreplugins/admin/plugin_routes.py +114 -0
- mindroot/coreplugins/admin/registry_settings_routes.py +140 -0
- mindroot/coreplugins/admin/router.py +116 -15
- mindroot/coreplugins/admin/service_models.py +1 -1
- mindroot/coreplugins/admin/settings_router.py +1 -0
- mindroot/coreplugins/admin/static/css/admin-custom.css +357 -2
- mindroot/coreplugins/admin/static/css/dark.css +1 -0
- mindroot/coreplugins/admin/static/css/default.css +4 -0
- mindroot/coreplugins/admin/static/js/about-info.js +367 -0
- mindroot/coreplugins/admin/static/js/agent-form.js +83 -3
- mindroot/coreplugins/admin/static/js/api-key-script.js +307 -0
- mindroot/coreplugins/admin/static/js/mcp-manager.js +348 -0
- mindroot/coreplugins/admin/static/js/mcp-publisher.js +780 -0
- mindroot/coreplugins/admin/static/js/persona-editor.js +34 -5
- mindroot/coreplugins/admin/static/js/plugin-toggle.js +1 -1
- mindroot/coreplugins/admin/static/js/recommended-plugin-install.js +63 -0
- mindroot/coreplugins/admin/static/js/registry-auth-section.js +132 -0
- mindroot/coreplugins/admin/static/js/registry-manager-base.js +613 -0
- mindroot/coreplugins/admin/static/js/registry-manager-publish-old-delete.js +166 -0
- mindroot/coreplugins/admin/static/js/registry-manager.js +351 -0
- mindroot/coreplugins/admin/static/js/registry-publish-section.js +377 -0
- mindroot/coreplugins/admin/static/js/registry-search-section.js +400 -0
- mindroot/coreplugins/admin/static/js/registry-search-section.js.bak +3 -0
- mindroot/coreplugins/admin/static/js/registry-settings.js +69 -0
- mindroot/coreplugins/admin/static/js/registry-shared-services.js +903 -0
- mindroot/coreplugins/admin/static/js/registry-simple-sections.js +85 -0
- mindroot/coreplugins/admin/static/js/secure-widget-manager.js +438 -0
- mindroot/coreplugins/admin/static/logo.png +0 -0
- mindroot/coreplugins/admin/templates/admin.jinja2 +275 -110
- mindroot/coreplugins/agent/Assistant/agent.json +27 -11
- mindroot/coreplugins/agent/agent.py +2 -2
- mindroot/coreplugins/agent/command_parser.py +25 -10
- mindroot/coreplugins/agent/templates/system.jinja2 +0 -12
- mindroot/coreplugins/chat/__init__.py +4 -1
- mindroot/coreplugins/chat/router.py +132 -20
- mindroot/coreplugins/chat/router_dedup_patch.py +20 -0
- mindroot/coreplugins/chat/services.py +31 -1
- mindroot/coreplugins/chat/static/css/action-fix.css +32 -0
- mindroot/coreplugins/chat/static/css/admin-custom.css +5 -3
- mindroot/coreplugins/chat/static/css/dark.css +24 -3
- mindroot/coreplugins/chat/static/css/default.css +24 -3
- mindroot/coreplugins/chat/static/css/main.css +1 -0
- mindroot/coreplugins/chat/static/js/action.js +137 -60
- mindroot/coreplugins/chat/static/js/chat-history.js +3 -0
- mindroot/coreplugins/chat/static/js/chat.js +59 -16
- mindroot/coreplugins/chat/static/js/chat.js.diff +221 -0
- mindroot/coreplugins/chat/static/js/chatform.js +2 -2
- mindroot/coreplugins/chat/static/site.webmanifest +1 -1
- mindroot/coreplugins/chat/templates/chat.jinja2 +3 -3
- mindroot/coreplugins/chat/widget_manager.py +139 -0
- mindroot/coreplugins/chat/widget_routes.py +287 -0
- mindroot/coreplugins/check_list/inject/admin.jinja2 +1 -1
- mindroot/coreplugins/email/__init__.py +2 -0
- mindroot/coreplugins/email/email_provider.py +2 -2
- mindroot/coreplugins/email/mod.py +100 -0
- mindroot/coreplugins/email/services.py +5 -3
- mindroot/coreplugins/email/smtp_handler.py +9 -3
- mindroot/coreplugins/email/test_email_service.py +75 -0
- mindroot/coreplugins/env_manager/mod.py +61 -25
- mindroot/coreplugins/home/router.py +37 -2
- mindroot/coreplugins/home/static/imgs/logo.png +0 -0
- mindroot/coreplugins/home/static/imgs/logo.png.bak +0 -0
- mindroot/coreplugins/home/static/imgs/logo_teal.png +0 -0
- mindroot/coreplugins/home/static/imgs/logo_teal2.png +0 -0
- mindroot/coreplugins/home/static/imgs/logo_teal_detailed.png +0 -0
- mindroot/coreplugins/home/static/imgs/logo_teal_python.png +0 -0
- mindroot/coreplugins/home/templates/home.jinja2 +15 -6
- mindroot/coreplugins/index/indices/default/index.json +39 -6
- mindroot/coreplugins/jwt_auth/middleware.py +47 -2
- mindroot/coreplugins/jwt_auth/mod.py +40 -17
- mindroot/coreplugins/l8n/__init__.py +6 -0
- mindroot/coreplugins/l8n/debug_loader.py +85 -0
- mindroot/coreplugins/l8n/debug_middleware.py +74 -0
- mindroot/coreplugins/l8n/l8n_constants.py +19 -0
- mindroot/coreplugins/l8n/language_detection.py +183 -0
- mindroot/coreplugins/l8n/middleware.py +151 -0
- mindroot/coreplugins/l8n/mod.py +277 -0
- mindroot/coreplugins/l8n/monkey_patch_to_delete.py +186 -0
- mindroot/coreplugins/l8n/test_enhanced.py +298 -0
- mindroot/coreplugins/l8n/test_l8n.py +95 -0
- mindroot/coreplugins/l8n/test_l8n_standalone.py +251 -0
- mindroot/coreplugins/l8n/test_middleware.py +272 -0
- mindroot/coreplugins/l8n/utils.py +232 -0
- mindroot/coreplugins/mcp_/__init__.py +14 -0
- mindroot/coreplugins/mcp_/catalog_commands.py +328 -0
- mindroot/coreplugins/mcp_/catalog_manager.py +263 -0
- mindroot/coreplugins/mcp_/dynamic_commands.py +154 -0
- mindroot/coreplugins/mcp_/mcp_manager.py +1031 -0
- mindroot/coreplugins/mcp_/mod.py +367 -0
- mindroot/coreplugins/mcp_/oauth_storage.py +144 -0
- mindroot/coreplugins/mcp_/server_installer.py +79 -0
- mindroot/coreplugins/mcp_/setup.py +26 -0
- mindroot/coreplugins/mcp_/test_dynamic_commands.py +134 -0
- mindroot/coreplugins/mcp_/testmcpclient.py +92 -0
- mindroot/coreplugins/persona/mod.py +12 -7
- mindroot/coreplugins/signup/templates/signup.jinja2 +1 -1
- mindroot/coreplugins/subscriptions/__init__.py +1 -0
- mindroot/coreplugins/subscriptions/mod.py +14 -3
- mindroot/coreplugins/subscriptions/router.py +3 -0
- mindroot/coreplugins/user_service/__init__.py +1 -2
- mindroot/coreplugins/user_service/admin_init.py +1 -0
- mindroot/coreplugins/user_service/email_service.py +72 -17
- mindroot/coreplugins/user_service/mod.py +10 -2
- mindroot/coreplugins/user_service/router.py +2 -0
- mindroot/lib/auth/api_key.py +28 -0
- mindroot/lib/cli/plugins.py +94 -0
- mindroot/lib/plugins/default_plugin_manifest.json +20 -0
- mindroot/lib/plugins/installation.py +5 -5
- mindroot/lib/plugins/l8n_static_handler.py +225 -0
- mindroot/lib/plugins/loader.py +33 -3
- mindroot/lib/plugins/loader_with_l8n.py +281 -0
- mindroot/lib/plugins/manifest.py +236 -24
- mindroot/lib/providers/commands.py +3 -1
- mindroot/lib/route_decorators.py +5 -5
- mindroot/lib/templates.py +183 -11
- mindroot/lib/utils/merge_arrays.py +1 -1
- mindroot/migrate.py +39 -20
- mindroot/registry/data_access.py +1 -1
- mindroot/server.py +42 -13
- mindroot/server_missing_normal_args.py +197 -0
- mindroot/server_prev.py +173 -0
- {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/METADATA +7 -2
- {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/RECORD +143 -113
- mindroot/coreplugins/admin/plugin_manager_backup.py +0 -615
- mindroot/coreplugins/admin/static/favicon/about.txt +0 -6
- mindroot/coreplugins/admin/static/favicon/android-chrome-512x512.png +0 -0
- mindroot/coreplugins/admin/static/favicon/apple-touch-icon.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon-16x16.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon-32x32.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon.ico +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/about.txt +0 -6
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/android-chrome-192x192.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/android-chrome-512x512.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/apple-touch-icon.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon-16x16.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon-32x32.png +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/favicon.ico +0 -0
- mindroot/coreplugins/admin/static/favicon/favicon_io (1)/site.webmanifest +0 -1
- mindroot/coreplugins/admin/static/favicon/logo.png +0 -0
- mindroot/coreplugins/admin/static/favicon/site.webmanifest +0 -1
- mindroot/coreplugins/admin/static/js/backup/agent-editor.js +0 -186
- mindroot/coreplugins/admin/static/js/backup/agent-form.js +0 -1133
- mindroot/coreplugins/admin/static/js/backup/agent-list.js +0 -94
- mindroot/coreplugins/chat/static/favicon/about.txt +0 -6
- mindroot/coreplugins/chat/static/favicon/android-chrome-192x192.png +0 -0
- mindroot/coreplugins/chat/static/favicon/android-chrome-512x512.png +0 -0
- mindroot/coreplugins/chat/static/favicon/apple-touch-icon.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon-16x16.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon-32x32.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon.ico +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/about.txt +0 -6
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/android-chrome-192x192.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/android-chrome-512x512.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/apple-touch-icon.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon-16x16.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon-32x32.png +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/favicon.ico +0 -0
- mindroot/coreplugins/chat/static/favicon/favicon_io (1)/site.webmanifest +0 -1
- mindroot/coreplugins/chat/static/favicon/logo.png +0 -0
- mindroot/coreplugins/chat/static/favicon/site.webmanifest +0 -1
- mindroot/coreplugins/index/default.json +0 -76
- mindroot/coreplugins/user_service/file_trigger_service.py +0 -12
- mindroot/coreplugins/user_service/hooks.py +0 -23
- /mindroot/coreplugins/{admin/static/favicon/android-chrome-192x192.png → home/static/imgs/backuplogo.png} +0 -0
- {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/WHEEL +0 -0
- {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/entry_points.txt +0 -0
- {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/licenses/LICENSE +0 -0
- {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/top_level.txt +0 -0
|
@@ -12,6 +12,7 @@ from lib.plugins import (
|
|
|
12
12
|
from lib.plugins.installation import download_github_files
|
|
13
13
|
from lib.streamcmd import stream_command_as_events
|
|
14
14
|
import asyncio
|
|
15
|
+
import httpx
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
router = APIRouter()
|
|
@@ -52,15 +53,22 @@ import sys, os, shlex
|
|
|
52
53
|
async def stream_install_plugin(request: StreamInstallRequest):
|
|
53
54
|
"""Stream the installation process of a plugin using SSE (POST method)."""
|
|
54
55
|
# Prepare the command based on the source
|
|
55
|
-
|
|
56
|
-
|
|
56
|
+
print("Stream Install Request:", request)
|
|
57
|
+
if request.source == 'github_direct' or request.source == 'github':
|
|
58
|
+
if request.source_path.startswith('https://'):
|
|
59
|
+
# For direct GitHub URLs, we can use the pip install directly
|
|
60
|
+
cmd = [sys.executable, '-m', 'pip', 'install', request.source_path, '-v', '--no-cache-dir']
|
|
61
|
+
else:
|
|
62
|
+
cmd = [sys.executable, '-m', 'pip', 'install', '-e', request.source_path, '-v', '--no-cache-dir']
|
|
57
63
|
elif request.source == 'local':
|
|
58
64
|
cmd = [sys.executable, '-m', 'pip', 'install', '-e', request.source_path, '-v', '--no-cache-dir']
|
|
59
65
|
elif request.source == 'pypi':
|
|
60
66
|
cmd = [sys.executable, '-m', 'pip', 'install', request.plugin, '-v', '--no-cache-dir']
|
|
61
67
|
else:
|
|
68
|
+
print("Invalid source")
|
|
62
69
|
return {"success": False, "message": "Invalid source"}
|
|
63
70
|
|
|
71
|
+
print("Command to execute:", cmd)
|
|
64
72
|
# For GitHub installations, use the plugin_install function which handles the download and extraction
|
|
65
73
|
if request.source == 'github':
|
|
66
74
|
try:
|
|
@@ -69,10 +77,14 @@ async def stream_install_plugin(request: StreamInstallRequest):
|
|
|
69
77
|
repo_path = parts[0]
|
|
70
78
|
tag = parts[1] if len(parts) > 1 else None
|
|
71
79
|
|
|
80
|
+
print(1)
|
|
81
|
+
|
|
72
82
|
async def stream_github_install():
|
|
83
|
+
print("dling")
|
|
73
84
|
yield {"event": "message", "data": f"Downloading GitHub repository {repo_path}..."}
|
|
74
85
|
|
|
75
86
|
try:
|
|
87
|
+
print("dling 2")
|
|
76
88
|
plugin_dir, _, plugin_info = download_github_files(repo_path, tag)
|
|
77
89
|
|
|
78
90
|
cmd = [sys.executable, '-m', 'pip', 'install', '-e', plugin_dir, '-v', '--no-cache-dir']
|
|
@@ -85,18 +97,24 @@ async def stream_install_plugin(request: StreamInstallRequest):
|
|
|
85
97
|
metadata=plugin_info
|
|
86
98
|
)
|
|
87
99
|
except Exception as e:
|
|
100
|
+
print(e)
|
|
88
101
|
yield {"event": "error", "data": f"Error installing from GitHub: {str(e)}"}
|
|
89
102
|
|
|
90
103
|
return EventSourceResponse(stream_github_install())
|
|
91
104
|
except Exception as e:
|
|
105
|
+
print(3)
|
|
106
|
+
print(e)
|
|
92
107
|
return {"success": False, "message": f"Error setting up GitHub installation: {str(e)}"}
|
|
93
108
|
|
|
94
109
|
# For other sources, use our streamcmd module to stream the command output
|
|
110
|
+
print("stream cmd as events")
|
|
95
111
|
return EventSourceResponse(stream_command_as_events(cmd))
|
|
112
|
+
|
|
96
113
|
@router.get("/stream-install-plugin", response_class=EventSourceResponse)
|
|
97
114
|
async def stream_install_plugin_get(request: Request):
|
|
98
115
|
"""Stream the installation process of a plugin using SSE (GET method)."""
|
|
99
116
|
# Extract parameters from query string
|
|
117
|
+
print("Stream Install GET Request:", request.query_params)
|
|
100
118
|
plugin = request.query_params.get("plugin", "")
|
|
101
119
|
source = request.query_params.get("source", "")
|
|
102
120
|
source_path = request.query_params.get("source_path", "")
|
|
@@ -114,22 +132,36 @@ async def stream_install_plugin_get(request: Request):
|
|
|
114
132
|
else:
|
|
115
133
|
return {"success": False, "message": "Invalid source"}
|
|
116
134
|
|
|
135
|
+
print("Command to execute:", cmd)
|
|
136
|
+
tag = None
|
|
117
137
|
# For GitHub installations, use the plugin_install function which handles the download and extraction
|
|
118
138
|
if source == 'github':
|
|
119
139
|
try:
|
|
120
140
|
# Use the streaming approach for GitHub installations
|
|
141
|
+
print("source_path:", source_path)
|
|
121
142
|
parts = source_path.split(':')
|
|
122
143
|
repo_path = parts[0]
|
|
123
144
|
tag = parts[1] if len(parts) > 1 else None
|
|
124
|
-
|
|
145
|
+
print("repo_path:", repo_path, "tag:", tag)
|
|
125
146
|
# First yield a message about downloading
|
|
147
|
+
#
|
|
126
148
|
async def stream_github_install():
|
|
127
149
|
yield {"event": "message", "data": f"Downloading GitHub repository {repo_path}..."}
|
|
128
|
-
|
|
150
|
+
repo_path_ = repo_path
|
|
151
|
+
tag_ = tag
|
|
129
152
|
# Download and extract the GitHub repository
|
|
130
153
|
try:
|
|
131
|
-
|
|
132
|
-
|
|
154
|
+
if source_path.startswith('https://'):
|
|
155
|
+
print("Processing direct GitHub URL")
|
|
156
|
+
repo_path_ = source_path
|
|
157
|
+
tag_ = None
|
|
158
|
+
parts = repo_path_.split('/')
|
|
159
|
+
if len(parts) >= 5:
|
|
160
|
+
repo_path_ = f"{parts[3]}/{parts[4]}"
|
|
161
|
+
|
|
162
|
+
print("repo_path_:", repo_path_)
|
|
163
|
+
plugin_dir, _, plugin_info = download_github_files(repo_path_, tag_)
|
|
164
|
+
print('ok')
|
|
133
165
|
# Now stream the installation from the local directory
|
|
134
166
|
cmd = [sys.executable, '-m', 'pip', 'install', '-e', plugin_dir, '-v', '--no-cache-dir']
|
|
135
167
|
async for event in stream_command_as_events(cmd):
|
|
@@ -138,15 +170,17 @@ async def stream_install_plugin_get(request: Request):
|
|
|
138
170
|
# Update the plugin manifest
|
|
139
171
|
update_plugin_manifest(
|
|
140
172
|
plugin_info['name'], 'github', os.path.abspath(plugin_dir),
|
|
141
|
-
remote_source=
|
|
173
|
+
remote_source=repo_path_, version=plugin_info.get('version', '0.0.1'),
|
|
142
174
|
metadata=plugin_info
|
|
143
175
|
)
|
|
144
176
|
except Exception as e:
|
|
145
|
-
|
|
177
|
+
trace = traceback.format_exc()
|
|
178
|
+
yield {"event": "error", "data": f"Error installing from GitHub: {str(e)} \n\n{trace}"}
|
|
146
179
|
|
|
147
180
|
return EventSourceResponse(stream_github_install())
|
|
148
181
|
except Exception as e:
|
|
149
|
-
|
|
182
|
+
trace = traceback.format_exc()
|
|
183
|
+
return {"success": False, "message": f"Error installing from GitHub: {str(e)}\n\n{trace}"}
|
|
150
184
|
|
|
151
185
|
# Use our new streamcmd module
|
|
152
186
|
return EventSourceResponse(stream_command_as_events(cmd))
|
|
@@ -360,3 +394,65 @@ def discover_plugins(directory):
|
|
|
360
394
|
continue
|
|
361
395
|
|
|
362
396
|
return discovered
|
|
397
|
+
|
|
398
|
+
async def publish_plugin_from_github(repo: str, registry_token: str, registry_url: str):
|
|
399
|
+
"""
|
|
400
|
+
Fetches plugin_info.json from a GitHub repo and publishes it to the registry.
|
|
401
|
+
"""
|
|
402
|
+
plugin_info = None
|
|
403
|
+
# Try to fetch from 'main' and then 'master' branch
|
|
404
|
+
for branch in ['main', 'master']:
|
|
405
|
+
url = f"https://raw.githubusercontent.com/{repo}/{branch}/plugin_info.json"
|
|
406
|
+
async with httpx.AsyncClient() as client:
|
|
407
|
+
try:
|
|
408
|
+
response = await client.get(url)
|
|
409
|
+
if response.status_code == 200:
|
|
410
|
+
plugin_info = response.json()
|
|
411
|
+
break
|
|
412
|
+
except httpx.RequestError as e:
|
|
413
|
+
# This might happen if the repo is private or other network issues
|
|
414
|
+
print(f"Error fetching from {url}: {e}")
|
|
415
|
+
continue
|
|
416
|
+
|
|
417
|
+
if not plugin_info:
|
|
418
|
+
raise Exception(f"Could not find or access plugin_info.json in repo {repo} on 'main' or 'master' branch.")
|
|
419
|
+
|
|
420
|
+
# Construct the payload for the registry's /publish endpoint
|
|
421
|
+
publish_data = {
|
|
422
|
+
"title": plugin_info.get("name"),
|
|
423
|
+
"description": plugin_info.get("description", ""),
|
|
424
|
+
"category": "plugin",
|
|
425
|
+
"content_type": "mindroot_plugin",
|
|
426
|
+
"version": plugin_info.get("version", "0.1.0"),
|
|
427
|
+
"github_url": f"https://github.com/{repo}",
|
|
428
|
+
"pypi_module": plugin_info.get("pypi_module"),
|
|
429
|
+
"commands": plugin_info.get("commands", []),
|
|
430
|
+
"services": plugin_info.get("services", []),
|
|
431
|
+
"tags": plugin_info.get("tags", ["plugin"]),
|
|
432
|
+
"dependencies": plugin_info.get("dependencies", []),
|
|
433
|
+
"data": {
|
|
434
|
+
"plugin_info": plugin_info,
|
|
435
|
+
"installation": {
|
|
436
|
+
"type": "github",
|
|
437
|
+
"source_path": repo
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
# Post the data to the registry
|
|
443
|
+
publish_url = f"{registry_url}/publish"
|
|
444
|
+
headers = {
|
|
445
|
+
"Authorization": f"Bearer {registry_token}",
|
|
446
|
+
"Content-Type": "application/json"
|
|
447
|
+
}
|
|
448
|
+
async with httpx.AsyncClient() as client:
|
|
449
|
+
response = await client.post(publish_url, json=publish_data, headers=headers)
|
|
450
|
+
|
|
451
|
+
if response.status_code >= 400:
|
|
452
|
+
try:
|
|
453
|
+
error_detail = response.json().get("detail", response.text)
|
|
454
|
+
except:
|
|
455
|
+
error_detail = response.text
|
|
456
|
+
raise Exception(f"Failed to publish to registry: {response.status_code} - {error_detail}")
|
|
457
|
+
|
|
458
|
+
return response.json()
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
from fastapi import APIRouter
|
|
2
|
+
from lib.route_decorators import requires_role
|
|
3
|
+
|
|
4
|
+
# Import the separate route modules
|
|
5
|
+
from .plugin_routes import router as plugin_routes
|
|
6
|
+
from .mcp_routes import router as mcp_routes
|
|
7
|
+
from .mcp_catalog_routes import router as mcp_catalog_routes
|
|
8
|
+
from .registry_settings_routes import router as registry_settings_routes
|
|
9
|
+
from .mcp_publish_routes import router as mcp_publish_routes
|
|
10
|
+
from .mcp_registry_routes import router as mcp_registry_routes
|
|
11
|
+
|
|
12
|
+
# Create main router with admin role requirement
|
|
13
|
+
router = APIRouter(
|
|
14
|
+
dependencies=[requires_role('admin')]
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
# Include all the sub-routers
|
|
18
|
+
router.include_router(plugin_routes, tags=["plugins"])
|
|
19
|
+
router.include_router(mcp_routes, tags=["mcp"])
|
|
20
|
+
router.include_router(mcp_catalog_routes, tags=["mcp-catalog"])
|
|
21
|
+
router.include_router(registry_settings_routes, tags=["registry-settings"])
|
|
22
|
+
router.include_router(mcp_publish_routes, tags=["mcp-publish"])
|
|
23
|
+
router.include_router(mcp_registry_routes, tags=["mcp-registry"])
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
from fastapi import APIRouter, HTTPException, Depends, Header
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
import json
|
|
4
|
+
from typing import Optional, List
|
|
5
|
+
from lib import plugins
|
|
6
|
+
from . import plugin_manager
|
|
7
|
+
from lib.auth.cognito import get_current_user
|
|
8
|
+
from lib.config import get_settings
|
|
9
|
+
|
|
10
|
+
# Import MCP components
|
|
11
|
+
try:
|
|
12
|
+
from mindroot.coreplugins.mcp.enhanced_mod import enhanced_mcp_manager
|
|
13
|
+
from mindroot.coreplugins.mcp.mod import MCPServer
|
|
14
|
+
except ImportError:
|
|
15
|
+
# Mock objects if MCP plugin is not fully installed, to prevent startup crash
|
|
16
|
+
enhanced_mcp_manager = None
|
|
17
|
+
MCPServer = None
|
|
18
|
+
|
|
19
|
+
router = APIRouter()
|
|
20
|
+
|
|
21
|
+
class PluginUpdateRequest(BaseModel):
|
|
22
|
+
plugins: dict
|
|
23
|
+
|
|
24
|
+
@router.post("/update-plugins")
|
|
25
|
+
def update_plugins(request: PluginUpdateRequest):
|
|
26
|
+
try:
|
|
27
|
+
with open('plugins.json', 'r') as file:
|
|
28
|
+
plugins_data = json.load(file)
|
|
29
|
+
|
|
30
|
+
for plugin in plugins_data:
|
|
31
|
+
if plugin['name'] in request.plugins:
|
|
32
|
+
plugin['enabled'] = request.plugins[plugin['name']]
|
|
33
|
+
|
|
34
|
+
with open('plugins.json', 'w') as file:
|
|
35
|
+
json.dump(plugins_data, file, indent=2)
|
|
36
|
+
|
|
37
|
+
plugins.load('data/plugin_manifest.json')
|
|
38
|
+
|
|
39
|
+
return {"message": "Plugins updated successfully"}
|
|
40
|
+
except Exception as e:
|
|
41
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
42
|
+
|
|
43
|
+
@router.get("/get-plugins")
|
|
44
|
+
async def get_plugins():
|
|
45
|
+
try:
|
|
46
|
+
return plugins.load_plugin_manifest()
|
|
47
|
+
except Exception as e:
|
|
48
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
49
|
+
|
|
50
|
+
class GithubPublishRequest(BaseModel):
|
|
51
|
+
repo: str
|
|
52
|
+
registry_url: str
|
|
53
|
+
|
|
54
|
+
@router.post("/plugins/publish_from_github")
|
|
55
|
+
async def publish_from_github(request: GithubPublishRequest, authorization: Optional[str] = Header(None), user: dict = Depends(get_current_user)):
|
|
56
|
+
if not user:
|
|
57
|
+
raise HTTPException(status_code=401, detail="Not authorized for Mindroot Admin")
|
|
58
|
+
|
|
59
|
+
registry_token = None
|
|
60
|
+
if authorization and authorization.startswith("Bearer "):
|
|
61
|
+
registry_token = authorization.split(" ")[1]
|
|
62
|
+
|
|
63
|
+
if not registry_token:
|
|
64
|
+
raise HTTPException(status_code=401, detail="Registry auth token not provided")
|
|
65
|
+
|
|
66
|
+
try:
|
|
67
|
+
result = await plugin_manager.publish_plugin_from_github(request.repo, registry_token, request.registry_url)
|
|
68
|
+
return {"message": f"Plugin '{result.get('title')}' published successfully!", "data": result}
|
|
69
|
+
except Exception as e:
|
|
70
|
+
import traceback
|
|
71
|
+
traceback.print_exc()
|
|
72
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
73
|
+
|
|
74
|
+
class GithubPublishRequest(BaseModel):
|
|
75
|
+
repo: str
|
|
76
|
+
|
|
77
|
+
@router.post("/plugins/publish_from_github")
|
|
78
|
+
async def publish_from_github(request: GithubPublishRequest, user: dict = Depends(get_current_user)):
|
|
79
|
+
if not user:
|
|
80
|
+
raise HTTPException(status_code=401, detail="Not authorized")
|
|
81
|
+
try:
|
|
82
|
+
result = await plugin_manager.publish_plugin_from_github(request.repo, user)
|
|
83
|
+
return {"message": "Plugin published successfully", "data": result}
|
|
84
|
+
except Exception as e:
|
|
85
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
86
|
+
|
|
87
|
+
# --- MCP Integration Routes ---
|
|
88
|
+
|
|
89
|
+
class McpServerRequest(BaseModel):
|
|
90
|
+
server_name: str
|
|
91
|
+
|
|
92
|
+
@router.get("/mcp/list")
|
|
93
|
+
async def list_mcp_servers(user: dict = Depends(get_current_user)):
|
|
94
|
+
if not enhanced_mcp_manager:
|
|
95
|
+
raise HTTPException(status_code=501, detail="MCP Plugin not available")
|
|
96
|
+
servers = [s.dict() for s in enhanced_mcp_manager.servers.values()]
|
|
97
|
+
return {"success": True, "data": servers}
|
|
98
|
+
|
|
99
|
+
@router.post("/mcp/add")
|
|
100
|
+
async def add_mcp_server(server_config: MCPServer, user: dict = Depends(get_current_user)):
|
|
101
|
+
if not enhanced_mcp_manager:
|
|
102
|
+
raise HTTPException(status_code=501, detail="MCP Plugin not available")
|
|
103
|
+
try:
|
|
104
|
+
# The server_config is a full MCPServer object from the registry
|
|
105
|
+
enhanced_mcp_manager.add_server(server_config.name, server_config)
|
|
106
|
+
return {"success": True, "message": f"Server '{server_config.name}' added."}
|
|
107
|
+
except Exception as e:
|
|
108
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
109
|
+
|
|
110
|
+
@router.post("/mcp/remove")
|
|
111
|
+
async def remove_mcp_server(request: McpServerRequest, user: dict = Depends(get_current_user)):
|
|
112
|
+
if not enhanced_mcp_manager:
|
|
113
|
+
raise HTTPException(status_code=501, detail="MCP Plugin not available")
|
|
114
|
+
try:
|
|
115
|
+
await enhanced_mcp_manager.remove_server(request.server_name)
|
|
116
|
+
return {"success": True, "message": f"Server '{request.server_name}' removed."}
|
|
117
|
+
except Exception as e:
|
|
118
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
119
|
+
|
|
120
|
+
@router.post("/mcp/connect")
|
|
121
|
+
async def connect_mcp_server(request: McpServerRequest, user: dict = Depends(get_current_user)):
|
|
122
|
+
if not enhanced_mcp_manager:
|
|
123
|
+
raise HTTPException(status_code=501, detail="MCP Plugin not available")
|
|
124
|
+
try:
|
|
125
|
+
success = await enhanced_mcp_manager.connect_server(request.server_name)
|
|
126
|
+
if success:
|
|
127
|
+
return {"success": True, "message": f"Server '{request.server_name}' connected."}
|
|
128
|
+
else:
|
|
129
|
+
raise HTTPException(status_code=500, detail=f"Failed to connect to server '{request.server_name}'. Check logs.")
|
|
130
|
+
except Exception as e:
|
|
131
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
132
|
+
|
|
133
|
+
@router.post("/mcp/disconnect")
|
|
134
|
+
async def disconnect_mcp_server(request: McpServerRequest, user: dict = Depends(get_current_user)):
|
|
135
|
+
if not enhanced_mcp_manager:
|
|
136
|
+
raise HTTPException(status_code=501, detail="MCP Plugin not available")
|
|
137
|
+
try:
|
|
138
|
+
success = await enhanced_mcp_manager.disconnect_server(request.server_name)
|
|
139
|
+
if success:
|
|
140
|
+
return {"success": True, "message": f"Server '{request.server_name}' disconnected."}
|
|
141
|
+
else:
|
|
142
|
+
raise HTTPException(status_code=500, detail=f"Failed to disconnect from server '{request.server_name}'.")
|
|
143
|
+
except Exception as e:
|
|
144
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
145
|
+
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
from fastapi import APIRouter, HTTPException, Header
|
|
2
|
+
from pydantic import BaseModel
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
from typing import Optional
|
|
6
|
+
from lib import plugins
|
|
7
|
+
from . import plugin_manager
|
|
8
|
+
from lib.route_decorators import requires_role
|
|
9
|
+
|
|
10
|
+
# Create router with admin role requirement
|
|
11
|
+
router = APIRouter(
|
|
12
|
+
dependencies=[requires_role('admin')]
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
class PluginUpdateRequest(BaseModel):
|
|
16
|
+
plugins: dict
|
|
17
|
+
|
|
18
|
+
class GithubPublishRequest(BaseModel):
|
|
19
|
+
repo: str
|
|
20
|
+
registry_url: Optional[str] = None
|
|
21
|
+
|
|
22
|
+
# --- Plugin Management Routes ---
|
|
23
|
+
|
|
24
|
+
@router.post("/update-plugins")
|
|
25
|
+
def update_plugins(request: PluginUpdateRequest):
|
|
26
|
+
"""Update plugin enabled/disabled status."""
|
|
27
|
+
try:
|
|
28
|
+
with open('plugins.json', 'r') as file:
|
|
29
|
+
plugins_data = json.load(file)
|
|
30
|
+
|
|
31
|
+
for plugin in plugins_data:
|
|
32
|
+
if plugin['name'] in request.plugins:
|
|
33
|
+
plugin['enabled'] = request.plugins[plugin['name']]
|
|
34
|
+
|
|
35
|
+
with open('plugins.json', 'w') as file:
|
|
36
|
+
json.dump(plugins_data, file, indent=2)
|
|
37
|
+
|
|
38
|
+
plugins.load('data/plugin_manifest.json')
|
|
39
|
+
|
|
40
|
+
return {"message": "Plugins updated successfully"}
|
|
41
|
+
except Exception as e:
|
|
42
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
43
|
+
|
|
44
|
+
@router.get("/get-plugins")
|
|
45
|
+
async def get_plugins():
|
|
46
|
+
"""Get list of all plugins."""
|
|
47
|
+
try:
|
|
48
|
+
return plugins.load_plugin_manifest()
|
|
49
|
+
except Exception as e:
|
|
50
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
51
|
+
|
|
52
|
+
@router.post("/plugins/publish_from_github")
|
|
53
|
+
async def publish_plugin_from_github(
|
|
54
|
+
request: GithubPublishRequest,
|
|
55
|
+
authorization: Optional[str] = Header(None)
|
|
56
|
+
):
|
|
57
|
+
"""Publish a plugin from GitHub repository to the registry.
|
|
58
|
+
|
|
59
|
+
This endpoint allows publishing a plugin by simply providing the GitHub
|
|
60
|
+
repository in the format 'username/repo'. It will fetch the plugin_info.json
|
|
61
|
+
from the repository and publish it to the configured registry.
|
|
62
|
+
|
|
63
|
+
The registry token can be provided via:
|
|
64
|
+
1. Authorization header: "Bearer <token>"
|
|
65
|
+
2. REGISTRY_TOKEN environment variable
|
|
66
|
+
3. registry_token in data/registry_settings.json
|
|
67
|
+
"""
|
|
68
|
+
try:
|
|
69
|
+
# Get registry token from multiple sources
|
|
70
|
+
registry_token = None
|
|
71
|
+
|
|
72
|
+
# 1. Try Authorization header
|
|
73
|
+
if authorization and authorization.startswith("Bearer "):
|
|
74
|
+
registry_token = authorization.split(" ")[1]
|
|
75
|
+
|
|
76
|
+
# 2. Try environment variable
|
|
77
|
+
if not registry_token:
|
|
78
|
+
registry_token = os.getenv('REGISTRY_TOKEN')
|
|
79
|
+
|
|
80
|
+
# 3. Try settings file
|
|
81
|
+
if not registry_token:
|
|
82
|
+
try:
|
|
83
|
+
settings_file = 'data/registry_settings.json'
|
|
84
|
+
if os.path.exists(settings_file):
|
|
85
|
+
with open(settings_file, 'r') as f:
|
|
86
|
+
settings = json.load(f)
|
|
87
|
+
registry_token = settings.get('registry_token')
|
|
88
|
+
except Exception as e:
|
|
89
|
+
print(f"Error reading registry settings: {e}")
|
|
90
|
+
|
|
91
|
+
if not registry_token:
|
|
92
|
+
raise HTTPException(
|
|
93
|
+
status_code=401,
|
|
94
|
+
detail="Registry authentication token not provided. Please provide via Authorization header, REGISTRY_TOKEN environment variable, or registry_settings.json file."
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
# Use the existing plugin_manager functionality
|
|
98
|
+
registry_url = request.registry_url or "https://registry.mindroot.io"
|
|
99
|
+
|
|
100
|
+
result = await plugin_manager.publish_plugin_from_github(
|
|
101
|
+
request.repo,
|
|
102
|
+
registry_token,
|
|
103
|
+
registry_url
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
"success": True,
|
|
108
|
+
"message": f"Plugin '{result.get('title', request.repo)}' published successfully!",
|
|
109
|
+
"data": result
|
|
110
|
+
}
|
|
111
|
+
except Exception as e:
|
|
112
|
+
import traceback
|
|
113
|
+
traceback.print_exc()
|
|
114
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
from fastapi import APIRouter, HTTPException
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
from lib.route_decorators import requires_role
|
|
5
|
+
|
|
6
|
+
# Create router with admin role requirement
|
|
7
|
+
router = APIRouter(
|
|
8
|
+
dependencies=[requires_role('admin')]
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
# --- Registry Settings Routes ---
|
|
12
|
+
|
|
13
|
+
@router.get("/registry/settings")
|
|
14
|
+
async def get_registry_settings():
|
|
15
|
+
"""Get registry settings including token status."""
|
|
16
|
+
try:
|
|
17
|
+
settings_file = 'data/registry_settings.json'
|
|
18
|
+
settings = {}
|
|
19
|
+
|
|
20
|
+
if os.path.exists(settings_file):
|
|
21
|
+
with open(settings_file, 'r') as f:
|
|
22
|
+
settings = json.load(f)
|
|
23
|
+
|
|
24
|
+
# Don't return the actual token, just indicate if it's set
|
|
25
|
+
return {
|
|
26
|
+
"success": True,
|
|
27
|
+
"data": {
|
|
28
|
+
"registry_url": settings.get("registry_url", "https://registry.mindroot.io"),
|
|
29
|
+
"has_token": bool(settings.get("registry_token")),
|
|
30
|
+
"token_source": "file" if settings.get("registry_token") else "env" if os.getenv('REGISTRY_TOKEN') else "none"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
except Exception as e:
|
|
34
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
35
|
+
|
|
36
|
+
@router.post("/registry/settings")
|
|
37
|
+
async def update_registry_settings(settings_data: dict):
|
|
38
|
+
"""Update registry settings."""
|
|
39
|
+
try:
|
|
40
|
+
settings_file = 'data/registry_settings.json'
|
|
41
|
+
|
|
42
|
+
# Ensure data directory exists
|
|
43
|
+
os.makedirs('data', exist_ok=True)
|
|
44
|
+
|
|
45
|
+
# Load existing settings
|
|
46
|
+
settings = {}
|
|
47
|
+
if os.path.exists(settings_file):
|
|
48
|
+
with open(settings_file, 'r') as f:
|
|
49
|
+
settings = json.load(f)
|
|
50
|
+
|
|
51
|
+
# Update with new data
|
|
52
|
+
settings.update(settings_data)
|
|
53
|
+
|
|
54
|
+
# Save updated settings
|
|
55
|
+
with open(settings_file, 'w') as f:
|
|
56
|
+
json.dump(settings, f, indent=2)
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
"success": True,
|
|
60
|
+
"message": "Registry settings updated successfully.",
|
|
61
|
+
"data": {
|
|
62
|
+
"registry_url": settings.get("registry_url", "https://registry.mindroot.io"),
|
|
63
|
+
"has_token": bool(settings.get("registry_token")),
|
|
64
|
+
"token_source": "file" if settings.get("registry_token") else "env" if os.getenv('REGISTRY_TOKEN') else "none"
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
except Exception as e:
|
|
68
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
69
|
+
|
|
70
|
+
@router.delete("/registry/settings/token")
|
|
71
|
+
async def clear_registry_token():
|
|
72
|
+
"""Clear the stored registry token."""
|
|
73
|
+
try:
|
|
74
|
+
settings_file = 'data/registry_settings.json'
|
|
75
|
+
|
|
76
|
+
if os.path.exists(settings_file):
|
|
77
|
+
with open(settings_file, 'r') as f:
|
|
78
|
+
settings = json.load(f)
|
|
79
|
+
|
|
80
|
+
# Remove token if it exists
|
|
81
|
+
if 'registry_token' in settings:
|
|
82
|
+
del settings['registry_token']
|
|
83
|
+
|
|
84
|
+
with open(settings_file, 'w') as f:
|
|
85
|
+
json.dump(settings, f, indent=2)
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
"success": True,
|
|
89
|
+
"message": "Registry token cleared successfully."
|
|
90
|
+
}
|
|
91
|
+
except Exception as e:
|
|
92
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
93
|
+
|
|
94
|
+
@router.post("/registry/test-connection")
|
|
95
|
+
async def test_registry_connection():
|
|
96
|
+
"""Test connection to the registry."""
|
|
97
|
+
try:
|
|
98
|
+
import httpx
|
|
99
|
+
|
|
100
|
+
settings_file = 'data/registry_settings.json'
|
|
101
|
+
registry_url = "https://registry.mindroot.io"
|
|
102
|
+
|
|
103
|
+
if os.path.exists(settings_file):
|
|
104
|
+
with open(settings_file, 'r') as f:
|
|
105
|
+
settings = json.load(f)
|
|
106
|
+
registry_url = settings.get("registry_url", registry_url)
|
|
107
|
+
|
|
108
|
+
# Test connection to registry
|
|
109
|
+
async with httpx.AsyncClient(timeout=10.0) as client:
|
|
110
|
+
response = await client.get(f"{registry_url}/stats")
|
|
111
|
+
|
|
112
|
+
if response.status_code == 200:
|
|
113
|
+
stats = response.json()
|
|
114
|
+
return {
|
|
115
|
+
"success": True,
|
|
116
|
+
"message": "Successfully connected to registry.",
|
|
117
|
+
"data": {
|
|
118
|
+
"registry_url": registry_url,
|
|
119
|
+
"stats": stats
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else:
|
|
123
|
+
return {
|
|
124
|
+
"success": False,
|
|
125
|
+
"message": f"Registry returned status code {response.status_code}",
|
|
126
|
+
"data": {
|
|
127
|
+
"registry_url": registry_url,
|
|
128
|
+
"status_code": response.status_code
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
except Exception as e:
|
|
133
|
+
return {
|
|
134
|
+
"success": False,
|
|
135
|
+
"message": f"Failed to connect to registry: {str(e)}",
|
|
136
|
+
"data": {
|
|
137
|
+
"registry_url": registry_url,
|
|
138
|
+
"error": str(e)
|
|
139
|
+
}
|
|
140
|
+
}
|