Files
timmy-config/scripts/skill_installer.py

119 lines
3.6 KiB
Python
Raw Normal View History

2026-04-08 11:40:40 +00:00
#!/usr/bin/env python3
"""
[OPS] Sovereign Skill Installer
Part of the Gemini Sovereign Infrastructure Suite.
Packages and installs Hermes skills onto remote wizard nodes.
"""
import os
import sys
import argparse
import subprocess
from pathlib import Path
#!/usr/bin/env python3
"""
[OPS] Sovereign Skill Installer
Part of the Gemini Sovereign Infrastructure Suite.
Packages and installs Hermes skills onto remote wizard nodes.
"""
import os
import sys
import argparse
import subprocess
from pathlib import Path
import yaml
2026-04-08 11:40:40 +00:00
# --- CONFIGURATION ---
# Load fleet inventory and path contracts from central timmy-config
def get_config_path():
return os.environ.get('TIMMY_CONFIG') or os.path.join(
os.path.dirname(os.path.abspath(__file__)), '..', 'config.yaml'
)
def load_fleet_inventory():
"""Return dict of {{host: {{ip, port, role, remote_root, capabilities}}}}"""
try:
with open(get_config_path(), 'r') as f:
cfg = yaml.safe_load(f)
inv = cfg.get('fleet', {}).get('inventory', {})
return inv if inv else {}
except Exception:
return {}
def get_path_contract(key, default):
"""Return path contract value from config."""
try:
with open(get_config_path(), 'r') as f:
cfg = yaml.safe_load(f)
return cfg.get('fleet', {}).get('path_contracts', {}).get(key, default)
except Exception:
return default
FLEET = load_fleet_inventory()
HERMES_ROOT = get_path_contract('hermes_agent_local', '../hermes-agent')
REMOTE_ROOT = get_path_contract('hermes_remote', '/opt/hermes')
2026-04-08 11:40:40 +00:00
SKILLS_DIR = "skills"
2026-04-08 11:40:40 +00:00
class SkillInstaller:
def __init__(self, host: str, ip: str):
self.host = host
self.ip = ip
self.hermes_path = Path(HERMES_ROOT).expanduser().resolve()
2026-04-08 11:40:40 +00:00
def log(self, message: str):
print(f"[*] {message}")
def error(self, message: str):
print(f"[!] ERROR: {message}")
sys.exit(1)
def install_skill(self, skill_name: str):
self.log(f"Installing skill '{skill_name}' to {self.host} ({self.ip})...")
skill_path = self.hermes_path / SKILLS_DIR / skill_name
if not skill_path.exists():
self.error(f"Skill '{skill_name}' not found in {skill_path}")
# 1. Compress skill
self.log("Compressing skill...")
tar_file = f"{skill_name}.tar.gz"
subprocess.run(["tar", "-czf", tar_file, "-C", str(skill_path.parent), skill_name])
# 2. Upload to remote
self.log("Uploading to remote...")
remote_path = f"{REMOTE_ROOT}/skills/{skill_name}"
subprocess.run(["ssh", f"root@{self.ip}", f"mkdir -p {REMOTE_ROOT}/skills"])
2026-04-08 11:40:40 +00:00
subprocess.run(["scp", tar_file, f"root@{self.ip}:/tmp/"])
# 3. Extract and register
self.log("Extracting and registering...")
extract_cmd = f"tar -xzf /tmp/{tar_file} -C {REMOTE_ROOT}/skills/ && rm /tmp/{tar_file}"
2026-04-08 11:40:40 +00:00
subprocess.run(["ssh", f"root@{self.ip}", extract_cmd])
# Registration logic (simplified)
# In a real scenario, we'd update the wizard's config.yaml
self.log(f"[SUCCESS] Skill '{skill_name}' installed on {self.host}")
# Cleanup local tar
os.remove(tar_file)
def main():
parser = argparse.ArgumentParser(description="Gemini Skill Installer")
parser.add_argument("host", help="Target host name")
parser.add_argument("ip", help="Target host IP")
parser.add_argument("skill", help="Skill name to install")
args = parser.parse_args()
installer = SkillInstaller(args.host, args.ip)
installer.install_skill(args.skill)
if __name__ == "__main__":
main()