machineconfig 2.95__py3-none-any.whl → 2.98__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 machineconfig might be problematic. Click here for more details.

@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env python3
2
2
  import shlex
3
3
  import subprocess
4
+ from machineconfig.cluster.sessions_managers.zellij_utils.monitoring_types import CommandStatusResult, ZellijSessionStatus, ComprehensiveStatus, ProcessInfo
4
5
  import psutil
5
6
  import random
6
7
  import string
7
- from typing import Dict, List, Optional, Any
8
+ from typing import List, Optional
8
9
  from pathlib import Path
9
10
  import logging
10
11
 
@@ -12,6 +13,8 @@ from rich.console import Console
12
13
 
13
14
  from machineconfig.utils.schemas.layouts.layout_types import LayoutConfig, TabConfig
14
15
 
16
+
17
+
15
18
  logging.basicConfig(level=logging.INFO)
16
19
  logger = logging.getLogger(__name__)
17
20
  console = Console()
@@ -154,7 +157,7 @@ class ZellijLayoutGenerator:
154
157
  return layout_content + "\n}\n"
155
158
 
156
159
  @staticmethod
157
- def check_command_status(tab_name: str, layout_config: LayoutConfig) -> Dict[str, Any]:
160
+ def check_command_status(tab_name: str, layout_config: LayoutConfig) -> CommandStatusResult:
158
161
  # Find the tab with the given name
159
162
  tab_config = None
160
163
  for tab in layout_config["layoutTabs"]:
@@ -163,39 +166,99 @@ class ZellijLayoutGenerator:
163
166
  break
164
167
 
165
168
  if tab_config is None:
166
- return {"status": "unknown", "error": f"Tab '{tab_name}' not found in layout config", "running": False, "pid": None, "command": None, "cwd": None}
169
+ return {"status": "unknown", "error": f"Tab '{tab_name}' not found in layout config", "running": False, "command": "", "cwd": "", "tab_name": tab_name, "processes": []}
167
170
 
168
171
  command = tab_config["command"]
169
172
  cwd = tab_config["startDir"]
170
- cmd, _ = ZellijLayoutGenerator._parse_command(command)
173
+ cmd, args = ZellijLayoutGenerator._parse_command(command)
171
174
 
172
175
  try:
173
- # Look for processes matching the command
174
- matching_processes = []
175
- for proc in psutil.process_iter(["pid", "name", "cmdline", "status"]):
176
+ # Look for processes matching the command more accurately
177
+ matching_processes: list[ProcessInfo] = []
178
+ for proc in psutil.process_iter(["pid", "name", "cmdline", "status", "ppid"]):
179
+ try:
180
+ if not proc.info["cmdline"] or len(proc.info["cmdline"]) == 0:
181
+ continue
182
+
183
+ # Skip processes that are already dead/zombie/stopped
184
+ if proc.info["status"] in ["zombie", "dead", "stopped"]:
185
+ continue
186
+
187
+ # Get the actual command from cmdline
188
+ proc_cmdline = proc.info["cmdline"]
189
+ proc_name = proc.info["name"]
190
+
191
+ # More precise matching logic
192
+ is_match = False
193
+
194
+ # Check if this is the exact command we're looking for
195
+ if proc_name == cmd:
196
+ # For exact name matches, also check if arguments match or if it's a reasonable match
197
+ if len(args) == 0 or any(arg in " ".join(proc_cmdline) for arg in args):
198
+ is_match = True
199
+
200
+ # Check if the command appears in the command line
201
+ elif cmd in proc_cmdline[0]:
202
+ is_match = True
203
+
204
+ # For script-based commands, check if the script name appears
205
+ elif any(cmd in arg for arg in proc_cmdline):
206
+ is_match = True
207
+
208
+ # Skip shell processes that are just wrappers unless they're running our specific command
209
+ if is_match and proc_name in ["bash", "sh", "zsh", "fish"]:
210
+ # Only count shell processes if they contain our specific command
211
+ full_cmdline = " ".join(proc_cmdline)
212
+ if cmd not in full_cmdline and not any(arg in full_cmdline for arg in args):
213
+ is_match = False
214
+
215
+ if is_match:
216
+ # Additional check: make sure the process is actually running something meaningful
217
+ try:
218
+ # Check if process has been running for a reasonable time and is active
219
+ proc_obj = psutil.Process(proc.info["pid"])
220
+ if proc_obj.status() not in ["running", "sleeping"]:
221
+ continue # Skip inactive processes
222
+
223
+ matching_processes.append({
224
+ "pid": proc.info["pid"],
225
+ "name": proc.info["name"],
226
+ "cmdline": proc.info["cmdline"],
227
+ "status": proc.info["status"]
228
+ })
229
+ except (psutil.NoSuchProcess, psutil.AccessDenied):
230
+ continue
231
+
232
+ except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
233
+ continue
234
+
235
+ # Filter out clearly finished processes or parent shells
236
+ active_processes = []
237
+ for proc_info in matching_processes:
176
238
  try:
177
- if proc.info["cmdline"] and len(proc.info["cmdline"]) > 0:
178
- # Check if the command matches
179
- if proc.info["name"] == cmd or cmd in proc.info["cmdline"][0] or any(cmd in arg for arg in proc.info["cmdline"]):
180
- matching_processes.append({"pid": proc.info["pid"], "name": proc.info["name"], "cmdline": proc.info["cmdline"], "status": proc.info["status"]})
181
- except (psutil.NoSuchProcess, psutil.AccessDenied):
239
+ proc = psutil.Process(proc_info["pid"])
240
+ # Double-check the process is still active
241
+ if proc.status() in ["running", "sleeping"] and proc.is_running():
242
+ active_processes.append(proc_info)
243
+ except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
244
+ # Process is gone, don't count it
182
245
  continue
183
246
 
184
- if matching_processes:
185
- return {"status": "running", "running": True, "processes": matching_processes, "command": command, "cwd": cwd, "tab_name": tab_name}
247
+ if active_processes:
248
+ return {"status": "running", "running": True, "processes": active_processes, "command": command, "cwd": cwd, "tab_name": tab_name}
186
249
  else:
187
250
  return {"status": "not_running", "running": False, "processes": [], "command": command, "cwd": cwd, "tab_name": tab_name}
188
251
 
189
252
  except Exception as e:
190
253
  logger.error(f"Error checking command status for tab '{tab_name}': {e}")
191
- return {"status": "error", "error": str(e), "running": False, "command": command, "cwd": cwd, "tab_name": tab_name}
254
+ return {"status": "error", "error": str(e), "running": False, "command": command, "cwd": cwd, "tab_name": tab_name, "processes": []}
192
255
 
193
- def check_all_commands_status(self) -> Dict[str, Dict[str, Any]]:
256
+ def check_all_commands_status(self) -> dict[str, CommandStatusResult]:
194
257
  if not self.layout_config:
195
258
  logger.warning("No layout config tracked. Make sure to create a layout first.")
196
259
  return {}
197
260
 
198
- status_report = {}
261
+ status_report: dict[str, CommandStatusResult] = {}
199
262
  for tab in self.layout_config["layoutTabs"]:
200
263
  tab_name = tab["tabName"]
201
264
  status_report[tab_name] = ZellijLayoutGenerator.check_command_status(tab_name, self.layout_config)
@@ -203,7 +266,7 @@ class ZellijLayoutGenerator:
203
266
  return status_report
204
267
 
205
268
  @staticmethod
206
- def check_zellij_session_status(session_name: str) -> Dict[str, Any]:
269
+ def check_zellij_session_status(session_name: str) -> ZellijSessionStatus:
207
270
  try:
208
271
  # Run zellij list-sessions command
209
272
  result = subprocess.run(["zellij", "list-sessions"], capture_output=True, text=True, timeout=10)
@@ -214,16 +277,16 @@ class ZellijLayoutGenerator:
214
277
 
215
278
  return {"zellij_running": True, "session_exists": session_running, "session_name": session_name, "all_sessions": sessions}
216
279
  else:
217
- return {"zellij_running": False, "error": result.stderr, "session_name": session_name}
280
+ return {"zellij_running": False, "session_name": session_name, "all_sessions": [], "error": result.stderr}
218
281
 
219
282
  except subprocess.TimeoutExpired:
220
- return {"zellij_running": False, "error": "Timeout while checking Zellij sessions", "session_name": session_name}
283
+ return {"zellij_running": False, "session_name": session_name, "all_sessions": [], "error": "Timeout while checking Zellij sessions"}
221
284
  except FileNotFoundError:
222
- return {"zellij_running": False, "error": "Zellij not found in PATH", "session_name": session_name}
285
+ return {"zellij_running": False, "session_name": session_name, "all_sessions": [], "error": "Zellij not found in PATH"}
223
286
  except Exception as e:
224
- return {"zellij_running": False, "error": str(e), "session_name": session_name}
287
+ return {"zellij_running": False, "session_name": session_name, "all_sessions": [], "error": str(e)}
225
288
 
226
- def get_comprehensive_status(self) -> Dict[str, Any]:
289
+ def get_comprehensive_status(self) -> ComprehensiveStatus:
227
290
  zellij_status = ZellijLayoutGenerator.check_zellij_session_status(self.session_name or "default")
228
291
  commands_status = self.check_all_commands_status()
229
292
 
@@ -6,14 +6,16 @@ import logging
6
6
  import subprocess
7
7
  import time
8
8
  from pathlib import Path
9
- from typing import Optional, Dict, List, Any
9
+ from typing import Optional, List
10
10
 
11
11
  from rich.console import Console
12
12
 
13
+ from machineconfig.cluster.sessions_managers.zellij_utils.monitoring_types import SessionReport, GlobalSummary, StartResult, ActiveSessionInfo
13
14
  from machineconfig.utils.utils5 import Scheduler
14
15
  from machineconfig.cluster.sessions_managers.zellij_local import ZellijLayoutGenerator
15
16
  from machineconfig.utils.schemas.layouts.layout_types import LayoutConfig
16
17
 
18
+
17
19
  logging.basicConfig(level=logging.INFO)
18
20
  logger = logging.getLogger(__name__)
19
21
  console = Console()
@@ -24,7 +26,7 @@ TMP_SERIALIZATION_DIR = Path.home().joinpath("tmp_results", "session_manager", "
24
26
  class ZellijLocalManager:
25
27
  """Manages multiple local zellij sessions and monitors their tabs and processes."""
26
28
 
27
- def __init__(self, session_layouts: list[LayoutConfig], ):
29
+ def __init__(self, session_layouts: list[LayoutConfig]):
28
30
  self.session_name_prefix = "LocalJobMgr"
29
31
  self.session_layouts = session_layouts # Store the original config
30
32
  self.managers: List[ZellijLayoutGenerator] = []
@@ -44,7 +46,7 @@ class ZellijLocalManager:
44
46
  """Get all managed session names."""
45
47
  return [manager.session_name for manager in self.managers if manager.session_name is not None]
46
48
 
47
- def start_all_sessions(self, poll_seconds: float = 5.0, poll_interval: float = 0.25) -> Dict[str, Any]:
49
+ def start_all_sessions(self, poll_seconds: float = 5.0, poll_interval: float = 0.25) -> dict[str, StartResult]:
48
50
  """Start all zellij sessions with their layouts without blocking on the interactive TUI.
49
51
 
50
52
  Rationale:
@@ -60,7 +62,7 @@ class ZellijLocalManager:
60
62
  Returns:
61
63
  Dict mapping session name to success metadata.
62
64
  """
63
- results: Dict[str, Any] = {}
65
+ results: dict[str, StartResult] = {}
64
66
  for manager in self.managers:
65
67
  session_name = manager.session_name
66
68
  try:
@@ -110,9 +112,9 @@ class ZellijLocalManager:
110
112
  logger.error(f"❌ Exception starting session '{key}': {e}")
111
113
  return results
112
114
 
113
- def kill_all_sessions(self) -> Dict[str, Any]:
115
+ def kill_all_sessions(self) -> dict[str, StartResult]:
114
116
  """Kill all managed zellij sessions."""
115
- results = {}
117
+ results: dict[str, StartResult] = {}
116
118
  for manager in self.managers:
117
119
  try:
118
120
  session_name = manager.session_name
@@ -158,9 +160,9 @@ class ZellijLocalManager:
158
160
  commands.append("")
159
161
  return "\n".join(commands)
160
162
 
161
- def check_all_sessions_status(self) -> Dict[str, Dict[str, Any]]:
163
+ def check_all_sessions_status(self) -> dict[str, SessionReport]:
162
164
  """Check the status of all sessions and their commands."""
163
- status_report = {}
165
+ status_report: dict[str, SessionReport] = {}
164
166
 
165
167
  for manager in self.managers:
166
168
  session_name = manager.session_name
@@ -185,7 +187,7 @@ class ZellijLocalManager:
185
187
 
186
188
  return status_report
187
189
 
188
- def get_global_summary(self) -> Dict[str, Any]:
190
+ def get_global_summary(self) -> GlobalSummary:
189
191
  """Get a global summary across all sessions."""
190
192
  all_status = self.check_all_sessions_status()
191
193
 
@@ -298,7 +300,8 @@ class ZellijLocalManager:
298
300
  running_count = sum(1 for row in status_data if row.get("running", False))
299
301
  if running_count == 0:
300
302
  print("\n⚠️ All commands have stopped. Stopping monitoring.")
301
- scheduler.max_cycles = scheduler.cycle
303
+ # Set max_cycles to current cycle + 1 to exit after this cycle
304
+ scheduler.max_cycles = scheduler.cycle + 1
302
305
  return
303
306
  else:
304
307
  print("No status data available")
@@ -420,9 +423,9 @@ class ZellijLocalManager:
420
423
  logger.error(f"Failed to delete session {session_id}: {e}")
421
424
  return False
422
425
 
423
- def list_active_sessions(self) -> List[Dict[str, Any]]:
426
+ def list_active_sessions(self) -> list[ActiveSessionInfo]:
424
427
  """List currently active zellij sessions managed by this instance."""
425
- active_sessions = []
428
+ active_sessions: list[ActiveSessionInfo] = []
426
429
 
427
430
  try:
428
431
  # Get all running zellij sessions
@@ -482,7 +485,7 @@ if __name__ == "__main__":
482
485
  ]
483
486
  try:
484
487
  # Create the local manager
485
- manager = ZellijLocalManager(sample_sessions,)
488
+ manager = ZellijLocalManager(sample_sessions)
486
489
  print(f"✅ Local manager created with {len(manager.managers)} sessions")
487
490
 
488
491
  # Show session names
@@ -0,0 +1,121 @@
1
+ # TypedDict definitions for better type safety
2
+ from typing import NotRequired, TypedDict, Optional
3
+
4
+ # Import concrete types to replace Any usage
5
+ from machineconfig.utils.schemas.layouts.layout_types import LayoutConfig
6
+
7
+
8
+ class ProcessInfo(TypedDict):
9
+ pid: int
10
+ name: str
11
+ cmdline: list[str]
12
+ status: str
13
+
14
+ class CommandStatus(TypedDict):
15
+ status: str
16
+ running: bool
17
+ processes: list[ProcessInfo]
18
+ command: str
19
+ cwd: str
20
+ tab_name: str
21
+ error: NotRequired[str]
22
+ pid: NotRequired[int]
23
+
24
+
25
+ class SessionStatus(TypedDict):
26
+ session_exists: bool
27
+ zellij_running: bool
28
+ session_name: str
29
+ all_sessions: list[str]
30
+ error: NotRequired[str]
31
+
32
+
33
+ class CommandSummary(TypedDict):
34
+ total_commands: int
35
+ running_commands: int
36
+ stopped_commands: int
37
+ session_healthy: bool
38
+
39
+
40
+ class CommandStatusResult(TypedDict):
41
+ status: str
42
+ running: bool
43
+ processes: list[ProcessInfo]
44
+ command: str
45
+ cwd: str
46
+ tab_name: str
47
+ error: NotRequired[str]
48
+ pid: NotRequired[int]
49
+
50
+
51
+ class ZellijSessionStatus(TypedDict):
52
+ zellij_running: bool
53
+ session_exists: NotRequired[bool]
54
+ session_name: str
55
+ all_sessions: list[str]
56
+ error: NotRequired[str]
57
+
58
+
59
+ class SessionReport(TypedDict):
60
+ session_status: ZellijSessionStatus # ZellijSessionStatus from zellij_local
61
+ commands_status: dict[str, CommandStatusResult] # dict[str, CommandStatusResult from zellij_local]
62
+ summary: CommandSummary
63
+
64
+
65
+ class GlobalSummary(TypedDict):
66
+ total_sessions: int
67
+ healthy_sessions: int
68
+ unhealthy_sessions: int
69
+ total_commands: int
70
+ running_commands: int
71
+ stopped_commands: int
72
+ all_sessions_healthy: bool
73
+ all_commands_running: bool
74
+
75
+
76
+ class StartResult(TypedDict):
77
+ success: bool
78
+ message: NotRequired[str]
79
+ error: NotRequired[str]
80
+
81
+
82
+ class StatusRow(TypedDict):
83
+ session: str
84
+ tab: str
85
+ running: bool
86
+ command: str
87
+ processes: int
88
+
89
+
90
+ class SessionMetadata(TypedDict):
91
+ session_name_prefix: str
92
+ created_at: str
93
+ num_managers: int
94
+ sessions: list[str]
95
+ manager_type: str
96
+
97
+
98
+ class ManagerData(TypedDict):
99
+ session_name: Optional[str]
100
+ layout_config: Optional[LayoutConfig] # Will be LayoutConfig from layout_types
101
+ layout_path: Optional[str]
102
+
103
+
104
+ class ActiveSessionInfo(TypedDict):
105
+ session_name: str
106
+ is_active: bool
107
+ tab_count: int
108
+ tabs: list[str]
109
+
110
+
111
+ class StatusSummary(TypedDict):
112
+ total_commands: int
113
+ running_commands: int
114
+ stopped_commands: int
115
+ session_healthy: bool
116
+
117
+
118
+ class ComprehensiveStatus(TypedDict):
119
+ zellij_session: ZellijSessionStatus
120
+ commands: dict[str, CommandStatusResult]
121
+ summary: StatusSummary
@@ -15,8 +15,8 @@ import sys
15
15
  from machineconfig.scripts.python.fire_agents_help_launch import prep_agent_launch, get_agents_launch_layout, AGENTS
16
16
  from machineconfig.scripts.python.fire_agents_help_search import search_files_by_pattern, search_python_files
17
17
  from machineconfig.scripts.python.fire_agents_load_balancer import chunk_prompts, SPLITTING_STRATEGY, DEFAULT_AGENT_CAP
18
- from machineconfig.cluster.sessions_managers.zellij_local_manager import ZellijLocalManager
19
18
  from machineconfig.utils.options import choose_one_option
19
+ from machineconfig.utils.schemas.layouts.layout_types import LayoutsFile
20
20
  from machineconfig.utils.ve import get_repo_root
21
21
 
22
22
  SEARCH_STRATEGIES: TypeAlias = Literal["file_path", "keyword_search", "filename_pattern"]
@@ -141,10 +141,22 @@ manager.run_monitoring_routine()
141
141
  if len(layoutfile["layouts"][0]["layoutTabs"]) > 25:
142
142
  print("Too many agents (>25) to launch. Skipping launch.")
143
143
  sys.exit(0)
144
+ from machineconfig.cluster.sessions_managers.zellij_local_manager import ZellijLocalManager
144
145
  manager = ZellijLocalManager(session_layouts=layoutfile["layouts"])
145
146
  manager.start_all_sessions()
146
147
  manager.run_monitoring_routine()
147
148
 
148
149
 
150
+ def launch_sequentially(layoutfile: "LayoutsFile"):
151
+ from machineconfig.utils.utils2 import split
152
+ from machineconfig.cluster.sessions_managers.zellij_local_manager import ZellijLocalManager
153
+ for layout_chunk in split(layoutfile["layouts"], every=3):
154
+ manager = ZellijLocalManager(session_layouts=layout_chunk)
155
+ manager.start_all_sessions()
156
+ manager.run_monitoring_routine()
157
+
158
+
159
+
160
+
149
161
  if __name__ == "__main__": # pragma: no cover
150
162
  main()
@@ -74,10 +74,10 @@ sleep 0.1
74
74
  model = "gemini-2.5-pro"
75
75
  # model = "gemini-2.5-flash-lite"
76
76
  # model = None # auto-select
77
- if model is None:
78
- model_arg = ""
79
- else:
80
- model_arg = f"--model {shlex.quote(model)}"
77
+ # if model is None:
78
+ # model_arg = ""
79
+ # else:
80
+ model_arg = f"--model {shlex.quote(model)}"
81
81
  # Need a real shell for the pipeline; otherwise '| gemini ...' is passed as args to 'cat'
82
82
  safe_path = shlex.quote(str(prompt_path))
83
83
  api_keys = get_gemini_api_keys()
@@ -4,13 +4,10 @@ from rich.panel import Panel
4
4
  from rich.console import Console
5
5
  import platform
6
6
  import subprocess
7
- from typing import Optional, Union, TypeVar, Iterable
7
+ from typing import Optional, Union, Iterable
8
8
  from machineconfig.utils.source_of_truth import WINDOWS_INSTALL_PATH, LINUX_INSTALL_PATH
9
9
 
10
10
 
11
- T = TypeVar("T")
12
-
13
-
14
11
  def check_tool_exists(tool_name: str) -> bool:
15
12
  if platform.system() == "Windows":
16
13
  tool_name = tool_name.replace(".exe", "") + ".exe"
@@ -35,20 +32,20 @@ def check_tool_exists(tool_name: str) -> bool:
35
32
  # return root_path.joinpath(tool_name).is_file()
36
33
 
37
34
 
38
- def choose_one_option(options: Iterable[T], header: str = "", tail: str = "", prompt: str = "", msg: str = "", default: Optional[T] = None, fzf: bool = False, custom_input: bool = False) -> T:
35
+ def choose_one_option[T](options: Iterable[T], header: str = "", tail: str = "", prompt: str = "", msg: str = "", default: Optional[T] = None, fzf: bool = False, custom_input: bool = False) -> T:
39
36
  choice_key = display_options(msg=msg, options=options, header=header, tail=tail, prompt=prompt, default=default, fzf=fzf, multi=False, custom_input=custom_input)
40
37
  assert not isinstance(choice_key, list)
41
38
  return choice_key
42
39
 
43
40
 
44
- def choose_multiple_options(options: Iterable[T], header: str = "", tail: str = "", prompt: str = "", msg: str = "", default: Optional[T] = None, custom_input: bool = False) -> list[T]:
41
+ def choose_multiple_options[T](options: Iterable[T], header: str = "", tail: str = "", prompt: str = "", msg: str = "", default: Optional[T] = None, custom_input: bool = False) -> list[T]:
45
42
  choice_key = display_options(msg=msg, options=options, header=header, tail=tail, prompt=prompt, default=default, fzf=True, multi=True, custom_input=custom_input)
46
43
  if isinstance(choice_key, list):
47
44
  return choice_key
48
45
  return [choice_key]
49
46
 
50
47
 
51
- def display_options(msg: str, options: Iterable[T], header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, fzf: bool = False, multi: bool = False, custom_input: bool = False) -> Union[T, list[T]]:
48
+ def display_options[T](msg: str, options: Iterable[T], header: str = "", tail: str = "", prompt: str = "", default: Optional[T] = None, fzf: bool = False, multi: bool = False, custom_input: bool = False) -> Union[T, list[T]]:
52
49
  # TODO: replace with https://github.com/tmbo/questionary
53
50
  # # also see https://github.com/charmbracelet/gum
54
51
  tool_name = "fzf"
@@ -5,11 +5,9 @@ from rich.console import Console
5
5
  from rich.panel import Panel
6
6
  import platform
7
7
  import subprocess
8
- from typing import TypeVar
9
8
  from pathlib import Path
10
9
 
11
10
 
12
- T = TypeVar("T")
13
11
  console = Console()
14
12
 
15
13
 
@@ -18,6 +18,19 @@ def randstr(length: int = 10, lower: bool = True, upper: bool = True, digits: bo
18
18
  return "".join(random.choices(population, k=length))
19
19
 
20
20
 
21
+ def split[T](iterable: list[T], every: int = 1, to: Optional[int] = None) -> list[list[T]]:
22
+ import math
23
+ every = every if to is None else math.ceil(len(iterable) / to)
24
+ res: list[list[T]] = []
25
+ for ix in range(0, len(iterable), every):
26
+ if ix + every < len(iterable):
27
+ tmp = iterable[ix : ix + every]
28
+ else:
29
+ tmp = iterable[ix : len(iterable)]
30
+ res.append(list(tmp))
31
+ return list(res)
32
+
33
+
21
34
  def read_ini(path: "Path", encoding: Optional[str] = None):
22
35
  if not Path(path).exists() or Path(path).is_dir():
23
36
  raise FileNotFoundError(f"File not found or is a directory: {path}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: machineconfig
3
- Version: 2.95
3
+ Version: 2.98
4
4
  Summary: Dotfiles management package
5
5
  Author-email: Alex Al-Saffar <programmer@usa.com>
6
6
  License: Apache 2.0
@@ -16,8 +16,8 @@ machineconfig/cluster/sessions_managers/wt_local.py,sha256=5tBgiWf-gsAvg1PrSEF7Z
16
16
  machineconfig/cluster/sessions_managers/wt_local_manager.py,sha256=EqSdzHx5VLkaxKXLfHXigpaGB3dXS5RS-04d-BiTgzI,24192
17
17
  machineconfig/cluster/sessions_managers/wt_remote.py,sha256=7KT5D0FM9wwY8N3Mp9uO9rv3z29n6HIDJfPSLWXOIIs,8755
18
18
  machineconfig/cluster/sessions_managers/wt_remote_manager.py,sha256=V7Z_1sLGfCjyGh2rxbtnDCzrzlMYrdY_6uG3Gljp20o,19758
19
- machineconfig/cluster/sessions_managers/zellij_local.py,sha256=I2iQAS7G13lxQa0KCfHHXYMo9_54dDUwOYNKW_AWlek,17081
20
- machineconfig/cluster/sessions_managers/zellij_local_manager.py,sha256=eemuygUYs6jjAns_xjX-W25T8pVw4Ds7aXK8WQQ2Ot0,23464
19
+ machineconfig/cluster/sessions_managers/zellij_local.py,sha256=dPrnSvRlzhs8tWY1pI3qOqKYexUKsRp5S3nPqAx5LfE,20557
20
+ machineconfig/cluster/sessions_managers/zellij_local_manager.py,sha256=AoUnXIL-K31HcO-Xto-eTcuvy3u5G0Y0VG4aRvlUzj8,23785
21
21
  machineconfig/cluster/sessions_managers/zellij_remote.py,sha256=G1SdycIHuCBMOnO5kfxDNN-boNxZ6PSh0xsZsDK8H1s,11039
22
22
  machineconfig/cluster/sessions_managers/zellij_remote_manager.py,sha256=2ZoSPrlsYJbugH8f8doNIDhmbv2M61DN9KIQNVUqE1k,8116
23
23
  machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py,sha256=CFGcZPFTZQJtFf0OvMUHhadZ0qbImCP3wxvbWYVcVYo,7445
@@ -27,6 +27,7 @@ machineconfig/cluster/sessions_managers/wt_utils/session_manager.py,sha256=-PNcY
27
27
  machineconfig/cluster/sessions_managers/wt_utils/status_reporter.py,sha256=EEscow4hsvLJ1roXEXxXg0QUEwetJmTq0VRm_1Vg6L0,9499
28
28
  machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py,sha256=lxeZVYIelUEf7pFSmg1F5jPbWfzuxQmZ2IBZsHMwFVk,2852
29
29
  machineconfig/cluster/sessions_managers/zellij_utils/layout_generator.py,sha256=OfMhdqDR43r8vEnTNSrV9hvMHG3cNl7EmJKB_ChJBJ8,5263
30
+ machineconfig/cluster/sessions_managers/zellij_utils/monitoring_types.py,sha256=v0bHlnxYVVvsDX-s_2W7UYAGZFNuIRD7VAPL5VejZ9o,2667
30
31
  machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py,sha256=8ilY7ntXWTsFy6-G-j2GZvakK4C43s5y5RFw_feOnDk,13298
31
32
  machineconfig/cluster/sessions_managers/zellij_utils/remote_executor.py,sha256=Nkd5QFw5yG2qD2-WLioqi-e3WKuOqGA8C35epGmB49g,2602
32
33
  machineconfig/cluster/sessions_managers/zellij_utils/session_manager.py,sha256=U_Gvhf-uPwY8jFoxg4aOWsIQbYjd8ueRsfPim1Wv0Pc,4936
@@ -172,8 +173,8 @@ machineconfig/scripts/python/devops_backup_retrieve.py,sha256=I7lghN_HJT1mPaZ7zc
172
173
  machineconfig/scripts/python/devops_devapps_install.py,sha256=xQaOHlr6ziHxrx7e5Ri7WQ-FjIW0hjzDhlPJC-3qwks,8596
173
174
  machineconfig/scripts/python/devops_update_repos.py,sha256=sqtkpQ2InUz-TFLhejN8EZqjZC1aBanAhohL6aHxPf0,8213
174
175
  machineconfig/scripts/python/dotfile.py,sha256=miL8mQH2AqPdnHSz0Cxa7qQavaOmzTD9DAF66H2PRzA,2259
175
- machineconfig/scripts/python/fire_agents.py,sha256=XIxOhI5q7DWpZJMs3rrhvdl0k8t-UICEpSp5W8Oc7bk,7716
176
- machineconfig/scripts/python/fire_agents_help_launch.py,sha256=70iFF0wmFYKvaPsUy35_bIw5ivn5EdonahjSw1GTMIs,6125
176
+ machineconfig/scripts/python/fire_agents.py,sha256=HoT2NjNDB3lmmGjcsvFVf8dTtCAQKBh_DtdhLClL_Bw,8202
177
+ machineconfig/scripts/python/fire_agents_help_launch.py,sha256=B3Pl2uLIk3EMttT1eRaJeBMoH1u_05PsMqEBYKyYogU,6127
177
178
  machineconfig/scripts/python/fire_agents_help_search.py,sha256=jEgPFMHL4s1VHQ1AJO1YdV2pyGdxdaCY5XxDUvC55nU,2991
178
179
  machineconfig/scripts/python/fire_agents_load_balancer.py,sha256=FeTfbp7n6jHHwMp4L6KVYO5HgenP57XpCyaab_vCwc0,2135
179
180
  machineconfig/scripts/python/fire_jobs.py,sha256=IRs0_KY8WvYdF4Zr9fF-wWyqZYTx-zfhAx5ExYbwpOg,16642
@@ -397,8 +398,8 @@ machineconfig/utils/installer.py,sha256=725N-0gmMbrWWh6_9sn-2w9aXrfjIPrnuu9o13T4
397
398
  machineconfig/utils/io_save.py,sha256=iC1YTH0MOlBS8bWUB8Xhdl4CG_bEKQ3OVwMohdCOazI,3145
398
399
  machineconfig/utils/links.py,sha256=5rDQ6Id-vTtJRFwOWX8xZDXIOR5lY0DgLy0HpxIKLhk,10247
399
400
  machineconfig/utils/notifications.py,sha256=q1kZM1y4ZmNhAHlEWZlzLE2HS3nnevrSUpunxd8rAT4,9307
400
- machineconfig/utils/options.py,sha256=F8w5AEvm8yxgipYpLgeHlXJtvoXigZvDJjSqA5TU9J0,8554
401
- machineconfig/utils/path.py,sha256=k0iyDaYsO-4yWCzjZ88-SxXDZLZPTGYX2WeSU8Pn-6w,8056
401
+ machineconfig/utils/options.py,sha256=wCP1oN-UFl8CbcCfJ7Orw_Ghy2ZKqNLiolzCGeRVfuk,8535
402
+ machineconfig/utils/path.py,sha256=csRyffpWYY5-sqeQCZyWi_z4b295EAztEmZWyvFYRW8,8012
402
403
  machineconfig/utils/path_reduced.py,sha256=hccGUTSMtggU-LpLNS7W_c1s-CqhjHMbTQ59-1EqgmY,52473
403
404
  machineconfig/utils/procs.py,sha256=aw2UyFvoJw69zDzfcxG46z-4O8MA5WsXvRpvFTqht-4,11484
404
405
  machineconfig/utils/scheduling.py,sha256=8xjeoR_D5QHT0d7299Mgsj6JUbvkE_PX_pWq3myi658,11184
@@ -406,7 +407,7 @@ machineconfig/utils/source_of_truth.py,sha256=GnjcVkKm11RyZFHGnPbne5YDEBYoZ5yryB
406
407
  machineconfig/utils/ssh.py,sha256=YBL2avhPUC43L1wxi2Doh2-cK-I7j81eUWEmfzAj9Jc,21196
407
408
  machineconfig/utils/terminal.py,sha256=k3xoMwJPGvmaeL9CxEIwrcQjRNN6EnJItoIIxXrUY8c,12258
408
409
  machineconfig/utils/upgrade_packages.py,sha256=H96zVJEWXJW07nh5vhjuSCrPtXGqoUb7xeJsFYYdmCI,3330
409
- machineconfig/utils/utils2.py,sha256=Y9bX4gaaPih7gbOeTcfPhQ3CcMFKGXgVCEQhVhljH4Q,2526
410
+ machineconfig/utils/utils2.py,sha256=wcvI9Vcz3A1syRL1qs75jVTmGsLyFh1ZIZlfGmG9cfE,2972
410
411
  machineconfig/utils/utils5.py,sha256=s2NFUBcm4Jeuet8sUC7WKGSrYT4BnqTog39bsynXLik,15166
411
412
  machineconfig/utils/ve.py,sha256=bi2bCiBbCdNICvXA8stXvMOmvtipd1dMePDTX_IYZcc,2765
412
413
  machineconfig/utils/ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
@@ -419,8 +420,8 @@ machineconfig/utils/installer_utils/installer_class.py,sha256=3jknokPmaN4LuBR5n6
419
420
  machineconfig/utils/schemas/fire_agents/fire_agents_input.py,sha256=Pkp5nzmzuFOKhVHhaAcue7gmCOKrm7m0ft_FSdDn6X8,2350
420
421
  machineconfig/utils/schemas/layouts/layout_types.py,sha256=9l8c02MVX_e-OOvGTlfM_Ef6wWw6_Wqru1g7HxWnhgU,615
421
422
  machineconfig/utils/schemas/repos/repos_types.py,sha256=beWlwPRNDBABmlzxHBYkAsMXS1MgFFdDZf4G2ge8J-I,408
422
- machineconfig-2.95.dist-info/METADATA,sha256=yL5o8Om_xVx_w4EfyWB6GlvVoLSIH0QySr-K1kwSxMs,7049
423
- machineconfig-2.95.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
424
- machineconfig-2.95.dist-info/entry_points.txt,sha256=71EzS7_2LTIigVxC1YXNxHXhC9mu5Me2Feyq2KocXBI,977
425
- machineconfig-2.95.dist-info/top_level.txt,sha256=porRtB8qms8fOIUJgK-tO83_FeH6Bpe12oUVC670teA,14
426
- machineconfig-2.95.dist-info/RECORD,,
423
+ machineconfig-2.98.dist-info/METADATA,sha256=4Vf7Rgil9VfPA1EYFpD30HClPoxIsQNerPqI3tP6Jpw,7049
424
+ machineconfig-2.98.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
425
+ machineconfig-2.98.dist-info/entry_points.txt,sha256=71EzS7_2LTIigVxC1YXNxHXhC9mu5Me2Feyq2KocXBI,977
426
+ machineconfig-2.98.dist-info/top_level.txt,sha256=porRtB8qms8fOIUJgK-tO83_FeH6Bpe12oUVC670teA,14
427
+ machineconfig-2.98.dist-info/RECORD,,