#!/usr/bin/env python3 import json import os import shutil import stat import subprocess import tempfile import threading import time import sys from queue import Queue class Colors: RED = '\033[0;31m' GREEN = '\033[0;32m' YELLOW = '\033[1;33m' BLUE = '\033[0;34m' RESET = '\033[0m' class ParallelTaskRunner: def __init__(self): self.tasks = [] self.results = Queue() self.task_positions = {} self.lock = threading.Lock() def add_task(self, name, command=None, action=None): """Add a task to the execution queue.""" if command is None and action is None: raise ValueError("Task must define a command or action") self.tasks.append({'name': name, 'command': command, 'action': action}) def _update_task_line(self, task_id, text): """Update a specific task's display line with thread-safe cursor movement.""" with self.lock: if task_id in self.task_positions: lines_up = len(self.task_positions) - self.task_positions[task_id] print(f"\033[{lines_up}A\r{text}\033[K\033[{lines_up}B", end="", flush=True) else: self.task_positions[task_id] = len(self.task_positions) print(text, flush=True) def _execute_task(self, task): """Execute a single task with real-time status updates.""" name = task['name'] command = task.get('command') action = task.get('action') task_id = threading.get_ident() self._update_task_line(task_id, f"{Colors.YELLOW}[STARTING]{Colors.RESET} {name}") if command is not None: try: process = subprocess.Popen( command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True ) # Animated progress indicator spinner_chars = "|/-\\" spinner_idx = 0 while process.poll() is None: spinner = f"{Colors.BLUE}[RUNNING]{Colors.RESET} {name} [{spinner_chars[spinner_idx]}]" self._update_task_line(task_id, spinner) spinner_idx = (spinner_idx + 1) % len(spinner_chars) time.sleep(0.1) stdout, _ = process.communicate() if process.returncode == 0: self._update_task_line(task_id, f"{Colors.GREEN}[COMPLETED]{Colors.RESET} {name}") self.results.put({'name': name, 'success': True, 'output': stdout}) else: self._update_task_line(task_id, f"{Colors.RED}[FAILED]{Colors.RESET} {name}") if stdout: with self.lock: print(f"\nError output for {name}:\n{stdout}") self.results.put({'name': name, 'success': False, 'output': stdout}) except Exception as e: self._update_task_line(task_id, f"{Colors.RED}[FAILED]{Colors.RESET} {name}") with self.lock: print(f"\nException for {name}: {e}") self.results.put({'name': name, 'success': False, 'output': str(e)}) return if action is None: return # Animated progress for callable actions spinner_chars = "|/-\\" spinner_idx = 0 result_container = {} exception_container = {} done_event = threading.Event() def run_action(): try: result_container['result'] = action() except Exception as exc: exception_container['exception'] = exc finally: done_event.set() worker = threading.Thread(target=run_action) worker.start() while not done_event.is_set(): spinner = f"{Colors.BLUE}[RUNNING]{Colors.RESET} {name} [{spinner_chars[spinner_idx]}]" self._update_task_line(task_id, spinner) spinner_idx = (spinner_idx + 1) % len(spinner_chars) time.sleep(0.1) worker.join() if exception_container: exception = exception_container['exception'] self._update_task_line(task_id, f"{Colors.RED}[FAILED]{Colors.RESET} {name}") with self.lock: print(f"\nException for {name}: {exception}") self.results.put({'name': name, 'success': False, 'output': str(exception)}) return result = result_container.get('result') success = True note = '' output = '' if isinstance(result, tuple): if len(result) == 3: success, note, output = result elif len(result) == 2: success, output = result elif isinstance(result, dict): success = bool(result.get('success', True)) note = result.get('note', '') output = result.get('output', '') elif isinstance(result, bool): success = result elif result is not None: output = str(result) if success: final_text = f"{Colors.GREEN}[COMPLETED]{Colors.RESET} {name}" if note: final_text += f" {note}" self._update_task_line(task_id, final_text) self.results.put({'name': name, 'success': True, 'output': output}) else: self._update_task_line(task_id, f"{Colors.RED}[FAILED]{Colors.RESET} {name}") if output: with self.lock: print(f"\nError output for {name}:\n{output}") self.results.put({'name': name, 'success': False, 'output': output}) def run_all(self): """Execute all tasks in parallel and return overall success status.""" print("Starting parallel upgrade tasks...\n") # Start all tasks threads = [ threading.Thread(target=self._execute_task, args=(task,)) for task in self.tasks ] for thread in threads: thread.start() for thread in threads: thread.join() # Display summary print(f"\n{'=' * 47}") # Count results properly all_results = [] while not self.results.empty(): all_results.append(self.results.get()) failed_tasks = [result for result in all_results if not result['success']] if not failed_tasks: print(f"{Colors.GREEN}All upgrade tasks completed successfully!{Colors.RESET}") return True else: print(f"{Colors.RED}FAILED TASKS:{Colors.RESET}") for task in failed_tasks: print(f"\n{Colors.RED}• {task['name']}{Colors.RESET}") if task['output']: # Print first 10 lines of error output output_lines = task['output'].strip().split('\n') error_lines = output_lines[:10] for line in error_lines: print(f" {line}") if len(output_lines) > 10: print(f" ... ({len(output_lines) - 10} more lines)") print(f"\n{Colors.RED}Total failed: {len(failed_tasks)} out of {len(all_results)} tasks{Colors.RESET}") return False NPM_PACKAGE_LOCKS = { # "@openai/codex": "0.40.0", } def ensure_homebrew(): if shutil.which("brew") is not None: return True, "(found)", "" candidate_paths = [ "/opt/homebrew/bin/brew", "/usr/local/bin/brew", "/home/linuxbrew/.linuxbrew/bin/brew", os.path.expanduser("~/.linuxbrew/bin/brew"), ] def configure_env(brew_path): env_result = subprocess.run( [brew_path, "shellenv"], capture_output=True, text=True ) for line in env_result.stdout.splitlines(): if not line.startswith("export "): continue key_val = line[len("export "):] if "=" not in key_val: continue key, val = key_val.split("=", 1) val = val.strip().strip('"') if "${PATH" in val or "$PATH" in val: current_path = os.environ.get("PATH", "") val = val.replace("${PATH+:$PATH}", f":{current_path}" if current_path else "") val = val.replace("$PATH", current_path) os.environ[key] = val if shutil.which("brew") is None: os.environ["PATH"] = f"{os.path.dirname(brew_path)}:{os.environ.get('PATH','')}" for path in candidate_paths: if os.path.exists(path): configure_env(path) return True, "(found)", "" install_output_parts = [] script_url = "https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh" try: with tempfile.NamedTemporaryFile(delete=False) as tmp: tmp_path = tmp.name fetch = subprocess.run( ["curl", "-fsSL", script_url, "-o", tmp_path], capture_output=True, text=True, ) install_output_parts.append(fetch.stdout or "") install_output_parts.append(fetch.stderr or "") if fetch.returncode != 0: os.unlink(tmp_path) return False, "", "".join(install_output_parts) env = os.environ.copy() env["NONINTERACTIVE"] = "1" run_install = subprocess.run( ["/bin/bash", tmp_path], capture_output=True, text=True, env=env, ) install_output_parts.append(run_install.stdout or "") install_output_parts.append(run_install.stderr or "") os.unlink(tmp_path) if run_install.returncode != 0: return False, "", "".join(install_output_parts) except Exception as exc: return False, "", f"{''.join(install_output_parts)}\n{exc}" for path in candidate_paths: if os.path.exists(path): configure_env(path) return True, "(installed)", "".join(install_output_parts) return False, "", "".join(install_output_parts) def ensure_git_available(): if shutil.which("git") is not None: return True, "(found)", "" result = subprocess.run( ["brew", "install", "git"], capture_output=True, text=True, ) output = f"{result.stdout or ''}{result.stderr or ''}" if result.returncode == 0: return True, "(installed)", output return False, "", output def ensure_npm_available(): def npm_works(): path_now = shutil.which("npm") if path_now is None: return False, "npm missing" check = subprocess.run(["npm", "--version"], capture_output=True, text=True) if check.returncode != 0: return False, f"npm --version failed: {check.stdout}{check.stderr}" return True, check.stdout.strip() ok, detail = npm_works() if ok: return True, f"(found {detail})", "" install = subprocess.run(["brew", "install", "node"], capture_output=True, text=True) output_parts = [install.stdout or '', install.stderr or ''] ok, detail = npm_works() if ok: return True, "(installed node)", ''.join(output_parts) reinstall = subprocess.run(["brew", "reinstall", "node"], capture_output=True, text=True) output_parts.extend([reinstall.stdout or '', reinstall.stderr or '']) ok, detail = npm_works() if ok: return True, "(reinstalled node)", ''.join(output_parts) return False, "", ''.join(output_parts) + detail def ensure_github_known_host(): ssh_dir = os.path.expanduser("~/.ssh") known_hosts = os.path.join(ssh_dir, "known_hosts") os.makedirs(ssh_dir, exist_ok=True) check = subprocess.run( ["ssh-keygen", "-F", "github.com"], capture_output=True, text=True, ) if check.returncode == 0 and check.stdout: return True scan = subprocess.run( ["ssh-keyscan", "-H", "github.com"], capture_output=True, text=True, ) if scan.returncode != 0: return False with open(known_hosts, "a", encoding="utf-8") as f: f.write(scan.stdout) return True def check_github_ssh(): if not ensure_github_known_host(): return False, "", "failed to add github.com host key" cmd = ["ssh", "-o", "BatchMode=yes", "-T", "git@github.com"] result = subprocess.run(cmd, capture_output=True, text=True) output = f"{result.stdout or ''}{result.stderr or ''}" ok_phrase = "successfully authenticated" if result.returncode in (0, 1) and ok_phrase in output: return True, "(auth ok)", output if "Permission denied" in output: return False, "", "GitHub SSH key rejected (Permission denied)" if "Could not resolve hostname" in output: return False, "", "Cannot reach github.com" return False, "", output def install_brew_packages(): """Install missing brew packages.""" print("📦 Checking brew packages...") # Get list of installed packages try: result = subprocess.run(['brew', 'list', '--formula', '-1'], capture_output=True, text=True, check=True) installed_packages = result.stdout.strip() except subprocess.CalledProcessError: print("❌ Failed to get brew package list") return False base_packages = [ # System utilities "htop", "dust", "ncdu", "fswatch", "pipx", "uv", # macOS GNU utilities / common core tools "coreutils", "gnu-tar", "gnu-getopt", "gnu-sed", # Development tools "node", "go", "gcc", "gdb", "automake", "cmake", "jsdoc3", "oven-sh/bun/bun", # Text processing and search "ripgrep", "the_silver_searcher", "fd", "fzf", "bat", "ccat", "tree", "jq", "yq", # File management "yazi", "sevenzip", # Git and version control "git", "git-delta", "git-flow", "jesseduffield/lazygit/lazygit", "gh", # Media and graphics "ffmpeg", "imagemagick", "poppler", "yt-dlp", # Network and web "wget", "speedtest-cli", "cliproxyapi", # Terminal and shell "tmux", "neovim", "starship", "rainbarf", "neofetch", "onefetch", "tldr", "bmon", "loc", # Applications "awscli", "azure-cli", "hashicorp/tap/terraform", ] mac_only = ["terminal-notifier"] linux_skip = ["oven-sh/bun/bun"] system = os.uname().sysname packages = base_packages + (mac_only if system == "Darwin" else []) if system == "Linux": packages = [p for p in packages if p not in linux_skip] missing_packages = [] installed_list = installed_packages.split('\n') for package in packages: # Handle tap packages (contains /) if "/" in package: package_name = package.split("/")[-1] # Extract package name after last / if package_name not in installed_list: missing_packages.append(package) else: if package not in installed_list: missing_packages.append(package) failed = [] if missing_packages: print(f"Installing {len(missing_packages)} missing packages...") for package in missing_packages: try: if package.startswith("hashicorp/tap/"): subprocess.run( ['brew', 'tap', 'hashicorp/tap'], capture_output=True, text=True, check=True ) subprocess.run(['brew', 'install', package], check=True) print(f"✅ Installed {package}") except subprocess.CalledProcessError: print(f"❌ Failed to install {package}") failed.append(package) if failed: print(f"❌ Failed packages: {', '.join(failed)}") return False return True else: print("✅ All packages already installed") return True def get_brew_outdated(): result = subprocess.run( ['brew', 'outdated', '--greedy', '--verbose', '--json=v2'], capture_output=True, text=True ) stdout = result.stdout.strip() if not stdout: return [] try: data = json.loads(stdout) except json.JSONDecodeError: return [] upgrades = [] for formula in data.get('formulae') or []: name = formula.get('name') installed = formula.get('installed_versions') or [] current = formula.get('current_version') if name and installed and current: from_version = installed[-1] upgrades.append(f"{name} {from_version}->{current}") for cask in data.get('casks') or []: name = cask.get('name') installed = cask.get('installed_versions') or [] current = cask.get('current_version') if isinstance(name, list): name = name[0] if name else None if name and installed and current: from_version = installed[-1] upgrades.append(f"{name} {from_version}->{current}") return upgrades def create_brew_update_action(): def action(): if shutil.which('brew') is None: return False, "", "brew not found" before = get_brew_outdated() output_parts = [] update_result = subprocess.run( ['brew', 'update'], capture_output=True, text=True ) output_parts.extend([update_result.stdout or "", update_result.stderr or ""]) if update_result.returncode != 0: return False, "", "".join(output_parts) upgrade_result = subprocess.run( ['brew', 'upgrade', '--greedy'], capture_output=True, text=True ) output_parts.extend([upgrade_result.stdout or "", upgrade_result.stderr or ""]) if upgrade_result.returncode != 0: return False, "", "".join(output_parts) after = get_brew_outdated() note = "" if before: before_names = {entry.split()[0] for entry in before if entry.split()} after_names = {entry.split()[0] for entry in after if entry.split()} if after else set() updated_entries = [entry for entry in before if entry.split() and entry.split()[0] not in after_names] if updated_entries: if len(updated_entries) > 6: shown = ", ".join(updated_entries[:6]) note = f"(updated: {shown}, +{len(updated_entries) - 6} more)" else: note = f"(updated: {', '.join(updated_entries)})" else: note = "(brew already up to date)" else: note = "(brew already up to date)" return True, note, "".join(output_parts) return action def get_npm_updates(packages, locks=None): packages = list(dict.fromkeys(packages)) locks = locks or {} list_result = subprocess.run( ['npm', 'list', '-g', '--json', '--depth=0'], capture_output=True, text=True ) installed_versions = {} if list_result.stdout.strip(): try: data = json.loads(list_result.stdout) dependencies = data.get('dependencies', {}) if isinstance(dependencies, dict): for name, info in dependencies.items(): if isinstance(info, dict): version = info.get('version') if version: installed_versions[name] = version except json.JSONDecodeError: pass # Only check "outdated" for packages that are not locked. unlocked_packages = [p for p in packages if p not in locks] outdated_info = {} if unlocked_packages: outdated_cmd = ['npm', 'outdated', '-g', '--json'] + unlocked_packages outdated_result = subprocess.run( outdated_cmd, capture_output=True, text=True ) if outdated_result.returncode not in (0, 1): raise subprocess.CalledProcessError( outdated_result.returncode, outdated_cmd, output=outdated_result.stdout, stderr=outdated_result.stderr ) stdout = outdated_result.stdout.strip() if stdout and stdout != 'null': try: data = json.loads(stdout) if isinstance(data, dict): outdated_info = data except json.JSONDecodeError: pass to_install = [] updates = [] installs = [] for package in packages: lock_version = locks.get(package) if lock_version: installed = installed_versions.get(package) if installed != lock_version: to_install.append(f"{package}@{lock_version}") if installed: updates.append(f"{package}@{lock_version}") else: installs.append(f"{package}@{lock_version}") # Skip normal outdated handling when locked continue if package in outdated_info: to_install.append(package) updates.append(package) continue if package not in installed_versions: to_install.append(package) installs.append(package) return to_install, updates, installs def format_package_for_install(package): return package if '@' in package[1:] else f"{package}@latest" def create_npm_update_action(packages, locks=None): packages = list(packages) locks = locks or {} def action(): to_install, updates, installs = get_npm_updates(packages, locks=locks) if not to_install: return True, "(up to date)", "" install_args = ['npm', 'install', '-g', '-f'] + [format_package_for_install(pkg) for pkg in to_install] try: result = subprocess.run( install_args, capture_output=True, text=True, check=True ) note_parts = [] if updates: note_parts.append(f"updated: {', '.join(updates)}") if installs: note_parts.append(f"installed: {', '.join(installs)}") note = f"({'; '.join(note_parts)})" if note_parts else '' output_parts = [part for part in [result.stdout, result.stderr] if part] return True, note, "".join(output_parts) except subprocess.CalledProcessError as err: output_parts = [part for part in [err.stdout, err.stderr] if part] output = "".join(output_parts) or str(err) return False, "", output return action def schedule_npm_updates(runner): if shutil.which('npm') is None: print(f"{Colors.RED}❌ npm not found; skipping npm package checks{Colors.RESET}") return packages = [ "@anthropic-ai/claude-code", "ccusage", "@ccusage/codex", "ccstatusline", "@openai/codex", "instant-markdown-d", "mcp-proxy", "opencode-ai", "yarn", "@google/gemini-cli", ] runner.add_task("Node Apps Update", action=create_npm_update_action(packages, locks=NPM_PACKAGE_LOCKS)) def interpret_action_result(result): success = True note = '' output = '' if isinstance(result, tuple): if len(result) == 3: success, note, output = result elif len(result) == 2: success, output = result elif isinstance(result, dict): success = bool(result.get('success', True)) note = result.get('note', '') output = result.get('output', '') elif isinstance(result, bool): success = result elif result is not None: output = str(result) return bool(success), note, output def ensure_symlink(target, link_name): target = os.path.expanduser(target) link_name = os.path.expanduser(link_name) if os.path.islink(link_name): if os.readlink(link_name) == target: return True, "(already linked)", "" os.remove(link_name) elif os.path.exists(link_name): backup = f"{link_name}.backup" while os.path.exists(backup): backup = f"{backup}.{int(time.time())}" os.rename(link_name, backup) os.symlink(target, link_name) return True, f"(linked to {target})", "" def create_symlink_action(target, link_name): def action(): return ensure_symlink(target, link_name) return action def ensure_repo(path, slug, must_exist=False): path = os.path.expanduser(path) repo_url = f"git@github.com:{slug}.git" def normalize(url): return url.rstrip("/").removesuffix(".git") if not os.path.exists(path): if must_exist: return False, "", f"{path} missing" os.makedirs(os.path.dirname(path), exist_ok=True) clone = subprocess.run( ["git", "clone", repo_url, path], capture_output=True, text=True, ) output = f"{clone.stdout or ''}{clone.stderr or ''}" if clone.returncode != 0: return False, "", output return True, "(cloned)", output git_dir = os.path.join(path, ".git") if not os.path.isdir(git_dir): return False, "", f"{path} exists but is not a git repo" remote = subprocess.run( ["git", "-C", path, "config", "--get", "remote.origin.url"], capture_output=True, text=True, ) remote_url = (remote.stdout or "").strip() if remote.returncode != 0: return False, "", f"cannot read remote for {path}" if normalize(remote_url) != normalize(repo_url): return False, "", f"remote mismatch: {remote_url}" fetch = subprocess.run( ["git", "-C", path, "fetch", "--all", "--prune"], capture_output=True, text=True, ) fetch_output = f"{fetch.stdout or ''}{fetch.stderr or ''}" if fetch.returncode != 0: return False, "", fetch_output pull = subprocess.run( ["git", "-C", path, "pull", "--ff-only"], capture_output=True, text=True, ) pull_output = f"{pull.stdout or ''}{pull.stderr or ''}" if pull.returncode != 0: return False, "", f"{fetch_output}{pull_output}" return True, "(updated)", f"{fetch_output}{pull_output}" def create_repo_action(path, slug, must_exist=False): def action(): return ensure_repo(path, slug, must_exist=must_exist) return action def is_writable(path): path = os.path.expanduser(path) try: test_file = os.path.join(path, ".writetest") with open(test_file, "w", encoding="utf-8") as f: f.write("ok") os.remove(test_file) return True except OSError: return False def ensure_paths_writable(paths): for p in paths: if not is_writable(p): return False, "", f"path not writable: {os.path.expanduser(p)}" return True, "(writable)", "" def ensure_system_build_tools(): if os.uname().sysname != "Linux": return True, "(not needed on macOS)", "" update = subprocess.run( ["sudo", "apt-get", "update", "-y"], capture_output=True, text=True, ) output_parts = [update.stdout or "", update.stderr or ""] if update.returncode != 0: return False, "", "".join(output_parts) install = subprocess.run( ["sudo", "apt-get", "install", "-y", "build-essential"], capture_output=True, text=True, ) output_parts.extend([install.stdout or "", install.stderr or ""]) if install.returncode != 0: return False, "", "".join(output_parts) return True, "(build-essential ok)", "".join(output_parts) def create_zshrc_action(): def action(): zshrc_path = os.path.expanduser("~/.zshrc") source_line = "source ~/.config/zsh/zshrc" if not os.path.exists(zshrc_path): with open(zshrc_path, "w", encoding="utf-8") as f: f.write(f"{source_line}\n") return True, "(created ~/.zshrc)", "" with open(zshrc_path, "r", encoding="utf-8") as f: lines = f.read().splitlines() if any(source_line in line for line in lines): return True, "(already sources config)", "" with open(zshrc_path, "a", encoding="utf-8") as f: if lines and lines[-1].strip() != "": f.write("\n") f.write(f"{source_line}\n") return True, "(added zsh source line)", "" return action def create_command_action(command): def action(): result = subprocess.run( command, capture_output=True, text=True, ) output = f"{result.stdout or ''}{result.stderr or ''}" return result.returncode == 0, "", output return action def create_agent_tracker_service_action(): def action(): env = os.environ.copy() env["HOMEBREW_NO_AUTO_UPDATE"] = "1" service = subprocess.run( ["bash", os.path.expanduser("~/.config/agent-tracker/scripts/install_brew_service.sh")], capture_output=True, text=True, env=env, ) output = f"{service.stdout or ''}{service.stderr or ''}" if service.returncode != 0: return False, "", output return True, "(service installed)", output return action def create_agent_tracker_build_action(): def action(): base = os.path.expanduser("~/.config/agent-tracker") if not os.path.isdir(base): return False, "", f"{base} missing" build = subprocess.run( ["bash", "-lc", "cd ~/.config/agent-tracker && ./install.sh"], capture_output=True, text=True, ) output = f"{build.stdout or ''}{build.stderr or ''}" if build.returncode != 0: return False, "", output binary = os.path.join(base, "bin", "tracker-server") if not os.path.isfile(binary): return False, "", f"tracker-server still missing after build ({binary})" return True, "(built)", output return action def run_inline(name, action): success, note, output = interpret_action_result(action()) prefix = f"{Colors.GREEN}[OK]{Colors.RESET}" if success else f"{Colors.RED}[FAIL]{Colors.RESET}" note_part = f" {note}" if note else "" print(f"{prefix} {name}{note_part}") if not success and output: lines = output.strip().splitlines() for line in lines[:10]: print(f" {line}") if len(lines) > 10: print(f" ... ({len(lines) - 10} more lines)") return success def main(): runner = ParallelTaskRunner() pre_steps = [ ("Homebrew Available", ensure_homebrew), ("Git Available", ensure_git_available), ("GitHub SSH Auth", check_github_ssh), ("Paths Writable", lambda: ensure_paths_writable(["~", "~/.config", "~/.config/bin"])), ("System Build Tools", ensure_system_build_tools), ("NPM Available", ensure_npm_available), ("Repo ~/.config", create_repo_action("~/.config", "theniceboy/.config", must_exist=True)), ("Repo ~/.config/nvim", create_repo_action("~/.config/nvim", "theniceboy/nvim")), ("Repo ~/.sconfig", create_repo_action("~/.sconfig", "theniceboy/.sconfig")), ("Repo ~/Github/mac-ctrl", create_repo_action("~/Github/mac-ctrl", "theniceboy/mac-ctrl")), ] for name, action in pre_steps: if not run_inline(name, action): sys.exit(1) # Install missing brew packages first if not install_brew_packages(): print("❌ Brew package installation failed") sys.exit(1) env_steps = [ ("Zsh Config Ensure", create_zshrc_action()), ("Symlink Tmux Config", create_symlink_action("~/.config/.tmux.conf", "~/.tmux.conf")), ("Symlink Claude Config", create_symlink_action("~/.config/claude", "~/.claude")), ("Symlink Codex Config", create_symlink_action("~/.config/codex", "~/.codex")), ("Symlink Ripgrep Config", create_symlink_action("~/.config/rg/ripgreprc", "~/.ripgreprc")), ("Agent Tracker Build", create_agent_tracker_build_action()), ("Agent Tracker Brew Service", create_agent_tracker_service_action()), ] for name, action in env_steps: if not run_inline(name, action): sys.exit(1) schedule_npm_updates(runner) runner.add_task("Homebrew Update", action=create_brew_update_action()) runner.add_task("Agent Tracker Build", "cd ~/.config/agent-tracker && ./install.sh") success = runner.run_all() sys.exit(0 if success else 1) if __name__ == "__main__": main()