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.
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 +105 -9
  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-publish-old-delete.js +166 -0
  35. mindroot/coreplugins/admin/static/js/registry-manager.js +351 -0
  36. mindroot/coreplugins/admin/static/js/registry-publish-section.js +377 -0
  37. mindroot/coreplugins/admin/static/js/registry-search-section.js +400 -0
  38. mindroot/coreplugins/admin/static/js/registry-search-section.js.bak +3 -0
  39. mindroot/coreplugins/admin/static/js/registry-settings.js +69 -0
  40. mindroot/coreplugins/admin/static/js/registry-shared-services.js +903 -0
  41. mindroot/coreplugins/admin/static/js/registry-simple-sections.js +85 -0
  42. mindroot/coreplugins/admin/static/js/secure-widget-manager.js +438 -0
  43. mindroot/coreplugins/admin/static/logo.png +0 -0
  44. mindroot/coreplugins/admin/templates/admin.jinja2 +275 -110
  45. mindroot/coreplugins/agent/Assistant/agent.json +27 -11
  46. mindroot/coreplugins/agent/agent.py +2 -2
  47. mindroot/coreplugins/agent/command_parser.py +25 -10
  48. mindroot/coreplugins/agent/templates/system.jinja2 +0 -12
  49. mindroot/coreplugins/chat/__init__.py +4 -1
  50. mindroot/coreplugins/chat/router.py +132 -20
  51. mindroot/coreplugins/chat/router_dedup_patch.py +20 -0
  52. mindroot/coreplugins/chat/services.py +31 -1
  53. mindroot/coreplugins/chat/static/css/action-fix.css +32 -0
  54. mindroot/coreplugins/chat/static/css/admin-custom.css +5 -3
  55. mindroot/coreplugins/chat/static/css/dark.css +24 -3
  56. mindroot/coreplugins/chat/static/css/default.css +24 -3
  57. mindroot/coreplugins/chat/static/css/main.css +1 -0
  58. mindroot/coreplugins/chat/static/js/action.js +137 -60
  59. mindroot/coreplugins/chat/static/js/chat-history.js +3 -0
  60. mindroot/coreplugins/chat/static/js/chat.js +59 -16
  61. mindroot/coreplugins/chat/static/js/chat.js.diff +221 -0
  62. mindroot/coreplugins/chat/static/js/chatform.js +2 -2
  63. mindroot/coreplugins/chat/static/site.webmanifest +1 -1
  64. mindroot/coreplugins/chat/templates/chat.jinja2 +3 -3
  65. mindroot/coreplugins/chat/widget_manager.py +139 -0
  66. mindroot/coreplugins/chat/widget_routes.py +287 -0
  67. mindroot/coreplugins/check_list/inject/admin.jinja2 +1 -1
  68. mindroot/coreplugins/email/__init__.py +2 -0
  69. mindroot/coreplugins/email/email_provider.py +2 -2
  70. mindroot/coreplugins/email/mod.py +100 -0
  71. mindroot/coreplugins/email/services.py +5 -3
  72. mindroot/coreplugins/email/smtp_handler.py +9 -3
  73. mindroot/coreplugins/email/test_email_service.py +75 -0
  74. mindroot/coreplugins/env_manager/mod.py +61 -25
  75. mindroot/coreplugins/home/router.py +37 -2
  76. mindroot/coreplugins/home/static/imgs/logo.png +0 -0
  77. mindroot/coreplugins/home/static/imgs/logo.png.bak +0 -0
  78. mindroot/coreplugins/home/static/imgs/logo_teal.png +0 -0
  79. mindroot/coreplugins/home/static/imgs/logo_teal2.png +0 -0
  80. mindroot/coreplugins/home/static/imgs/logo_teal_detailed.png +0 -0
  81. mindroot/coreplugins/home/static/imgs/logo_teal_python.png +0 -0
  82. mindroot/coreplugins/home/templates/home.jinja2 +15 -6
  83. mindroot/coreplugins/index/indices/default/index.json +39 -6
  84. mindroot/coreplugins/jwt_auth/middleware.py +47 -2
  85. mindroot/coreplugins/jwt_auth/mod.py +40 -17
  86. mindroot/coreplugins/l8n/__init__.py +6 -0
  87. mindroot/coreplugins/l8n/debug_loader.py +85 -0
  88. mindroot/coreplugins/l8n/debug_middleware.py +74 -0
  89. mindroot/coreplugins/l8n/l8n_constants.py +19 -0
  90. mindroot/coreplugins/l8n/language_detection.py +183 -0
  91. mindroot/coreplugins/l8n/middleware.py +151 -0
  92. mindroot/coreplugins/l8n/mod.py +277 -0
  93. mindroot/coreplugins/l8n/monkey_patch_to_delete.py +186 -0
  94. mindroot/coreplugins/l8n/test_enhanced.py +298 -0
  95. mindroot/coreplugins/l8n/test_l8n.py +95 -0
  96. mindroot/coreplugins/l8n/test_l8n_standalone.py +251 -0
  97. mindroot/coreplugins/l8n/test_middleware.py +272 -0
  98. mindroot/coreplugins/l8n/utils.py +232 -0
  99. mindroot/coreplugins/mcp_/__init__.py +14 -0
  100. mindroot/coreplugins/mcp_/catalog_commands.py +328 -0
  101. mindroot/coreplugins/mcp_/catalog_manager.py +263 -0
  102. mindroot/coreplugins/mcp_/dynamic_commands.py +154 -0
  103. mindroot/coreplugins/mcp_/mcp_manager.py +1031 -0
  104. mindroot/coreplugins/mcp_/mod.py +367 -0
  105. mindroot/coreplugins/mcp_/oauth_storage.py +144 -0
  106. mindroot/coreplugins/mcp_/server_installer.py +79 -0
  107. mindroot/coreplugins/mcp_/setup.py +26 -0
  108. mindroot/coreplugins/mcp_/test_dynamic_commands.py +134 -0
  109. mindroot/coreplugins/mcp_/testmcpclient.py +92 -0
  110. mindroot/coreplugins/persona/mod.py +12 -7
  111. mindroot/coreplugins/signup/templates/signup.jinja2 +1 -1
  112. mindroot/coreplugins/subscriptions/__init__.py +1 -0
  113. mindroot/coreplugins/subscriptions/mod.py +14 -3
  114. mindroot/coreplugins/subscriptions/router.py +3 -0
  115. mindroot/coreplugins/user_service/__init__.py +1 -2
  116. mindroot/coreplugins/user_service/admin_init.py +1 -0
  117. mindroot/coreplugins/user_service/email_service.py +72 -17
  118. mindroot/coreplugins/user_service/mod.py +10 -2
  119. mindroot/coreplugins/user_service/router.py +2 -0
  120. mindroot/lib/auth/api_key.py +28 -0
  121. mindroot/lib/cli/plugins.py +94 -0
  122. mindroot/lib/plugins/default_plugin_manifest.json +20 -0
  123. mindroot/lib/plugins/installation.py +5 -5
  124. mindroot/lib/plugins/l8n_static_handler.py +225 -0
  125. mindroot/lib/plugins/loader.py +33 -3
  126. mindroot/lib/plugins/loader_with_l8n.py +281 -0
  127. mindroot/lib/plugins/manifest.py +236 -24
  128. mindroot/lib/providers/commands.py +3 -1
  129. mindroot/lib/route_decorators.py +5 -5
  130. mindroot/lib/templates.py +183 -11
  131. mindroot/lib/utils/merge_arrays.py +1 -1
  132. mindroot/migrate.py +39 -20
  133. mindroot/registry/data_access.py +1 -1
  134. mindroot/server.py +42 -13
  135. mindroot/server_missing_normal_args.py +197 -0
  136. mindroot/server_prev.py +173 -0
  137. {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/METADATA +7 -2
  138. {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/RECORD +143 -113
  139. mindroot/coreplugins/admin/plugin_manager_backup.py +0 -615
  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.6.0.dist-info}/WHEEL +0 -0
  181. {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/entry_points.txt +0 -0
  182. {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/licenses/LICENSE +0 -0
  183. {mindroot-9.3.0.dist-info → mindroot-9.6.0.dist-info}/top_level.txt +0 -0
@@ -1,615 +0,0 @@
1
- from fastapi import APIRouter, HTTPException, Request, BackgroundTasks
2
- from pydantic import BaseModel
3
- from sse_starlette.sse import EventSourceResponse
4
- import traceback
5
- import os
6
- import json
7
- from typing import List, Optional
8
- from lib.plugins import (
9
- load_plugin_manifest, update_plugin_manifest, plugin_install,
10
- save_plugin_manifest, plugin_update, toggle_plugin_state, get_plugin_path
11
- )
12
- import asyncio
13
-
14
-
15
- router = APIRouter()
16
-
17
- class DirectoryRequest(BaseModel):
18
- directory: str
19
-
20
- class PluginRequest(BaseModel):
21
- plugin: str
22
-
23
- class GitHubPluginRequest(BaseModel):
24
- plugin: str
25
- url: Optional[str] = None
26
- github_url: Optional[str] = None
27
-
28
- class TogglePluginRequest(BaseModel):
29
- plugin: str
30
- enabled: bool
31
-
32
- class InstallFromIndexRequest(BaseModel):
33
- plugin: str
34
- index_name: str
35
-
36
- class StreamInstallRequest(BaseModel):
37
- plugin: str
38
- source: str
39
- source_path: str = None
40
-
41
-
42
- class PluginMetadata(BaseModel):
43
- description: Optional[str] = None
44
- commands: Optional[List[str]] = None
45
- services: Optional[List[str]] = None
46
- dependencies: Optional[List[str]] = None
47
-
48
- import sys, fcntl, os, shlex, queue, subprocess as sp
49
-
50
-
51
- # Simple approach to capture output in real-time
52
- async def run_command_with_output(cmd, queue):
53
- """Run a command and capture output in real-time."""
54
- print(f"Running command: {cmd}")
55
-
56
- # Start the process
57
- process = sp.Popen(
58
- cmd,
59
- stdout=sp.PIPE,
60
- stderr=sp.PIPE,
61
- text=True,
62
- bufsize=1 # Line buffered
63
- )
64
-
65
- # Helper function to read from a pipe and put lines in the queue
66
- async def read_pipe(pipe, is_stderr=False):
67
- for line in iter(pipe.readline, ''):
68
- if not line: # Empty line means EOF
69
- break
70
- line = line.strip()
71
- if line:
72
- if is_stderr:
73
- if "A new release of pip is available" in line or line.startswith("WARNING:"):
74
- await queue.put(("warning", line))
75
- else:
76
- await queue.put(("error", line))
77
- else:
78
- await queue.put(("message", line))
79
- await asyncio.sleep(0.01) # Small delay to allow other tasks to run
80
-
81
- # Create tasks to read from stdout and stderr
82
- stdout_task = asyncio.create_task(read_pipe(process.stdout))
83
- stderr_task = asyncio.create_task(read_pipe(process.stderr, True))
84
-
85
- # Wait for the process to complete
86
- return_code = await asyncio.get_event_loop().run_in_executor(None, process.wait)
87
-
88
- # Wait for the reading tasks to complete
89
- await stdout_task
90
- await stderr_task
91
-
92
- # Close the pipes
93
- process.stdout.close()
94
- process.stderr.close()
95
-
96
- # Return the return code
97
- return return_code
98
-
99
-
100
- # Generator for SSE events
101
- async def stream_command_output(cmd, message):
102
- """Stream command output as SSE events."""
103
- # Send initial message
104
- yield {"event": "message", "data": message}
105
-
106
- # Create a queue for output
107
- output_queue = asyncio.Queue()
108
-
109
- # Run the command and capture output
110
- return_code = await run_command_with_output(cmd, output_queue)
111
-
112
- # Stream output from the queue
113
- while not output_queue.empty():
114
- event_type, data = await output_queue.get()
115
- yield {"event": event_type, "data": data}
116
-
117
- # Send completion message
118
- if return_code == 0:
119
- yield {"event": "complete", "data": "Installation completed successfully"}
120
- else:
121
- yield {"event": "error", "data": "Installation failed"}
122
-
123
- import sys, io, subprocess
124
-
125
-
126
- async def stream_install_generator(plugin_name, source, source_path=None, remote_source=None):
127
- """Generator for streaming plugin installation output."""
128
- # Create a subprocess to capture output
129
- import subprocess
130
- import sys
131
- import io
132
- from contextlib import redirect_stdout, redirect_stderr
133
- import threading, select
134
-
135
- # Send initial message
136
- yield {"event": "message", "data": f"Starting installation of {plugin_name} from {source}..."}
137
-
138
- # Create string buffers to capture output
139
- stdout_buffer = io.StringIO()
140
- stderr_buffer = io.StringIO()
141
- output_queue = []
142
- installation_complete = threading.Event()
143
- installation_success = [False] # Use a list to make it mutable in the inner function
144
- import time, fcntl, os, shlex, queue, subprocess as sp
145
-
146
- # Helper function to read from pipes without blocking
147
- def read_pipes_nonblocking(process):
148
- stdout_data = []
149
- stderr_data = []
150
-
151
- # Set pipes to non-blocking mode
152
- if not process.stdout or not process.stderr:
153
- return stdout_data, stderr_data
154
-
155
-
156
- # Set stdout to non-blocking
157
- stdout_fd = process.stdout.fileno()
158
- fl = fcntl.fcntl(stdout_fd, fcntl.F_GETFL)
159
- fcntl.fcntl(stdout_fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
160
-
161
- # Set stderr to non-blocking
162
- stderr_fd = process.stderr.fileno()
163
- fl = fcntl.fcntl(stderr_fd, fcntl.F_GETFL)
164
- fcntl.fcntl(stderr_fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
165
-
166
- # Read from pipes
167
- try:
168
- readable, _, _ = select.select([process.stdout, process.stderr], [], [], 0.1)
169
- except (ValueError, select.error):
170
- return stdout_data, stderr_data
171
-
172
- if process.stdout in readable:
173
- try:
174
- stdout_line = process.stdout.readline()
175
- if stdout_line:
176
- stdout_data.append(stdout_line.strip())
177
- except IOError:
178
- pass
179
-
180
- if process.stderr in readable:
181
- try:
182
- stderr_line = process.stderr.readline()
183
- if stderr_line:
184
- stderr_data.append(stderr_line.strip())
185
- except IOError:
186
- pass
187
-
188
- return stdout_data, stderr_data
189
-
190
- # Helper function to determine if a stderr line is an actual error or just a warning/notice
191
- def is_actual_error(line):
192
- # Pip upgrade notices are not actual errors
193
- if "A new release of pip is available" in line:
194
- return False
195
- if "pip is being invoked by an old script wrapper" in line:
196
- return False
197
- # Add other known non-error stderr messages here
198
- if line.startswith("WARNING:") or line.startswith("DEPRECATION:"):
199
- return False
200
- # Consider everything else as an error
201
- return True
202
-
203
- # Capture output directly using subprocess.check_output
204
- # Thread function to continuously read from a pipe
205
- def reader_thread(pipe, queue, is_stderr=False, stderr_lines=None):
206
- try:
207
- with pipe:
208
- for line in iter(pipe.readline, ''):
209
- if not line: # Empty line means EOF
210
- break
211
- line = line.strip()
212
- if is_stderr:
213
- # Always add stderr lines to the all_stderr_lines list
214
- if stderr_lines is not None:
215
- stderr_lines.append(line)
216
-
217
- if is_actual_error(line):
218
- print(f"ERROR LINE: {line}") # Debug
219
- queue.put(("error", line))
220
- else:
221
- print(f"WARNING LINE: {line}") # Debug
222
- queue.put(("warning", line))
223
- else:
224
- queue.put(("message", line))
225
- except Exception as e:
226
- queue.put(("error", f"Error reading from {'stderr' if is_stderr else 'stdout'}: {str(e)}"))
227
- finally:
228
- if is_stderr:
229
- queue.put(("reader_done", "stderr"))
230
- else:
231
- queue.put(("reader_done", "stdout"))
232
-
233
- # Helper function to check if a line contains meaningful output
234
- def is_meaningful_output(line):
235
- return bool(line and line.strip() and not line.isspace())
236
-
237
- # Helper function to run a pip command and capture output
238
- def run_pip_command(cmd, message):
239
- output_queue.append(("message", message))
240
-
241
- all_stderr_lines = []
242
-
243
- # Make all_stderr_lines accessible to the reader_thread function
244
- # Use shell=True for better output capture on some systems
245
- if isinstance(cmd, list):
246
- cmd_str = ' '.join(shlex.quote(str(arg)) for arg in cmd)
247
- else:
248
- cmd_str = cmd
249
-
250
- process = subprocess.Popen(
251
- # Use shell=True to get more verbose output
252
- cmd_str,
253
- stdout=subprocess.PIPE,
254
- stderr=subprocess.PIPE,
255
- text=True,
256
- shell=True,
257
- bufsize=1 # Line buffered
258
- )
259
-
260
- output_queue.append(("message", f"Running: {cmd_str}"))
261
-
262
- # Create a queue for thread communication
263
- q = queue.Queue()
264
-
265
- # Start reader threads
266
- stdout_thread = threading.Thread(target=reader_thread, args=(process.stdout, q, False, None))
267
- stderr_thread = threading.Thread(target=reader_thread, args=(process.stderr, q, True, all_stderr_lines))
268
- stdout_thread.daemon = True
269
- stderr_thread.daemon = True
270
- stdout_thread.start()
271
- stderr_thread.start()
272
-
273
- # Track which readers are done
274
- readers_done = set()
275
-
276
- # Read output in real-time using non-blocking approach
277
- # Process queue items until both readers are done and process has exited
278
- while len(readers_done) < 2 or process.poll() is None:
279
- try:
280
- item_type, item = q.get(timeout=0.1)
281
-
282
- if item_type == "reader_done":
283
- readers_done.add(item) # item will be "stdout" or "stderr"
284
- elif item_type == "error":
285
- all_stderr_lines.append(item)
286
- output_queue.append(("error", item))
287
- # Send error event
288
- yield {"event": "error", "data": item}
289
- elif item_type == "warning":
290
- all_stderr_lines.append(item)
291
- output_queue.append(("warning", item))
292
- # Send warning event
293
- yield {"event": "warning", "data": item}
294
- else: # message
295
- if is_meaningful_output(item):
296
- output_queue.append(("message", item))
297
- # Send message event
298
- yield {"event": "message", "data": item}
299
-
300
- q.task_done()
301
- except queue.Empty:
302
- # No output available, just continue
303
- pass
304
-
305
- # Wait for threads to finish
306
- stdout_thread.join()
307
- stderr_thread.join()
308
-
309
- # If the process failed but only had warnings (not actual errors), consider it a success
310
- if process.returncode != 0:
311
- # Debug
312
- print(f"All stderr lines: {all_stderr_lines}")
313
- print(f"Output queue: {output_queue}")
314
-
315
- # Check if there were any actual errors in stderr
316
- has_actual_errors = any(is_actual_error(line) for line in all_stderr_lines)
317
- print(f"Process returned {process.returncode}, has_actual_errors={has_actual_errors}")
318
-
319
- # If no actual errors, consider it a success
320
- if not has_actual_errors:
321
- return 0
322
-
323
- # Add a final message with the return code
324
- output_queue.append(("message", f"Process completed with return code: {process.returncode}"))
325
- yield {"event": "message", "data": f"Process completed with return code: {process.returncode}"}
326
-
327
- success_status = process.returncode == 0 or not any(is_actual_error(line) for line in all_stderr_lines)
328
-
329
- # Add a final success/error message
330
- if success_status:
331
- yield {"event": "complete", "data": "Installation completed successfully"}
332
- else:
333
- yield {"event": "error", "data": "Installation failed"}
334
-
335
- # Return the process return code
336
- return process.returncode
337
-
338
- # Function to run pip install and capture output
339
- def run_pip_install():
340
- try:
341
- if source == 'github':
342
- # First download the GitHub repo
343
- output_queue.append(("message", f"Downloading GitHub repository {source_path}..."))
344
-
345
- # Install from GitHub
346
- cmd = [sys.executable, '-m', 'pip', 'install', '-e', f"{source_path}", '-v']
347
- return_code = run_pip_command(cmd, f"Installing {plugin_name} from GitHub repository {source_path}...")
348
- installation_success[0] = (return_code == 0)
349
-
350
- elif source == 'local':
351
- # Run pip install for local source
352
- cmd = [sys.executable, '-m', 'pip', 'install', '-e', source_path, '-v']
353
- return_code = run_pip_command(cmd, f"Installing from local path: {source_path}")
354
- installation_success[0] = (return_code == 0)
355
- print(f"Local installation result: return_code={return_code}, success={installation_success[0]}")
356
- elif source == 'pypi':
357
- # Run pip install for PyPI package
358
- cmd = [sys.executable, '-m', 'pip', 'install', plugin_name, '-v']
359
- return_code = run_pip_command(cmd, f"Installing from PyPI: {plugin_name}")
360
- installation_success[0] = (return_code == 0)
361
- print(f"PyPI installation result: return_code={return_code}, success={installation_success[0]}")
362
- installation_complete.set()
363
- except Exception as e:
364
- trace = traceback.format_exc()
365
- output_queue.append(("error", f"Error installing plugin: {str(e)}"))
366
- for line in trace.splitlines():
367
- output_queue.append(("error", line))
368
- installation_complete.set()
369
-
370
- try:
371
- # Start the installation process
372
- thread = threading.Thread(target=run_pip_install)
373
- thread.start()
374
-
375
- # Stream output from the queue while waiting for installation to complete
376
- while not installation_complete.is_set() or output_queue:
377
- if output_queue:
378
- event_type, data = output_queue.pop(0)
379
- yield {"event": event_type, "data": data}
380
- else:
381
- await asyncio.sleep(0.1)
382
-
383
- # Send completion message
384
- if installation_success[0]:
385
- yield {"event": "complete", "data": f"Plugin {plugin_name} installed successfully"}
386
- else:
387
- yield {"event": "error", "data": f"Failed to install plugin {plugin_name}"}
388
-
389
- except Exception as e:
390
- trace = traceback.format_exc()
391
- yield {"event": "error", "data": f"Error installing plugin: {str(e)}"}
392
- for line in trace.splitlines():
393
- yield {"event": "error", "data": line}
394
-
395
- @router.post("/stream-install-plugin", response_class=EventSourceResponse)
396
- async def stream_install_plugin(request: StreamInstallRequest):
397
- """Stream the installation process of a plugin using SSE."""
398
- return EventSourceResponse(stream_install_generator(
399
- request.plugin, request.source, request.source_path))
400
-
401
- @router.get("/stream-install-plugin", response_class=EventSourceResponse)
402
- async def stream_install_plugin_get(request: Request):
403
- """Stream the installation process of a plugin using SSE (GET method)."""
404
- # Extract parameters from query string
405
- plugin = request.query_params.get("plugin", "")
406
- source = request.query_params.get("source", "")
407
- source_path = request.query_params.get("source_path", "")
408
-
409
- # Use the new simpler approach
410
- if source == 'github':
411
- cmd = [sys.executable, '-m', 'pip', 'install', '-e', source_path, '-v']
412
- message = f"Installing {plugin} from GitHub repository {source_path}..."
413
- elif source == 'local':
414
- cmd = [sys.executable, '-m', 'pip', 'install', '-e', source_path, '-v']
415
- message = f"Installing from local path: {source_path}"
416
- elif source == 'pypi':
417
- cmd = [sys.executable, '-m', 'pip', 'install', plugin, '-v']
418
- message = f"Installing from PyPI: {plugin}"
419
- else:
420
- return {"success": False, "message": "Invalid source"}
421
-
422
- # Return SSE response
423
- return EventSourceResponse(stream_command_output(cmd, message))
424
-
425
- @router.get("/get-all-plugins")
426
- async def get_all_plugins():
427
- try:
428
- manifest = load_plugin_manifest()
429
- plugins = []
430
-
431
- # Process core plugins
432
- for plugin_name, plugin_info in manifest['plugins']['core'].items():
433
- plugins.append({
434
- "name": plugin_name,
435
- "category": "core",
436
- "enabled": plugin_info['enabled'],
437
- "source": "core",
438
- "remote_source": plugin_name,
439
- "version": "1.0.0",
440
- "description": plugin_info.get('metadata', {}).get('description', '')
441
- })
442
-
443
- # Process installed plugins
444
- for plugin_name, plugin_info in manifest['plugins']['installed'].items():
445
- plugins.append({
446
- "name": plugin_name,
447
- "category": "installed",
448
- "enabled": plugin_info['enabled'],
449
- "source": plugin_info['source'],
450
- "remote_source": plugin_info.get('remote_source', plugin_info.get('github_url')),
451
- "source_path": plugin_info.get('source_path'),
452
- "version": plugin_info.get('version', '0.0.1'),
453
- "description": plugin_info.get('metadata', {}).get('description', ''),
454
- "index_source": plugin_info.get('metadata', {}).get('index_source')
455
- })
456
-
457
- return {"success": True, "data": plugins}
458
- except Exception as e:
459
- trace = traceback.format_exc()
460
- return {"success": False, "message": f"Error fetching plugins: {str(e)}\n\n{trace}"}
461
-
462
-
463
- @router.post("/scan-directory")
464
- async def scan_directory(request: DirectoryRequest):
465
- try:
466
- directory = request.directory
467
- if not os.path.isdir(directory):
468
- return {"success": False, "message": "Invalid directory path"}
469
-
470
- discovered_plugins = discover_plugins(directory)
471
- manifest = load_plugin_manifest()
472
- print("discoverd_plugins", discovered_plugins)
473
- # Update installed plugins from discovered ones
474
- for plugin_name, plugin_info in discovered_plugins.items():
475
- plugin_info['source'] = 'local'
476
- plugin_info['metadata'] = plugin_info.get('metadata', {}) or {
477
- "description": plugin_info.get('description', ''),
478
- "install_date": plugin_info.get('install_date', ''),
479
- "commands": plugin_info.get('commands', []),
480
- "services": plugin_info.get('services', [])
481
- }
482
- print(plugin_info)
483
- manifest['plugins']['installed'][plugin_name] = plugin_info
484
-
485
- # Prepare plugin list for response
486
- plugins_list = [{
487
- "name": name,
488
- "description": info.get('metadata', {}).get('description', info.get('description', ''))
489
- } for name, info in discovered_plugins.items()]
490
-
491
- save_plugin_manifest(manifest)
492
- return {"success": True,
493
- "message": f"Scanned {len(discovered_plugins)} plugins in {directory}",
494
- "plugins": plugins_list}
495
- except Exception as e:
496
- trace = traceback.format_exc()
497
- return {"success": False, "message": f"Error during scan: {str(e)}\n\n{trace}"}
498
-
499
- @router.post("/install-local-plugin")
500
- async def install_local_plugin(request: PluginRequest):
501
- try:
502
- plugin_name = request.plugin
503
- plugin_path = get_plugin_path(plugin_name)
504
-
505
- if not plugin_path:
506
- return {"success": False, "message": "Plugin path not found"}
507
-
508
- success = await plugin_install(plugin_name, source='local', source_path=plugin_path)
509
- if success:
510
- return {"success": True, "message": f"Plugin {plugin_name} installed successfully"}
511
- else:
512
- return {"success": False, "message": "Installation failed"}
513
- except Exception as e:
514
- trace = traceback.format_exc()
515
- return {"success": False, "message": f"Error installing plugin: {str(e)}\n\n{trace}"}
516
-
517
-
518
- @router.post("/install-x-github-plugin")
519
- async def install_github_plugin(request: GitHubPluginRequest):
520
- try:
521
- print("Request:", request)
522
- url = request.url or request.github_url
523
- success = await plugin_install('test', source='github', source_path=url)
524
- if success:
525
- return {"success": True, "message": "Plugin installed successfully from GitHub"}
526
- else:
527
- return {"success": False, "message": "Installation failed"}
528
- except Exception as e:
529
- trace = traceback.format_exc()
530
- return {"success": False, "message": f"Error installing from GitHub: {str(e)}\n\n{trace}"}
531
-
532
- @router.post("/install-from-index")
533
- async def install_from_index(request: InstallFromIndexRequest):
534
- try:
535
- # Load the index to get plugin information
536
- index_path = os.path.join('indices', f"{request.index_name}.json")
537
- if not os.path.exists(index_path):
538
- return {"success": False, "message": "Index not found"}
539
-
540
- with open(index_path, 'r') as f:
541
- index_data = json.load(f)
542
-
543
- # Find plugin in index
544
- plugin_data = None
545
- for plugin in index_data.get('plugins', []):
546
- if plugin['name'] == request.plugin:
547
- plugin_data = plugin
548
- break
549
-
550
- if not plugin_data:
551
- return {"success": False, "message": "Plugin not found in index"}
552
-
553
- # Install the plugin
554
- if plugin_data.get('github_url'):
555
- success = await plugin_install(
556
- request.plugin,
557
- source='github',
558
- source_path=plugin_data['github_url']
559
- )
560
- elif plugin_data.get('source_path'):
561
- success = await plugin_install(
562
- request.plugin,
563
- source='local',
564
- source_path=plugin_data['source_path']
565
- )
566
- else:
567
- return {"success": False, "message": "No valid installation source in index"}
568
-
569
- if success:
570
- # Update plugin metadata with index information
571
- manifest = load_plugin_manifest()
572
- if request.plugin in manifest['plugins']['installed']:
573
- manifest['plugins']['installed'][request.plugin]['metadata']['index_source'] = request.index_name
574
- save_plugin_manifest(manifest)
575
-
576
- return {"success": True, "message": f"Plugin {request.plugin} installed successfully from index"}
577
- else:
578
- return {"success": False, "message": "Installation failed"}
579
-
580
- except Exception as e:
581
- trace = traceback.format_exc()
582
- return {"success": False, "message": f"Error installing from index: {str(e)}\n\n{trace}"}
583
-
584
-
585
- @router.post("/toggle-plugin")
586
- async def toggle_plugin(request: TogglePluginRequest):
587
- try:
588
- success = toggle_plugin_state(request.plugin, request.enabled)
589
- if success:
590
- return {"success": True, "message": f"Plugin {request.plugin} {'enabled' if request.enabled else 'disabled'} successfully"}
591
- else:
592
- return {"success": False, "message": "Failed to toggle plugin state"}
593
- except Exception as e:
594
- trace = traceback.format_exc()
595
- return {"success": False, "message": f"Error toggling plugin: {str(e)}\n\n{trace}"}
596
-
597
- # Helper function
598
- def discover_plugins(directory):
599
- discovered = {}
600
- for item in os.listdir(directory):
601
- item_path = os.path.join(directory, item)
602
- plugin_info_path = os.path.join(item_path, 'plugin_info.json')
603
-
604
- if os.path.isfile(plugin_info_path):
605
- try:
606
- with open(plugin_info_path, 'r') as f:
607
- plugin_info = json.load(f)
608
- plugin_info['enabled'] = False
609
- plugin_info['source_path'] = item_path
610
- discovered[plugin_info['name']] = plugin_info
611
- except json.JSONDecodeError:
612
- print(f"Error reading plugin info for {item}")
613
- continue
614
-
615
- return discovered
@@ -1,6 +0,0 @@
1
- This favicon was generated using the following font:
2
-
3
- - Font Title: Major Mono Display
4
- - Font Author: Copyright 2018 The Major Mono Project Authors (https://github.com/googlefonts/majormono)
5
- - Font Source: https://fonts.gstatic.com/s/majormonodisplay/v16/RWmVoLyb5fEqtsfBX9PDZIGr2tFubRhLCn2QIndPww.ttf
6
- - Font License: SIL Open Font License, 1.1 (http://scripts.sil.org/OFL))
@@ -1,6 +0,0 @@
1
- This favicon was generated using the following font:
2
-
3
- - Font Title: Major Mono Display
4
- - Font Author: Copyright 2018 The Major Mono Project Authors (https://github.com/googlefonts/majormono)
5
- - Font Source: https://fonts.gstatic.com/s/majormonodisplay/v16/RWmVoLyb5fEqtsfBX9PDZIGr2tFubRhLCn2QIndPww.ttf
6
- - Font License: SIL Open Font License, 1.1 (http://scripts.sil.org/OFL))
@@ -1 +0,0 @@
1
- {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
@@ -1 +0,0 @@
1
- {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}