diff --git a/hermes_cli/pets.py b/hermes_cli/pets.py index 5c2c703ea7..e5d10b3203 100644 --- a/hermes_cli/pets.py +++ b/hermes_cli/pets.py @@ -16,7 +16,7 @@ def _cmd_list(args) -> int: """List gallery pets (or only installed ones with ``--installed``).""" from agent.pet import store - if getattr(args, "installed", False): + if args.installed: pets = store.installed_pets() if not pets: print("No pets installed. Try: hermes pets install boba") @@ -34,11 +34,11 @@ def _cmd_list(args) -> int: _err(f"✗ {exc}") return 1 - query = (getattr(args, "query", "") or "").strip().lower() + query = (args.query or "").strip().lower() if query: entries = [e for e in entries if query in e.slug.lower() or query in e.display_name.lower()] - limit = getattr(args, "limit", 0) or 0 + limit = args.limit or 0 shown = entries[:limit] if limit > 0 else entries installed = {p.slug for p in store.installed_pets()} @@ -58,14 +58,14 @@ def _cmd_install(args) -> int: slug = args.slug.strip() try: - pet = store.install_pet(slug, force=getattr(args, "force", False)) + pet = store.install_pet(slug, force=args.force) except (store.PetStoreError, ManifestError) as exc: _err(f"✗ install failed: {exc}") return 1 print(f"✓ installed {pet.display_name} → {pet.directory}") - if getattr(args, "select", False) or not _has_active_pet(): + if args.select or not _has_active_pet(): _set_active(slug) print(f"✓ {pet.display_name} is now the active pet (display.pet.slug={slug}, enabled)") else: @@ -87,7 +87,7 @@ def _cmd_remove(args) -> int: def _cmd_select(args) -> int: from agent.pet import store - slug = (getattr(args, "slug", "") or "").strip() + slug = (args.slug or "").strip() if not slug: pets = store.installed_pets() if not pets: @@ -124,12 +124,9 @@ def _cmd_scale(args) -> int: def _cmd_show(args) -> int: - """Animate the active (or named) pet in the terminal. - - Uses the shared :class:`~agent.pet.render.PetRenderer` — full graphics protocol - (kitty/iTerm2/sixel) when the terminal supports it, else a truecolor Unicode half-block - fallback. Ctrl+C to stop. - """ + """Animate the active (or named) pet in the terminal via the shared PetRenderer + (kitty/iTerm2/sixel when supported, else truecolor half-block fallback). Ctrl+C stops.""" + import shutil import time from agent.pet import store @@ -137,31 +134,26 @@ def _cmd_show(args) -> int: from agent.pet.render import build_renderer cfg = _pet_config() - slug = (getattr(args, "slug", "") or "").strip() or str(cfg.get("slug", "") or "") + slug = (args.slug or "").strip() or str(cfg.get("slug", "") or "") pet = store.resolve_active_pet(slug) if pet is None: _err("✗ no pet to show — run: hermes pets install boba") return 1 - mode_cfg = getattr(args, "mode", None) or str(cfg.get("render_mode", "auto") or "auto") - scale = float(getattr(args, "scale", 0) or cfg.get("scale", DEFAULT_SCALE) or DEFAULT_SCALE) + mode_cfg = args.mode or str(cfg.get("render_mode", "auto") or "auto") + scale = float(args.scale or cfg.get("scale", DEFAULT_SCALE) or DEFAULT_SCALE) cols = resolve_cols(scale, cfg.get("unicode_cols", 0)) - renderer = build_renderer( - pet.spritesheet, - configured_mode=mode_cfg, - scale=scale, - unicode_cols=cols, - ) + renderer = build_renderer(pet.spritesheet, configured_mode=mode_cfg, scale=scale, unicode_cols=cols) if not renderer.available: _err(f"✗ cannot render here (no TTY / graphics disabled). Effective mode: {renderer.mode}.") return 1 # Which states to play: one named state, or cycle the driveable rows. - requested = (getattr(args, "state", "") or "").strip().lower() + requested = (args.state or "").strip().lower() if requested: states = [requested] - elif getattr(args, "cycle", False): + elif args.cycle: states = [s for s in STATE_ROWS if s in {e.value for e in PetState}] else: states = [PetState.IDLE.value] @@ -169,11 +161,8 @@ def _cmd_show(args) -> int: is_unicode = renderer.mode == "unicode" frame_delay = max(0.05, (LOOP_MS / 1000.0) / max(1, renderer.frame_count(states[0]) or 1)) - # Right-align the sprite against the terminal's right edge — half-blocks by - # indenting each row, graphics protocols by padding the cursor to the right - # column before the image draws (kitty/iTerm/sixel all render at the cursor). - import shutil - + # Right-align against the terminal's right edge — half-blocks by indenting each row, graphics + # protocols by padding the cursor (kitty/iTerm/sixel all render at the cursor). term_cols = shutil.get_terminal_size((80, 24)).columns sprite_cols = cols if is_unicode else max(1, int(renderer.frame_w * renderer.scale) // 8) indent = " " * max(0, term_cols - sprite_cols - 1) @@ -187,8 +176,7 @@ def _cmd_show(args) -> int: loops = 0 while True: for state in states: - count = renderer.frame_count(state) or 1 - for i in range(count): + for i in range(renderer.frame_count(state) or 1): encoded = renderer.frame(state, i) if is_unicode: if indent: @@ -197,8 +185,7 @@ def _cmd_show(args) -> int: out.write(f"\x1b[{prev_lines}F") # cursor up to redraw in place out.write(encoded) out.write("\x1b[0m\n") - # Lines drawn = sprite rows + the trailing newline; move - # back up exactly that many so the next frame overwrites. + # Lines drawn = sprite rows + trailing newline; the next frame overwrites. prev_lines = encoded.count("\n") + 1 else: out.write("\x1b[2J\x1b[3J\x1b[H") # clear for image protocols @@ -207,7 +194,7 @@ def _cmd_show(args) -> int: out.flush() time.sleep(frame_delay) loops += 1 - if getattr(args, "once", False) and loops >= len(states): + if args.once and loops >= len(states): break except KeyboardInterrupt: pass @@ -261,9 +248,7 @@ def _cmd_doctor(args) -> int: return 0 -# ───────────────────────────────────────────────────────────────────────── -# config helpers -# ───────────────────────────────────────────────────────────────────────── +# ── config helpers ──────────────────────────────────────────────────────── def _pet_config() -> dict: from hermes_cli.config import load_config @@ -284,11 +269,10 @@ def _pet_enabled(cfg: dict) -> bool: def _update_pet_config(when_slug: str | None = None, **values) -> bool: - """Write ``display.pet.*`` keys and save config. + """Write ``display.pet.*`` keys and save config; returns whether anything was written. - With ``when_slug`` the write only happens when the currently configured slug equals it (used - by remove/rename so an inactive pet never disturbs the active one). Returns whether anything - was written. + With ``when_slug`` the write only happens when the configured slug equals it (remove/rename + must never disturb a different active pet). """ from hermes_cli.config import load_config, save_config @@ -312,12 +296,9 @@ def _set_enabled(enabled: bool) -> None: def set_pet_scale(value: float | str) -> tuple[float, str | None]: - """Set ``display.pet.scale`` (clamped to bounds). Returns ``(applied, error)``. - - The single write path behind ``/pet scale`` and the desktop slider, so every surface that - resolves scale from config picks it up identically. *error* is set (and nothing written) only - when *value* isn't a number. - """ + """Set ``display.pet.scale`` (clamped). Returns ``(applied, error)``; *error* is set (and + nothing written) only when *value* isn't a number. Single write path behind ``/pet scale`` + and the desktop slider.""" from agent.pet.constants import clamp_scale try: @@ -334,8 +315,7 @@ def toggle_pet_display() -> tuple[bool, str | None, str | None]: from agent.pet import store cfg = _pet_config() - slug = str(cfg.get("slug", "") or "") - pet = store.resolve_active_pet(slug) + pet = store.resolve_active_pet(str(cfg.get("slug", "") or "")) if _pet_enabled(cfg): _set_enabled(False) @@ -373,20 +353,13 @@ def print_pet_gallery(*, limit: int = 20) -> None: def _clear_active_if(slug: str) -> bool: - """Disable + unset the active pet iff it's ``slug`` (e.g. after removal). - - Returns whether anything changed, so callers don't write config needlessly. - """ + """Disable + unset the active pet iff it's ``slug`` (e.g. after removal).""" return _update_pet_config(when_slug=slug, slug="", enabled=False) def _rename_active_if(old_slug: str, new_slug: str) -> bool: - """Repoint the active pet from ``old_slug`` to ``new_slug`` iff it's active. - - Used when a rename realigns a pet's slug/dir: if the renamed pet was the active one, the config - must follow or surfaces point at a now-missing dir. Preserves the ``enabled`` flag. Returns - whether anything changed. - """ + """Repoint the active pet from ``old_slug`` to ``new_slug`` iff it's active (a rename moves + the dir; config must follow). Preserves ``enabled``.""" if not new_slug or old_slug == new_slug: return False return _update_pet_config(when_slug=old_slug, slug=new_slug) @@ -408,9 +381,7 @@ def _interactive_pick(pets) -> str: return "" -# ───────────────────────────────────────────────────────────────────────── -# argparse wiring -# ───────────────────────────────────────────────────────────────────────── +# ── argparse wiring ─────────────────────────────────────────────────────── # (name, help, handler, [((flags...), add_argument kwargs), ...]) — registration order is menu order. _SUBCOMMANDS = (