mindroot 9.0.0__py3-none-any.whl → 9.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of mindroot might be problematic. Click here for more details.

@@ -258,8 +258,9 @@ async def delete_chat_session(request: Request, log_id: str, user=Depends(requir
258
258
  try:
259
259
  # Try to determine the agent name from the context file first
260
260
  agent_name = "unknown"
261
- context_file_path = f"data/context/{user.username}/context_{log_id}.json"
262
-
261
+ context_dir = os.environ.get('CHATCONTEXT_DIR', 'data/context')
262
+ context_file_path = f"{context_dir}/{user.username}/context_{log_id}.json"
263
+
263
264
  if os.path.exists(context_file_path):
264
265
  try:
265
266
  with open(context_file_path, 'r') as f:
@@ -64,7 +64,7 @@ def scan_directory_for_env_vars(directory):
64
64
  # Use grep to find os.environ references - much faster than parsing each file
65
65
  cmd = [
66
66
  'grep', '-r',
67
- '-E', "(os\.environ\[\"|'|os\.environ\.get\(\"|')",
67
+ '-E', r"os\.environ(\.get\(|\[)",
68
68
  '--include=*.py',
69
69
  '--exclude-dir=venv',
70
70
  '--exclude-dir=env',
@@ -132,6 +132,37 @@ async def scan_env_vars(params=None, context=None):
132
132
  }
133
133
  all_env_vars.update(env_vars)
134
134
 
135
+ # Also scan the coreplugins directory
136
+ coreplugins_path = '/files/mindroot/src/mindroot/coreplugins'
137
+ if os.path.isdir(coreplugins_path):
138
+ env_vars = scan_directory_for_env_vars(coreplugins_path)
139
+ if env_vars:
140
+ if 'core' not in results:
141
+ results['core'] = {
142
+ 'plugin_name': 'core',
143
+ 'category': 'core',
144
+ 'env_vars': []
145
+ }
146
+ results['core']['env_vars'].extend(list(env_vars))
147
+ all_env_vars.update(env_vars)
148
+
149
+ # Also scan the lib directory
150
+ lib_path = '/files/mindroot/src/mindroot/lib'
151
+ if os.path.isdir(lib_path):
152
+ env_vars = scan_directory_for_env_vars(lib_path)
153
+ if env_vars:
154
+ if 'core' not in results:
155
+ results['core'] = {
156
+ 'plugin_name': 'core',
157
+ 'category': 'core',
158
+ 'env_vars': []
159
+ }
160
+ # Add only new variables to avoid duplicates
161
+ existing_vars = set(results['core']['env_vars'])
162
+ new_vars = env_vars - existing_vars
163
+ results['core']['env_vars'].extend(list(new_vars))
164
+ all_env_vars.update(new_vars)
165
+
135
166
  # Get current environment variables
136
167
  current_env = {}
137
168
  for var_name in all_env_vars:
@@ -22,9 +22,11 @@ async def home(request: Request):
22
22
 
23
23
  # Try to sort agents by last access time
24
24
  agent_access_times = []
25
+ chatlog_dir = os.environ.get('CHATLOG_DIR', 'data/chat')
25
26
  for agent in agent_dirs:
26
27
  # Look for the most recent log file for this agent
27
- log_pattern = f"data/chatlogs/{agent}_*.json"
28
+ # Search across all user directories for this agent's logs
29
+ log_pattern = f"{chatlog_dir}/*/{agent}/chatlog_*.json"
28
30
  log_files = glob.glob(log_pattern)
29
31
 
30
32
  if log_files:
@@ -10,7 +10,9 @@ print("--- AH Startup ---")
10
10
  async def on_load(app):
11
11
  print(termcolor.colored("startup plugin calling startup() hook...", 'yellow', 'on_green'))
12
12
 
13
- for dirs in ['data/context', 'data/chat']:
13
+ context_dir = os.environ.get('CHATCONTEXT_DIR', 'data/context')
14
+ chatlog_dir = os.environ.get('CHATLOG_DIR', 'data/chat')
15
+ for dirs in [context_dir, chatlog_dir]:
14
16
  os.makedirs(dirs, exist_ok=True)
15
17
 
16
18
  context = ChatContext(user='startup')
@@ -3,3 +3,8 @@ from .mod import *
3
3
 
4
4
  from .admin_init import *
5
5
 
6
+
7
+ from .password_reset_service import *
8
+ from .router import router
9
+ from .file_trigger_service import *
10
+ from .hooks import *
@@ -0,0 +1,72 @@
1
+ import asyncio
2
+ import json
3
+ import os
4
+ import logging
5
+ from datetime import datetime
6
+ from lib.providers.services import service, ServiceProvider
7
+
8
+ logger = logging.getLogger(__name__)
9
+
10
+ # Use paths relative to the process working directory
11
+ REQUEST_DIR = "data/password_resets/requests"
12
+ GENERATED_DIR = "data/password_resets/generated"
13
+ CHECK_INTERVAL_SECONDS = 30
14
+
15
+ @service()
16
+ async def process_password_reset_requests(context=None):
17
+ """Processes password reset request files from a directory."""
18
+ # Ensure directories exist
19
+ os.makedirs(REQUEST_DIR, exist_ok=True)
20
+ os.makedirs(GENERATED_DIR, exist_ok=True)
21
+
22
+ sp = ServiceProvider()
23
+ initiate_reset = sp.get('user_service.initiate_password_reset')
24
+
25
+ if not (initiate_reset and callable(initiate_reset)):
26
+ logger.error("Could not get 'user_service.initiate_password_reset' service.")
27
+ return
28
+
29
+ for filename in os.listdir(REQUEST_DIR):
30
+ request_file_path = os.path.join(REQUEST_DIR, filename)
31
+ if not os.path.isfile(request_file_path):
32
+ continue
33
+
34
+ try:
35
+ with open(request_file_path, 'r') as f:
36
+ data = json.load(f)
37
+
38
+ username = data.get("username")
39
+ is_admin_reset = data.get("is_admin_reset", False)
40
+
41
+ if not username:
42
+ raise ValueError("Username is missing from request file.")
43
+
44
+ logger.info(f"Processing password reset request for user: {username}")
45
+ token = await initiate_reset(username=username, is_admin_reset=is_admin_reset)
46
+
47
+ reset_link = f"/user_service/reset-password/{token}"
48
+ generated_file_path = os.path.join(GENERATED_DIR, f"{username}_{datetime.utcnow().strftime('%Y%m%d%H%M%S')}.json")
49
+ with open(generated_file_path, 'w') as f:
50
+ json.dump({"username": username, "reset_link": reset_link, "token": token}, f, indent=2)
51
+
52
+ logger.info(f"Successfully generated password reset link for {username}.")
53
+ os.remove(request_file_path)
54
+
55
+ except Exception as e:
56
+ logger.error(f"Failed to process reset request file {filename}: {e}")
57
+ error_file_path = os.path.join(REQUEST_DIR, f"{filename}.error")
58
+ os.rename(request_file_path, error_file_path)
59
+
60
+ @service()
61
+ async def start_file_watcher_service(context=None):
62
+ """Starts a background task to watch for password reset request files."""
63
+ logger.info("Starting password reset file watcher service.")
64
+ async def watcher_loop():
65
+ while True:
66
+ try:
67
+ await process_password_reset_requests()
68
+ except Exception as e:
69
+ logger.error(f"Error in password reset watcher loop: {e}")
70
+ await asyncio.sleep(CHECK_INTERVAL_SECONDS)
71
+
72
+ asyncio.create_task(watcher_loop())
@@ -0,0 +1,23 @@
1
+ import asyncio
2
+ import logging
3
+ from lib.providers.hooks import hook
4
+ from lib.providers.services import ServiceProvider
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ @hook('startup')
9
+ async def on_user_service_startup(app, context=None):
10
+ """
11
+ Startup hook for the user_service plugin.
12
+ """
13
+ logger.info("User service startup hook triggered.")
14
+ try:
15
+ sp = ServiceProvider()
16
+ start_watcher = sp.get('user_service.start_file_watcher_service')
17
+ if start_watcher and callable(start_watcher):
18
+ logger.info("Starting the password reset file watcher service via startup hook.")
19
+ asyncio.create_task(start_watcher())
20
+ else:
21
+ logger.error("Could not find 'user_service.start_file_watcher_service' during startup.")
22
+ except Exception as e:
23
+ logger.error(f"Error starting file watcher service from hook: {e}")
@@ -26,3 +26,10 @@ class UserCreate(BaseModel):
26
26
  class UserInRequest(UserBase):
27
27
  """User data as attached to request.state.user"""
28
28
  token_data: dict
29
+
30
+
31
+ class PasswordResetToken(BaseModel):
32
+ """Data for password reset tokens"""
33
+ token: str
34
+ expires_at: str
35
+ is_admin_reset: bool = False
@@ -0,0 +1,79 @@
1
+ from lib.providers.services import service
2
+ from .models import UserAuth, PasswordResetToken
3
+ import bcrypt
4
+ import json
5
+ import os
6
+ import secrets
7
+ from datetime import datetime, timedelta
8
+
9
+ USER_DATA_ROOT = "data/users"
10
+ RESET_TOKEN_VALIDITY_HOURS = 1
11
+
12
+ @service()
13
+ async def initiate_password_reset(username: str, is_admin_reset: bool = False, context=None) -> str:
14
+ """Initiates a password reset, generates a token, and stores it."""
15
+ user_dir = os.path.join(USER_DATA_ROOT, username)
16
+ if not os.path.exists(user_dir):
17
+ raise ValueError("User not found")
18
+
19
+ token = secrets.token_urlsafe(32)
20
+ expires_at = datetime.utcnow() + timedelta(hours=RESET_TOKEN_VALIDITY_HOURS)
21
+
22
+ reset_data = PasswordResetToken(
23
+ token=token,
24
+ expires_at=expires_at.isoformat(),
25
+ is_admin_reset=is_admin_reset
26
+ )
27
+
28
+ reset_file_path = os.path.join(user_dir, "password_reset.json")
29
+ with open(reset_file_path, 'w') as f:
30
+ json.dump(reset_data.dict(), f, indent=2)
31
+
32
+ # In a real application, you would email this token to the user.
33
+ # For this implementation, we return it directly.
34
+ return token
35
+
36
+ @service()
37
+ async def reset_password_with_token(token: str, new_password: str, context=None) -> bool:
38
+ """Resets a user's password using a valid reset token."""
39
+ for username in os.listdir(USER_DATA_ROOT):
40
+ user_dir = os.path.join(USER_DATA_ROOT, username)
41
+ reset_file_path = os.path.join(user_dir, "password_reset.json")
42
+
43
+ if not os.path.exists(reset_file_path):
44
+ continue
45
+
46
+ with open(reset_file_path, 'r') as f:
47
+ try:
48
+ reset_data = PasswordResetToken(**json.load(f))
49
+ except (json.JSONDecodeError, TypeError):
50
+ continue
51
+
52
+ if reset_data.token == token:
53
+ if datetime.fromisoformat(reset_data.expires_at) < datetime.utcnow():
54
+ os.remove(reset_file_path) # Expired token
55
+ raise ValueError("Password reset token has expired.")
56
+
57
+ auth_file_path = os.path.join(user_dir, "auth.json")
58
+ if not os.path.exists(auth_file_path):
59
+ os.remove(reset_file_path)
60
+ raise FileNotFoundError("User auth file not found.")
61
+
62
+ with open(auth_file_path, 'r+') as auth_file:
63
+ auth_data_dict = json.load(auth_file)
64
+ auth_data = UserAuth(**auth_data_dict)
65
+
66
+ new_password_hash = bcrypt.hashpw(new_password.encode(), bcrypt.gensalt()).decode()
67
+ auth_data.password_hash = new_password_hash
68
+
69
+ if reset_data.is_admin_reset and 'admin' not in auth_data.roles:
70
+ auth_data.roles.append('admin')
71
+
72
+ auth_file.seek(0)
73
+ json.dump(auth_data.dict(), auth_file, indent=2, default=str)
74
+ auth_file.truncate()
75
+
76
+ os.remove(reset_file_path) # Invalidate token after use
77
+ return True
78
+
79
+ raise ValueError("Invalid password reset token.")
@@ -0,0 +1,36 @@
1
+ from fastapi import APIRouter, Request, Form, Depends
2
+ from fastapi.responses import HTMLResponse, RedirectResponse
3
+ from lib.templates import render
4
+ from .password_reset_service import reset_password_with_token, initiate_password_reset
5
+ from lib.providers.services import service_manager
6
+ from lib.providers import ProviderManager
7
+
8
+ router = APIRouter()
9
+
10
+ @router.get("/reset-password/{token}")
11
+ async def get_reset_password_form(request: Request, token: str):
12
+ return await render('reset_password', {"request": request, "token": token, "error": None, "success": False})
13
+
14
+ @router.post("/reset-password/{token}")
15
+ async def handle_reset_password(request: Request, token: str, password: str = Form(...), confirm_password: str = Form(...), services: ProviderManager = Depends(lambda: service_manager)):
16
+ if password != confirm_password:
17
+ return await render('reset_password', {"request": request, "token": token, "error": "Passwords do not match.", "success": False})
18
+
19
+ try:
20
+ success = await services.get('user_service.reset_password_with_token')(token=token, new_password=password)
21
+ if success:
22
+ return await render('reset_password', {"request": request, "token": token, "error": None, "success": True})
23
+ else:
24
+ return await render('reset_password', {"request": request, "token": token, "error": "Invalid or expired token.", "success": False})
25
+ except ValueError as e:
26
+ return await render('reset_password', {"request": request, "token": token, "error": str(e), "success": False})
27
+
28
+ # This is an admin-only function to generate a reset link.
29
+ # In a real app, this would be more protected.
30
+ @router.get("/admin/initiate-reset/{username}")
31
+ async def admin_initiate_reset(username: str, services: ProviderManager = Depends(lambda: service_manager)):
32
+ try:
33
+ token = await services.get('user_service.initiate_password_reset')(username=username)
34
+ return HTMLResponse(f'<h1>Password Reset Link</h1><p>Share this link with the user: <a href="/user_service/reset-password/{token}">/user_service/reset-password/{token}</a></p>')
35
+ except ValueError as e:
36
+ return HTMLResponse(f'<h1>Error</h1><p>{str(e)}</p>', status_code=404)
@@ -0,0 +1,39 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Reset Password</title>
7
+ <style>
8
+ body { font-family: sans-serif; background-color: #1a1a1a; color: #f0f0f0; display: flex; justify-content: center; align-items: center; height: 100vh; margin: 0; }
9
+ .container { background-color: #2a2a2a; padding: 2rem; border-radius: 8px; box-shadow: 0 4px 8px rgba(0,0,0,0.3); width: 100%; max-width: 400px; }
10
+ h1 { text-align: center; color: #fff; }
11
+ form { display: flex; flex-direction: column; gap: 1rem; }
12
+ label { font-weight: bold; }
13
+ input[type="password"] { padding: 0.75rem; border-radius: 4px; border: 1px solid #444; background-color: #333; color: #f0f0f0; }
14
+ button { padding: 0.75rem; border: none; border-radius: 4px; background-color: #007bff; color: white; font-size: 1rem; cursor: pointer; }
15
+ button:hover { background-color: #0056b3; }
16
+ .error { color: #ff4d4d; background-color: #402a2a; padding: 1rem; border-radius: 4px; text-align: center; }
17
+ .success { color: #4dff4d; background-color: #2a402a; padding: 1rem; border-radius: 4px; text-align: center; }
18
+ </style>
19
+ </head>
20
+ <body>
21
+ <div class="container">
22
+ <h1>Reset Your Password</h1>
23
+ {% if success %}
24
+ <div class="success">Your password has been reset successfully. You can now log in with your new password.</div>
25
+ {% else %}
26
+ <form method="post">
27
+ <label for="password">New Password</label>
28
+ <input type="password" id="password" name="password" required minlength="8">
29
+ <label for="confirm_password">Confirm New Password</label>
30
+ <input type="password" id="confirm_password" name="confirm_password" required minlength="8">
31
+ <button type="submit">Reset Password</button>
32
+ </form>
33
+ {% if error %}
34
+ <p class="error">{{ error }}</p>
35
+ {% endif %}
36
+ {% endif %}
37
+ </div>
38
+ </body>
39
+ </html>
@@ -93,7 +93,8 @@ class ChatContext:
93
93
  raise ValueError('log_id is not set for the context.')
94
94
  else:
95
95
  pass
96
- context_file = f'data/context/{self.username}/context_{self.log_id}.json'
96
+ context_dir = os.environ.get('CHATCONTEXT_DIR', 'data/context')
97
+ context_file = f'{context_dir}/{self.username}/context_{self.log_id}.json'
97
98
  await aiofiles.os.makedirs(os.path.dirname(context_file), exist_ok=True)
98
99
  try:
99
100
  async with aiofiles.open(context_file, 'r') as f:
@@ -112,7 +113,8 @@ class ChatContext:
112
113
  raise ValueError('log_id is not set for the context.')
113
114
  else:
114
115
  pass
115
- context_file = f'data/context/{self.username}/context_{self.log_id}.json'
116
+ context_dir = os.environ.get('CHATCONTEXT_DIR', 'data/context')
117
+ context_file = f'{context_dir}/{self.username}/context_{self.log_id}.json'
116
118
  await aiofiles.os.makedirs(os.path.dirname(context_file), exist_ok=True)
117
119
  self.data['log_id'] = self.log_id
118
120
  context_data = {'data': self.data, 'chat_log': self.chat_log._get_log_data()}
@@ -133,7 +135,8 @@ class ChatContext:
133
135
 
134
136
  async def load_context(self, log_id):
135
137
  self.log_id = log_id
136
- context_file = f'data/context/{self.username}/context_{log_id}.json'
138
+ context_dir = os.environ.get('CHATCONTEXT_DIR', 'data/context')
139
+ context_file = f'{context_dir}/{self.username}/context_{log_id}.json'
137
140
  if await aiofiles.os.path.exists(context_file):
138
141
  async with aiofiles.open(context_file, 'r') as f:
139
142
  content = await f.read()
@@ -238,7 +241,8 @@ class ChatContext:
238
241
  print(f"Chatlog file not found for deletion: {chatlog_file_to_delete}")
239
242
 
240
243
  # ChatContext File (Agent is not part of the context file path structure)
241
- context_file_to_delete = os.path.join('data/context', user, f'context_{log_id}.json')
244
+ context_dir = os.environ.get('CHATCONTEXT_DIR', 'data/context')
245
+ context_file_to_delete = os.path.join(context_dir, user, f'context_{log_id}.json')
242
246
  if await aiofiles.os.path.exists(context_file_to_delete):
243
247
  try:
244
248
  await aiofiles.os.remove(context_file_to_delete)
mindroot/server.py CHANGED
@@ -36,7 +36,6 @@ def create_directories():
36
36
  root = get_project_root()
37
37
  directories = [
38
38
  "imgs",
39
- "data/chat",
40
39
  "models",
41
40
  "models/face",
42
41
  "models/llm",
@@ -46,6 +45,9 @@ def create_directories():
46
45
  "personas/shared",
47
46
  "data/sessions"
48
47
  ]
48
+ chatlog_dir = os.environ.get('CHATLOG_DIR', 'data/chat')
49
+ directories.append(chatlog_dir)
50
+
49
51
  for directory in directories:
50
52
  (root / directory).mkdir(parents=True, exist_ok=True)
51
53
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mindroot
3
- Version: 9.0.0
3
+ Version: 9.1.0
4
4
  Summary: MindRoot AI Agent Framework
5
5
  Requires-Python: >=3.9
6
6
  License-File: LICENSE
@@ -1,5 +1,5 @@
1
1
  mindroot/__init__.py,sha256=OrFRGt_fdSYjolLXUzjSX2CIn1cOAm6l47ENNAkwmgQ,83
2
- mindroot/server.py,sha256=akguygtkh_-EOJas4FfZkNEl0Lzm9URew5Y41xiXo88,4811
2
+ mindroot/server.py,sha256=vFJSZ4FPI7YBWDlBfGihNMh1IiwBo9R0Sz8dM15cm4k,4888
3
3
  mindroot/coreplugins/admin/__init__.py,sha256=388n_hMskU0TnZ4xT10US_kFkya-EPBjWcv7AZf_HOk,74
4
4
  mindroot/coreplugins/admin/agent_importer.py,sha256=8hQLO64iKtPA5gv9-mLUfUCcnRp3IH9-sTfgkPsrmqo,6376
5
5
  mindroot/coreplugins/admin/agent_router.py,sha256=mstYUURNRb9MpfNwqMYC7WY3FOQJ-5H0TNNz4mDSjFM,9220
@@ -452,7 +452,7 @@ mindroot/coreplugins/chat/commands.py,sha256=vlgGOvwvjpCbSsW25x4HaeFzeRNWXoEKrdq
452
452
  mindroot/coreplugins/chat/format_result_msgs.py,sha256=daEdpEyAJIa8b2VkCqSKcw8PaExcB6Qro80XNes_sHA,2
453
453
  mindroot/coreplugins/chat/mod.py,sha256=Xydjv3feKJJRbwdiB7raqiQnWtaS_2GcdC9bXYQX3nE,425
454
454
  mindroot/coreplugins/chat/models.py,sha256=GRcRuDUAJFpyWERPMxkxUaZ21igNlWeeamriruEKiEQ,692
455
- mindroot/coreplugins/chat/router.py,sha256=iZLohQknhwj-DOo4xAO4WrD6jCoZ5TBoe6d4cBgeMME,11272
455
+ mindroot/coreplugins/chat/router.py,sha256=pj2-zmlNyPxsqb7kY1iWeK9eMYHoaGcVN1oQY26Jl0I,11337
456
456
  mindroot/coreplugins/chat/services.py,sha256=MNFuRs6g2IF6hyo2rqa4aU8U2N7Z_7ZJbstv7sCfIYw,17968
457
457
  mindroot/coreplugins/chat/utils.py,sha256=BiE14PpsAcQSO5vbU88klHGm8cAXJDXxgVgva-EXybU,155
458
458
  mindroot/coreplugins/chat/static/assistant.png,sha256=oAt1ctkFKLSPBoAZGNnSixooW9ANVIk1GwniauVWDXo,215190
@@ -913,7 +913,7 @@ mindroot/coreplugins/email/test_batch.py,sha256=gCe_E02K0sCrOWi4meEELx0rckeCRXaF
913
913
  mindroot/coreplugins/email/test_email.py,sha256=SrG6Luts0r9YbDqSsBT8I45gIKZi3s3Q5d_dPNxj1Q0,5328
914
914
  mindroot/coreplugins/email/backup/mod.py,sha256=6vvFGpOzCCfEDYlaJBEixRgNRIfvp-mcD6siCwvtxcI,2118
915
915
  mindroot/coreplugins/env_manager/__init__.py,sha256=zcQypdwauAdtsj1Be-QZcPeLU_T3LiYaoWKWmIRKsnY,100
916
- mindroot/coreplugins/env_manager/mod.py,sha256=_wSNZHhDgdM2oBp3NTLd3O-Eribbm7nBsyztvRj9ZhA,6968
916
+ mindroot/coreplugins/env_manager/mod.py,sha256=fcap4fmo1c_mW8ED8V6W6QErg8_RwfzWIPuZL6vHw1o,8176
917
917
  mindroot/coreplugins/env_manager/router.py,sha256=f0d3BoeuwdUXBVJ4Kco8PURIWue5g_xI-5PGi6_6pF8,1185
918
918
  mindroot/coreplugins/env_manager/inject/admin.jinja2,sha256=BLGzp3Osc1aKo5-4rOwM3Gy4-FdoJQh7oYcTMrkaW-s,462
919
919
  mindroot/coreplugins/env_manager/static/css/env-manager.css,sha256=Y-538HHrkYtiyX-l15sYUJ6mmspbJVJZniHQKz6sL9g,4288
@@ -924,7 +924,7 @@ mindroot/coreplugins/events/router.py,sha256=a-77306_fQPNHPXP5aYtbpfC0gbqMBNRu04
924
924
  mindroot/coreplugins/events/backup/mod.py,sha256=9QeJpg6WKwxRdjiKVHD1froguTe86FS2-2wWm1B9xa8,1832
925
925
  mindroot/coreplugins/events/backup/router_backup.py,sha256=ImU8xoxdSd45V1yOFVqdtDQ614V6CMsDZQ1gtJj0Mnk,254
926
926
  mindroot/coreplugins/home/mod.py,sha256=MsSFjiLMLJZ7QhUPpVBWKiyDnCzryquRyr329NoCACI,2
927
- mindroot/coreplugins/home/router.py,sha256=kzPg2eIimG_2Qa1bZ0gKCmoo2uzd8GurrePODOO1How,1982
927
+ mindroot/coreplugins/home/router.py,sha256=VZN9dDm70NTXgP_u-6Yi8lcEEgx6wocI3brqb898Ons,2120
928
928
  mindroot/coreplugins/home/static/css/dark.css,sha256=Q9FHaEsf9xeJjtouyKgr1Su6vTzsN07NHxxqDrDfyx8,14259
929
929
  mindroot/coreplugins/home/static/css/default.css,sha256=zlQjpwNvuJP_zULCWkFycWudF1GRQz9Y5mvdKpzXXFc,13354
930
930
  mindroot/coreplugins/home/static/css/enhanced.css,sha256=oYGR_abXN5-wTmabQ3_QfSOoZ8sDRajsJJAfBgk-voE,3800
@@ -1725,7 +1725,7 @@ mindroot/coreplugins/signup/static/imgs/logo.png,sha256=ZfPlCqCjU0_TXte5gvEx81zR
1725
1725
  mindroot/coreplugins/signup/templates/signup.html,sha256=RdG-s-P2JvUcN8AFOdCFGVei71ijs0ZEE4_LRsWGEf8,5814
1726
1726
  mindroot/coreplugins/signup/templates/signup.jinja2,sha256=Fzq0fZVQFM7tpi4Y5kumxv9Nis8FLpVTUAIawHck4ZQ,6051
1727
1727
  mindroot/coreplugins/startup/__init__.py,sha256=ciWKt5NdIobetUMqXqctfsSSLKw2lwhQDB2PVhvc2Ak,21
1728
- mindroot/coreplugins/startup/mod.py,sha256=zqA3rIok6FtGV0M82ZplwhdRfzXNi16AIoYG_e-Itio,572
1728
+ mindroot/coreplugins/startup/mod.py,sha256=6NsrpuVFvLeKGjgV3erCBBeYhy5Y3pT8gJmI64Eq8DM,698
1729
1729
  mindroot/coreplugins/subscriptions/__init__.py,sha256=hW2m-zjPerMEqUFEcv265zjMr1TIms2Ofnni4MvKDes,95
1730
1730
  mindroot/coreplugins/subscriptions/credit_integration.py,sha256=X92tUNcWxvJXv2uCzmhLKTnjbhZC_pLlWYEPLlhQbCY,2103
1731
1731
  mindroot/coreplugins/subscriptions/default_templates.py,sha256=S32UkKZLbLyEYUn1Js34TWTvwVA7paYP72UYRJiqv6Y,9749
@@ -1759,19 +1759,24 @@ mindroot/coreplugins/usage/router.py,sha256=gXxpbA3HL_jV1Ngi0TlET_mXRawBmK2-4huo
1759
1759
  mindroot/coreplugins/usage/storage.py,sha256=zJLq7e3Od7w8K0OCzf3ANenFCeqMbi88BsXy2wQQ6L4,5931
1760
1760
  mindroot/coreplugins/usage/templates/base.jinja2,sha256=Rcr4i-9luLZW7jDKzBBelUKpEPmzCCBqQmXVQTAX8QE,1441
1761
1761
  mindroot/coreplugins/usage/templates/usage.jinja2,sha256=bSAjGbgx-hgzkPR0u1OIoJiOdajHgCCDRa7RVUOywJs,7722
1762
- mindroot/coreplugins/user_service/__init__.py,sha256=2zPaAzH3GBAInWKCK0OAsEcqNeLDmKpS0qwOrfcV9PI,49
1762
+ mindroot/coreplugins/user_service/__init__.py,sha256=YdQYM2GS6I2vB6r4ilODQjcV6LKTnKlCmo1EbfCQeVE,171
1763
1763
  mindroot/coreplugins/user_service/admin_init.py,sha256=wQ28wmohiER0iMEyz4kypSbSDkD38LE7WXSgZyo0-pA,3923
1764
1764
  mindroot/coreplugins/user_service/email_service.py,sha256=ptcnvPIxF7kY9NgdDlNZYGirzi6ruY8sWmuMp-fHLgA,1591
1765
+ mindroot/coreplugins/user_service/file_trigger_service.py,sha256=HyIvY_6UHE1oJgxDx-PsPcQa1l2cG9Cg4P_EvsnMVCI,2853
1766
+ mindroot/coreplugins/user_service/hooks.py,sha256=4di2j0tVhEKplDaG9fTlRtErIflZPYBfC3iHGBtUgh0,875
1765
1767
  mindroot/coreplugins/user_service/mod.py,sha256=CW0CtNp4EOg3HsNS-PlwVVy4F5q0v0RJyAvifKVhFRM,4856
1766
- mindroot/coreplugins/user_service/models.py,sha256=cBveRJrJzOVS8p_w2Vdsh7NSNeu15YYJ2sIjWBcT0Ho,989
1768
+ mindroot/coreplugins/user_service/models.py,sha256=s_dy881Ob8Ng94PQ-Iw9lYq306l3QsnDLVk7e3OED5w,1137
1769
+ mindroot/coreplugins/user_service/password_reset_service.py,sha256=K77GPP-APQOsqRxMzoaOb5OA7MCwubSMdRNcau7BJ3M,2992
1767
1770
  mindroot/coreplugins/user_service/role_service.py,sha256=e6XrxhMC4903C-Y515XSC544uXAik6-CSee-TIPGIwA,2329
1771
+ mindroot/coreplugins/user_service/router.py,sha256=IH89Ahk9T9WyAkvEi7Kgv6EIJ0jKOuN6u6sZ32mfDRs,2201
1768
1772
  mindroot/coreplugins/user_service/backup/admin_service.py,sha256=scc59rxlZz4uuVvgjf-9HL2gKi7-uiCdSt6LjWJILR8,4259
1769
1773
  mindroot/coreplugins/user_service/backup/admin_setup.py,sha256=JGszAw8nVtnNiisSUGu9jtoStKGyN44KpbRlKAhDJho,3001
1774
+ mindroot/coreplugins/user_service/templates/reset_password.jinja2,sha256=GE2rHNmSUlAS5EoJuu9g3KsUel5RNMKMVYTfxhi2IPM,2097
1770
1775
  mindroot/lib/__init__.py,sha256=388n_hMskU0TnZ4xT10US_kFkya-EPBjWcv7AZf_HOk,74
1771
1776
  mindroot/lib/buchatlog.py,sha256=LJZc3ksKgJcStltmHrrwNLaON3EDzhOKVAWj0Wl22wk,5861
1772
1777
  mindroot/lib/buchatlog2.py,sha256=Va9FteBWePEjWD9OZcw-OtQfEb-IoCVGTmJeMRaX9is,13729
1773
1778
  mindroot/lib/butemplates.py,sha256=gfHGPTOjvoEenXsR7xokNuqMjOAPuC2DawheH1Ae4bU,12196
1774
- mindroot/lib/chatcontext.py,sha256=AB1w4l7xP7AQ57HBIEaSsM3QU5Ff_gFkzNUjrN-1SMY,12149
1779
+ mindroot/lib/chatcontext.py,sha256=CXk-pX-7RG3NiRFsAZWERWxnuFJOHH7FHtOLm-kGRXE,12437
1775
1780
  mindroot/lib/chatlog.py,sha256=QbUNPgCnr9KhreQt4A_kXNICy1-JsVjpLKYt6URYhaQ,19390
1776
1781
  mindroot/lib/chatlog_optimized.py,sha256=rL7KBP-V4_cGgMLihxPm3HoKcjFEyA1uEtPtqvkOa3A,20011
1777
1782
  mindroot/lib/json_escape.py,sha256=5cAmAdNbnYX2uyfQcnse2fFtNI0CdB-AfZ23RwaDm-k,884
@@ -1821,9 +1826,9 @@ mindroot/protocols/services/stream_chat.py,sha256=fMnPfwaB5fdNMBLTEg8BXKAGvrELKH
1821
1826
  mindroot/registry/__init__.py,sha256=40Xy9bmPHsgdIrOzbtBGzf4XMqXVi9P8oZTJhn0r654,151
1822
1827
  mindroot/registry/component_manager.py,sha256=WZFNPg4SNvpqsM5NFiC2DpgmrJQCyR9cNhrCBpp30Qk,995
1823
1828
  mindroot/registry/data_access.py,sha256=NgNMamxIjaKeYxzxnVaQz1Y-Rm0AI51si3788_JHUTM,5316
1824
- mindroot-9.0.0.dist-info/licenses/LICENSE,sha256=8plAmZh8y9ccuuqFFz4kp7G-cO_qsPgAOoHNvabSB4U,1070
1825
- mindroot-9.0.0.dist-info/METADATA,sha256=i4IIp-P5zEaWK4wSnqMibkdfj2zqD4watQVrSug-_-8,891
1826
- mindroot-9.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
1827
- mindroot-9.0.0.dist-info/entry_points.txt,sha256=0bpyjMccLttx6VcjDp6zfJPN0Kk0rffor6IdIbP0j4c,50
1828
- mindroot-9.0.0.dist-info/top_level.txt,sha256=gwKm7DmNjhdrCJTYCrxa9Szne4lLpCtrEBltfsX-Mm8,9
1829
- mindroot-9.0.0.dist-info/RECORD,,
1829
+ mindroot-9.1.0.dist-info/licenses/LICENSE,sha256=8plAmZh8y9ccuuqFFz4kp7G-cO_qsPgAOoHNvabSB4U,1070
1830
+ mindroot-9.1.0.dist-info/METADATA,sha256=ydC57sIWRLy3f2wJXLGSW3Fzhn2lVqsS1KqQbGuPO1g,891
1831
+ mindroot-9.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
1832
+ mindroot-9.1.0.dist-info/entry_points.txt,sha256=0bpyjMccLttx6VcjDp6zfJPN0Kk0rffor6IdIbP0j4c,50
1833
+ mindroot-9.1.0.dist-info/top_level.txt,sha256=gwKm7DmNjhdrCJTYCrxa9Szne4lLpCtrEBltfsX-Mm8,9
1834
+ mindroot-9.1.0.dist-info/RECORD,,