From 5264289b4f5bc1ccd56e6376baf82f11fccd428b Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Wed, 2 Sep 2026 20:47:59 -0700 Subject: [PATCH] =?UTF-8?q?refactor(hermes=5Fcli):=20profiles=20cluster=20?= =?UTF-8?q?=E2=80=94=20AST-neutral=20argument/collection=20packing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hermes_cli/profile_cmd.py | 45 +++++++----------------------- hermes_cli/profile_describer.py | 8 ++---- hermes_cli/profile_distribution.py | 45 +++++++++--------------------- hermes_cli/profiles.py | 43 ++++++++-------------------- 4 files changed, 36 insertions(+), 105 deletions(-) diff --git a/hermes_cli/profile_cmd.py b/hermes_cli/profile_cmd.py index f233533e18..32d9626149 100644 --- a/hermes_cli/profile_cmd.py +++ b/hermes_cli/profile_cmd.py @@ -157,13 +157,8 @@ def _profile_use(args): def _profile_create(args): from hermes_cli.profiles import ( - _get_wrapper_dir, - _is_wrapper_dir_in_path, - check_alias_collision, - create_profile, - create_wrapper_script, - get_active_profile_name, - seed_profile_skills, + _get_wrapper_dir, _is_wrapper_dir_in_path, check_alias_collision, create_profile, + create_wrapper_script, get_active_profile_name, seed_profile_skills, ) name = args.profile_name @@ -177,13 +172,8 @@ def _profile_create(args): try: profile_dir = create_profile( - name=name, - clone_from=clone_from, - clone_all=clone_all, - clone_config=clone_config, - no_alias=no_alias, - no_skills=no_skills, - description=getattr(args, "description", None), + name=name, clone_from=clone_from, clone_all=clone_all, clone_config=clone_config, + no_alias=no_alias, no_skills=no_skills, description=getattr(args, "description", None), ) except (ValueError, FileExistsError, FileNotFoundError) as e: _die(f"Error: {e}") @@ -339,17 +329,9 @@ def _profile_describe(args): def _profile_show(args): name = args.profile_name from hermes_cli.profiles import ( - get_profile_dir, - profile_exists, - _read_config_model, - _check_gateway_running, - _served_by_running_multiplexer, - _count_skills, - _read_distribution_meta, - _wrapper_path, - find_alias_for_profile, - format_profile_label, - read_profile_meta, + get_profile_dir, profile_exists, _read_config_model, _check_gateway_running, + _served_by_running_multiplexer, _count_skills, _read_distribution_meta, _wrapper_path, + find_alias_for_profile, format_profile_label, read_profile_meta, ) if not profile_exists(name): @@ -381,13 +363,8 @@ def _profile_show(args): def _profile_alias(args): from hermes_cli.profiles import ( - _get_wrapper_dir, - _is_wrapper_dir_in_path, - check_alias_collision, - create_wrapper_script, - profile_exists, - remove_wrapper_script, - validate_alias_name, + _get_wrapper_dir, _is_wrapper_dir_in_path, check_alias_collision, create_wrapper_script, + profile_exists, remove_wrapper_script, validate_alias_name, ) name = args.profile_name @@ -477,9 +454,7 @@ def _profile_install(args): return plan = install_distribution( - args.source, - name=getattr(args, "install_name", None), - force=getattr(args, "force", False), + args.source, name=getattr(args, "install_name", None), force=getattr(args, "force", False), create_alias=getattr(args, "alias", False), ) print(f"\nāœ“ Installed '{plan.manifest.name}' v{plan.manifest.version}") diff --git a/hermes_cli/profile_describer.py b/hermes_cli/profile_describer.py index e94dc5897f..f92b2813c0 100644 --- a/hermes_cli/profile_describer.py +++ b/hermes_cli/profile_describer.py @@ -152,12 +152,8 @@ def describe_profile( return DescribeOutcome(canon, False, "auxiliary client unavailable") user_msg = _USER_TEMPLATE.format( - name=canon, - model=(model or "(unset)"), - provider=(provider or "(unset)"), - skill_count=len(all_skills), - skill_cap=MAX_SKILLS_FOR_PROMPT, - skill_list=skill_list, + name=canon, model=(model or "(unset)"), provider=(provider or "(unset)"), skill_count=len(all_skills), + skill_cap=MAX_SKILLS_FOR_PROMPT, skill_list=skill_list, ) try: diff --git a/hermes_cli/profile_distribution.py b/hermes_cli/profile_distribution.py index d661f4f559..408f64311c 100644 --- a/hermes_cli/profile_distribution.py +++ b/hermes_cli/profile_distribution.py @@ -83,9 +83,7 @@ class EnvRequirement: if not name: raise DistributionError("env_requires entry missing 'name'") return cls( - name=name, - description=_str(data, "description"), - required=bool(data.get("required", True)), + name=name, description=_str(data, "description"), required=bool(data.get("required", True)), default=data.get("default"), ) @@ -129,29 +127,21 @@ class DistributionManifest: if dist_owned_raw and not isinstance(dist_owned_raw, list): raise DistributionError("distribution_owned must be a list") return cls( - name=name, - version=_str(data, "version", "0.1.0"), - description=_str(data, "description"), - hermes_requires=_str(data, "hermes_requires"), - author=_str(data, "author"), - license=_str(data, "license"), - env_requires=[EnvRequirement.from_dict(e) for e in env_raw], + name=name, version=_str(data, "version", "0.1.0"), description=_str(data, "description"), + hermes_requires=_str(data, "hermes_requires"), author=_str(data, "author"), + license=_str(data, "license"), env_requires=[EnvRequirement.from_dict(e) for e in env_raw], distribution_owned=[str(p).strip().strip("/") for p in dist_owned_raw if str(p).strip()], - source=_str(data, "source"), - installed_at=_str(data, "installed_at"), + source=_str(data, "source"), installed_at=_str(data, "installed_at"), ) def to_dict(self) -> Dict[str, Any]: out: Dict[str, Any] = {"name": self.name, "version": self.version} # Key order is the on-disk YAML order (write_manifest uses sort_keys=False). optional = ( - ("description", self.description), - ("hermes_requires", self.hermes_requires), - ("author", self.author), - ("license", self.license), + ("description", self.description), ("hermes_requires", self.hermes_requires), + ("author", self.author), ("license", self.license), ("env_requires", [e.to_dict() for e in self.env_requires]), - ("distribution_owned", self.distribution_owned), - ("source", self.source), + ("distribution_owned", self.distribution_owned), ("source", self.source), ("installed_at", self.installed_at), ) out.update((k, v) for k, v in optional if v) @@ -221,8 +211,7 @@ def _env_template_from_manifest(manifest: DistributionManifest) -> str: """Generate a ``.env.template`` body from env_requires.""" lines = [ "# Environment variables required by this Hermes distribution.", - "# Copy to `.env` and fill in your own values before running.", - "", + "# Copy to `.env` and fill in your own values before running.", "", ] for req in manifest.env_requires: if req.description: @@ -257,11 +246,8 @@ def _git_clone(url: str, dest: Path) -> None: url = f"https://{url.rstrip('/')}" try: subprocess.run( - ["git", "clone", "--depth", "1", url, str(dest)], - check=True, - capture_output=True, - stdin=subprocess.DEVNULL, - env=noninteractive_git_env(), + ["git", "clone", "--depth", "1", url, str(dest)], check=True, capture_output=True, + stdin=subprocess.DEVNULL, env=noninteractive_git_env(), ) except FileNotFoundError as exc: raise DistributionError("git is required for git-URL installs") from exc @@ -371,13 +357,8 @@ def plan_install( target_dir = get_profile_dir(canon) existing = target_dir.is_dir() return InstallPlan( - manifest=manifest, - staged_dir=staged, - provenance=provenance, - target_dir=target_dir, - existing=existing, - preserves_config=existing, - has_cron=_has_cron_jobs(staged), + manifest=manifest, staged_dir=staged, provenance=provenance, target_dir=target_dir, existing=existing, + preserves_config=existing, has_cron=_has_cron_jobs(staged), ) diff --git a/hermes_cli/profiles.py b/hermes_cli/profiles.py index 6026250eb9..7469901a4d 100644 --- a/hermes_cli/profiles.py +++ b/hermes_cli/profiles.py @@ -122,8 +122,7 @@ _RESERVED_NAMES = frozenset({"hermes", "default", "test", "tmp", "root", "sudo"} _HERMES_SUBCOMMANDS = frozenset({ "chat", "model", "gateway", "setup", "whatsapp", "login", "logout", "status", "cron", "doctor", "dump", "config", "pairing", "skills", "tools", - "mcp", "sessions", "insights", "version", "update", "uninstall", - "profile", "plugins", "honcho", "acp", + "mcp", "sessions", "insights", "version", "update", "uninstall", "profile", "plugins", "honcho", "acp", }) @@ -655,10 +654,7 @@ def read_profile_meta(profile_dir: Path) -> dict: def write_profile_meta( - profile_dir: Path, - *, - description: Optional[str] = None, - description_auto: Optional[bool] = None, + profile_dir: Path, *, description: Optional[str] = None, description_auto: Optional[bool] = None, display_name: Optional[str] = None, ) -> None: """Update ``profile.yaml`` in place: only passed fields are overwritten; the file is @@ -721,21 +717,11 @@ def _profile_info(name: str, path: Path, *, is_default: bool, alias_name: Option if not is_default: gateway_running = gateway_running or _served_by_running_multiplexer(name) return ProfileInfo( - name=name, - path=path, - is_default=is_default, - gateway_running=gateway_running, - model=model, - provider=provider, - has_env=(path / ".env").exists(), - skill_count=_count_skills(path), - alias_path=alias_path, - alias_name=alias_name, - distribution_name=dist_name, - distribution_version=dist_version, - distribution_source=dist_source, - description=meta.get("description", ""), - description_auto=meta.get("description_auto", False), + name=name, path=path, is_default=is_default, gateway_running=gateway_running, model=model, + provider=provider, has_env=(path / ".env").exists(), skill_count=_count_skills(path), + alias_path=alias_path, alias_name=alias_name, distribution_name=dist_name, + distribution_version=dist_version, distribution_source=dist_source, + description=meta.get("description", ""), description_auto=meta.get("description_auto", False), display_name=meta.get("display_name", ""), ) @@ -884,13 +870,8 @@ def _bootstrap_profile_dir(profile_dir: Path, source_dir: Optional[Path]) -> Non def create_profile( - name: str, - clone_from: Optional[str] = None, - clone_all: bool = False, - clone_config: bool = False, - no_alias: bool = False, - no_skills: bool = False, - description: Optional[str] = None, + name: str, clone_from: Optional[str] = None, clone_all: bool = False, clone_config: bool = False, + no_alias: bool = False, no_skills: bool = False, description: Optional[str] = None, ) -> Path: """Create a new profile directory and return its path. @@ -1590,10 +1571,8 @@ _EXPORT_CREDENTIAL_FILES = frozenset({"auth.json", ".env"}) # Text/config suffixes secret-scrubbed on export; binary DBs, images etc. are left alone. _EXPORT_REDACT_SUFFIXES = frozenset({ - ".md", ".txt", ".yaml", ".yml", ".json", ".jsonl", - ".toml", ".ini", ".cfg", ".conf", ".py", ".sh", - ".bash", ".zsh", ".js", ".ts", ".tsx", ".jsx", - ".css", ".html", ".xml", ".csv", + ".md", ".txt", ".yaml", ".yml", ".json", ".jsonl", ".toml", ".ini", ".cfg", ".conf", ".py", ".sh", + ".bash", ".zsh", ".js", ".ts", ".tsx", ".jsx", ".css", ".html", ".xml", ".csv", }) # ``Path(".cursorrules").suffix`` is "" — name-match; ``*.env.example`` uses endswith. _EXPORT_REDACT_NAMES = frozenset({".cursorrules"})