meerschaum 3.0.0rc4__py3-none-any.whl → 3.0.0rc8__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 (117) hide show
  1. meerschaum/_internal/arguments/_parser.py +14 -2
  2. meerschaum/_internal/cli/__init__.py +6 -0
  3. meerschaum/_internal/cli/daemons.py +103 -0
  4. meerschaum/_internal/cli/entry.py +220 -0
  5. meerschaum/_internal/cli/workers.py +435 -0
  6. meerschaum/_internal/docs/index.py +1 -2
  7. meerschaum/_internal/entry.py +44 -8
  8. meerschaum/_internal/shell/Shell.py +115 -24
  9. meerschaum/_internal/shell/__init__.py +4 -1
  10. meerschaum/_internal/static.py +4 -1
  11. meerschaum/_internal/term/TermPageHandler.py +1 -2
  12. meerschaum/_internal/term/__init__.py +40 -6
  13. meerschaum/_internal/term/tools.py +33 -8
  14. meerschaum/actions/__init__.py +6 -4
  15. meerschaum/actions/api.py +39 -11
  16. meerschaum/actions/attach.py +1 -0
  17. meerschaum/actions/delete.py +4 -2
  18. meerschaum/actions/edit.py +27 -8
  19. meerschaum/actions/login.py +8 -8
  20. meerschaum/actions/register.py +13 -7
  21. meerschaum/actions/reload.py +22 -5
  22. meerschaum/actions/restart.py +14 -0
  23. meerschaum/actions/show.py +69 -4
  24. meerschaum/actions/start.py +135 -14
  25. meerschaum/actions/stop.py +36 -3
  26. meerschaum/actions/sync.py +6 -1
  27. meerschaum/api/__init__.py +35 -13
  28. meerschaum/api/_events.py +2 -2
  29. meerschaum/api/_oauth2.py +47 -4
  30. meerschaum/api/dash/callbacks/dashboard.py +29 -0
  31. meerschaum/api/dash/callbacks/jobs.py +3 -2
  32. meerschaum/api/dash/callbacks/login.py +10 -1
  33. meerschaum/api/dash/callbacks/register.py +9 -2
  34. meerschaum/api/dash/pages/login.py +2 -2
  35. meerschaum/api/dash/pipes.py +72 -36
  36. meerschaum/api/dash/webterm.py +14 -6
  37. meerschaum/api/models/_pipes.py +7 -1
  38. meerschaum/api/resources/static/js/terminado.js +3 -0
  39. meerschaum/api/resources/static/js/xterm-addon-unicode11.js +2 -0
  40. meerschaum/api/resources/templates/termpage.html +1 -0
  41. meerschaum/api/routes/_jobs.py +23 -11
  42. meerschaum/api/routes/_login.py +73 -5
  43. meerschaum/api/routes/_pipes.py +6 -4
  44. meerschaum/api/routes/_webterm.py +3 -3
  45. meerschaum/config/__init__.py +60 -13
  46. meerschaum/config/_default.py +89 -61
  47. meerschaum/config/_edit.py +10 -8
  48. meerschaum/config/_formatting.py +2 -0
  49. meerschaum/config/_patch.py +4 -2
  50. meerschaum/config/_paths.py +127 -12
  51. meerschaum/config/_read_config.py +32 -12
  52. meerschaum/config/_version.py +1 -1
  53. meerschaum/config/environment.py +262 -0
  54. meerschaum/config/stack/__init__.py +7 -5
  55. meerschaum/connectors/_Connector.py +1 -2
  56. meerschaum/connectors/__init__.py +37 -2
  57. meerschaum/connectors/api/_APIConnector.py +1 -1
  58. meerschaum/connectors/api/_jobs.py +11 -0
  59. meerschaum/connectors/api/_pipes.py +7 -1
  60. meerschaum/connectors/instance/_plugins.py +9 -1
  61. meerschaum/connectors/instance/_tokens.py +20 -3
  62. meerschaum/connectors/instance/_users.py +8 -1
  63. meerschaum/connectors/parse.py +1 -1
  64. meerschaum/connectors/sql/_create_engine.py +3 -0
  65. meerschaum/connectors/sql/_pipes.py +93 -79
  66. meerschaum/connectors/sql/_users.py +8 -1
  67. meerschaum/connectors/valkey/_ValkeyConnector.py +3 -3
  68. meerschaum/connectors/valkey/_pipes.py +7 -5
  69. meerschaum/core/Pipe/__init__.py +45 -71
  70. meerschaum/core/Pipe/_attributes.py +66 -90
  71. meerschaum/core/Pipe/_cache.py +555 -0
  72. meerschaum/core/Pipe/_clear.py +0 -11
  73. meerschaum/core/Pipe/_data.py +0 -50
  74. meerschaum/core/Pipe/_deduplicate.py +0 -13
  75. meerschaum/core/Pipe/_delete.py +12 -21
  76. meerschaum/core/Pipe/_drop.py +11 -23
  77. meerschaum/core/Pipe/_dtypes.py +1 -1
  78. meerschaum/core/Pipe/_index.py +8 -14
  79. meerschaum/core/Pipe/_sync.py +12 -18
  80. meerschaum/core/Plugin/_Plugin.py +7 -1
  81. meerschaum/core/Token/_Token.py +1 -1
  82. meerschaum/core/User/_User.py +1 -2
  83. meerschaum/jobs/_Executor.py +88 -4
  84. meerschaum/jobs/_Job.py +146 -36
  85. meerschaum/jobs/systemd.py +7 -2
  86. meerschaum/plugins/__init__.py +277 -81
  87. meerschaum/utils/daemon/Daemon.py +197 -42
  88. meerschaum/utils/daemon/FileDescriptorInterceptor.py +0 -1
  89. meerschaum/utils/daemon/RotatingFile.py +63 -36
  90. meerschaum/utils/daemon/StdinFile.py +53 -13
  91. meerschaum/utils/daemon/__init__.py +18 -5
  92. meerschaum/utils/daemon/_names.py +6 -3
  93. meerschaum/utils/debug.py +34 -4
  94. meerschaum/utils/dtypes/__init__.py +5 -1
  95. meerschaum/utils/formatting/__init__.py +4 -1
  96. meerschaum/utils/formatting/_jobs.py +1 -1
  97. meerschaum/utils/formatting/_pipes.py +47 -46
  98. meerschaum/utils/formatting/_shell.py +33 -9
  99. meerschaum/utils/misc.py +22 -38
  100. meerschaum/utils/packages/__init__.py +15 -13
  101. meerschaum/utils/packages/_packages.py +1 -0
  102. meerschaum/utils/pipes.py +33 -5
  103. meerschaum/utils/process.py +1 -1
  104. meerschaum/utils/prompt.py +172 -143
  105. meerschaum/utils/sql.py +12 -2
  106. meerschaum/utils/threading.py +42 -0
  107. meerschaum/utils/venv/__init__.py +2 -0
  108. meerschaum/utils/warnings.py +19 -13
  109. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/METADATA +3 -1
  110. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/RECORD +116 -110
  111. meerschaum/config/_environment.py +0 -145
  112. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/WHEEL +0 -0
  113. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/entry_points.txt +0 -0
  114. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/licenses/LICENSE +0 -0
  115. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/licenses/NOTICE +0 -0
  116. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/top_level.txt +0 -0
  117. {meerschaum-3.0.0rc4.dist-info → meerschaum-3.0.0rc8.dist-info}/zip-safe +0 -0
meerschaum/actions/api.py CHANGED
@@ -6,7 +6,10 @@ Start the Meerschaum WebAPI with the `api` action.
6
6
  """
7
7
 
8
8
  from __future__ import annotations
9
+
9
10
  import os
11
+
12
+ import meerschaum as mrsm
10
13
  from meerschaum.utils.typing import SuccessTuple, Optional, List, Any
11
14
 
12
15
 
@@ -93,6 +96,7 @@ def _api_start(
93
96
  action: Optional[List[str]] = None,
94
97
  host: Optional[str] = None,
95
98
  port: Optional[int] = None,
99
+ webterm_port: Optional[int] = None,
96
100
  workers: Optional[int] = None,
97
101
  mrsm_instance: Optional[str] = None,
98
102
  no_dash: bool = False,
@@ -119,6 +123,10 @@ def _api_start(
119
123
  The address to bind to.
120
124
  If `None`, use '0.0.0.0'.
121
125
 
126
+ webterm_port: Optional[int], default None
127
+ Port to bind the webterm server to.
128
+ If `None`, use 8765.
129
+
122
130
  workers: Optional[int], default None
123
131
  How many worker threads to run.
124
132
  If `None`, defaults to the number of CPU cores or 1 on Android.
@@ -149,8 +157,6 @@ def _api_start(
149
157
 
150
158
  from meerschaum.utils.packages import (
151
159
  attempt_import,
152
- venv_contains_package,
153
- pip_install,
154
160
  run_python_package,
155
161
  )
156
162
  from meerschaum.utils.misc import is_int, filter_keywords
@@ -164,9 +170,10 @@ def _api_start(
164
170
  API_UVICORN_CONFIG_PATH,
165
171
  CACHE_RESOURCES_PATH,
166
172
  PACKAGE_ROOT_PATH,
173
+ ROOT_DIR_PATH,
167
174
  )
168
175
  from meerschaum.config._patch import apply_patch_to_config
169
- from meerschaum.config._environment import get_env_vars
176
+ from meerschaum.config.environment import get_env_vars
170
177
  from meerschaum._internal.static import STATIC_CONFIG, SERVER_ID
171
178
  from meerschaum.connectors.parse import parse_instance_keys
172
179
  from meerschaum.utils.pool import get_pool
@@ -187,9 +194,9 @@ def _api_start(
187
194
  uvicorn_config_path = API_UVICORN_RESOURCES_PATH / SERVER_ID / 'config.json'
188
195
  uvicorn_env_path = API_UVICORN_RESOURCES_PATH / SERVER_ID / 'uvicorn.env'
189
196
 
190
- api_config = deepcopy(get_config('system', 'api'))
197
+ api_config = deepcopy(get_config('api'))
191
198
  cf = _config()
192
- forwarded_allow_ips = get_config('system', 'api', 'uvicorn', 'forwarded_allow_ips')
199
+ forwarded_allow_ips = get_config('api', 'uvicorn', 'forwarded_allow_ips')
193
200
  uvicorn_config = api_config['uvicorn']
194
201
  if port is None:
195
202
  ### default
@@ -223,7 +230,7 @@ def _api_start(
223
230
  instance_connector = parse_instance_keys(mrsm_instance, debug=debug)
224
231
  if instance_connector.type == 'api' and instance_connector.protocol != 'https':
225
232
  allow_http_parent = get_config(
226
- 'system', 'api', 'permissions', 'chaining', 'insecure_parent_instance'
233
+ 'api', 'permissions', 'chaining', 'insecure_parent_instance'
227
234
  )
228
235
  if not allow_http_parent:
229
236
  return False, (
@@ -233,7 +240,7 @@ def _api_start(
233
240
  f" - Ensure that '{instance_connector}' is available over HTTPS, " +
234
241
  "and with `edit config`,\n" +
235
242
  f" change the `protocol` for '{instance_connector}' to 'https'.\n\n" +
236
- " - Run `edit config system` and search for `permissions`.\n" +
243
+ " - Run `edit config api` and search for `permissions`.\n" +
237
244
  " Under `api:permissions:chaining`, change the value of " +
238
245
  "`insecure_parent_instance` to `true`,\n" +
239
246
  " then restart the API process."
@@ -244,6 +251,7 @@ def _api_start(
244
251
  'host': host,
245
252
  'env_file': str(uvicorn_env_path.as_posix()),
246
253
  'mrsm_instance': mrsm_instance,
254
+ 'webterm_port': webterm_port,
247
255
  'no_dash': no_dash,
248
256
  'no_webterm': no_webterm or no_auth,
249
257
  'no_auth': no_auth,
@@ -261,9 +269,22 @@ def _api_start(
261
269
  uvicorn_config['use_colors'] = (not nopretty) if nopretty else ANSI
262
270
 
263
271
  api_config['uvicorn'] = uvicorn_config
264
- cf['system']['api']['uvicorn'] = uvicorn_config
272
+ cf['api']['uvicorn'] = uvicorn_config
265
273
  if secure:
266
- cf['system']['api']['permissions']['actions']['non_admin'] = False
274
+ cf['api']['permissions']['actions']['non_admin'] = False
275
+
276
+ if not uvicorn_config['no_webterm']:
277
+ from meerschaum._internal.term.tools import is_webterm_running
278
+ if webterm_port is None:
279
+ webterm_port = int(mrsm.get_config('api', 'webterm', 'port'))
280
+ if is_webterm_running(port=webterm_port):
281
+ return (
282
+ False,
283
+ (
284
+ f"Webterm is running on port {webterm_port}. "
285
+ "Start the API again with `--webterm-port`."
286
+ )
287
+ )
267
288
 
268
289
  custom_keys = [
269
290
  'mrsm_instance',
@@ -273,6 +294,7 @@ def _api_start(
273
294
  'private',
274
295
  'debug',
275
296
  'production',
297
+ 'webterm_port',
276
298
  ]
277
299
 
278
300
  ### write config to a temporary file to communicate with uvicorn threads
@@ -299,6 +321,7 @@ def _api_start(
299
321
  MRSM_SERVER_ID: SERVER_ID,
300
322
  MRSM_RUNTIME: 'api',
301
323
  MRSM_CONFIG: json.loads(os.environ.get(MRSM_CONFIG, '{}')),
324
+ MRSM_ROOT_DIR: ROOT_DIR_PATH.as_posix(),
302
325
  'FORWARDED_ALLOW_IPS': forwarded_allow_ips,
303
326
  'TERM': os.environ.get('TERM', 'screen-256color'),
304
327
  'SHELL': os.environ.get('SHELL', '/bin/bash'),
@@ -326,10 +349,10 @@ def _api_start(
326
349
  env_text = ''
327
350
  for key, val in env_dict.items():
328
351
  value = str(
329
- json.dumps(val, default=json_serialize_value)
352
+ json.dumps(val, default=json_serialize_value, separators=(',', ':'))
330
353
  if isinstance(val, (dict))
331
354
  else val
332
- ).replace('\\', '\\\\').replace("'", "'\\''")
355
+ ).replace('\\', '\\\\').replace("'", "\\'")
333
356
  env_text += f"{key}='{value}'\n"
334
357
  with open(uvicorn_env_path, 'w+', encoding='utf-8') as f:
335
358
  if debug:
@@ -394,11 +417,16 @@ def _api_start(
394
417
  except KeyboardInterrupt:
395
418
  pass
396
419
 
420
+ old_stdin = sys.stdin
421
+ sys.stdin = None
422
+
397
423
  if production:
398
424
  _run_gunicorn()
399
425
  else:
400
426
  _run_uvicorn()
401
427
 
428
+ sys.stdin = old_stdin
429
+
402
430
  ### Cleanup
403
431
  if uvicorn_config_path.parent.exists():
404
432
  shutil.rmtree(uvicorn_config_path.parent)
@@ -9,6 +9,7 @@ Attach to running jobs.
9
9
  import meerschaum as mrsm
10
10
  from meerschaum.utils.typing import Optional, List, Any, SuccessTuple
11
11
 
12
+
12
13
  def attach(
13
14
  action: Optional[List[str]] = None,
14
15
  executor_keys: Optional[str] = None,
@@ -18,7 +18,7 @@ def delete(
18
18
  Delete an element.
19
19
 
20
20
  Command:
21
- `delete {config, pipes, plugins, users, connectors, jobs}`
21
+ `delete {config, pipes, plugins, users, connectors, jobs, venvs}`
22
22
 
23
23
  """
24
24
  from meerschaum.actions import choose_subaction
@@ -159,7 +159,9 @@ def _delete_config(
159
159
  answer = yes_no(
160
160
  "Are you sure you want to delete the following configuration files?" +
161
161
  f"{sep + sep.join([str(p) for p in paths])}\n",
162
- default='n', noask=noask, yes=yes
162
+ default='n',
163
+ noask=noask,
164
+ yes=yes,
163
165
  )
164
166
 
165
167
  if answer or force:
@@ -67,7 +67,12 @@ def _complete_edit(
67
67
  return default_action_completer(action=(['edit'] + action), **kw)
68
68
 
69
69
 
70
- def _edit_config(action: Optional[List[str]] = None, **kw: Any) -> SuccessTuple:
70
+ def _edit_config(
71
+ action: Optional[List[str]] = None,
72
+ noask: bool = False,
73
+ debug: bool = False,
74
+ **kwargs: Any
75
+ ) -> SuccessTuple:
71
76
  """
72
77
  Edit Meerschaum configuration files.
73
78
 
@@ -87,11 +92,26 @@ def _edit_config(action: Optional[List[str]] = None, **kw: Any) -> SuccessTuple:
87
92
  ```
88
93
  """
89
94
  from meerschaum.config._edit import edit_config
90
- if action is None:
91
- action = []
92
- if len(action) == 0:
93
- action.append('meerschaum')
94
- return edit_config(keys=action, **kw)
95
+ from meerschaum.config._read_config import get_possible_keys
96
+ from meerschaum.actions import actions
97
+ from meerschaum.utils.prompt import choose
98
+
99
+ if not action:
100
+ action = [
101
+ choose(
102
+ "Choose a configuration file to edit:",
103
+ get_possible_keys(),
104
+ default='meerschaum',
105
+ noask=noask,
106
+ )
107
+ ]
108
+
109
+ edit_success, edit_msg = edit_config(keys=action, debug=debug, **kwargs)
110
+ if not edit_success:
111
+ return edit_success, edit_msg
112
+
113
+ return actions['reload'](debug=debug)
114
+
95
115
 
96
116
  def _complete_edit_config(action: Optional[List[str]] = None, **kw: Any) -> List[str]:
97
117
  from meerschaum.config._read_config import get_possible_keys
@@ -343,7 +363,6 @@ def _edit_plugins(
343
363
  from meerschaum.utils.warnings import warn
344
364
  from meerschaum.utils.prompt import prompt, yes_no
345
365
  from meerschaum.utils.misc import edit_file
346
- from meerschaum.utils.packages import reload_meerschaum
347
366
  from meerschaum.actions import actions
348
367
 
349
368
  if not action:
@@ -376,7 +395,7 @@ def _edit_plugins(
376
395
  continue
377
396
 
378
397
  edit_file(plugin_file_path)
379
- reload_meerschaum(debug=debug)
398
+ actions['reload'](debug=debug)
380
399
 
381
400
  return True, "Success"
382
401
 
@@ -10,13 +10,13 @@ from __future__ import annotations
10
10
  from meerschaum.utils.typing import SuccessTuple, Optional, List, Any
11
11
 
12
12
  def login(
13
- action: Optional[List[str]] = None,
14
- connector_keys: Optional[List[str]] = None,
15
- yes: bool = False,
16
- noask: bool = False,
17
- debug: bool = False,
18
- **kw: Any
19
- ) -> SuccessTuple:
13
+ action: Optional[List[str]] = None,
14
+ connector_keys: Optional[List[str]] = None,
15
+ yes: bool = False,
16
+ noask: bool = False,
17
+ debug: bool = False,
18
+ **kw: Any
19
+ ) -> SuccessTuple:
20
20
  """
21
21
  Log into a Meerschaum API instance.
22
22
  """
@@ -56,7 +56,7 @@ def login(
56
56
  for k in _keys:
57
57
  try:
58
58
  _connectors.append(parse_instance_keys(k))
59
- except Exception as e:
59
+ except Exception:
60
60
  warn(f"Unable to build connector '{k}'. Is it registered?", stack=False)
61
61
 
62
62
  meerschaum_config = get_config('meerschaum')
@@ -512,14 +512,20 @@ def _register_tokens(
512
512
  "To which user should this token be registered? Enter the number.",
513
513
  usernames,
514
514
  )
515
- user_id = instance_connector.get_user_id(
516
- User(username, instance=mrsm_instance),
517
- debug=debug,
518
- )
519
- if user_id is None:
520
- return False, f"Cannot load ID for user '{username}'."
515
+ else:
516
+ username = getattr(instance_connector, 'username')
517
+
518
+ if not username:
519
+ return False, "Cannot register a token without a user."
520
+
521
+ user_id = instance_connector.get_user_id(
522
+ User(username, instance=mrsm_instance),
523
+ debug=debug,
524
+ )
525
+ if user_id is None:
526
+ return False, f"Cannot load ID for user '{username}'."
521
527
 
522
- user = User(username, user_id=user_id, instance=mrsm_instance)
528
+ user = User(username, user_id=user_id, instance=mrsm_instance)
523
529
 
524
530
  token = Token(
525
531
  label=name,
@@ -9,13 +9,30 @@ Reload the running Meerschaum instance.
9
9
  from __future__ import annotations
10
10
  from meerschaum.utils.typing import Any, SuccessTuple, List, Optional
11
11
 
12
+
12
13
  def reload(
13
- action: Optional[List[str]] = None,
14
- debug: bool = False,
15
- **kw: Any
16
- ) -> SuccessTuple:
14
+ action: Optional[List[str]] = None,
15
+ debug: bool = False,
16
+ _stop_daemons: bool = True,
17
+ **kw: Any
18
+ ) -> SuccessTuple:
17
19
  """
18
20
  Reload the running Meerschaum instance.
19
21
  """
20
22
  from meerschaum.utils.packages import reload_meerschaum
21
- return reload_meerschaum(debug=debug)
23
+ from meerschaum.actions import actions
24
+
25
+ if _stop_daemons:
26
+ from meerschaum._internal.cli.workers import get_existing_cli_worker_indices
27
+ indices = get_existing_cli_worker_indices()
28
+ cli_action = 'restart' if indices else 'stop'
29
+
30
+ stop_daemon_success, stop_daemon_msg = actions[cli_action](['daemons'], debug=debug, **kw)
31
+ if not stop_daemon_success:
32
+ return stop_daemon_success, stop_daemon_msg
33
+
34
+ reload_success, reload_msg = reload_meerschaum(debug=debug)
35
+ if not reload_success:
36
+ return reload_success, reload_msg
37
+
38
+ return True, "Success"
@@ -7,6 +7,7 @@ Restart stopped jobs which have not been manually stopped.
7
7
 
8
8
  from meerschaum.utils.typing import SuccessTuple, Optional, List, Any
9
9
 
10
+
10
11
  def restart(
11
12
  action: Optional[List[str]] = None,
12
13
  executor_keys: Optional[str] = None,
@@ -18,6 +19,7 @@ def restart(
18
19
  from meerschaum.actions import choose_subaction
19
20
  attach_options = {
20
21
  'jobs': _restart_jobs,
22
+ 'daemons': _restart_daemons,
21
23
  }
22
24
  return choose_subaction(action, attach_options, executor_keys=executor_keys, **kwargs)
23
25
 
@@ -111,3 +113,15 @@ def _restart_jobs(
111
113
  time.sleep(min_seconds)
112
114
 
113
115
  return check_success, check_msg
116
+
117
+
118
+ def _restart_daemons(
119
+ action: Optional[List[str]] = None,
120
+ **kwargs
121
+ ) -> SuccessTuple:
122
+ """
123
+ Restart the CLI daemons.
124
+ """
125
+ from meerschaum.actions import actions
126
+ kwargs['force'] = True
127
+ return actions['start'](['daemons'], **kwargs)
@@ -46,6 +46,7 @@ def show(
46
46
  'schedules' : _show_schedules,
47
47
  'venvs' : _show_venvs,
48
48
  'tokens' : _show_tokens,
49
+ 'daemons' : _show_daemons,
49
50
  }
50
51
  return choose_subaction(action, show_options, **kw)
51
52
 
@@ -205,7 +206,7 @@ def _show_version(nopretty: bool = False, **kw : Any) -> SuccessTuple:
205
206
  msg = "Meerschaum v" + version
206
207
  _print = info
207
208
  _print(msg)
208
- return (True, "Success")
209
+ return True, "Success"
209
210
 
210
211
 
211
212
  def _show_connectors(
@@ -563,6 +564,7 @@ def _complete_show_packages(
563
564
 
564
565
  return possibilities
565
566
 
567
+
566
568
  def _show_jobs(
567
569
  action: Optional[List[str]] = None,
568
570
  executor_keys: Optional[str] = None,
@@ -763,7 +765,7 @@ def _show_environment(
763
765
  """
764
766
  import os
765
767
  from meerschaum.utils.formatting import pprint
766
- from meerschaum.config._environment import get_env_vars
768
+ from meerschaum.config.environment import get_env_vars
767
769
  pprint(
768
770
  {
769
771
  env_var: os.environ[env_var]
@@ -930,7 +932,7 @@ def _show_tokens(
930
932
  from meerschaum.utils.packages import import_rich
931
933
  from meerschaum.utils.formatting import UNICODE, get_console
932
934
  rich = import_rich()
933
- rich_table, rich_json = mrsm.attempt_import('rich.table', 'rich.json')
935
+ rich_table, rich_json, rich_box = mrsm.attempt_import('rich.table', 'rich.json', 'rich.box')
934
936
 
935
937
  conn = parse_instance_keys(mrsm_instance)
936
938
 
@@ -994,7 +996,11 @@ def _show_tokens(
994
996
  else "[-]"
995
997
  )
996
998
 
997
- table = rich_table.Table(title=f"Registered Tokens on instance '{conn}'")
999
+ table = rich_table.Table(
1000
+ title=f"Registered Tokens on instance '{conn}'",
1001
+ box=rich_box.ROUNDED,
1002
+ title_style='bold',
1003
+ )
998
1004
  table.add_column("ID")
999
1005
  table.add_column("Label")
1000
1006
  table.add_column("User")
@@ -1018,6 +1024,65 @@ def _show_tokens(
1018
1024
  return True, "Success"
1019
1025
 
1020
1026
 
1027
+ def _show_daemons() -> SuccessTuple:
1028
+ """
1029
+ Print information about the running CLI daemons.
1030
+ """
1031
+ from meerschaum._internal.cli.workers import get_existing_cli_workers
1032
+ workers = get_existing_cli_workers()
1033
+ if not workers:
1034
+ return True, "No CLI daemons are running."
1035
+
1036
+ rich_table, rich_box, rich_text = mrsm.attempt_import('rich.table', 'rich.box', 'rich.text')
1037
+ table = rich_table.Table(
1038
+ rich_table.Column("Index", justify='center'),
1039
+ rich_table.Column("Status"),
1040
+ rich_table.Column("Locked", justify='center'),
1041
+ rich_table.Column("Ready", justify='center'),
1042
+ rich_table.Column("Actions"),
1043
+ title='CLI Daemons',
1044
+ box=rich_box.ROUNDED,
1045
+ title_style='bold',
1046
+ )
1047
+
1048
+ running_icon = mrsm.get_config('formatting', 'emoji', 'running')
1049
+ paused_icon = mrsm.get_config('formatting', 'emoji', 'paused')
1050
+ stopped_icon = mrsm.get_config('formatting', 'emoji', 'stopped')
1051
+ locked_icon = mrsm.get_config('formatting', 'emoji', 'locked')
1052
+ unlocked_icon = mrsm.get_config('formatting', 'emoji', 'unlocked')
1053
+ status_styles = {
1054
+ 'running': 'green',
1055
+ 'stopped': 'red',
1056
+ 'paused': 'yellow',
1057
+ }
1058
+
1059
+ for worker in workers:
1060
+ status = worker.job.status
1061
+ status_text = rich_text.Text(
1062
+ status,
1063
+ style=status_styles[status],
1064
+ )
1065
+ if not worker.is_ready():
1066
+ ready_icon = stopped_icon
1067
+ else:
1068
+ ready_icon = (
1069
+ running_icon
1070
+ if status == 'running'
1071
+ else paused_icon
1072
+ )
1073
+
1074
+ table.add_row(
1075
+ str(worker.ix),
1076
+ status_text,
1077
+ (locked_icon if worker.lock_path.exists() else unlocked_icon),
1078
+ ready_icon,
1079
+ str(worker.job.daemon.rotating_log.get_latest_subfile_index()),
1080
+ )
1081
+
1082
+ mrsm.pprint(table)
1083
+ return True, "Success"
1084
+
1085
+
1021
1086
  ### NOTE: This must be the final statement of the module.
1022
1087
  ### Any subactions added below these lines will not
1023
1088
  ### be added to the `help` docstring.