machineconfig 5.26__py3-none-any.whl → 5.28__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 (61) hide show
  1. machineconfig/cluster/sessions_managers/zellij_local_manager.py +1 -0
  2. machineconfig/jobs/installer/installer_data.json +17 -0
  3. machineconfig/jobs/installer/linux_scripts/brave.sh +4 -14
  4. machineconfig/jobs/installer/linux_scripts/docker.sh +5 -17
  5. machineconfig/jobs/installer/linux_scripts/docker_start.sh +6 -14
  6. machineconfig/jobs/installer/linux_scripts/edge.sh +3 -11
  7. machineconfig/jobs/installer/linux_scripts/nerdfont.sh +5 -17
  8. machineconfig/jobs/installer/linux_scripts/pgsql.sh +3 -11
  9. machineconfig/jobs/installer/linux_scripts/redis.sh +5 -17
  10. machineconfig/jobs/installer/linux_scripts/timescaledb.sh +6 -20
  11. machineconfig/jobs/installer/linux_scripts/vscode.sh +5 -17
  12. machineconfig/jobs/installer/linux_scripts/warp-cli.sh +5 -17
  13. machineconfig/jobs/installer/linux_scripts/wezterm.sh +3 -11
  14. machineconfig/jobs/installer/package_groups.py +70 -111
  15. machineconfig/jobs/linux/msc/lid.sh +2 -8
  16. machineconfig/jobs/linux/msc/network.sh +2 -8
  17. machineconfig/scripts/cloud/init.sh +6 -20
  18. machineconfig/scripts/linux/share_cloud.sh +11 -25
  19. machineconfig/scripts/python/agents.py +22 -31
  20. machineconfig/scripts/python/cloud_repo_sync.py +14 -29
  21. machineconfig/scripts/python/devops.py +7 -10
  22. machineconfig/scripts/python/helpers/repo_sync_helpers.py +1 -1
  23. machineconfig/scripts/python/helpers_fire/fire_agents_help_launch.py +30 -48
  24. machineconfig/scripts/python/helpers_fire/fire_agents_helper_types.py +24 -6
  25. machineconfig/scripts/python/helpers_fire/fire_crush.json +14 -0
  26. machineconfig/scripts/python/helpers_fire/fire_crush.py +37 -0
  27. machineconfig/scripts/python/helpers_fire/fire_cursor_agents.py +23 -0
  28. machineconfig/scripts/python/helpers_fire/fire_gemini.py +41 -0
  29. machineconfig/scripts/python/helpers_fire/fire_q.py +19 -0
  30. machineconfig/scripts/python/helpers_fire/prompt.txt +2 -0
  31. machineconfig/scripts/python/helpers_fire/template.ps1 +0 -0
  32. machineconfig/scripts/python/helpers_fire/template.sh +31 -0
  33. machineconfig/scripts/python/interactive.py +21 -19
  34. machineconfig/scripts/python/repos.py +4 -1
  35. machineconfig/scripts/python/secure_repo.py +15 -0
  36. machineconfig/settings/broot/br.sh +0 -4
  37. machineconfig/setup_linux/__init__.py +2 -2
  38. machineconfig/setup_linux/apps.sh +7 -9
  39. machineconfig/setup_linux/apps_desktop.sh +11 -35
  40. machineconfig/setup_linux/apps_gui.sh +4 -14
  41. machineconfig/setup_linux/nix/cli_installation.sh +9 -29
  42. machineconfig/setup_linux/web_shortcuts/interactive.sh +1 -1
  43. machineconfig/setup_windows/__init__.py +2 -2
  44. machineconfig/utils/code.py +3 -3
  45. machineconfig/utils/files/read.py +1 -1
  46. machineconfig/utils/installer.py +15 -21
  47. machineconfig/utils/installer_utils/installer.py +3 -4
  48. machineconfig/utils/installer_utils/installer_abc.py +4 -4
  49. machineconfig/utils/installer_utils/installer_class.py +11 -46
  50. machineconfig/utils/io.py +0 -1
  51. {machineconfig-5.26.dist-info → machineconfig-5.28.dist-info}/METADATA +3 -3
  52. {machineconfig-5.26.dist-info → machineconfig-5.28.dist-info}/RECORD +59 -52
  53. {machineconfig-5.26.dist-info → machineconfig-5.28.dist-info}/entry_points.txt +0 -1
  54. machineconfig/scripts/linux/cloud_repo_sync +0 -2
  55. machineconfig/scripts/windows/cloud_repo_sync.ps1 +0 -1
  56. /machineconfig/setup_linux/{repos.sh → machineconfig.sh} +0 -0
  57. /machineconfig/setup_linux/{ve.sh → uv.sh} +0 -0
  58. /machineconfig/setup_windows/{repos.ps1 → machineconfig.ps1} +0 -0
  59. /machineconfig/setup_windows/{ve.ps1 → uv.ps1} +0 -0
  60. {machineconfig-5.26.dist-info → machineconfig-5.28.dist-info}/WHEEL +0 -0
  61. {machineconfig-5.26.dist-info → machineconfig-5.28.dist-info}/top_level.txt +0 -0
@@ -3,31 +3,31 @@ from rich.console import Console
3
3
  from rich.panel import Panel
4
4
  import typer
5
5
 
6
- from machineconfig.utils.io import read_ini
7
6
  from machineconfig.utils.path_extended import PathExtended
8
7
  from machineconfig.utils.terminal import Response
9
8
  from machineconfig.utils.source_of_truth import CONFIG_PATH, DEFAULTS_PATH
10
- from machineconfig.utils.options import choose_from_options
11
9
  from machineconfig.utils.code import get_shell_file_executing_python_script, write_shell_script_to_file
12
10
 
13
11
  import platform
14
12
  import subprocess
15
13
  from typing import Optional, Literal
16
14
  from pathlib import Path
17
- import sys
15
+
18
16
 
19
17
  console = Console()
20
18
 
21
19
 
22
20
  def main(
23
21
  cloud: Optional[str] = typer.Option(None, "--cloud", "-c", help="Cloud storage profile name. If not provided, uses default from config."),
24
- path: Optional[str] = typer.Option(None, "--path", "-p", help="Path to the local repository. Defaults to current working directory."),
22
+ repo: Optional[str] = typer.Option(None, "--repo", "-r", help="Path to the local repository. Defaults to current working directory."),
25
23
  message: Optional[str] = typer.Option(None, "--message", "-m", help="Commit message for local changes."),
26
24
  on_conflict: Literal["ask", "pushLocalMerge", "overwriteLocal", "InspectRepos", "RemoveLocalRclone"] = typer.Option("ask", "--on-conflict", "-oc", help="Action to take on merge conflict. Default is 'ask'."),
27
25
  pwd: Optional[str] = typer.Option(None, "--password", help="Password for encryption/decryption of the remote repository."),
28
26
  ):
29
27
  if cloud is None:
30
28
  try:
29
+ from machineconfig.utils.io import read_ini
30
+
31
31
  cloud_resolved = read_ini(DEFAULTS_PATH)["general"]["rclone_config_name"]
32
32
  console.print(Panel(f"⚠️ Using default cloud: `{cloud_resolved}` from {DEFAULTS_PATH}", title="Default Cloud", border_style="yellow"))
33
33
  except FileNotFoundError:
@@ -35,7 +35,7 @@ def main(
35
35
  return ""
36
36
  else:
37
37
  cloud_resolved = cloud
38
- repo_local_root = PathExtended.cwd() if path is None else PathExtended(path).expanduser().absolute()
38
+ repo_local_root = PathExtended.cwd() if repo is None else PathExtended(repo).expanduser().absolute()
39
39
  repo_local_obj = git.Repo(repo_local_root, search_parent_directories=True)
40
40
  repo_local_root = PathExtended(repo_local_obj.working_dir) # cwd might have been in a sub directory of repo_root, so its better to redefine it.
41
41
  PathExtended(CONFIG_PATH).joinpath("remote").mkdir(parents=True, exist_ok=True)
@@ -139,15 +139,17 @@ git commit -am "finished merging"
139
139
 
140
140
  console.print(Panel("🔄 RESOLVE MERGE CONFLICT\nChoose an option to resolve the conflict:", title_align="left", border_style="blue"))
141
141
 
142
- print(f"• 1️⃣ {option1:75} 👉 {shell_file_1}")
143
- print(f"• 2️⃣ {option2:75} 👉 {shell_file_2}")
144
- print(f"• 3️⃣ {option3:75} 👉 {shell_file_3}")
145
- print(f"• 4️⃣ {option4:75} 👉 {shell_file_4}")
142
+ print(f"• {option1:75} 👉 {shell_file_1}")
143
+ print(f"• {option2:75} 👉 {shell_file_2}")
144
+ print(f"• {option3:75} 👉 {shell_file_3}")
145
+ print(f"• {option4:75} 👉 {shell_file_4}")
146
+ print("\n\n")
146
147
 
147
148
  program_content = None
148
149
  match on_conflict:
149
150
  case "ask":
150
- choice = choose_from_options(multi=False, msg="Choose one option", options=[option1, option2, option3, option4], fzf=False)
151
+ import questionary
152
+ choice = questionary.select("Choose one option:", choices=[option1, option2, option3, option4]).ask()
151
153
  if choice == option1:
152
154
  program_content = shell_file_1.read_text(encoding="utf-8")
153
155
  elif choice == option2:
@@ -168,24 +170,7 @@ git commit -am "finished merging"
168
170
  program_content = program_4
169
171
  case _:
170
172
  raise ValueError(f"Unknown action: {on_conflict}")
171
- # PROGRAM_PATH.write_text(program_content, encoding="utf-8")
172
- subprocess.run(program_content, shell=True, check=True)
173
-
173
+ from machineconfig.utils.code import run_shell_script
174
+ run_shell_script(script=program_content)
174
175
  return program_content
175
176
 
176
-
177
- def args_parser():
178
- # Check if no arguments provided (excluding the script name)
179
- if len(sys.argv) == 1:
180
- app = typer.Typer(add_completion=False, help="Sync a local git repository with a remote encrypted cloud copy.")
181
- app.command()(main)
182
- app(["--help"])
183
- return
184
-
185
- app = typer.Typer(add_completion=False, no_args_is_help=True, help="Sync a local git repository with a remote encrypted cloud copy.")
186
- app.command()(main)
187
- app()
188
-
189
-
190
- if __name__ == "__main__":
191
- args_parser()
@@ -1,17 +1,15 @@
1
1
  """devops with emojis"""
2
2
 
3
3
  import typer
4
- from typing import Literal, Annotated, Optional, get_args
4
+ from typing import Literal, Annotated, Optional
5
5
 
6
- import machineconfig.scripts.python.share_terminal as share_terminal
7
6
  import machineconfig.scripts.python.repos as repos
8
- from machineconfig.jobs.installer.package_groups import PACKAGE_GROUPS
9
-
7
+ import machineconfig.scripts.python.share_terminal as share_terminal
10
8
 
11
9
  app = typer.Typer(help="🛠️ DevOps operations", no_args_is_help=True)
12
10
  @app.command(no_args_is_help=True)
13
11
  def install( which: Optional[str] = typer.Option(None, "--which", "-w", help="Comma-separated list of program names to install."),
14
- group: Optional[PACKAGE_GROUPS] = typer.Option(None, "--group", "-g", help=f"Group name (one of {list(get_args(PACKAGE_GROUPS))})"),
12
+ group: Optional[str] = typer.Option(None, "--group", "-g", help="Groups names. A group is bundle of apps. See available groups when running interactively."),
15
13
  interactive: bool = typer.Option(False, "--interactive", "-ia", help="Interactive selection of programs to install."),
16
14
  ) -> None:
17
15
  """📦 Install essential packages"""
@@ -30,7 +28,6 @@ app.add_typer(nw_apps, name="network")
30
28
  self_app = typer.Typer(help="🔄 SELF operations subcommands", no_args_is_help=True)
31
29
  app.add_typer(self_app, name="self")
32
30
 
33
-
34
31
  @self_app.command()
35
32
  def update():
36
33
  """🔄 UPDATE essential repos"""
@@ -53,12 +50,12 @@ def clone():
53
50
  from machineconfig.utils.code import run_shell_script
54
51
  from machineconfig.profile.shell import create_default_shell_profile
55
52
  if platform.system() == "Windows":
56
- from machineconfig.setup_windows import REPOS
53
+ from machineconfig.setup_windows import MACHINECONFIG
57
54
  create_default_shell_profile(method="copy")
58
55
  else:
59
- from machineconfig.setup_linux import REPOS
56
+ from machineconfig.setup_linux import MACHINECONFIG
60
57
  create_default_shell_profile(method="reference")
61
- run_shell_script(REPOS.read_text(encoding="utf-8"))
58
+ run_shell_script(MACHINECONFIG.read_text(encoding="utf-8"))
62
59
 
63
60
 
64
61
 
@@ -125,7 +122,7 @@ def setup():
125
122
  else:
126
123
  raise NotImplementedError(f"Platform {platform.system()} is not supported.")
127
124
  from machineconfig.utils.code import run_shell_script
128
- run_shell_script(program=program)
125
+ run_shell_script(script=program)
129
126
 
130
127
 
131
128
  @app_data.command()
@@ -82,5 +82,5 @@ def check_dotfiles_version_is_beyond(commit_dtm: str, update: bool) -> bool:
82
82
  console.print(Panel(f"🔄 UPDATE REQUIRED | Updating dotfiles because {dtm} < {datetime.fromisoformat(commit_dtm)}", border_style="bold blue", expand=False))
83
83
  from machineconfig.scripts.python.cloud_repo_sync import main
84
84
 
85
- main(cloud=None, path=dotfiles_path)
85
+ main(cloud=None, repo=dotfiles_path)
86
86
  return res
@@ -2,13 +2,12 @@
2
2
  import random
3
3
  import shlex
4
4
  from pathlib import Path
5
- from machineconfig.scripts.python.helpers_fire.fire_agents_helper_types import AGENTS, AGENT_NAME_FORMATTER
5
+ from machineconfig.scripts.python.helpers_fire.fire_agents_helper_types import AGENTS, AGENT_NAME_FORMATTER, MATCHINE, PROVIDER, MODEL
6
6
 
7
7
 
8
- def get_gemini_api_keys() -> list[str]:
8
+ def get_api_keys(provider: PROVIDER) -> list[str]:
9
9
  from machineconfig.utils.io import read_ini
10
-
11
- config = read_ini(Path.home().joinpath("dotfiles/creds/llm/gemini/api_keys.ini"))
10
+ config = read_ini(Path.home().joinpath(f"dotfiles/creds/llm/{provider}/api_keys.ini"))
12
11
  res: list[str] = []
13
12
  for a_section_name in list(config.sections()):
14
13
  a_section = config[a_section_name]
@@ -16,11 +15,12 @@ def get_gemini_api_keys() -> list[str]:
16
15
  api_key = a_section["api_key"].strip()
17
16
  if api_key:
18
17
  res.append(api_key)
19
- print(f"Found {len(res)} Gemini API keys configured.")
18
+ print(f"Found {len(res)} {provider} API keys configured.")
20
19
  return res
21
20
 
22
21
 
23
- def prep_agent_launch(agents_dir: Path, prompts_material: list[str], prompt_prefix: str, keep_material_in_separate_file: bool, agent: AGENTS, *, job_name: str) -> None:
22
+ def prep_agent_launch(repo_root: Path, agents_dir: Path, prompts_material: list[str], prompt_prefix: str, keep_material_in_separate_file: bool,
23
+ machine: MATCHINE, model: MODEL, provider: PROVIDER, agent: AGENTS, *, job_name: str) -> None:
24
24
  agents_dir.mkdir(parents=True, exist_ok=True)
25
25
  prompt_folder = agents_dir / "prompts"
26
26
  prompt_folder.mkdir(parents=True, exist_ok=True)
@@ -32,21 +32,22 @@ def prep_agent_launch(agents_dir: Path, prompts_material: list[str], prompt_pref
32
32
  if keep_material_in_separate_file:
33
33
  prompt_material_path = prompt_root / f"agent_{idx}_material.txt"
34
34
  prompt_material_path.write_text(a_prompt_material, encoding="utf-8")
35
- prompt_path.write_text(prompt_prefix + f"""\nPlease only look @ {prompt_material_path}. You don't need to do any other work beside the content of this material file.""", encoding="utf-8")
35
+ prompt_path.write_text(prompt_prefix + f"""\nPlease only look @ {prompt_material_path.relative_to(repo_root)}. You don't need to do any other work beside the content of this material file.""", encoding="utf-8")
36
36
  else:
37
37
  prompt_material_path = prompt_path
38
38
  prompt_path.write_text(prompt_prefix + """\nPlease only look @ the following:\n""" + a_prompt_material, encoding="utf-8")
39
39
 
40
40
  agent_cmd_launch_path = prompt_root / AGENT_NAME_FORMATTER.format(idx=idx) # e.g., agent_0_cmd.sh
41
41
  random_sleep_time = random.uniform(0, 5)
42
- cmd_prefix = f"""
43
- #!/usr/bin/env bash
44
-
45
- sleep 5
46
- timeout 3 copilot --banner
42
+ cmd_prefix = f"""#!/usr/bin/env bash
47
43
 
48
- # AGENT-{idx}-LAUNCH-SCRIPT
49
- # Auto-generated by fire_agents.py
44
+ echo "Using machine: {machine}, model: {model}, provider: {provider}, and agent: {agent}"
45
+ echo "{job_name}-{idx} CMD {agent_cmd_launch_path}"
46
+ echo "{job_name}-{idx} PROMPT {prompt_path}"
47
+ echo "{job_name}-{idx} CONTEXT {prompt_material_path}"
48
+ echo "Starting agent {agent} in 5 seconds... Press Ctrl+C to cancel."
49
+ # sleep 5
50
+ # timeout 3 copilot --banner
50
51
 
51
52
  export FIRE_AGENTS_AGENT_NAME="{agent}"
52
53
  export FIRE_AGENTS_JOB_NAME="{job_name}"
@@ -56,56 +57,37 @@ export FIRE_AGENTS_AGENT_LAUNCHER="{agent_cmd_launch_path}"
56
57
 
57
58
  echo "Sleeping for {random_sleep_time:.2f} seconds to stagger agent startups..."
58
59
  sleep {random_sleep_time:.2f}
59
- echo "Launching agent {agent} with prompt from {prompt_path}"
60
- echo "Launching agent {agent} with command from {agent_cmd_launch_path}"
61
60
  echo "--------START OF AGENT OUTPUT--------"
62
61
  sleep 0.1
63
62
 
64
63
  """
65
64
  match agent:
66
65
  case "gemini":
67
- model = "gemini-2.5-pro"
68
- # model = "gemini-2.5-flash-lite"
69
- # model = None # auto-select
70
- # if model is None:
71
- # model_arg = ""
72
- # else:
73
- model_arg = f"--model {shlex.quote(model)}"
74
- # Need a real shell for the pipeline; otherwise '| gemini ...' is passed as args to 'cat'
75
- safe_path = shlex.quote(str(prompt_path))
76
- api_keys = get_gemini_api_keys()
77
- api_key = api_keys[idx % len(api_keys)] if api_keys else ""
78
- # Export the environment variable so it's available to subshells
79
- cmd = f"""
80
-
81
- export GEMINI_API_KEY={shlex.quote(api_key)}
82
- echo "Using Gemini API key $GEMINI_API_KEY"
83
-
84
- gemini {model_arg} --yolo --prompt {safe_path}
85
- """
66
+ assert provider == "google", "Gemini agent only works with google provider."
67
+ api_keys = get_api_keys(provider="google")
68
+ api_key = api_keys[idx % len(api_keys)] if len(api_keys) > 0 else None
69
+ from machineconfig.scripts.python.helpers_fire.fire_gemini import fire_gemini
70
+ cmd = fire_gemini(api_key=api_key, prompt_path=prompt_path, machine=machine)
86
71
  case "cursor-agent":
87
- # As originally implemented
88
- cmd = f"""
89
-
90
- cursor-agent --print --output-format text {prompt_path}
91
-
92
- """
72
+ from machineconfig.scripts.python.helpers_fire.fire_cursor_agents import fire_cursor
73
+ cmd = fire_cursor(prompt_path=prompt_path, machine=machine, api_key=None)
74
+ raise NotImplementedError("Cursor agent is not implemented yet, api key missing")
93
75
  case "crush":
94
- cmd = f"""
95
- crush run {prompt_path}
96
- """
76
+ from machineconfig.scripts.python.helpers_fire.fire_crush import fire_crush
77
+ api_keys = get_api_keys(provider=provider)
78
+ api_key = api_keys[idx % len(api_keys)] if len(api_keys) > 0 else None
79
+ cmd = fire_crush(api_key=api_key, prompt_path=prompt_path, machine=machine, repo_root=repo_root, model=model, provider=provider)
97
80
  case "q":
98
- cmd = f"""
99
- q chat --no-interactive --trust-all-tools {prompt_path}
100
- """
81
+ from machineconfig.scripts.python.helpers_fire.fire_q import fire_q
82
+ cmd = fire_q(api_key="", prompt_path=prompt_path, machine=machine)
101
83
  case _:
102
84
  raise ValueError(f"Unsupported agent type: {agent}")
85
+
103
86
  cmd_postfix = """
104
87
  sleep 0.1
105
88
  echo "---------END OF AGENT OUTPUT---------"
106
89
  """
107
90
  agent_cmd_launch_path.write_text(cmd_prefix + cmd + cmd_postfix, encoding="utf-8")
108
-
109
91
  return None
110
92
 
111
93
 
@@ -1,12 +1,30 @@
1
1
 
2
- from typing import Literal, TypeAlias
2
+ from typing import Literal, TypeAlias, TypedDict
3
3
 
4
4
 
5
- AGENTS: TypeAlias = Literal[
6
- "cursor-agent", "gemini", "crush", "q"
7
- # warp terminal
8
- ]
9
- AGENT_NAME_FORMATTER = "agent_{idx}_cmd.sh" # e.g., agent_0_cmd.sh
5
+ AGENTS: TypeAlias = Literal["cursor-agent", "gemini", "crush", "q", "opencode"]
6
+ MATCHINE: TypeAlias = Literal["local", "docker"]
7
+ PROVIDER: TypeAlias = Literal["azure", "google", "aws", "openai", "anthropic", "openrouter", "xai"]
8
+ MODEL: TypeAlias = Literal["zai/glm-4.6", "anthropic/sonnet-4.5", "google/gemini-2.5-pro", "openai/gpt-5-codex",
9
+ "openrouter/supernova", "x-ai/grok-4-fast:free",
10
+ ]
11
+ PROVIDER2MODEL: dict[PROVIDER, list[MODEL]] = {
12
+ "azure": ["zai/glm-4.6"],
13
+ "google": ["google/gemini-2.5-pro"],
14
+ "aws": [],
15
+ "openai": ["openai/gpt-5-codex"],
16
+ "anthropic": ["anthropic/sonnet-4.5"],
17
+ "openrouter": ["openrouter/supernova"],
18
+ "xai": ["x-ai/grok-4-fast:free"]
19
+ }
20
+
21
+ class AI_SPEC(TypedDict):
22
+ provider: PROVIDER
23
+ model: MODEL
24
+ agent: AGENTS
25
+ machine: MATCHINE
10
26
 
27
+
28
+ AGENT_NAME_FORMATTER = "agent_{idx}_cmd.sh" # e.g., agent_0_cmd.sh
11
29
  SEARCH_STRATEGIES: TypeAlias = Literal["file_path", "keyword_search", "filename_pattern"]
12
30
 
@@ -0,0 +1,14 @@
1
+ {
2
+ "models": {
3
+ "large": {
4
+ "model": "{model}",
5
+ "provider": "{provider}",
6
+ "max_tokens": 100000
7
+ }
8
+ },
9
+ "providers": {
10
+ "openrouter": {
11
+ "api_key": "{api_key}"
12
+ }
13
+ }
14
+ }
@@ -0,0 +1,37 @@
1
+
2
+ from pathlib import Path
3
+ # import shlex
4
+ from typing import Optional
5
+ from machineconfig.scripts.python.helpers_fire.fire_agents_helper_types import MATCHINE, PROVIDER, MODEL
6
+
7
+
8
+ def fire_crush(api_key: Optional[str], model: MODEL, provider: PROVIDER, machine: MATCHINE, prompt_path: Path, repo_root: Path) -> str:
9
+ match machine:
10
+ case "local":
11
+ cmd = f"""
12
+ crush run {prompt_path}
13
+ """
14
+ case "docker":
15
+ assert api_key is not None, "API key is required for Crush agent in docker mode."
16
+ json_path = Path(__file__).parent / "fire_crush.json"
17
+ json_template = json_path.read_text(encoding="utf-8")
18
+ json_filled = json_template.replace("{api_key}", api_key).replace("{model}", model).replace("{provider}", provider)
19
+ import tempfile
20
+ temp_config_file_local = tempfile.mkstemp(suffix=".json")[1]
21
+ Path(temp_config_file_local).write_text(json_filled, encoding="utf-8")
22
+ cmd = f"""
23
+
24
+ # -e "PATH_PROMPT=$PATH_PROMPT"
25
+ # opencode --model "{provider}/{model}" run {prompt_path}
26
+
27
+
28
+ echo "Running prompt @ {prompt_path.relative_to(repo_root)} using Docker with Crush..."
29
+ docker run -it --rm \
30
+ -v "{repo_root}:/workspace/{repo_root.name}" \
31
+ -v "{temp_config_file_local}:/root/.local/share/crush/crush.json" \
32
+ -w "/workspace/{repo_root.name}" \
33
+ statistician/machineconfig:latest \
34
+ bash -i -c "source ~/.bashrc && cd /workspace/{repo_root.name} && cat /root/.local/share/crush/crush.json && crush run 'Please act on contents of this prompt ./{prompt_path.relative_to(repo_root)}'"
35
+
36
+ """
37
+ return cmd
@@ -0,0 +1,23 @@
1
+
2
+
3
+ from pathlib import Path
4
+ # import shlex
5
+ from machineconfig.scripts.python.helpers_fire.fire_agents_helper_types import MATCHINE
6
+ from typing import Optional
7
+
8
+ def fire_cursor(api_key: Optional[str], prompt_path: Path, machine: MATCHINE) -> str:
9
+ match machine:
10
+ case "local":
11
+ # Export the environment variable so it's available to subshells
12
+ cmd = f"""
13
+
14
+ cursor-agent --print --output-format text {prompt_path}
15
+
16
+ """
17
+ case "docker":
18
+ cmd = f"""
19
+
20
+ cursor-agent --print --output-format text {prompt_path}
21
+
22
+ """
23
+ return cmd
@@ -0,0 +1,41 @@
1
+
2
+ from pathlib import Path
3
+ import shlex
4
+ from machineconfig.scripts.python.helpers_fire.fire_agents_helper_types import MATCHINE
5
+ from typing import Optional
6
+
7
+
8
+ def fire_gemini(api_key: Optional[str], prompt_path: Path, machine: MATCHINE) -> str:
9
+ model = "gemini-2.5-pro"
10
+ # model = "gemini-2.5-flash-lite"
11
+ # model = None # auto-select
12
+ # if model is None:
13
+ # model_arg = ""
14
+ # else:
15
+ model_arg = f"--model {shlex.quote(model)}"
16
+ # Need a real shell for the pipeline; otherwise '| gemini ...' is passed as args to 'cat'
17
+ safe_path = shlex.quote(str(prompt_path))
18
+
19
+ match machine:
20
+ case "local":
21
+ # Export the environment variable so it's available to subshells
22
+ if api_key is not None:
23
+ define_api_key = f"""export GEMINI_API_KEY="{shlex.quote(api_key)}" """
24
+ else:
25
+ define_api_key = "echo 'Warning: No GEMINI_API_KEY provided, hoping it is set in the environment.'"
26
+ cmd = f"""
27
+ {define_api_key}
28
+ echo "Using Gemini API key $GEMINI_API_KEY"
29
+ gemini {model_arg} --yolo --prompt {safe_path}
30
+ """
31
+ case "docker":
32
+ cmd = """
33
+ docker run -it --rm \
34
+ -e GEMINI_API_KEY="$GEMINI_API_KEY" \
35
+ -v "/home/alex/code/machineconfig:/workspace/machineconfig" \
36
+ -w "/workspace/machineconfig" \
37
+ gemini-cli:latest \
38
+ gemini --prompt "$PATH_PROMPT"
39
+ """
40
+ return cmd
41
+
@@ -0,0 +1,19 @@
1
+
2
+ from pathlib import Path
3
+ import shlex
4
+ from machineconfig.scripts.python.helpers_fire.fire_agents_helper_types import MATCHINE
5
+
6
+
7
+ def fire_q(api_key: str, prompt_path: Path, machine: MATCHINE) -> str:
8
+ safe_path = shlex.quote(str(prompt_path))
9
+
10
+ match machine:
11
+ case "local":
12
+ cmd = f"""
13
+ q chat --no-interactive --trust-all-tools {safe_path}
14
+ """
15
+ case "docker":
16
+ cmd = f"""
17
+ q chat --no-interactive --trust-all-tools {safe_path}
18
+ """
19
+ return cmd
@@ -0,0 +1,2 @@
1
+
2
+ please read the code then explain it in English, put your answer in a markdown file with random name at repo root.
File without changes
@@ -0,0 +1,31 @@
1
+
2
+ #!/bin/bash
3
+ # set -e # Exit immediately if a command exits with a non-zero status.
4
+
5
+ JOB_NAME="outpatient_mapping"
6
+ REPO_ROOT="$HOME/code/machineconfig"
7
+ CONTEXT_PATH="$REPO_ROOT/src/machineconfig/scripts/python/fire_jobs.py"
8
+ PROMPT_PATH="$REPO_ROOT/src/machineconfig/scripts/python/helpers_fire/prompt.txt"
9
+
10
+ AGENTS_DIR="$REPO_ROOT/.ai/agents/$JOB_NAME"
11
+ LAYOUT_PATH_UNBALANCED="$REPO_ROOT/.ai/agents/$JOB_NAME/layout_unbalanced.json"
12
+
13
+ agents create \
14
+ --context-path "$CONTEXT_PATH" \
15
+ --tasks-per-prompt 1 \
16
+ --machine docker \
17
+ --agent crush \
18
+ --model "zai/glm-4.6" \
19
+ --provider openrouter \
20
+ --separator 'def ' \
21
+ --prompt-path "$PROMPT_PATH" \
22
+ --output-path "$LAYOUT_PATH_UNBALANCED" \
23
+ --agents-dir "$AGENTS_DIR"
24
+
25
+ # LAYOUT_BALANCED_PATH="$REPO_ROOT/.ai/agents/$JOB_NAME/layout_balanced.json"
26
+ # sessions balance-load $LAYOUT_PATH --max-thresh 6 --breaking-method moreLayouts --thresh-type number --output-path $LAYOUT_BALANCED_PATH
27
+ # sessions run $LAYOUT_BALANCED_PATH --kill-upon-completion
28
+
29
+ sessions run $LAYOUT_PATH_UNBALANCED
30
+
31
+ # agents collect $AGENTS_DIR "$REPO_ROOT/.ai/agents/$JOB_NAME/collected.txt"
@@ -95,17 +95,22 @@ def display_dotfiles_instructions() -> None:
95
95
 
96
96
  def get_installation_choices() -> list[str]:
97
97
  """Get user choices for installation options."""
98
- choices = [
99
- Choice(value="upgrade_system", title="🔄 Upgrade System Packages - Update all system packages", checked=False),
100
- Choice(value="ESSENTIAL_SYSTEM", title="📥 Install Apps - Install base system applications", checked=False),
101
- Choice(value="ESSENTIAL", title="⚡ Install CLI Apps - Command-line tools installation", checked=False),
102
- Choice(value="DEV_SYSTEM", title="🛠️ Install Development Tools - rust, libssl-dev, ffmpeg, wezterm, brave, code", checked=False),
103
- Choice(value="TerminalEyeCandy", title="🎨 Install ASCII Art Libraries - Terminal visualization tools", checked=False),
104
- Choice(value="install_repos", title="🐍 Install Repos - Set up Python environment and repositories permanently.", checked=False),
105
- Choice(value="install_ssh_server", title="🔒 Install SSH Server - Set up remote access", checked=False),
106
- Choice(value="install_shell_profile", title="🐚 Configure Shell Profile - Source machineconfig shell initialization", checked=False),
107
- Choice(value="retrieve_repositories", title="📚 Retrieve Repositories - Clone repositories to ~/code", checked=False),
108
- Choice(value="retrieve_data", title="💾 Retrieve Data - Backup restoration", checked=False),
98
+ if platform.system() == "Windows":
99
+ choices = []
100
+ else:
101
+ choices = [
102
+ Choice(value="upgrade_system", title="🔄 Upgrade System Package Manager", checked=False),
103
+ ]
104
+ choices += [
105
+ Choice(value="install_repos", title="🐍 Install machineconfig.", checked=False),
106
+ Choice(value="ESSENTIAL_SYSTEM", title="📥 Install Essential System Packages.", checked=False),
107
+ Choice(value="ESSENTIAL", title=" Install CLI apps essentials", checked=False),
108
+ Choice(value="DEV_SYSTEM", title="🛠️ Install CLI apps development.", checked=False),
109
+ Choice(value="TerminalEyeCandy", title="🎨 Install CLI apps terminal eye candy.", checked=False),
110
+ Choice(value="install_ssh_server", title="🔒 Install SSH Server", checked=False),
111
+ Choice(value="install_shell_profile", title="🐚 Configure Shell Profile.", checked=False),
112
+ Choice(value="retrieve_repositories", title="📚 Retrieve Repositories", checked=False),
113
+ Choice(value="retrieve_data", title="💾 Retrieve Data.", checked=False),
109
114
  ]
110
115
  # Add Windows-specific options
111
116
  if platform.system() == "Windows":
@@ -117,12 +122,10 @@ def get_installation_choices() -> list[str]:
117
122
  def execute_installations(selected_options: list[str]) -> None:
118
123
  if platform.system() == "Windows":
119
124
  from machineconfig import setup_windows as module
120
- script_path = Path(module.__file__).parent / "ve.ps1"
121
- run_shell_script(script_path.read_text(encoding="utf-8"))
125
+ run_shell_script(module.UV.read_text(encoding="utf-8"))
122
126
  else:
123
127
  from machineconfig import setup_linux as module
124
- script_path = Path(module.__file__).parent / "ve.sh"
125
- run_shell_script(script_path.read_text(encoding="utf-8"))
128
+ run_shell_script(module.UV.read_text(encoding="utf-8"))
126
129
 
127
130
  for maybe_a_group in selected_options:
128
131
  if maybe_a_group in ("ESSENTIAL", "DEV", "ESSENTIAL_SYSTEM", "DEV_SYSTEM", "TerminalEyeCandy"):
@@ -147,10 +150,10 @@ def execute_installations(selected_options: list[str]) -> None:
147
150
  if "install_repos" in selected_options:
148
151
  console.print(Panel("🐍 [bold green]PYTHON ENVIRONMENT[/bold green]\n[italic]Virtual environment setup[/italic]", border_style="green"))
149
152
  if platform.system() == "Windows":
150
- from machineconfig.setup_windows import REPOS
153
+ from machineconfig.setup_windows import MACHINECONFIG
151
154
  else:
152
- from machineconfig.setup_linux import REPOS
153
- run_shell_script(REPOS.read_text(encoding="utf-8"))
155
+ from machineconfig.setup_linux import MACHINECONFIG
156
+ run_shell_script(MACHINECONFIG.read_text(encoding="utf-8"))
154
157
 
155
158
  if "install_ssh_server" in selected_options:
156
159
  console.print(Panel("🔒 [bold red]SSH SERVER[/bold red]\n[italic]Remote access setup[/italic]", border_style="red"))
@@ -207,7 +210,6 @@ def main() -> None:
207
210
  console.print("❌ Installation cancelled.", style="bold red")
208
211
  sys.exit(0)
209
212
  execute_installations(selected_options)
210
- display_dotfiles_instructions()
211
213
  display_completion_message()
212
214
 
213
215
 
@@ -8,13 +8,13 @@ in the event that username@github.com is not mentioned in the remote url.
8
8
  from pathlib import Path
9
9
  from typing import Annotated, Optional
10
10
  import typer
11
+ from machineconfig.scripts.python.secure_repo import main as secure_repo_main
11
12
 
12
13
 
13
14
  app = typer.Typer(help="� Manage development repositories", no_args_is_help=True)
14
15
  sync_app = typer.Typer(help="� Manage repository specifications and syncing", no_args_is_help=True)
15
16
  app.add_typer(sync_app, name="sync", help="� Sync repositories using saved specs")
16
17
 
17
-
18
18
  DirectoryArgument = Annotated[Optional[str], typer.Argument(help="📁 Folder containing repos or the specs JSON file to use.")]
19
19
  RecursiveOption = Annotated[bool, typer.Option("--recursive", "-r", help="🔍 Recurse into nested repositories.")]
20
20
  NoSyncOption = Annotated[bool, typer.Option("--no-sync", help="🚫 Disable automatic uv sync after pulls.")]
@@ -101,6 +101,9 @@ def analyze(directory: DirectoryArgument = None) -> None:
101
101
  analyze_repo_development(repo_path=repo_path)
102
102
 
103
103
 
104
+ app.command(name="secure", no_args_is_help=True, help="🔐 Securely sync git repository to/from cloud with encryption")(secure_repo_main)
105
+
106
+
104
107
  @app.command(no_args_is_help=True)
105
108
  def viz(
106
109
  repo: str = typer.Option(Path.cwd().__str__(), "--repo", "-r", help="Path to git repository to visualize"),
@@ -0,0 +1,15 @@
1
+
2
+ import typer
3
+ from typing import Optional, Literal
4
+
5
+
6
+
7
+ def main(
8
+ cloud: Optional[str] = typer.Option(None, "--cloud", "-c", help="Cloud storage profile name. If not provided, uses default from config."),
9
+ repo: Optional[str] = typer.Option(None, "--repo", "-r", help="Path to the local repository. Defaults to current working directory."),
10
+ message: Optional[str] = typer.Option(None, "--message", "-m", help="Commit message for local changes."),
11
+ on_conflict: Literal["ask", "pushLocalMerge", "overwriteLocal", "InspectRepos", "RemoveLocalRclone"] = typer.Option("ask", "--on-conflict", "-oc", help="Action to take on merge conflict. Default is 'ask'."),
12
+ pwd: Optional[str] = typer.Option(None, "--password", help="Password for encryption/decryption of the remote repository."),
13
+ ):
14
+ from machineconfig.scripts.python.cloud_repo_sync import main as program_content
15
+ program_content(cloud=cloud, repo=repo, message=message, on_conflict=on_conflict, pwd=pwd)
@@ -1,7 +1,5 @@
1
1
  #!/bin/bash
2
- #=======================================================================
3
2
  # 🌳 BROOT SHELL INTEGRATION 🌳
4
- #=======================================================================
5
3
  # This script was automatically generated by the broot program
6
4
  # More information can be found in https://github.com/Canop/broot
7
5
  #
@@ -40,7 +38,6 @@ function br {
40
38
  fi
41
39
  }
42
40
 
43
- #=======================================================================
44
41
  # ℹ️ USAGE:
45
42
  # br [options]
46
43
  #
@@ -48,4 +45,3 @@ function br {
48
45
  # br # Open broot in the current directory
49
46
  # br /path/to/dir # Open broot in the specified directory
50
47
  # br -h # Show broot help
51
- #=======================================================================