machineconfig 2.8__py3-none-any.whl → 2.94__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.
- machineconfig/cluster/sessions_managers/zellij_local_manager.py +4 -20
- machineconfig/jobs/python/python_ve_symlink.py +1 -1
- machineconfig/scripts/python/__pycache__/devops.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/devops_update_repos.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/fire_agents.cpython-313.pyc +0 -0
- machineconfig/scripts/python/__pycache__/repos_helper_update.cpython-313.pyc +0 -0
- machineconfig/scripts/python/ai/mcinit.py +2 -14
- machineconfig/scripts/python/devops.py +18 -64
- machineconfig/scripts/python/devops_add_identity.py +6 -2
- machineconfig/scripts/python/devops_add_ssh_key.py +5 -2
- machineconfig/scripts/python/devops_backup_retrieve.py +3 -15
- machineconfig/scripts/python/devops_devapps_install.py +8 -6
- machineconfig/scripts/python/devops_update_repos.py +122 -226
- machineconfig/scripts/python/fire_agents.py +111 -198
- machineconfig/scripts/python/fire_agents_help_launch.py +142 -0
- machineconfig/scripts/python/fire_agents_help_search.py +82 -0
- machineconfig/scripts/python/fire_agents_load_balancer.py +52 -0
- machineconfig/scripts/python/fire_jobs.py +2 -1
- machineconfig/scripts/python/helpers/cloud_helpers.py +2 -5
- machineconfig/scripts/python/repos.py +1 -1
- machineconfig/scripts/python/repos_helper_update.py +265 -0
- machineconfig/utils/installer_utils/installer_class.py +3 -3
- machineconfig/utils/notifications.py +24 -4
- machineconfig/utils/path.py +2 -1
- machineconfig/utils/procs.py +7 -7
- machineconfig/utils/schemas/fire_agents/fire_agents_input.py +70 -0
- machineconfig/utils/schemas/layouts/layout_types.py +0 -1
- machineconfig/utils/source_of_truth.py +2 -0
- machineconfig/utils/ve.py +9 -5
- {machineconfig-2.8.dist-info → machineconfig-2.94.dist-info}/METADATA +7 -10
- {machineconfig-2.8.dist-info → machineconfig-2.94.dist-info}/RECORD +34 -27
- {machineconfig-2.8.dist-info → machineconfig-2.94.dist-info}/WHEEL +0 -0
- {machineconfig-2.8.dist-info → machineconfig-2.94.dist-info}/entry_points.txt +0 -0
- {machineconfig-2.8.dist-info → machineconfig-2.94.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
from typing import TypedDict
|
|
3
|
+
import subprocess
|
|
4
|
+
import git
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class RepositoryUpdateResult(TypedDict):
|
|
9
|
+
"""Result of updating a single repository."""
|
|
10
|
+
repo_path: str
|
|
11
|
+
status: str # "success", "error", "skipped", "auth_failed"
|
|
12
|
+
had_uncommitted_changes: bool
|
|
13
|
+
uncommitted_files: list[str]
|
|
14
|
+
commit_before: str
|
|
15
|
+
commit_after: str
|
|
16
|
+
commits_changed: bool
|
|
17
|
+
pyproject_changed: bool
|
|
18
|
+
dependencies_changed: bool
|
|
19
|
+
uv_sync_ran: bool
|
|
20
|
+
uv_sync_success: bool
|
|
21
|
+
remotes_processed: list[str]
|
|
22
|
+
remotes_skipped: list[str]
|
|
23
|
+
error_message: str | None
|
|
24
|
+
is_machineconfig_repo: bool
|
|
25
|
+
permissions_updated: bool
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def set_permissions_recursive(path: Path, executable: bool = True) -> None:
|
|
29
|
+
"""Set permissions recursively for a directory."""
|
|
30
|
+
if not path.exists():
|
|
31
|
+
return
|
|
32
|
+
if path.is_file():
|
|
33
|
+
if executable:
|
|
34
|
+
path.chmod(0o755)
|
|
35
|
+
else:
|
|
36
|
+
path.chmod(0o644)
|
|
37
|
+
elif path.is_dir():
|
|
38
|
+
path.chmod(0o755)
|
|
39
|
+
for item in path.rglob("*"):
|
|
40
|
+
set_permissions_recursive(item, executable)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def run_uv_sync(repo_path: Path) -> bool:
|
|
44
|
+
"""Run uv sync in the given repository path. Returns True if successful."""
|
|
45
|
+
try:
|
|
46
|
+
print(f"🔄 Running uv sync in {repo_path}")
|
|
47
|
+
# Run uv sync with output directly to terminal (no capture)
|
|
48
|
+
subprocess.run(["uv", "sync"], cwd=repo_path, check=True)
|
|
49
|
+
print("✅ uv sync completed successfully")
|
|
50
|
+
return True
|
|
51
|
+
except subprocess.CalledProcessError as e:
|
|
52
|
+
print(f"❌ uv sync failed with return code {e.returncode}")
|
|
53
|
+
return False
|
|
54
|
+
except FileNotFoundError:
|
|
55
|
+
print("⚠️ uv command not found. Please install uv first.")
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def get_file_hash(file_path: Path) -> str | None:
|
|
60
|
+
"""Get SHA256 hash of a file, return None if file doesn't exist."""
|
|
61
|
+
if not file_path.exists():
|
|
62
|
+
return None
|
|
63
|
+
import hashlib
|
|
64
|
+
return hashlib.sha256(file_path.read_bytes()).hexdigest()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def update_repository(repo: git.Repo, auto_sync: bool, allow_password_prompt: bool) -> RepositoryUpdateResult:
|
|
68
|
+
"""Update a single repository and return detailed information about what happened."""
|
|
69
|
+
repo_path = Path(repo.working_dir)
|
|
70
|
+
print(f"🔄 {'Updating ' + str(repo_path):.^80}")
|
|
71
|
+
|
|
72
|
+
# Initialize result dict
|
|
73
|
+
result: RepositoryUpdateResult = {
|
|
74
|
+
"repo_path": str(repo_path),
|
|
75
|
+
"status": "success",
|
|
76
|
+
"had_uncommitted_changes": False,
|
|
77
|
+
"uncommitted_files": [],
|
|
78
|
+
"commit_before": "",
|
|
79
|
+
"commit_after": "",
|
|
80
|
+
"commits_changed": False,
|
|
81
|
+
"pyproject_changed": False,
|
|
82
|
+
"dependencies_changed": False,
|
|
83
|
+
"uv_sync_ran": False,
|
|
84
|
+
"uv_sync_success": False,
|
|
85
|
+
"remotes_processed": [],
|
|
86
|
+
"remotes_skipped": [],
|
|
87
|
+
"error_message": None,
|
|
88
|
+
"is_machineconfig_repo": "machineconfig" in str(repo_path),
|
|
89
|
+
"permissions_updated": False,
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
# Check git status first
|
|
93
|
+
print("📊 Checking git status...")
|
|
94
|
+
if repo.is_dirty():
|
|
95
|
+
# Get the list of modified files
|
|
96
|
+
changed_files_raw = [item.a_path for item in repo.index.diff(None)]
|
|
97
|
+
changed_files_raw.extend([item.a_path for item in repo.index.diff("HEAD")])
|
|
98
|
+
# Filter out None values and remove duplicates
|
|
99
|
+
changed_files = list(set(file for file in changed_files_raw if file is not None))
|
|
100
|
+
|
|
101
|
+
result["had_uncommitted_changes"] = True
|
|
102
|
+
result["uncommitted_files"] = changed_files
|
|
103
|
+
print(f"⚠️ Repository has uncommitted changes: {', '.join(changed_files)}")
|
|
104
|
+
|
|
105
|
+
# Repository has uncommitted changes - cannot update
|
|
106
|
+
result["status"] = "error"
|
|
107
|
+
result["error_message"] = f"Cannot update repository - there are pending changes in: {', '.join(changed_files)}. Please commit or stash your changes first."
|
|
108
|
+
raise RuntimeError(result["error_message"])
|
|
109
|
+
else:
|
|
110
|
+
print("✅ Repository is clean")
|
|
111
|
+
|
|
112
|
+
# Check if this repo has pyproject.toml
|
|
113
|
+
pyproject_path = repo_path / "pyproject.toml"
|
|
114
|
+
|
|
115
|
+
# Get hashes before pull
|
|
116
|
+
pyproject_hash_before = get_file_hash(pyproject_path)
|
|
117
|
+
|
|
118
|
+
# Get current commit hash before pull
|
|
119
|
+
result["commit_before"] = repo.head.commit.hexsha
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
# Use subprocess for git pull to get better output control
|
|
123
|
+
|
|
124
|
+
# Get list of remotes
|
|
125
|
+
remotes = list(repo.remotes)
|
|
126
|
+
if not remotes:
|
|
127
|
+
print("⚠️ No remotes configured for this repository")
|
|
128
|
+
result["status"] = "skipped"
|
|
129
|
+
result["error_message"] = "No remotes configured for this repository"
|
|
130
|
+
return result
|
|
131
|
+
|
|
132
|
+
for remote in remotes:
|
|
133
|
+
try:
|
|
134
|
+
print(f"📥 Fetching from {remote.name}...")
|
|
135
|
+
|
|
136
|
+
# Set up environment for git commands
|
|
137
|
+
env = None
|
|
138
|
+
if not allow_password_prompt:
|
|
139
|
+
# Disable interactive prompts
|
|
140
|
+
import os
|
|
141
|
+
|
|
142
|
+
env = os.environ.copy()
|
|
143
|
+
env["GIT_TERMINAL_PROMPT"] = "0"
|
|
144
|
+
env["GIT_ASKPASS"] = "echo" # Returns empty string for any credential request
|
|
145
|
+
|
|
146
|
+
# First fetch to see what's available
|
|
147
|
+
fetch_result = subprocess.run(
|
|
148
|
+
["git", "fetch", remote.name, "--verbose"],
|
|
149
|
+
cwd=repo_path,
|
|
150
|
+
capture_output=True,
|
|
151
|
+
text=True,
|
|
152
|
+
env=env,
|
|
153
|
+
timeout=30, # Add timeout to prevent hanging
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
# Check if fetch failed due to authentication
|
|
157
|
+
if fetch_result.returncode != 0 and not allow_password_prompt:
|
|
158
|
+
auth_error_indicators = [
|
|
159
|
+
"Authentication failed",
|
|
160
|
+
"Password for",
|
|
161
|
+
"Username for",
|
|
162
|
+
"could not read Username",
|
|
163
|
+
"could not read Password",
|
|
164
|
+
"fatal: Authentication failed",
|
|
165
|
+
"fatal: could not read Username",
|
|
166
|
+
"fatal: could not read Password",
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
error_output = (fetch_result.stderr or "") + (fetch_result.stdout or "")
|
|
170
|
+
if any(indicator in error_output for indicator in auth_error_indicators):
|
|
171
|
+
print(f"⚠️ Skipping {remote.name} - authentication required but password prompts are disabled")
|
|
172
|
+
continue
|
|
173
|
+
|
|
174
|
+
if fetch_result.stdout:
|
|
175
|
+
print(f"📡 Fetch output: {fetch_result.stdout.strip()}")
|
|
176
|
+
if fetch_result.stderr:
|
|
177
|
+
print(f"📡 Fetch info: {fetch_result.stderr.strip()}")
|
|
178
|
+
|
|
179
|
+
# Now pull with verbose output
|
|
180
|
+
print(f"📥 Pulling from {remote.name}/{repo.active_branch.name}...")
|
|
181
|
+
pull_result = subprocess.run(["git", "pull", remote.name, repo.active_branch.name, "--verbose"], cwd=repo_path, capture_output=True, text=True, env=env, timeout=30)
|
|
182
|
+
|
|
183
|
+
# Check if pull failed due to authentication
|
|
184
|
+
if pull_result.returncode != 0 and not allow_password_prompt:
|
|
185
|
+
auth_error_indicators = [
|
|
186
|
+
"Authentication failed",
|
|
187
|
+
"Password for",
|
|
188
|
+
"Username for",
|
|
189
|
+
"could not read Username",
|
|
190
|
+
"could not read Password",
|
|
191
|
+
"fatal: Authentication failed",
|
|
192
|
+
"fatal: could not read Username",
|
|
193
|
+
"fatal: could not read Password",
|
|
194
|
+
]
|
|
195
|
+
|
|
196
|
+
error_output = (pull_result.stderr or "") + (pull_result.stdout or "")
|
|
197
|
+
if any(indicator in error_output for indicator in auth_error_indicators):
|
|
198
|
+
print(f"⚠️ Skipping pull from {remote.name} - authentication required but password prompts are disabled")
|
|
199
|
+
continue
|
|
200
|
+
|
|
201
|
+
if pull_result.stdout:
|
|
202
|
+
print(f"📦 Pull output: {pull_result.stdout.strip()}")
|
|
203
|
+
if pull_result.stderr:
|
|
204
|
+
print(f"📦 Pull info: {pull_result.stderr.strip()}")
|
|
205
|
+
|
|
206
|
+
# Check if pull was successful
|
|
207
|
+
if pull_result.returncode == 0:
|
|
208
|
+
result["remotes_processed"].append(remote.name)
|
|
209
|
+
# Check if commits changed
|
|
210
|
+
result["commit_after"] = repo.head.commit.hexsha
|
|
211
|
+
if result["commit_before"] != result["commit_after"]:
|
|
212
|
+
result["commits_changed"] = True
|
|
213
|
+
print(f"✅ Repository updated: {result['commit_before'][:8]} → {result['commit_after'][:8]}")
|
|
214
|
+
else:
|
|
215
|
+
print("✅ Already up to date")
|
|
216
|
+
else:
|
|
217
|
+
result["remotes_skipped"].append(remote.name)
|
|
218
|
+
print(f"❌ Pull failed with return code {pull_result.returncode}")
|
|
219
|
+
|
|
220
|
+
except Exception as e:
|
|
221
|
+
result["remotes_skipped"].append(remote.name)
|
|
222
|
+
print(f"⚠️ Failed to pull from {remote.name}: {e}")
|
|
223
|
+
continue
|
|
224
|
+
|
|
225
|
+
# Check if pyproject.toml changed after pull
|
|
226
|
+
pyproject_hash_after = get_file_hash(pyproject_path)
|
|
227
|
+
|
|
228
|
+
if pyproject_hash_before != pyproject_hash_after:
|
|
229
|
+
print("📋 pyproject.toml has changed")
|
|
230
|
+
result["pyproject_changed"] = True
|
|
231
|
+
result["dependencies_changed"] = True
|
|
232
|
+
|
|
233
|
+
# Special handling for machineconfig repository
|
|
234
|
+
if result["is_machineconfig_repo"]:
|
|
235
|
+
print("🛠 Special handling for machineconfig repository...")
|
|
236
|
+
scripts_path = Path.home() / "scripts"
|
|
237
|
+
if scripts_path.exists():
|
|
238
|
+
set_permissions_recursive(scripts_path)
|
|
239
|
+
result["permissions_updated"] = True
|
|
240
|
+
print(f"✅ Set permissions for {scripts_path}")
|
|
241
|
+
|
|
242
|
+
linux_jobs_path = repo_path / "src" / "machineconfig" / "jobs" / "linux"
|
|
243
|
+
if linux_jobs_path.exists():
|
|
244
|
+
set_permissions_recursive(linux_jobs_path)
|
|
245
|
+
result["permissions_updated"] = True
|
|
246
|
+
print(f"✅ Set permissions for {linux_jobs_path}")
|
|
247
|
+
|
|
248
|
+
lf_exe_path = repo_path / "src" / "machineconfig" / "settings" / "lf" / "linux" / "exe"
|
|
249
|
+
if lf_exe_path.exists():
|
|
250
|
+
set_permissions_recursive(lf_exe_path)
|
|
251
|
+
result["permissions_updated"] = True
|
|
252
|
+
print(f"✅ Set permissions for {lf_exe_path}")
|
|
253
|
+
|
|
254
|
+
# Run uv sync if dependencies changed and auto_sync is enabled
|
|
255
|
+
if result["dependencies_changed"] and auto_sync:
|
|
256
|
+
result["uv_sync_ran"] = True
|
|
257
|
+
result["uv_sync_success"] = run_uv_sync(repo_path)
|
|
258
|
+
|
|
259
|
+
return result
|
|
260
|
+
|
|
261
|
+
except Exception as e:
|
|
262
|
+
result["status"] = "error"
|
|
263
|
+
result["error_message"] = str(e)
|
|
264
|
+
print(f"❌ Error updating repository {repo_path}: {e}")
|
|
265
|
+
return result
|
|
@@ -94,14 +94,14 @@ class Installer:
|
|
|
94
94
|
|
|
95
95
|
if old_version_cli == new_version_cli:
|
|
96
96
|
print(f"ℹ️ Same version detected: {old_version_cli}")
|
|
97
|
-
return f"""
|
|
97
|
+
return f"""📦️ 😑 {self.exe_name}, same version: {old_version_cli}"""
|
|
98
98
|
else:
|
|
99
99
|
print(f"🚀 Update successful: {old_version_cli} ➡️ {new_version_cli}")
|
|
100
|
-
return f"""
|
|
100
|
+
return f"""📦️ 🤩 {self.exe_name} updated from {old_version_cli} ➡️ TO ➡️ {new_version_cli}"""
|
|
101
101
|
|
|
102
102
|
except Exception as ex:
|
|
103
103
|
print(f"❌ ERROR: Installation failed for {self.exe_name}: {ex}")
|
|
104
|
-
return f"""
|
|
104
|
+
return f"""📦️ ❌ Failed to install `{self.name}` with error: {ex}"""
|
|
105
105
|
|
|
106
106
|
def install(self, version: Optional[str]):
|
|
107
107
|
print(f"\n{'=' * 80}\n🔧 INSTALLATION PROCESS: {self.exe_name} 🔧\n{'=' * 80}")
|
|
@@ -14,7 +14,8 @@ import imaplib
|
|
|
14
14
|
from email.mime.text import MIMEText
|
|
15
15
|
from email.mime.multipart import MIMEMultipart
|
|
16
16
|
from typing import Optional, Any, Union
|
|
17
|
-
from
|
|
17
|
+
from rich.console import Console
|
|
18
|
+
from rich.markdown import Markdown
|
|
18
19
|
|
|
19
20
|
|
|
20
21
|
def download_to_memory(path: Path, allow_redirects: bool = True, timeout: Optional[float] = None, params: Any = None) -> "Any":
|
|
@@ -30,8 +31,27 @@ def get_github_markdown_css() -> str:
|
|
|
30
31
|
return download_to_memory(Path(pp)).text
|
|
31
32
|
|
|
32
33
|
|
|
33
|
-
def md2html(body: str):
|
|
34
|
-
|
|
34
|
+
def md2html(body: str) -> str:
|
|
35
|
+
"""Convert markdown to HTML using Rich library."""
|
|
36
|
+
# Use Rich's HTML export functionality to convert markdown to HTML
|
|
37
|
+
console = Console(record=True, width=120)
|
|
38
|
+
markdown_obj = Markdown(body)
|
|
39
|
+
console.print(markdown_obj)
|
|
40
|
+
html_output = console.export_html(inline_styles=True)
|
|
41
|
+
|
|
42
|
+
# Try to load GitHub CSS style, fallback to basic style if not found
|
|
43
|
+
gh_style_path = Path(__file__).parent.joinpath("gh_style.css")
|
|
44
|
+
if gh_style_path.exists():
|
|
45
|
+
gh_style = gh_style_path.read_text()
|
|
46
|
+
else:
|
|
47
|
+
# Fallback basic styling
|
|
48
|
+
gh_style = """
|
|
49
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; }
|
|
50
|
+
h1, h2, h3, h4, h5, h6 { color: #0366d6; }
|
|
51
|
+
code { background-color: #f6f8fa; padding: 2px 4px; border-radius: 3px; }
|
|
52
|
+
pre { background-color: #f6f8fa; padding: 16px; border-radius: 6px; overflow: auto; }
|
|
53
|
+
"""
|
|
54
|
+
|
|
35
55
|
return f"""
|
|
36
56
|
<!DOCTYPE html>
|
|
37
57
|
<html>
|
|
@@ -51,7 +71,7 @@ def md2html(body: str):
|
|
|
51
71
|
</style>
|
|
52
72
|
<body>
|
|
53
73
|
<div class="markdown-body">
|
|
54
|
-
{
|
|
74
|
+
{html_output}
|
|
55
75
|
</div>
|
|
56
76
|
</body>
|
|
57
77
|
</html>"""
|
machineconfig/utils/path.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from machineconfig.utils.path_reduced import PathExtended as PathExtended
|
|
2
2
|
from machineconfig.utils.options import choose_one_option
|
|
3
|
+
from machineconfig.utils.source_of_truth import EXCLUDE_DIRS
|
|
3
4
|
from rich.console import Console
|
|
4
5
|
from rich.panel import Panel
|
|
5
6
|
import platform
|
|
@@ -54,7 +55,7 @@ def find_scripts(root: Path, name_substring: str, suffixes: set[str]) -> tuple[l
|
|
|
54
55
|
partial_path_matches = []
|
|
55
56
|
for entry in root.iterdir():
|
|
56
57
|
if entry.is_dir():
|
|
57
|
-
if entry.name in
|
|
58
|
+
if entry.name in set(EXCLUDE_DIRS):
|
|
58
59
|
# prune this entire subtree
|
|
59
60
|
continue
|
|
60
61
|
tmp1, tmp2 = find_scripts(entry, name_substring, suffixes)
|
machineconfig/utils/procs.py
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import psutil
|
|
4
4
|
from rich.progress import Progress, SpinnerColumn, TextColumn
|
|
5
|
-
from
|
|
5
|
+
from zoneinfo import ZoneInfo
|
|
6
6
|
from machineconfig.utils.options import display_options
|
|
7
7
|
from typing import Optional, Any
|
|
8
8
|
from rich.console import Console
|
|
9
9
|
from rich.panel import Panel
|
|
10
|
-
from datetime import datetime
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
11
|
from machineconfig.utils.utils2 import pprint
|
|
12
12
|
|
|
13
13
|
console = Console()
|
|
@@ -72,8 +72,8 @@ class ProcessManager:
|
|
|
72
72
|
try:
|
|
73
73
|
mem_usage_mb = proc.memory_info().rss / (1024 * 1024)
|
|
74
74
|
# Convert create_time to local timezone
|
|
75
|
-
create_time_utc = datetime.fromtimestamp(proc.create_time(), tz=timezone
|
|
76
|
-
create_time_local = create_time_utc.astimezone(
|
|
75
|
+
create_time_utc = datetime.fromtimestamp(proc.create_time(), tz=timezone.utc)
|
|
76
|
+
create_time_local = create_time_utc.astimezone(ZoneInfo("Australia/Adelaide"))
|
|
77
77
|
|
|
78
78
|
process_info.append(
|
|
79
79
|
{
|
|
@@ -222,13 +222,13 @@ def get_age(create_time: Any) -> str:
|
|
|
222
222
|
try:
|
|
223
223
|
if isinstance(create_time, (int, float)):
|
|
224
224
|
# Handle timestampz
|
|
225
|
-
create_time_utc = datetime.fromtimestamp(create_time, tz=timezone
|
|
226
|
-
create_time_local = create_time_utc.astimezone(
|
|
225
|
+
create_time_utc = datetime.fromtimestamp(create_time, tz=timezone.utc)
|
|
226
|
+
create_time_local = create_time_utc.astimezone(ZoneInfo("Australia/Adelaide"))
|
|
227
227
|
else:
|
|
228
228
|
# Already a datetime object
|
|
229
229
|
create_time_local = create_time
|
|
230
230
|
|
|
231
|
-
now_local = datetime.now(tz=
|
|
231
|
+
now_local = datetime.now(tz=ZoneInfo("Australia/Adelaide"))
|
|
232
232
|
age = now_local - create_time_local
|
|
233
233
|
return str(age)
|
|
234
234
|
except Exception as e:
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"""TypedDict definitions for fire_agents.py inputs.
|
|
2
|
+
|
|
3
|
+
This module defines the structured input types for the fire_agents main function,
|
|
4
|
+
capturing all user inputs collected during interactive execution.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import TypedDict, Literal, NotRequired
|
|
9
|
+
from machineconfig.scripts.python.fire_agents_load_balancer import SPLITTING_STRATEGY
|
|
10
|
+
from machineconfig.scripts.python.fire_agents_help_launch import AGENTS
|
|
11
|
+
|
|
12
|
+
SEARCH_STRATEGIES = Literal["file_path", "keyword_search", "filename_pattern"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class FilePathSearchInput(TypedDict):
|
|
16
|
+
"""Input for file_path search strategy."""
|
|
17
|
+
file_path: str
|
|
18
|
+
separator: str # Default: "\n"
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class KeywordSearchInput(TypedDict):
|
|
22
|
+
"""Input for keyword_search strategy."""
|
|
23
|
+
keyword: str
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class FilenamePatternSearchInput(TypedDict):
|
|
27
|
+
"""Input for filename_pattern search strategy."""
|
|
28
|
+
pattern: str # e.g., '*.py', '*test*', 'config.*'
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class AgentCapSplittingInput(TypedDict):
|
|
32
|
+
"""Input for agent_cap splitting strategy."""
|
|
33
|
+
agent_cap: int # Default: 6
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TaskRowsSplittingInput(TypedDict):
|
|
37
|
+
"""Input for task_rows splitting strategy."""
|
|
38
|
+
task_rows: int # Default: 13
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class FireAgentsMainInput(TypedDict):
|
|
42
|
+
"""Complete input structure for fire_agents main function."""
|
|
43
|
+
|
|
44
|
+
# Core configuration
|
|
45
|
+
repo_root: Path
|
|
46
|
+
search_strategy: SEARCH_STRATEGIES
|
|
47
|
+
splitting_strategy: SPLITTING_STRATEGY
|
|
48
|
+
agent_selected: AGENTS
|
|
49
|
+
prompt_prefix: str
|
|
50
|
+
job_name: str # Default: "AI_Agents"
|
|
51
|
+
keep_material_in_separate_file: bool # Default: False
|
|
52
|
+
max_agents: int # Default: 25
|
|
53
|
+
|
|
54
|
+
# Search strategy specific inputs (only one will be present based on search_strategy)
|
|
55
|
+
file_path_input: NotRequired[FilePathSearchInput]
|
|
56
|
+
keyword_search_input: NotRequired[KeywordSearchInput]
|
|
57
|
+
filename_pattern_input: NotRequired[FilenamePatternSearchInput]
|
|
58
|
+
|
|
59
|
+
# Splitting strategy specific inputs (only one will be present based on splitting_strategy)
|
|
60
|
+
agent_cap_input: NotRequired[AgentCapSplittingInput]
|
|
61
|
+
task_rows_input: NotRequired[TaskRowsSplittingInput]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class FireAgentsRuntimeData(TypedDict):
|
|
65
|
+
"""Runtime data derived from inputs during execution."""
|
|
66
|
+
|
|
67
|
+
prompt_material: str
|
|
68
|
+
separator: str
|
|
69
|
+
prompt_material_re_splitted: list[str]
|
|
70
|
+
random_name: str # 3-character random string for session naming
|
|
@@ -5,6 +5,8 @@ Utils
|
|
|
5
5
|
import machineconfig
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
|
|
8
|
+
EXCLUDE_DIRS = [".links", ".ai", ".scripts", ".venv", ".git", ".idea", ".vscode", "node_modules", "__pycache__", ".mypy_cache"]
|
|
9
|
+
|
|
8
10
|
LIBRARY_ROOT = Path(machineconfig.__file__).resolve().parent
|
|
9
11
|
REPO_ROOT = LIBRARY_ROOT.parent.parent
|
|
10
12
|
|
machineconfig/utils/ve.py
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
|
|
1
2
|
from machineconfig.utils.path_reduced import PathExtended as PathExtended
|
|
2
3
|
from machineconfig.utils.utils2 import read_ini
|
|
3
4
|
import platform
|
|
4
5
|
from typing import Optional
|
|
6
|
+
from pathlib import Path
|
|
5
7
|
|
|
6
8
|
|
|
7
9
|
def get_ve_path_and_ipython_profile(init_path: PathExtended) -> tuple[Optional[str], Optional[str]]:
|
|
@@ -38,15 +40,17 @@ def get_ve_path_and_ipython_profile(init_path: PathExtended) -> tuple[Optional[s
|
|
|
38
40
|
return ve_path, ipy_profile
|
|
39
41
|
|
|
40
42
|
|
|
41
|
-
def get_repo_root(
|
|
43
|
+
def get_repo_root(path: Path) -> Optional[Path]:
|
|
42
44
|
from git import Repo, InvalidGitRepositoryError
|
|
43
45
|
|
|
44
46
|
try:
|
|
45
|
-
repo = Repo(
|
|
46
|
-
|
|
47
|
+
repo = Repo(str(path), search_parent_directories=True)
|
|
48
|
+
root = repo.working_tree_dir
|
|
49
|
+
if root is not None:
|
|
50
|
+
return Path(root)
|
|
47
51
|
except InvalidGitRepositoryError:
|
|
48
|
-
|
|
49
|
-
return
|
|
52
|
+
pass
|
|
53
|
+
return None
|
|
50
54
|
|
|
51
55
|
|
|
52
56
|
def get_ve_activate_line(ve_root: str):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: machineconfig
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.94
|
|
4
4
|
Summary: Dotfiles management package
|
|
5
5
|
Author-email: Alex Al-Saffar <programmer@usa.com>
|
|
6
6
|
License: Apache 2.0
|
|
@@ -13,21 +13,18 @@ Requires-Python: >=3.13
|
|
|
13
13
|
Description-Content-Type: text/markdown
|
|
14
14
|
Requires-Dist: cryptography>=44.0.2
|
|
15
15
|
Requires-Dist: fire>=0.7.0
|
|
16
|
-
Requires-Dist: gitpython>=3.1.44
|
|
17
16
|
Requires-Dist: joblib>=1.5.2
|
|
18
|
-
Requires-Dist: markdown>=3.9
|
|
19
17
|
Requires-Dist: paramiko>=3.5.1
|
|
20
|
-
Requires-Dist: psutil>=7.0.0
|
|
21
|
-
Requires-Dist: pydantic>=2.11.3
|
|
22
|
-
Requires-Dist: pyfzf>=0.3.1
|
|
23
|
-
Requires-Dist: pyjson5>=1.6.9
|
|
24
|
-
Requires-Dist: pytz>=2025.2
|
|
25
|
-
Requires-Dist: pyyaml>=6.0.2
|
|
26
18
|
Requires-Dist: randomname>=0.2.1
|
|
27
|
-
Requires-Dist: rclone-python>=0.1.23
|
|
28
19
|
Requires-Dist: requests>=2.32.5
|
|
29
20
|
Requires-Dist: rich>=14.0.0
|
|
30
21
|
Requires-Dist: tenacity>=9.1.2
|
|
22
|
+
Requires-Dist: psutil>=7.0.0
|
|
23
|
+
Requires-Dist: gitpython>=3.1.44
|
|
24
|
+
Requires-Dist: pyfzf>=0.3.1
|
|
25
|
+
Requires-Dist: rclone-python>=0.1.23
|
|
26
|
+
Requires-Dist: pyjson5>=1.6.9
|
|
27
|
+
Requires-Dist: pyyaml>=6.0.2
|
|
31
28
|
Requires-Dist: toml>=0.10.2
|
|
32
29
|
Requires-Dist: tomli>=2.2.1
|
|
33
30
|
Provides-Extra: windows
|