machineconfig 2.1__py3-none-any.whl → 2.3__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.

Files changed (127) hide show
  1. machineconfig/cluster/sessions_managers/enhanced_command_runner.py +0 -2
  2. machineconfig/cluster/sessions_managers/layout_types.py +29 -0
  3. machineconfig/cluster/sessions_managers/wt_local.py +68 -62
  4. machineconfig/cluster/sessions_managers/wt_local_manager.py +51 -22
  5. machineconfig/cluster/sessions_managers/wt_remote.py +30 -108
  6. machineconfig/cluster/sessions_managers/wt_remote_manager.py +14 -11
  7. machineconfig/cluster/sessions_managers/wt_utils/layout_generator.py +33 -37
  8. machineconfig/cluster/sessions_managers/wt_utils/process_monitor.py +22 -17
  9. machineconfig/cluster/sessions_managers/wt_utils/session_manager.py +59 -10
  10. machineconfig/cluster/sessions_managers/wt_utils/status_reporter.py +16 -14
  11. machineconfig/cluster/sessions_managers/zellij_local.py +75 -57
  12. machineconfig/cluster/sessions_managers/zellij_local_manager.py +51 -23
  13. machineconfig/cluster/sessions_managers/zellij_remote.py +47 -27
  14. machineconfig/cluster/sessions_managers/zellij_remote_manager.py +13 -12
  15. machineconfig/cluster/sessions_managers/zellij_utils/example_usage.py +14 -10
  16. machineconfig/cluster/sessions_managers/zellij_utils/layout_generator.py +31 -15
  17. machineconfig/cluster/sessions_managers/zellij_utils/process_monitor.py +47 -21
  18. machineconfig/cluster/sessions_managers/zellij_utils/session_manager.py +1 -1
  19. machineconfig/cluster/sessions_managers/zellij_utils/status_reporter.py +8 -7
  20. machineconfig/cluster/templates/utils.py +0 -35
  21. machineconfig/jobs/python/check_installations.py +1 -1
  22. machineconfig/jobs/python_custom_installers/dev/code.py +0 -13
  23. machineconfig/jobs/python_generic_installers/config.json +1 -1
  24. machineconfig/profile/create.py +13 -4
  25. machineconfig/profile/create_hardlinks.py +3 -1
  26. machineconfig/profile/shell.py +8 -7
  27. machineconfig/scripts/__init__.py +0 -2
  28. machineconfig/scripts/linux/devops +6 -4
  29. machineconfig/scripts/python/ai/generate_files.py +14 -15
  30. machineconfig/scripts/python/ai/mcinit.py +8 -5
  31. machineconfig/scripts/python/archive/tmate_conn.py +5 -5
  32. machineconfig/scripts/python/archive/tmate_start.py +7 -7
  33. machineconfig/scripts/python/choose_wezterm_theme.py +35 -32
  34. machineconfig/scripts/python/cloud_copy.py +22 -13
  35. machineconfig/scripts/python/cloud_mount.py +35 -23
  36. machineconfig/scripts/python/cloud_repo_sync.py +38 -25
  37. machineconfig/scripts/python/cloud_sync.py +4 -4
  38. machineconfig/scripts/python/croshell.py +37 -28
  39. machineconfig/scripts/python/devops.py +46 -27
  40. machineconfig/scripts/python/devops_add_identity.py +15 -25
  41. machineconfig/scripts/python/devops_add_ssh_key.py +7 -7
  42. machineconfig/scripts/python/devops_backup_retrieve.py +17 -15
  43. machineconfig/scripts/python/devops_devapps_install.py +26 -20
  44. machineconfig/scripts/python/devops_update_repos.py +142 -57
  45. machineconfig/scripts/python/dotfile.py +16 -14
  46. machineconfig/scripts/python/fire_agents.py +30 -23
  47. machineconfig/scripts/python/fire_jobs.py +86 -98
  48. machineconfig/scripts/python/fire_jobs_args_helper.py +84 -0
  49. machineconfig/scripts/python/fire_jobs_layout_helper.py +66 -0
  50. machineconfig/scripts/python/ftpx.py +24 -14
  51. machineconfig/scripts/python/get_zellij_cmd.py +8 -7
  52. machineconfig/scripts/python/helpers/cloud_helpers.py +33 -28
  53. machineconfig/scripts/python/helpers/helpers2.py +25 -14
  54. machineconfig/scripts/python/helpers/helpers4.py +44 -31
  55. machineconfig/scripts/python/helpers/helpers5.py +1 -1
  56. machineconfig/scripts/python/helpers/repo_sync_helpers.py +31 -9
  57. machineconfig/scripts/python/mount_nfs.py +8 -15
  58. machineconfig/scripts/python/mount_nw_drive.py +10 -5
  59. machineconfig/scripts/python/mount_ssh.py +8 -6
  60. machineconfig/scripts/python/repos.py +215 -57
  61. machineconfig/scripts/python/snapshot.py +0 -1
  62. machineconfig/scripts/python/start_slidev.py +10 -5
  63. machineconfig/scripts/python/start_terminals.py +22 -16
  64. machineconfig/scripts/python/viewer_template.py +0 -1
  65. machineconfig/scripts/python/wifi_conn.py +49 -76
  66. machineconfig/scripts/python/wsl_windows_transfer.py +8 -6
  67. machineconfig/settings/lf/linux/lfrc +1 -0
  68. machineconfig/setup_linux/web_shortcuts/croshell.sh +5 -0
  69. machineconfig/setup_linux/web_shortcuts/interactive.sh +1 -1
  70. machineconfig/setup_linux/web_shortcuts/ssh.sh +0 -4
  71. machineconfig/setup_windows/wt_and_pwsh/set_pwsh_theme.py +3 -12
  72. machineconfig/setup_windows/wt_and_pwsh/set_wt_settings.py +1 -1
  73. machineconfig/utils/code.py +2 -3
  74. machineconfig/utils/installer.py +2 -2
  75. machineconfig/utils/installer_utils/installer_abc.py +2 -4
  76. machineconfig/utils/installer_utils/installer_class.py +6 -4
  77. machineconfig/utils/links.py +103 -33
  78. machineconfig/utils/notifications.py +52 -38
  79. machineconfig/utils/options.py +14 -21
  80. machineconfig/utils/path.py +12 -12
  81. machineconfig/utils/path_reduced.py +239 -200
  82. machineconfig/utils/procs.py +1 -1
  83. machineconfig/utils/source_of_truth.py +27 -0
  84. machineconfig/utils/ssh.py +9 -19
  85. machineconfig/utils/terminal.py +4 -2
  86. machineconfig/utils/upgrade_packages.py +91 -0
  87. machineconfig/utils/utils2.py +1 -2
  88. machineconfig/utils/utils5.py +23 -11
  89. machineconfig/utils/ve.py +4 -1
  90. {machineconfig-2.1.dist-info → machineconfig-2.3.dist-info}/METADATA +13 -13
  91. {machineconfig-2.1.dist-info → machineconfig-2.3.dist-info}/RECORD +105 -121
  92. machineconfig-2.3.dist-info/entry_points.txt +2 -0
  93. machineconfig/cluster/sessions_managers/archive/create_zellij_template.py +0 -59
  94. machineconfig/cluster/sessions_managers/archive/session_managers.py +0 -183
  95. machineconfig/cluster/sessions_managers/demo_rich_zellij.py +0 -0
  96. machineconfig/jobs/__pycache__/__init__.cpython-313.pyc +0 -0
  97. machineconfig/jobs/python_custom_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  98. machineconfig/jobs/python_generic_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  99. machineconfig/jobs/python_linux_installers/__pycache__/__init__.cpython-313.pyc +0 -0
  100. machineconfig/scripts/__pycache__/__init__.cpython-313.pyc +0 -0
  101. machineconfig/scripts/python/__pycache__/__init__.cpython-313.pyc +0 -0
  102. machineconfig/scripts/python/__pycache__/croshell.cpython-313.pyc +0 -0
  103. machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
  104. machineconfig/scripts/python/__pycache__/devops_devapps_install.cpython-313.pyc +0 -0
  105. machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
  106. machineconfig/scripts/python/__pycache__/fire_jobs.cpython-313.pyc +0 -0
  107. machineconfig/scripts/python/ai/__pycache__/__init__.cpython-313.pyc +0 -0
  108. machineconfig/scripts/python/ai/__pycache__/generate_files.cpython-313.pyc +0 -0
  109. machineconfig/scripts/python/ai/__pycache__/mcinit.cpython-313.pyc +0 -0
  110. machineconfig/scripts/python/helpers/__pycache__/__init__.cpython-313.pyc +0 -0
  111. machineconfig/scripts/python/helpers/__pycache__/helpers4.cpython-313.pyc +0 -0
  112. machineconfig/setup_linux/web_shortcuts/all.sh +0 -48
  113. machineconfig/setup_linux/web_shortcuts/update_system.sh +0 -48
  114. machineconfig/utils/utils.py +0 -97
  115. /machineconfig/cluster/{cloud_manager.py → remote/cloud_manager.py} +0 -0
  116. /machineconfig/cluster/{data_transfer.py → remote/data_transfer.py} +0 -0
  117. /machineconfig/cluster/{distribute.py → remote/distribute.py} +0 -0
  118. /machineconfig/cluster/{file_manager.py → remote/file_manager.py} +0 -0
  119. /machineconfig/cluster/{job_params.py → remote/job_params.py} +0 -0
  120. /machineconfig/cluster/{loader_runner.py → remote/loader_runner.py} +0 -0
  121. /machineconfig/cluster/{remote_machine.py → remote/remote_machine.py} +0 -0
  122. /machineconfig/cluster/{script_execution.py → remote/script_execution.py} +0 -0
  123. /machineconfig/cluster/{script_notify_upon_completion.py → remote/script_notify_upon_completion.py} +0 -0
  124. /machineconfig/{cluster/sessions_managers/archive/__init__.py → scripts/python/fire_jobs_streamlit_helper.py} +0 -0
  125. /machineconfig/setup_linux/web_shortcuts/{tmp.sh → android.sh} +0 -0
  126. {machineconfig-2.1.dist-info → machineconfig-2.3.dist-info}/WHEEL +0 -0
  127. {machineconfig-2.1.dist-info → machineconfig-2.3.dist-info}/top_level.txt +0 -0
@@ -1,4 +1,3 @@
1
-
2
1
  """Utility to launch multiple AI agent prompts in a Zellij session.
3
2
 
4
3
  Improved design notes:
@@ -16,19 +15,21 @@ from math import ceil
16
15
  from typing import Literal, TypeAlias, get_args, Iterable
17
16
 
18
17
  from machineconfig.cluster.sessions_managers.zellij_local_manager import ZellijLocalManager
18
+ from machineconfig.cluster.sessions_managers.layout_types import TabConfig, LayoutConfig
19
19
  from machineconfig.utils.utils2 import randstr
20
20
  import random
21
21
  # import time
22
22
 
23
- AGENTS: TypeAlias = Literal["cursor-agent", "gemini", "crush", "q",
24
- # warp terminal
25
- ]
26
- TabConfig = dict[str, tuple[str, str]] # tab name -> (cwd, command)
23
+ AGENTS: TypeAlias = Literal[
24
+ "cursor-agent", "gemini", "crush", "q", "onlyPrepPromptFiles"
25
+ # warp terminal
26
+ ]
27
27
  DEFAULT_AGENT_CAP = 6
28
28
 
29
29
 
30
30
  def get_gemini_api_keys() -> list[str]:
31
31
  from machineconfig.utils.utils2 import read_ini
32
+
32
33
  config = read_ini(Path.home().joinpath("dotfiles/creds/llm/gemini/api_keys.ini"))
33
34
  res: list[str] = []
34
35
  for a_section_name in list(config.sections()):
@@ -45,12 +46,17 @@ def get_gemini_api_keys() -> list[str]:
45
46
  def _search_python_files(repo_root: Path, keyword: str) -> list[Path]:
46
47
  """Return all Python files under repo_root whose text contains keyword.
47
48
 
48
- Errors reading individual files are ignored (decoded with 'ignore').
49
+ Notes:
50
+ - Skips any paths that reside under a directory named ".venv" at any depth.
51
+ - Errors reading individual files are ignored (decoded with 'ignore').
49
52
  """
50
53
  py_files = list(repo_root.rglob("*.py"))
51
54
  keyword_lower = keyword.lower()
52
55
  matches: list[Path] = []
53
56
  for f in py_files:
57
+ # Skip anything under a .venv directory anywhere in the path
58
+ if any(part == ".venv" for part in f.parts):
59
+ continue
54
60
  try:
55
61
  if keyword_lower in f.read_text(encoding="utf-8", errors="ignore").lower():
56
62
  matches.append(f)
@@ -63,6 +69,8 @@ def _search_python_files(repo_root: Path, keyword: str) -> list[Path]:
63
69
  def _write_list_file(target: Path, files: Iterable[Path]) -> None:
64
70
  target.parent.mkdir(parents=True, exist_ok=True)
65
71
  target.write_text("\n".join(str(f) for f in files), encoding="utf-8")
72
+
73
+
66
74
  def _chunk_prompts(prompts: list[str], max_agents: int) -> list[str]:
67
75
  prompts = [p for p in prompts if p.strip() != ""] # drop blank entries
68
76
  if len(prompts) <= max_agents:
@@ -73,6 +81,8 @@ def _chunk_prompts(prompts: list[str], max_agents: int) -> list[str]:
73
81
  for i in range(0, len(prompts), chunk_size):
74
82
  grouped.append("\nTargeted Locations:\n".join(prompts[i : i + chunk_size]))
75
83
  return grouped
84
+
85
+
76
86
  def _confirm(message: str, default_no: bool = True) -> bool:
77
87
  suffix = "[y/N]" if default_no else "[Y/n]"
78
88
  answer = input(f"{message} {suffix} ").strip().lower()
@@ -83,7 +93,7 @@ def _confirm(message: str, default_no: bool = True) -> bool:
83
93
  return False
84
94
 
85
95
 
86
- def launch_agents(repo_root: Path, prompts: list[str], agent: AGENTS, *, max_agents: int = DEFAULT_AGENT_CAP) -> TabConfig:
96
+ def launch_agents(repo_root: Path, prompts: list[str], agent: AGENTS, *, max_agents: int = DEFAULT_AGENT_CAP) -> list[TabConfig]:
87
97
  """Create tab configuration for a set of agent prompts.
88
98
 
89
99
  If number of prompts exceeds max_agents, ask user for confirmation.
@@ -93,16 +103,12 @@ def launch_agents(repo_root: Path, prompts: list[str], agent: AGENTS, *, max_age
93
103
  raise ValueError("No prompts provided")
94
104
 
95
105
  if len(prompts) > max_agents:
96
- proceed = _confirm(
97
- message=(
98
- f"You are about to launch {len(prompts)} agents which exceeds the cap ({max_agents}). Proceed?"
99
- )
100
- )
106
+ proceed = _confirm(message=(f"You are about to launch {len(prompts)} agents which exceeds the cap ({max_agents}). Proceed?"))
101
107
  if not proceed:
102
108
  print("Aborting per user choice.")
103
- return {}
109
+ return []
104
110
 
105
- tab_config: TabConfig = {}
111
+ tab_config: list[TabConfig] = []
106
112
  tmp_dir = repo_root / ".ai" / f"tmp_prompts/{randstr()}"
107
113
  tmp_dir.mkdir(parents=True, exist_ok=True)
108
114
 
@@ -145,6 +151,10 @@ crush run {prompt_path}
145
151
  case "q":
146
152
  cmd = f"""
147
153
  q chat --no-interactive --trust-all-tools {prompt_path}
154
+ """
155
+ case "onlyPrepPromptFiles":
156
+ cmd = f"""
157
+ echo "Prepared prompt file at {prompt_path}"
148
158
  """
149
159
  case _:
150
160
  raise ValueError(f"Unsupported agent type: {agent}")
@@ -161,9 +171,9 @@ sleep 0.1
161
171
  sleep 0.1
162
172
  echo "---------END OF AGENT OUTPUT---------"
163
173
  """
164
- cmd_path.write_text(cmd_prefix+cmd+cmd_postfix, encoding="utf-8")
174
+ cmd_path.write_text(cmd_prefix + cmd + cmd_postfix, encoding="utf-8")
165
175
  fire_cmd = f"bash {shlex.quote(str(cmd_path))}"
166
- tab_config[f"Agent{idx}"] = (str(repo_root), fire_cmd)
176
+ tab_config.append(TabConfig(tabName=f"Agent{idx}", startDir=str(repo_root), command=fire_cmd))
167
177
 
168
178
  print(f"Launching a template with #{len(tab_config)} agents")
169
179
  return tab_config
@@ -208,20 +218,17 @@ def main(): # noqa: C901 - (complexity acceptable for CLI glue)
208
218
  combined_prompts = [prefix + "\n" + p for p in combined_prompts]
209
219
 
210
220
  from machineconfig.utils.options import choose_one_option
221
+
211
222
  agent_selected = choose_one_option(header="Select agent type", options=get_args(AGENTS))
212
223
 
213
- tab_config = launch_agents(
214
- repo_root=repo_root,
215
- prompts=combined_prompts,
216
- agent=agent_selected,
217
- max_agents=DEFAULT_AGENT_CAP,
218
- )
224
+ tab_config = launch_agents(repo_root=repo_root, prompts=combined_prompts, agent=agent_selected, max_agents=DEFAULT_AGENT_CAP)
219
225
  if not tab_config:
220
226
  return
221
227
 
222
228
  from machineconfig.utils.utils2 import randstr
229
+
223
230
  random_name = randstr(length=3)
224
- manager = ZellijLocalManager(session2zellij_tabs={"Agents": tab_config}, session_name_prefix=random_name)
231
+ manager = ZellijLocalManager(session_layouts=[LayoutConfig(layoutName="Agents", layoutTabs=tab_config)], session_name_prefix=random_name)
225
232
  manager.start_all_sessions()
226
233
  manager.run_monitoring_routine()
227
234
 
@@ -7,61 +7,34 @@ fire
7
7
 
8
8
  """
9
9
 
10
-
11
10
  from machineconfig.scripts.python.helpers.helpers4 import search_for_files_of_interest
12
11
  from machineconfig.scripts.python.helpers.helpers4 import convert_kwargs_to_fire_kwargs_str
13
12
  from machineconfig.scripts.python.helpers.helpers4 import parse_pyfile
14
13
  from machineconfig.scripts.python.helpers.helpers4 import get_import_module_code
15
14
  from machineconfig.utils.ve import get_repo_root, get_ve_activate_line, get_ve_path_and_ipython_profile
16
- from machineconfig.utils.utils import display_options, choose_one_option, PROGRAM_PATH, match_file_name, sanitize_path
15
+ from machineconfig.utils.options import display_options, choose_one_option
16
+ from machineconfig.utils.path import match_file_name, sanitize_path
17
+ from machineconfig.utils.source_of_truth import PROGRAM_PATH
17
18
  from machineconfig.utils.path_reduced import PathExtended as PathExtended
18
19
  from machineconfig.utils.io_save import save_toml
19
20
  from machineconfig.utils.utils2 import randstr, read_toml
21
+ from machineconfig.scripts.python.fire_jobs_args_helper import get_args, FireJobArgs, extract_kwargs
20
22
  import platform
21
23
  from typing import Optional
22
- import argparse
23
24
  import os
24
25
 
25
26
 
26
- str2obj = {"True": True, "False": False, "None": None}
27
+ def main(args: FireJobArgs) -> None:
28
+ if args.layout:
29
+ from machineconfig.scripts.python.fire_jobs_layout_helper import handle_layout_args
27
30
 
31
+ return handle_layout_args(args)
28
32
 
29
- def main() -> None:
30
- parser = argparse.ArgumentParser()
31
- parser.add_argument("path", nargs='?', type=str, help="The directory containing the jobs", default=".")
32
- parser.add_argument("function", nargs='?', type=str, help="Fuction to run", default=None)
33
- parser.add_argument("--ve", "-v", type=str, help="virtual enviroment name", default="")
34
- parser.add_argument("--cmd", "-B", action="store_true", help="Create a cmd fire command to launch the the job asynchronously.")
35
- parser.add_argument("--interactive", "-i", action="store_true", help="Whether to run the job interactively using IPython")
36
- parser.add_argument("--debug", "-d", action="store_true", help="debug")
37
- parser.add_argument("--choose_function", "-c", action="store_true", help="debug")
38
- parser.add_argument("--loop", "-l", action="store_true", help="infinite recusion (runs again after completion/interruption)")
39
- parser.add_argument("--jupyter", "-j", action="store_true", help="open in a jupyter notebook")
40
- parser.add_argument("--submit_to_cloud", "-C", action="store_true", help="submit to cloud compute")
41
- parser.add_argument("--remote", "-r", action="store_true", help="launch on a remote machine")
42
- parser.add_argument("--module", "-m", action="store_true", help="launch the main file")
43
- parser.add_argument("--streamlit", "-S", action="store_true", help="run as streamlit app")
44
- parser.add_argument("--environment", "-E", type=str, help="Choose ip, localhost, hostname or arbitrary url", default="")
45
- parser.add_argument("--holdDirectory", "-D", action="store_true", help="hold current directory and avoid cd'ing to the script directory")
46
- parser.add_argument("--PathExport", "-P", action="store_true", help="augment the PYTHONPATH with repo root.")
47
- parser.add_argument("--git_pull", "-g", action="store_true", help="Start by pulling the git repo")
48
- parser.add_argument("--optimized", "-O", action="store_true", help="Run the optimized version of the function")
49
- parser.add_argument("--Nprocess", "-p", type=int, help="Number of processes to use", default=1)
50
- parser.add_argument("--zellij_tab", "-z", type=str, dest="zellij_tab", help="open in a new zellij tab")
51
- parser.add_argument("--watch", "-w", action="store_true", help="watch the file for changes")
52
- parser.add_argument("--kw", nargs="*", default=None, help="keyword arguments to pass to the function in the form of k1 v1 k2 v2 ... (meaning k1=v1, k2=v2, etc)")
53
- try:
54
- args = parser.parse_args()
55
- except Exception as ex:
56
- print(f"❌ Failed to parse arguments: {ex}")
57
- parser.print_help()
58
- raise ex
59
33
  path_obj = sanitize_path(args.path)
60
- # print(f"Passed path sanitied to {path_obj}")
61
34
  if not path_obj.exists():
62
- path_obj = match_file_name(sub_string=args.path, search_root=PathExtended.cwd())
63
- else: pass
64
- if path_obj.is_dir():
35
+ suffixes = {".py", ".sh", ".ps1"}
36
+ choice_file = match_file_name(sub_string=args.path, search_root=PathExtended.cwd(), suffixes=suffixes)
37
+ elif path_obj.is_dir():
65
38
  print(f"🔍 Searching recursively for Python, PowerShell and Shell scripts in directory `{path_obj}`")
66
39
  files = search_for_files_of_interest(path_obj)
67
40
  print(f"🔍 Got #{len(files)} results.")
@@ -71,23 +44,13 @@ def main() -> None:
71
44
  choice_file = path_obj
72
45
  repo_root = get_repo_root(str(choice_file))
73
46
  print(f"💾 Selected file: {choice_file}.\nRepo root: {repo_root}")
74
-
75
47
  ve_root_from_file, ipy_profile = get_ve_path_and_ipython_profile(choice_file)
76
- if ipy_profile is None: ipy_profile = "default"
77
- activate_ve_line = get_ve_activate_line(ve_root=args.ve or ve_root_from_file or "$HOME/code/machineconfig/.venv")
48
+ if ipy_profile is None:
49
+ ipy_profile = "default"
50
+ activate_ve_line = get_ve_activate_line(ve_root=args.ve or ve_root_from_file or "$HOME/code/machineconfig/.venv")
78
51
 
79
- # Convert args.kw to dictionary
80
52
  if choice_file.suffix == ".py":
81
- if args.kw is not None:
82
- assert len(args.kw) % 2 == 0, f"args.kw must be a list of even length. Got {len(args.kw)}"
83
- kwargs = dict(zip(args.kw[::2], args.kw[1::2]))
84
- for key, value in kwargs.items():
85
- if value in str2obj:
86
- kwargs[key] = str2obj[value]
87
- if args.function is None: # if user passed arguments and forgot to pass function, then assume they want to run the main function.
88
- args.choose_function = True
89
- else:
90
- kwargs = {}
53
+ kwargs = extract_kwargs(args)
91
54
  else:
92
55
  kwargs = {}
93
56
 
@@ -99,19 +62,23 @@ def main() -> None:
99
62
  choice_function_tmp = display_options(msg="Choose a function to run", options=options, fzf=True, multi=False)
100
63
  assert isinstance(choice_function_tmp, str), f"choice_function must be a string. Got {type(choice_function_tmp)}"
101
64
  choice_index = options.index(choice_function_tmp)
102
- choice_function = choice_function_tmp.split(' -- ')[0]
65
+ choice_function = choice_function_tmp.split(" -- ")[0]
103
66
  choice_function_args = func_args[choice_index]
104
67
 
105
- if choice_function == "RUN AS MAIN": choice_function = None
68
+ if choice_function == "RUN AS MAIN":
69
+ choice_function = None
106
70
  if len(choice_function_args) > 0 and len(kwargs) == 0:
107
71
  for item in choice_function_args:
108
72
  kwargs[item.name] = input(f"Please enter a value for argument `{item.name}` (type = {item.type}) (default = {item.default}) : ") or item.default
109
73
  elif choice_file.suffix == ".sh": # in this case, we choos lines.
110
74
  options = []
111
75
  for line in choice_file.read_text(encoding="utf-8").splitlines():
112
- if line.startswith("#"): continue
113
- if line == "": continue
114
- if line.startswith("echo"): continue
76
+ if line.startswith("#"):
77
+ continue
78
+ if line == "":
79
+ continue
80
+ if line.startswith("echo"):
81
+ continue
115
82
  options.append(line)
116
83
  chosen_lines = display_options(msg="Choose a line to run", options=options, fzf=True, multi=True)
117
84
  choice_file = PathExtended.tmpfile(suffix=".sh")
@@ -124,9 +91,10 @@ def main() -> None:
124
91
  if choice_file.suffix == ".py":
125
92
  if args.streamlit:
126
93
  import socket
94
+
127
95
  s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
128
96
  try:
129
- s.connect(('8.8.8.8', 1))
97
+ s.connect(("8.8.8.8", 1))
130
98
  local_ip_v4 = s.getsockname()[0]
131
99
  except Exception:
132
100
  local_ip_v4 = socket.gethostbyname(socket.gethostname())
@@ -140,7 +108,8 @@ def main() -> None:
140
108
  toml_path = toml_path_maybe
141
109
  elif choice_file.parent.name == "pages":
142
110
  toml_path_maybe = choice_file.parent.parent.joinpath(".streamlit/config.toml")
143
- if toml_path_maybe.exists(): toml_path = toml_path_maybe
111
+ if toml_path_maybe.exists():
112
+ toml_path = toml_path_maybe
144
113
  if toml_path is not None:
145
114
  print(f"📄 Reading config.toml @ {toml_path}")
146
115
  config = read_toml(toml_path)
@@ -152,10 +121,14 @@ def main() -> None:
152
121
  secrets_template_path = PathExtended.home().joinpath(f"dotfiles/creds/streamlit/{PathExtended(repo_root).name}/{choice_file.name}/secrets.toml")
153
122
  if args.environment != "" and not secrets_path.exists() and secrets_template_path.exists():
154
123
  secrets_template = read_toml(secrets_template_path)
155
- if args.environment == "ip": host_url = f"http://{local_ip_v4}:{port}/oauth2callback"
156
- elif args.environment == "localhost": host_url = f"http://localhost:{port}/oauth2callback"
157
- elif args.environment == "hostname": host_url = f"http://{computer_name}:{port}/oauth2callback"
158
- else: host_url = f"http://{args.environment}:{port}/oauth2callback"
124
+ if args.environment == "ip":
125
+ host_url = f"http://{local_ip_v4}:{port}/oauth2callback"
126
+ elif args.environment == "localhost":
127
+ host_url = f"http://localhost:{port}/oauth2callback"
128
+ elif args.environment == "hostname":
129
+ host_url = f"http://{computer_name}:{port}/oauth2callback"
130
+ else:
131
+ host_url = f"http://{args.environment}:{port}/oauth2callback"
159
132
  try:
160
133
  secrets_template["auth"]["redirect_uri"] = host_url
161
134
  secrets_template["auth"]["cookie_secret"] = randstr(35)
@@ -167,11 +140,14 @@ def main() -> None:
167
140
  message = f"🚀 Streamlit app is running @:\n1- http://{local_ip_v4}:{port}\n2- http://{computer_name}:{port}\n3- http://localhost:{port}"
168
141
  from rich.panel import Panel
169
142
  from rich import print as rprint
143
+
170
144
  rprint(Panel(message))
171
145
  exe = f"streamlit run --server.address 0.0.0.0 --server.headless true --server.port {port}"
172
146
  # exe = f"cd '{choice_file.parent}'; " + exe
173
- elif args.interactive is False: exe = "python"
174
- elif args.jupyter: exe = "jupyter-lab"
147
+ elif args.interactive is False:
148
+ exe = "python"
149
+ elif args.jupyter:
150
+ exe = "jupyter-lab"
175
151
  else:
176
152
  exe = f"ipython -i --no-banner --profile {ipy_profile} "
177
153
  elif choice_file.suffix == ".ps1" or choice_file.suffix == ".sh":
@@ -181,7 +157,9 @@ def main() -> None:
181
157
  else:
182
158
  raise NotImplementedError(f"File type {choice_file.suffix} not supported, in the sense that I don't know how to fire it.")
183
159
 
184
- if args.module or (args.debug and args.choose_function): # because debugging tools do not support choosing functions and don't interplay with fire module. So the only way to have debugging and choose function options is to import the file as a module into a new script and run the function of interest there and debug the new script.
160
+ if (
161
+ args.module or (args.debug and args.choose_function)
162
+ ): # because debugging tools do not support choosing functions and don't interplay with fire module. So the only way to have debugging and choose function options is to import the file as a module into a new script and run the function of interest there and debug the new script.
185
163
  assert choice_file.suffix == ".py", f"File must be a python file to be imported as a module. Got {choice_file}"
186
164
  import_line = get_import_module_code(str(choice_file))
187
165
  if repo_root is not None:
@@ -201,11 +179,15 @@ except (ImportError, ModuleNotFoundError) as ex:
201
179
  print(fr"✅ Successfully imported `{choice_file}`")
202
180
  """
203
181
  if choice_function is not None:
204
- txt = txt + f"""
205
- res = {choice_function}({('**' + str(kwargs)) if kwargs else ''})
182
+ txt = (
183
+ txt
184
+ + f"""
185
+ res = {choice_function}({("**" + str(kwargs)) if kwargs else ""})
206
186
  """
187
+ )
207
188
 
208
- txt = f"""
189
+ txt = (
190
+ f"""
209
191
  try:
210
192
  from rich.panel import Panel
211
193
  from rich.console import Console
@@ -214,8 +196,10 @@ try:
214
196
  console.print(Panel(Syntax(code=r'''{txt}''', lexer='python'), title='Import Script'), style="bold red")
215
197
  except ImportError as _ex:
216
198
  print(r'''{txt}''')
217
- """ + txt
218
- choice_file = PathExtended.tmp().joinpath(f'tmp_scripts/python/{PathExtended(choice_file).parent.name}_{PathExtended(choice_file).stem}_{randstr()}.py')
199
+ """
200
+ + txt
201
+ )
202
+ choice_file = PathExtended.tmp().joinpath(f"tmp_scripts/python/{PathExtended(choice_file).parent.name}_{PathExtended(choice_file).stem}_{randstr()}.py")
219
203
  choice_file.parent.mkdir(parents=True, exist_ok=True)
220
204
  choice_file.write_text(txt, encoding="utf-8")
221
205
 
@@ -225,7 +209,8 @@ except ImportError as _ex:
225
209
  command = f"{exe} -m ipdb {choice_file} " # pudb is not available on windows machines, use poor man's debugger instead.
226
210
  elif platform.system() in ["Linux", "Darwin"]:
227
211
  command = f"{exe} -m pudb {choice_file} " # TODO: functions not supported yet in debug mode.
228
- else: raise NotImplementedError(f"Platform {platform.system()} not supported.")
212
+ else:
213
+ raise NotImplementedError(f"Platform {platform.system()} not supported.")
229
214
  elif args.module:
230
215
  # both selected function and kwargs are mentioned in the made up script, therefore no need for fire module.
231
216
  command = f"{exe} {choice_file} "
@@ -249,15 +234,19 @@ except ImportError as _ex:
249
234
  # command = f"cd {choice_file.parent}\n{exe} {choice_file.name}\ncd {PathExtended.cwd()}"
250
235
  command = f"{exe} {choice_file} "
251
236
  if not args.cmd:
252
- if "ipdb" in command: command = f"pip install ipdb\n{command}"
253
- if "pudb" in command: command = f"pip install pudb\n{command}"
237
+ if "ipdb" in command:
238
+ command = f"pip install ipdb\n{command}"
239
+ if "pudb" in command:
240
+ command = f"pip install pudb\n{command}"
254
241
  command = f"{activate_ve_line}\n{command}"
255
242
  else:
256
243
  # CMD equivalent
257
- if "ipdb" in command: command = f"pip install ipdb & {command}"
258
- if "pudb" in command: command = f"pip install pudb & {command}"
244
+ if "ipdb" in command:
245
+ command = f"pip install ipdb & {command}"
246
+ if "pudb" in command:
247
+ command = f"pip install pudb & {command}"
259
248
  new_line = "\n"
260
- command = fr"""start cmd -Argument "/k {activate_ve_line.replace(".ps1", ".bat").replace(". ", "")} & {command.replace(new_line, " & ")} " """ # this works from powershell
249
+ command = rf"""start cmd -Argument "/k {activate_ve_line.replace(".ps1", ".bat").replace(". ", "")} & {command.replace(new_line, " & ")} " """ # this works from powershell
261
250
 
262
251
  if args.submit_to_cloud:
263
252
  command = f"""
@@ -267,21 +256,13 @@ python -m machineconfig.cluster.templates.cli_click --file {choice_file} """
267
256
  command += f"--function {choice_function} "
268
257
 
269
258
  if args.Nprocess > 1:
270
- # lines = [f""" zellij action new-tab --name nProcess{randstr(2)}"""]
271
- # command = command.replace(". activate_ve", ". $HOME/scripts/activate_ve")
272
- # for an_arg in range(args.Nprocess):
273
- # sub_command = f"{command} --idx={an_arg} --idx_max={args.Nprocess}"
274
- # if args.optimized:
275
- # sub_command = sub_command.replace("python ", "python -OO ")
276
- # sub_command_path = PathExtended.tmpfile(suffix=".sh").write_text(sub_command, encoding="utf-8")
277
- # lines.append(f"""zellij action new-pane -- bash {sub_command_path} """)
278
- # lines.append("sleep 5") # python tends to freeze if you launch instances within 1 microsecond of each other
279
- # command = "\n".join(lines)
280
- tab_config = {}
281
- for an_arg in range(args.Nprocess):
282
- tab_config[f"tab{an_arg}"] = (str(PathExtended.cwd()), f"uv run -m fire {choice_file} {choice_function} --idx={an_arg} --idx_max={args.Nprocess}")
283
259
  from machineconfig.cluster.sessions_managers.zellij_local import run_zellij_layout
284
- run_zellij_layout(tab_config=tab_config, session_name=None)
260
+ from machineconfig.cluster.sessions_managers.layout_types import LayoutConfig
261
+
262
+ layout: LayoutConfig = {"layoutName": "fireNprocess", "layoutTabs": []}
263
+ for an_arg in range(args.Nprocess):
264
+ layout["layoutTabs"].append({"tabName": f"tab{an_arg}", "startDir": str(PathExtended.cwd()), "command": f"uv run -m fire {choice_file} {choice_function} --idx={an_arg} --idx_max={args.Nprocess}"})
265
+ run_zellij_layout(layout_config=layout)
285
266
  return None
286
267
 
287
268
  if args.optimized:
@@ -293,6 +274,7 @@ python -m machineconfig.cluster.templates.cli_click --file {choice_file} """
293
274
  from rich.panel import Panel
294
275
  from rich.console import Console
295
276
  from rich.syntax import Syntax
277
+
296
278
  console = Console()
297
279
 
298
280
  if args.zellij_tab is not None:
@@ -301,17 +283,22 @@ python -m machineconfig.cluster.templates.cli_click --file {choice_file} """
301
283
  comman_path__.write_text(command, encoding="utf-8")
302
284
  console.print(Panel(Syntax(command, lexer="shell"), title=f"🔥 fire command @ {comman_path__}: "), style="bold red")
303
285
  import subprocess
286
+
304
287
  existing_tab_names = subprocess.run(["zellij", "action", "query-tab-names"], capture_output=True, text=True, check=True).stdout.splitlines()
305
288
  if args.zellij_tab in existing_tab_names:
306
289
  print(f"⚠️ Tab name `{args.zellij_tab}` already exists. Please choose a different name.")
307
290
  # args.zellij_tab = input("Please enter a new tab name: ")
308
291
  args.zellij_tab += f"_{randstr(3)}"
309
292
  from machineconfig.cluster.sessions_managers.zellij_local import run_command_in_zellij_tab
293
+
310
294
  command = run_command_in_zellij_tab(command=str(comman_path__), tab_name=args.zellij_tab, cwd=None)
311
- if args.watch: command = "watchexec --restart --exts py,sh,ps1 " + command
312
- if args.git_pull: command = f"\ngit -C {choice_file.parent} pull\n" + command
295
+ if args.watch:
296
+ command = "watchexec --restart --exts py,sh,ps1 " + command
297
+ if args.git_pull:
298
+ command = f"\ngit -C {choice_file.parent} pull\n" + command
313
299
  if args.PathExport:
314
- if platform.system() in ["Linux", "Darwin"]: export_line = f"""export PYTHONPATH="{repo_root}""" + """:${PYTHONPATH}" """
300
+ if platform.system() in ["Linux", "Darwin"]:
301
+ export_line = f"""export PYTHONPATH="{repo_root}""" + """:${PYTHONPATH}" """
315
302
  elif platform.system() == "Windows":
316
303
  # export_line = f"""set PYTHONPATH="{repo_root}""" + """:%PYTHONPATH%" """
317
304
  # powershell equivalent
@@ -327,8 +314,8 @@ python -m machineconfig.cluster.templates.cli_click --file {choice_file} """
327
314
  command = command + "\nsleep 0.5"
328
315
  elif platform.system() == "Windows":
329
316
  # command = command + "timeout 0.5\n"
330
- #pwsh equivalent
331
- command ="$ErrorActionPreference = 'SilentlyContinue';\n" + command + "\nStart-Sleep -Seconds 0.5"
317
+ # pwsh equivalent
318
+ command = "$ErrorActionPreference = 'SilentlyContinue';\n" + command + "\nStart-Sleep -Seconds 0.5"
332
319
  else:
333
320
  raise NotImplementedError(f"Platform {platform.system()} not supported.")
334
321
  command = command + f"\n. {program_path}"
@@ -337,6 +324,7 @@ python -m machineconfig.cluster.templates.cli_click --file {choice_file} """
337
324
  program_path.write_text(command, encoding="utf-8")
338
325
 
339
326
 
340
- if __name__ == '__main__':
327
+ if __name__ == "__main__":
341
328
  # options, func_args = parse_pyfile(file_path="C:/Users/aalsaf01/code/machineconfig/myresources/crocodile/core.py")
342
- main()
329
+ args = get_args()
330
+ main(args)
@@ -0,0 +1,84 @@
1
+ from dataclasses import dataclass
2
+ from typing import Optional
3
+ import argparse
4
+
5
+
6
+ @dataclass
7
+ class FireJobArgs:
8
+ """Type-safe dataclass for fire_jobs command line arguments."""
9
+
10
+ path: str = "."
11
+ function: Optional[str] = None
12
+ ve: str = ""
13
+ cmd: bool = False
14
+ interactive: bool = False
15
+ debug: bool = False
16
+ choose_function: bool = False
17
+ loop: bool = False
18
+ jupyter: bool = False
19
+ submit_to_cloud: bool = False
20
+ remote: bool = False
21
+ module: bool = False
22
+ streamlit: bool = False
23
+ environment: str = ""
24
+ holdDirectory: bool = False
25
+ PathExport: bool = False
26
+ git_pull: bool = False
27
+ optimized: bool = False
28
+ Nprocess: int = 1
29
+ zellij_tab: Optional[str] = None
30
+ watch: bool = False
31
+ kw: Optional[list[str]] = None
32
+ layout: bool = False
33
+
34
+
35
+ def get_args() -> FireJobArgs:
36
+ parser = argparse.ArgumentParser()
37
+ parser.add_argument("path", nargs="?", type=str, help="The directory containing the jobs", default=".")
38
+ parser.add_argument("function", nargs="?", type=str, help="Fuction to run", default=None)
39
+ parser.add_argument("--ve", "-v", type=str, help="virtual enviroment name", default="")
40
+ parser.add_argument("--cmd", "-B", action="store_true", help="Create a cmd fire command to launch the the job asynchronously.")
41
+ parser.add_argument("--interactive", "-i", action="store_true", help="Whether to run the job interactively using IPython")
42
+ parser.add_argument("--debug", "-d", action="store_true", help="debug")
43
+ parser.add_argument("--choose_function", "-c", action="store_true", help="debug")
44
+ parser.add_argument("--loop", "-l", action="store_true", help="infinite recusion (runs again after completion/interruption)")
45
+ parser.add_argument("--jupyter", "-j", action="store_true", help="open in a jupyter notebook")
46
+ parser.add_argument("--submit_to_cloud", "-C", action="store_true", help="submit to cloud compute")
47
+ parser.add_argument("--remote", "-r", action="store_true", help="launch on a remote machine")
48
+ parser.add_argument("--module", "-m", action="store_true", help="launch the main file")
49
+ parser.add_argument("--streamlit", "-S", action="store_true", help="run as streamlit app")
50
+ parser.add_argument("--environment", "-E", type=str, help="Choose ip, localhost, hostname or arbitrary url", default="")
51
+ parser.add_argument("--holdDirectory", "-D", action="store_true", help="hold current directory and avoid cd'ing to the script directory")
52
+ parser.add_argument("--PathExport", "-P", action="store_true", help="augment the PYTHONPATH with repo root.")
53
+ parser.add_argument("--git_pull", "-g", action="store_true", help="Start by pulling the git repo")
54
+ parser.add_argument("--optimized", "-O", action="store_true", help="Run the optimized version of the function")
55
+ parser.add_argument("--Nprocess", "-p", type=int, help="Number of processes to use", default=1)
56
+ parser.add_argument("--zellij_tab", "-z", type=str, dest="zellij_tab", help="open in a new zellij tab")
57
+ parser.add_argument("--watch", "-w", action="store_true", help="watch the file for changes")
58
+ parser.add_argument("--kw", nargs="*", default=None, help="keyword arguments to pass to the function in the form of k1 v1 k2 v2 ... (meaning k1=v1, k2=v2, etc)")
59
+ parser.add_argument("--layout", "-L", action="store_true", help="use layout configuration (Zellij Or WindowsTerminal)")
60
+
61
+ try:
62
+ args_raw = parser.parse_args()
63
+ except Exception as ex:
64
+ print(f"❌ Failed to parse arguments: {ex}")
65
+ parser.print_help()
66
+ raise ex
67
+ args = FireJobArgs(**vars(args_raw))
68
+ return args
69
+
70
+
71
+ def extract_kwargs(args: FireJobArgs) -> dict[str, object]:
72
+ str2obj = {"True": True, "False": False, "None": None}
73
+ if args.kw is not None:
74
+ assert len(args.kw) % 2 == 0, f"args.kw must be a list of even length. Got {len(args.kw)}"
75
+ kwargs = dict(zip(args.kw[::2], args.kw[1::2]))
76
+ kwargs: dict[str, object]
77
+ for key, value in kwargs.items():
78
+ if value in str2obj:
79
+ kwargs[key] = str2obj[str(value)]
80
+ if args.function is None: # if user passed arguments and forgot to pass function, then assume they want to run the main function.
81
+ args.choose_function = True
82
+ else:
83
+ kwargs = {}
84
+ return kwargs
@@ -0,0 +1,66 @@
1
+ from pathlib import Path
2
+ from machineconfig.cluster.sessions_managers.layout_types import LayoutConfig, LayoutsFile
3
+ from typing import Optional, TYPE_CHECKING
4
+ from machineconfig.scripts.python.helpers.helpers4 import search_for_files_of_interest
5
+ from machineconfig.utils.options import choose_one_option
6
+ from machineconfig.utils.path import match_file_name, sanitize_path
7
+ from machineconfig.utils.path_reduced import PathExtended as PathExtended
8
+
9
+ if TYPE_CHECKING:
10
+ from machineconfig.scripts.python.fire_jobs_args_helper import FireJobArgs
11
+
12
+
13
+ def select_layout(layouts_json_file: Path, layout_name: Optional[str]):
14
+ import json
15
+
16
+ layout_file: LayoutsFile = json.loads(layouts_json_file.read_text(encoding="utf-8"))
17
+ if len(layout_file["layouts"]) == 0:
18
+ raise ValueError(f"No layouts found in {layouts_json_file}")
19
+ if layout_name is None:
20
+ options = [layout["layoutName"] for layout in layout_file["layouts"]]
21
+ from machineconfig.utils.options import choose_one_option
22
+
23
+ layout_name = choose_one_option(options=options, prompt="Choose a layout configuration:", fzf=True)
24
+ print(f"Selected layout: {layout_name}")
25
+ layout_chosen = next((layout for layout in layout_file["layouts"] if layout["layoutName"] == layout_name), None)
26
+ if layout_chosen is None:
27
+ layout_chosen = next((layout for layout in layout_file["layouts"] if layout["layoutName"].lower() == layout_name.lower()), None)
28
+ if layout_chosen is None:
29
+ available_layouts = [layout["layoutName"] for layout in layout_file["layouts"]]
30
+ raise ValueError(f"Layout '{layout_name}' not found. Available layouts: {available_layouts}")
31
+ return layout_chosen
32
+
33
+
34
+ def launch_layout(layout_config: LayoutConfig) -> Optional[Exception]:
35
+ import platform
36
+
37
+ if platform.system() == "Linux" or platform.system() == "Darwin":
38
+ print("🧑‍💻 Launching layout using Zellij terminal multiplexer...")
39
+ from machineconfig.cluster.sessions_managers.zellij_local import run_zellij_layout
40
+
41
+ run_zellij_layout(layout_config=layout_config)
42
+ elif platform.system() == "Windows":
43
+ print("🧑‍💻 Launching layout using Windows Terminal...")
44
+ from machineconfig.cluster.sessions_managers.wt_local import run_wt_layout
45
+
46
+ run_wt_layout(layout_config=layout_config)
47
+ else:
48
+ print(f"❌ Unsupported platform: {platform.system()}")
49
+ return None
50
+
51
+
52
+ def handle_layout_args(args: "FireJobArgs") -> None:
53
+ # args.function = args.path
54
+ # args.path = "layout.json"
55
+ path_obj = sanitize_path(args.path)
56
+ if not path_obj.exists():
57
+ choice_file = match_file_name(sub_string=args.path, search_root=PathExtended.cwd(), suffixes={".json"})
58
+ elif path_obj.is_dir():
59
+ print(f"🔍 Searching recursively for Python, PowerShell and Shell scripts in directory `{path_obj}`")
60
+ files = search_for_files_of_interest(path_obj)
61
+ print(f"🔍 Got #{len(files)} results.")
62
+ choice_file = choose_one_option(options=files, fzf=True)
63
+ choice_file = PathExtended(choice_file)
64
+ else:
65
+ choice_file = path_obj
66
+ launch_layout(layout_config=select_layout(layouts_json_file=choice_file, layout_name=args.function))