mindroot 9.2.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.
- 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 +63 -0
- mindroot/coreplugins/admin/plugin_router.py +1 -1
- 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-old.js +385 -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 +857 -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/handlers/plugin_ops.py +1 -1
- mindroot/coreplugins/index/indices/default/index.json +6 -6
- mindroot/coreplugins/jwt_auth/middleware.py +47 -1
- 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/password_reset_service.py +180 -27
- mindroot/coreplugins/user_service/router.py +84 -22
- 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 +238 -17
- 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 +49 -0
- mindroot/registry/data_access.py +1 -1
- mindroot/server.py +47 -13
- mindroot/server_missing_normal_args.py +197 -0
- mindroot/server_prev.py +173 -0
- {mindroot-9.2.0.dist-info → mindroot-9.5.0.dist-info}/METADATA +7 -2
- {mindroot-9.2.0.dist-info → mindroot-9.5.0.dist-info}/RECORD +147 -114
- 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 -72
- 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.2.0.dist-info → mindroot-9.5.0.dist-info}/WHEEL +0 -0
- {mindroot-9.2.0.dist-info → mindroot-9.5.0.dist-info}/entry_points.txt +0 -0
- {mindroot-9.2.0.dist-info → mindroot-9.5.0.dist-info}/licenses/LICENSE +0 -0
- {mindroot-9.2.0.dist-info → mindroot-9.5.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# need to import Path
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from .l8n_constants import *
|
|
4
|
+
import json
|
|
5
|
+
import re
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
# Set up logging for l8n warnings
|
|
9
|
+
logger = logging.getLogger('l8n')
|
|
10
|
+
logger.setLevel(logging.WARNING)
|
|
11
|
+
|
|
12
|
+
# Create console handler with formatting
|
|
13
|
+
if not logger.handlers:
|
|
14
|
+
console_handler = logging.StreamHandler()
|
|
15
|
+
console_handler.setLevel(logging.WARNING)
|
|
16
|
+
formatter = logging.Formatter('\033[91m[L8N WARNING]\033[0m %(message)s')
|
|
17
|
+
console_handler.setFormatter(formatter)
|
|
18
|
+
logger.addHandler(console_handler)
|
|
19
|
+
|
|
20
|
+
def extract_plugin_root(absolute_path: str) -> str:
|
|
21
|
+
"""
|
|
22
|
+
Extract the plugin root from an absolute path.
|
|
23
|
+
|
|
24
|
+
For core plugins: everything after 'coreplugins/'
|
|
25
|
+
For external plugins: everything after 'src/[plugin_name]/'
|
|
26
|
+
|
|
27
|
+
Examples:
|
|
28
|
+
- /files/mindroot/src/mindroot/coreplugins/chat/templates/chat.jinja2 -> chat/templates/chat.jinja2
|
|
29
|
+
- /some/path/src/my_plugin/templates/page.jinja2 -> my_plugin/templates/page.jinja2
|
|
30
|
+
"""
|
|
31
|
+
path = Path(absolute_path)
|
|
32
|
+
parts = path.parts
|
|
33
|
+
|
|
34
|
+
# Find coreplugins in the path
|
|
35
|
+
if 'coreplugins' in parts:
|
|
36
|
+
coreplugins_idx = parts.index('coreplugins')
|
|
37
|
+
if coreplugins_idx + 1 < len(parts):
|
|
38
|
+
return '/'.join(parts[coreplugins_idx + 1:])
|
|
39
|
+
|
|
40
|
+
# Find src/[plugin_name] pattern for external plugins
|
|
41
|
+
for i, part in enumerate(parts):
|
|
42
|
+
if part == 'src' and i + 1 < len(parts):
|
|
43
|
+
# Check if this looks like a plugin (has templates, static, etc.)
|
|
44
|
+
potential_plugin = parts[i + 1]
|
|
45
|
+
if i + 2 < len(parts): # Has more path after src/plugin_name
|
|
46
|
+
return '/'.join(parts[i + 1:])
|
|
47
|
+
|
|
48
|
+
# Fallback: return the filename
|
|
49
|
+
return path.name
|
|
50
|
+
|
|
51
|
+
def extract_translation_keys(content: str) -> set:
|
|
52
|
+
"""
|
|
53
|
+
Extract all __TRANSLATE_key__ placeholders from content.
|
|
54
|
+
|
|
55
|
+
Args:
|
|
56
|
+
content: Content to scan for translation keys
|
|
57
|
+
|
|
58
|
+
Returns:
|
|
59
|
+
Set of translation keys found in the content
|
|
60
|
+
"""
|
|
61
|
+
pattern = r'__TRANSLATE_([a-z0-9_]+)__'
|
|
62
|
+
matches = re.findall(pattern, content)
|
|
63
|
+
return set(matches)
|
|
64
|
+
|
|
65
|
+
def replace_placeholders(content: str, language: str, plugin_path: str = None) -> str:
|
|
66
|
+
"""
|
|
67
|
+
Replace __TRANSLATE_key__ placeholders with actual translations.
|
|
68
|
+
|
|
69
|
+
This function now validates that ALL required translations are available
|
|
70
|
+
for the specified language. If any translations are missing, it logs
|
|
71
|
+
a strong warning and returns None to indicate fallback is needed.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
content: Template content with placeholders
|
|
75
|
+
language: Language code for translations
|
|
76
|
+
plugin_path: Path to the localized file (used to determine which plugin's translations to use)
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Content with placeholders replaced by translations, or None if translations are incomplete
|
|
80
|
+
"""
|
|
81
|
+
if not plugin_path:
|
|
82
|
+
# No plugin path provided, return content unchanged
|
|
83
|
+
logger.warning("No plugin path provided for translation replacement.")
|
|
84
|
+
return content
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
# Extract all translation keys from the content
|
|
88
|
+
required_keys = extract_translation_keys(content)
|
|
89
|
+
|
|
90
|
+
if not required_keys:
|
|
91
|
+
# No translation keys found, return content as-is
|
|
92
|
+
return content
|
|
93
|
+
|
|
94
|
+
# Extract plugin name from the localized file path
|
|
95
|
+
# Path format: .../localized_files/[coreplugins|external_plugins]/[plugin_name]/...
|
|
96
|
+
path_parts = Path(plugin_path).parts
|
|
97
|
+
|
|
98
|
+
# Find 'localized_files' in the path
|
|
99
|
+
if 'localized_files' in path_parts:
|
|
100
|
+
idx = path_parts.index('localized_files')
|
|
101
|
+
if idx + 2 < len(path_parts):
|
|
102
|
+
# Get plugin name (should be at idx+2)
|
|
103
|
+
plugin_type = path_parts[idx + 1] # 'coreplugins' or 'external_plugins'
|
|
104
|
+
plugin_name = path_parts[idx + 2]
|
|
105
|
+
|
|
106
|
+
# Load translations for this plugin
|
|
107
|
+
translations_path = TRANSLATIONS_DIR / plugin_type / plugin_name / "translations.json"
|
|
108
|
+
plugin_translations = {}
|
|
109
|
+
if translations_path.exists():
|
|
110
|
+
try:
|
|
111
|
+
with open(translations_path, 'r', encoding='utf-8') as f:
|
|
112
|
+
plugin_translations = json.load(f)
|
|
113
|
+
except Exception as e:
|
|
114
|
+
logger.warning(f"Could not load translations from {translations_path}: {e}")
|
|
115
|
+
return None # Fallback to original file
|
|
116
|
+
|
|
117
|
+
if language in plugin_translations:
|
|
118
|
+
translations = plugin_translations[language]
|
|
119
|
+
|
|
120
|
+
# Check if ALL required translations are available
|
|
121
|
+
missing_keys = required_keys - set(translations.keys())
|
|
122
|
+
|
|
123
|
+
if missing_keys:
|
|
124
|
+
# Some translations are missing - log strong warning and return None
|
|
125
|
+
missing_list = ', '.join(sorted(missing_keys))
|
|
126
|
+
logger.warning(
|
|
127
|
+
f"\n" +
|
|
128
|
+
f"="*80 + "\n" +
|
|
129
|
+
f"MISSING TRANSLATIONS DETECTED!\n" +
|
|
130
|
+
f"Plugin: {plugin_name}\n" +
|
|
131
|
+
f"Language: {language}\n" +
|
|
132
|
+
f"File: {plugin_path}\n" +
|
|
133
|
+
f"Missing keys: {missing_list}\n" +
|
|
134
|
+
f"Falling back to original file to avoid showing placeholders.\n" +
|
|
135
|
+
f"="*80
|
|
136
|
+
)
|
|
137
|
+
return None # Signal that fallback is needed
|
|
138
|
+
|
|
139
|
+
# All translations are available - proceed with replacement
|
|
140
|
+
def replace_match(match):
|
|
141
|
+
key = match.group(1)
|
|
142
|
+
return translations.get(key, match.group(0)) # This shouldn't happen now
|
|
143
|
+
|
|
144
|
+
return re.sub(r'__TRANSLATE_([a-z0-9_]+)__', replace_match, content)
|
|
145
|
+
else:
|
|
146
|
+
# No translations for this language
|
|
147
|
+
logger.warning(
|
|
148
|
+
f"\n" +
|
|
149
|
+
f"="*80 + "\n" +
|
|
150
|
+
f"NO TRANSLATIONS FOR LANGUAGE!\n" +
|
|
151
|
+
f"Plugin: {plugin_name}\n" +
|
|
152
|
+
f"Language: {language}\n" +
|
|
153
|
+
f"File: {plugin_path}\n" +
|
|
154
|
+
f"Required keys: {', '.join(sorted(required_keys))}\n" +
|
|
155
|
+
f"Falling back to original file.\n" +
|
|
156
|
+
f"="*80
|
|
157
|
+
)
|
|
158
|
+
return None # Signal that fallback is needed
|
|
159
|
+
else:
|
|
160
|
+
logger.warning(f"Could not extract plugin info from path: {plugin_path}")
|
|
161
|
+
return None
|
|
162
|
+
else:
|
|
163
|
+
logger.warning(f"Path does not contain 'localized_files': {plugin_path}")
|
|
164
|
+
return None
|
|
165
|
+
|
|
166
|
+
except Exception as e:
|
|
167
|
+
logger.warning(f"Error in replace_placeholders: {e}")
|
|
168
|
+
return None # Fallback to original file on any error
|
|
169
|
+
|
|
170
|
+
def get_localized_file_path(original_path: str) -> Path:
|
|
171
|
+
"""
|
|
172
|
+
Convert an original file path to its localized version path.
|
|
173
|
+
|
|
174
|
+
Examples:
|
|
175
|
+
- chat/templates/chat.jinja2 -> localized_files/coreplugins/chat/templates/chat.i18n.jinja2
|
|
176
|
+
- my_plugin/templates/page.jinja2 -> localized_files/external_plugins/my_plugin/templates/page.i18n.jinja2
|
|
177
|
+
"""
|
|
178
|
+
plugin_root = extract_plugin_root(original_path)
|
|
179
|
+
path = Path(plugin_root)
|
|
180
|
+
|
|
181
|
+
# Determine if this is a core plugin or external plugin based on the absolute path
|
|
182
|
+
if 'coreplugins' in original_path:
|
|
183
|
+
base_dir = LOCALIZED_FILES_DIR / "coreplugins"
|
|
184
|
+
else:
|
|
185
|
+
base_dir = LOCALIZED_FILES_DIR / "external_plugins"
|
|
186
|
+
|
|
187
|
+
# Add .i18n before the file extension
|
|
188
|
+
stem = path.stem
|
|
189
|
+
suffix = path.suffix
|
|
190
|
+
new_filename = f"{stem}.i18n{suffix}"
|
|
191
|
+
|
|
192
|
+
localized_path = base_dir / path.parent / new_filename
|
|
193
|
+
return localized_path
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def load_plugin_translations(plugin_path: str):
|
|
197
|
+
"""Load translations for a specific plugin from disk."""
|
|
198
|
+
|
|
199
|
+
translations_file = get_plugin_translations_path(plugin_path)
|
|
200
|
+
|
|
201
|
+
if translations_file.exists():
|
|
202
|
+
try:
|
|
203
|
+
with open(translations_file, 'r', encoding='utf-8') as f:
|
|
204
|
+
return json.load(f)
|
|
205
|
+
except Exception as e:
|
|
206
|
+
logger.warning(f"Could not load translations from {translations_file}: {e}")
|
|
207
|
+
else:
|
|
208
|
+
logger.warning(f"Translations file does not exist at {translations_file}")
|
|
209
|
+
return {}
|
|
210
|
+
|
|
211
|
+
def get_plugin_translations_path(original_path: str) -> Path:
|
|
212
|
+
"""
|
|
213
|
+
Get the path where translations should be stored for a given file.
|
|
214
|
+
|
|
215
|
+
Example:
|
|
216
|
+
/files/mindroot/src/mindroot/coreplugins/check_list/inject/admin.jinja2
|
|
217
|
+
-> translations/coreplugins/check_list/translations.json
|
|
218
|
+
"""
|
|
219
|
+
plugin_root = extract_plugin_root(original_path)
|
|
220
|
+
|
|
221
|
+
# Determine if this is a core plugin or external plugin based on the absolute path
|
|
222
|
+
if 'coreplugins' in original_path:
|
|
223
|
+
base_dir = TRANSLATIONS_DIR / "coreplugins"
|
|
224
|
+
else:
|
|
225
|
+
base_dir = TRANSLATIONS_DIR / "external_plugins"
|
|
226
|
+
|
|
227
|
+
# Get just the plugin name (first part of plugin_root)
|
|
228
|
+
plugin_name = Path(plugin_root).parts[0]
|
|
229
|
+
|
|
230
|
+
return base_dir / plugin_name / "translations.json"
|
|
231
|
+
|
|
232
|
+
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# This import is required for the plugin to load properly
|
|
2
|
+
from .mod import *
|
|
3
|
+
|
|
4
|
+
# Import catalog features
|
|
5
|
+
try:
|
|
6
|
+
from .catalog_commands import *
|
|
7
|
+
except ImportError:
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
# Import additional commands
|
|
11
|
+
try:
|
|
12
|
+
from .additional_commands import *
|
|
13
|
+
except ImportError:
|
|
14
|
+
pass
|
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import asyncio
|
|
3
|
+
from typing import Dict, List, Optional, Any
|
|
4
|
+
from lib.providers.commands import command
|
|
5
|
+
from .catalog_manager import MCPCatalogManager
|
|
6
|
+
from .mod import mcp_manager, MCPServer
|
|
7
|
+
|
|
8
|
+
# Global catalog manager
|
|
9
|
+
# Use current working directory for data files
|
|
10
|
+
catalog_manager = MCPCatalogManager(working_dir=os.getcwd())
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@command()
|
|
14
|
+
async def mcp_catalog_list(category: str = None, context=None):
|
|
15
|
+
"""List MCP servers from catalog
|
|
16
|
+
|
|
17
|
+
Example:
|
|
18
|
+
{ "mcp_catalog_list": {} }
|
|
19
|
+
{ "mcp_catalog_list": { "category": "utilities" } }
|
|
20
|
+
"""
|
|
21
|
+
# Update running status first
|
|
22
|
+
catalog = catalog_manager.update_server_status()
|
|
23
|
+
|
|
24
|
+
if category:
|
|
25
|
+
servers = catalog_manager.get_servers_by_category(category)
|
|
26
|
+
else:
|
|
27
|
+
servers = catalog.get("servers", {})
|
|
28
|
+
|
|
29
|
+
# Format for display
|
|
30
|
+
result = []
|
|
31
|
+
for name, server in servers.items():
|
|
32
|
+
result.append({
|
|
33
|
+
"name": name,
|
|
34
|
+
"display_name": server.get("display_name", name),
|
|
35
|
+
"description": server.get("description", ""),
|
|
36
|
+
"category": server.get("category", "unknown"),
|
|
37
|
+
"install_method": server.get("install_method", "manual"),
|
|
38
|
+
"installed": server.get("installed", False),
|
|
39
|
+
"running": server.get("running", False),
|
|
40
|
+
"status": server.get("status", "unknown"),
|
|
41
|
+
"tools": server.get("tools", []),
|
|
42
|
+
"tags": server.get("tags", [])
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
"servers": result,
|
|
47
|
+
"count": len(result),
|
|
48
|
+
"categories": catalog_manager.get_categories()
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@command()
|
|
53
|
+
async def mcp_catalog_search(query: str, context=None):
|
|
54
|
+
"""Search MCP servers in catalog
|
|
55
|
+
|
|
56
|
+
Example:
|
|
57
|
+
{ "mcp_catalog_search": { "query": "calculator" } }
|
|
58
|
+
{ "mcp_catalog_search": { "query": "database" } }
|
|
59
|
+
"""
|
|
60
|
+
servers = catalog_manager.search_servers(query)
|
|
61
|
+
|
|
62
|
+
result = []
|
|
63
|
+
for name, server in servers.items():
|
|
64
|
+
result.append({
|
|
65
|
+
"name": name,
|
|
66
|
+
"display_name": server.get("display_name", name),
|
|
67
|
+
"description": server.get("description", ""),
|
|
68
|
+
"category": server.get("category", "unknown"),
|
|
69
|
+
"installed": server.get("installed", False),
|
|
70
|
+
"running": server.get("running", False),
|
|
71
|
+
"tags": server.get("tags", [])
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
"query": query,
|
|
76
|
+
"results": result,
|
|
77
|
+
"count": len(result)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
@command()
|
|
82
|
+
async def mcp_catalog_install(server_name: str, context=None):
|
|
83
|
+
"""Install an MCP server from catalog
|
|
84
|
+
|
|
85
|
+
Example:
|
|
86
|
+
{ "mcp_catalog_install": { "server_name": "calculator" } }
|
|
87
|
+
"""
|
|
88
|
+
server_info = catalog_manager.get_server_info(server_name)
|
|
89
|
+
if not server_info:
|
|
90
|
+
return f"Server {server_name} not found in catalog"
|
|
91
|
+
|
|
92
|
+
# Create server config from catalog info
|
|
93
|
+
server = MCPServer(
|
|
94
|
+
name=server_info["name"],
|
|
95
|
+
description=server_info["description"],
|
|
96
|
+
command=server_info["command"],
|
|
97
|
+
args=server_info["args"],
|
|
98
|
+
env=server_info.get("env", {})
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
# Add to manager
|
|
102
|
+
mcp_manager.add_server(server_name, server)
|
|
103
|
+
|
|
104
|
+
# Install server if needed (for uvx/npx packages)
|
|
105
|
+
install_method = server_info.get("install_method", "manual")
|
|
106
|
+
install_package = server_info.get("install_package")
|
|
107
|
+
|
|
108
|
+
if install_method in ["uvx", "npx"] and install_package:
|
|
109
|
+
try:
|
|
110
|
+
import subprocess
|
|
111
|
+
if install_method == "uvx":
|
|
112
|
+
result = subprocess.run(["uvx", "--help"], capture_output=True)
|
|
113
|
+
if result.returncode != 0:
|
|
114
|
+
return f"uvx not available. Please install uv first."
|
|
115
|
+
elif install_method == "npx":
|
|
116
|
+
result = subprocess.run(["npx", "--version"], capture_output=True)
|
|
117
|
+
if result.returncode != 0:
|
|
118
|
+
return f"npx not available. Please install Node.js first."
|
|
119
|
+
except Exception as e:
|
|
120
|
+
return f"Error checking {install_method}: {e}"
|
|
121
|
+
|
|
122
|
+
# Mark as installed (basic implementation)
|
|
123
|
+
success = True # For now, assume installation succeeds
|
|
124
|
+
if success:
|
|
125
|
+
catalog_manager.mark_server_installed(server_name, True)
|
|
126
|
+
return f"Successfully installed {server_name}"
|
|
127
|
+
else:
|
|
128
|
+
return f"Failed to install {server_name}"
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
@command()
|
|
132
|
+
async def mcp_catalog_install_and_run(server_name: str, context=None):
|
|
133
|
+
"""Install and run an MCP server from catalog
|
|
134
|
+
|
|
135
|
+
Example:
|
|
136
|
+
{ "mcp_catalog_install_and_run": { "server_name": "calculator" } }
|
|
137
|
+
"""
|
|
138
|
+
try:
|
|
139
|
+
# Get server info from catalog
|
|
140
|
+
server_info = catalog_manager.get_server_info(server_name)
|
|
141
|
+
if not server_info:
|
|
142
|
+
return f"Server {server_name} not found in catalog"
|
|
143
|
+
|
|
144
|
+
print(f"Installing and running {server_name}...")
|
|
145
|
+
|
|
146
|
+
# Create server configuration
|
|
147
|
+
print(f"Creating server configuration for {server_name}...")
|
|
148
|
+
|
|
149
|
+
server_config = MCPServer(
|
|
150
|
+
name=server_name,
|
|
151
|
+
description=server_info.get('description', f'MCP Server: {server_name}'),
|
|
152
|
+
command=server_info['command'],
|
|
153
|
+
args=server_info.get('args', []),
|
|
154
|
+
env=server_info.get('env', {}),
|
|
155
|
+
install_method=server_info.get('install_method', 'manual'),
|
|
156
|
+
install_package=server_info.get('install_package'),
|
|
157
|
+
auto_install=True,
|
|
158
|
+
installed=False
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
# Add server to manager
|
|
162
|
+
mcp_manager.add_server(server_name, server_config)
|
|
163
|
+
|
|
164
|
+
# Connect to the server
|
|
165
|
+
success = await mcp_manager.connect_server(server_name)
|
|
166
|
+
|
|
167
|
+
if success:
|
|
168
|
+
# Get final server status
|
|
169
|
+
catalog_manager.update_server_status()
|
|
170
|
+
|
|
171
|
+
# Import and call the refresh function to update dynamic commands
|
|
172
|
+
try:
|
|
173
|
+
from .mod import mcp_refresh_dynamic_commands
|
|
174
|
+
await mcp_refresh_dynamic_commands()
|
|
175
|
+
except Exception as e:
|
|
176
|
+
print(f"Warning: Could not refresh dynamic commands: {e}")
|
|
177
|
+
|
|
178
|
+
return f"Successfully installed and connected to {server_name}. MCP tools are now available as commands."
|
|
179
|
+
else:
|
|
180
|
+
return f"Installed {server_name} but failed to connect. Check server configuration."
|
|
181
|
+
|
|
182
|
+
except Exception as e:
|
|
183
|
+
print(f"Error in mcp_catalog_install_and_run: {e}")
|
|
184
|
+
return f"Error installing and running {server_name}: {str(e)}"
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
@command()
|
|
188
|
+
async def mcp_catalog_stop(server_name: str, context=None):
|
|
189
|
+
"""Stop an MCP server
|
|
190
|
+
|
|
191
|
+
Example:
|
|
192
|
+
{ "mcp_catalog_stop": { "server_name": "calculator" } }
|
|
193
|
+
"""
|
|
194
|
+
success = await mcp_manager.disconnect_server(server_name)
|
|
195
|
+
|
|
196
|
+
if success:
|
|
197
|
+
catalog_manager.update_server_status()
|
|
198
|
+
return f"Successfully stopped {server_name}"
|
|
199
|
+
else:
|
|
200
|
+
return f"Failed to stop {server_name}"
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
@command()
|
|
204
|
+
async def mcp_catalog_status(context=None):
|
|
205
|
+
"""Get status of all catalog servers
|
|
206
|
+
|
|
207
|
+
Example:
|
|
208
|
+
{ "mcp_catalog_status": {} }
|
|
209
|
+
"""
|
|
210
|
+
catalog = catalog_manager.update_server_status()
|
|
211
|
+
servers = catalog.get("servers", {})
|
|
212
|
+
|
|
213
|
+
status_summary = {
|
|
214
|
+
"total": len(servers),
|
|
215
|
+
"installed": 0,
|
|
216
|
+
"running": 0,
|
|
217
|
+
"available": 0
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
server_status = []
|
|
221
|
+
|
|
222
|
+
for name, server in servers.items():
|
|
223
|
+
installed = server.get("installed", False)
|
|
224
|
+
running = server.get("running", False)
|
|
225
|
+
|
|
226
|
+
if installed:
|
|
227
|
+
status_summary["installed"] += 1
|
|
228
|
+
if running:
|
|
229
|
+
status_summary["running"] += 1
|
|
230
|
+
if server.get("status") == "available":
|
|
231
|
+
status_summary["available"] += 1
|
|
232
|
+
|
|
233
|
+
server_status.append({
|
|
234
|
+
"name": name,
|
|
235
|
+
"display_name": server.get("display_name", name),
|
|
236
|
+
"installed": installed,
|
|
237
|
+
"running": running,
|
|
238
|
+
"category": server.get("category", "unknown")
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
"summary": status_summary,
|
|
243
|
+
"servers": server_status
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
|
|
247
|
+
@command()
|
|
248
|
+
async def mcp_catalog_info(server_name: str, context=None):
|
|
249
|
+
"""Get detailed info about a catalog server
|
|
250
|
+
|
|
251
|
+
Example:
|
|
252
|
+
{ "mcp_catalog_info": { "server_name": "calculator" } }
|
|
253
|
+
"""
|
|
254
|
+
server_info = catalog_manager.get_server_info(server_name)
|
|
255
|
+
|
|
256
|
+
if not server_info:
|
|
257
|
+
return f"Server {server_name} not found in catalog"
|
|
258
|
+
|
|
259
|
+
# Update running status
|
|
260
|
+
running_status = catalog_manager.detect_running_servers()
|
|
261
|
+
server_info["running"] = running_status.get(server_name, False)
|
|
262
|
+
|
|
263
|
+
return server_info
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
@command()
|
|
267
|
+
async def mcp_catalog_categories(context=None):
|
|
268
|
+
"""Get list of server categories
|
|
269
|
+
|
|
270
|
+
Example:
|
|
271
|
+
{ "mcp_catalog_categories": {} }
|
|
272
|
+
"""
|
|
273
|
+
categories = catalog_manager.get_categories()
|
|
274
|
+
catalog = catalog_manager.load_catalog()
|
|
275
|
+
|
|
276
|
+
# Count servers per category
|
|
277
|
+
category_counts = {}
|
|
278
|
+
for server in catalog.get("servers", {}).values():
|
|
279
|
+
category = server.get("category", "unknown")
|
|
280
|
+
category_counts[category] = category_counts.get(category, 0) + 1
|
|
281
|
+
|
|
282
|
+
return {
|
|
283
|
+
"categories": categories,
|
|
284
|
+
"counts": category_counts
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
@command()
|
|
289
|
+
async def mcp_catalog_add_custom(server_info: Dict[str, Any], context=None):
|
|
290
|
+
"""Add a custom server to the catalog
|
|
291
|
+
|
|
292
|
+
Example:
|
|
293
|
+
{ "mcp_catalog_add_custom": {
|
|
294
|
+
"server_info": {
|
|
295
|
+
"name": "my_server",
|
|
296
|
+
"display_name": "My Custom Server",
|
|
297
|
+
"description": "Custom MCP server",
|
|
298
|
+
"command": "python",
|
|
299
|
+
"args": ["-m", "my_mcp_server"],
|
|
300
|
+
"category": "custom",
|
|
301
|
+
"install_method": "manual"
|
|
302
|
+
}
|
|
303
|
+
}}
|
|
304
|
+
"""
|
|
305
|
+
success = catalog_manager.add_custom_server(server_info)
|
|
306
|
+
|
|
307
|
+
if success:
|
|
308
|
+
return f"Successfully added custom server {server_info.get('name')}"
|
|
309
|
+
else:
|
|
310
|
+
return "Failed to add custom server - missing required fields"
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
@command()
|
|
314
|
+
async def mcp_catalog_refresh(context=None):
|
|
315
|
+
"""Refresh catalog and update server status
|
|
316
|
+
|
|
317
|
+
Example:
|
|
318
|
+
{ "mcp_catalog_refresh": {} }
|
|
319
|
+
"""
|
|
320
|
+
catalog = catalog_manager.update_server_status()
|
|
321
|
+
running_count = sum(1 for server in catalog.get("servers", {}).values()
|
|
322
|
+
if server.get("running", False))
|
|
323
|
+
|
|
324
|
+
return {
|
|
325
|
+
"message": "Catalog refreshed",
|
|
326
|
+
"total_servers": len(catalog.get("servers", {})),
|
|
327
|
+
"running_servers": running_count
|
|
328
|
+
}
|