Ports Minas's PR #172 (against pre-rename `da` CLI on main) and applies the principle to the post-rename `agnes` CLI. Two distinct failure modes on Windows consoles whose default codepage is cp1250 (cs-CZ) / cp1252 (en-US): 1. `agnes pull` and other Rich-progress codepaths UnicodeEncodeError on Braille spinner glyphs. Fix: `cli/main.py` reconfigures stdout/stderr to UTF-8 with errors='replace' at import time on `sys.platform == 'win32'` so Rich's legacy-Windows render path emits decodable bytes. Wrapped in try/except so pytest's captured streams (which aren't TextIOWrapper) don't break. 2. `agnes skills list` and `agnes skills show` UnicodeDecodeError when reading skill markdown containing em-dashes / accented chars. Default `Path.read_text()` uses locale.getpreferredencoding(False), which is the broken codepage on Windows. Fix: every call site passes encoding='utf-8' explicitly. Broader scope than #172 because: - The bootstrap rewrite renamed/removed several files Minas's PR patched (`cli/commands/analyst.py` -> rolled into init.py; `cli/commands/sync.py` -> split into pull/push). Those targets no longer exist; the equivalent code lives in init.py. - Other call sites Minas didn't touch (still bare in his branch) are patched here too — config.py / update_check.py / snapshot_meta.py / setup.py / skills.py — so the codebase has zero locale-default text I/O in cli/. Side cleanup: stale `Run `da`` reference in snapshot_meta.py:88 fixed to `agnes` while touching the file.
32 lines
1 KiB
Python
32 lines
1 KiB
Python
"""Skills command — agnes skills. Knowledge base for AI agents."""
|
|
|
|
from pathlib import Path
|
|
|
|
import typer
|
|
|
|
skills_app = typer.Typer(help="Built-in knowledge base for AI agents")
|
|
|
|
SKILLS_DIR = Path(__file__).parent.parent / "skills"
|
|
|
|
|
|
@skills_app.command("list")
|
|
def list_skills():
|
|
"""List available skills."""
|
|
if not SKILLS_DIR.exists():
|
|
typer.echo("No skills directory found.")
|
|
return
|
|
for f in sorted(SKILLS_DIR.glob("*.md")):
|
|
name = f.stem
|
|
# Read first line as description
|
|
first_line = f.read_text(encoding="utf-8").split("\n")[0].strip("# ").strip()
|
|
typer.echo(f" {name:25s} {first_line}")
|
|
|
|
|
|
@skills_app.command("show")
|
|
def show_skill(name: str = typer.Argument(..., help="Skill name to display")):
|
|
"""Display a skill's content."""
|
|
skill_file = SKILLS_DIR / f"{name}.md"
|
|
if not skill_file.exists():
|
|
typer.echo(f"Skill '{name}' not found. Run: agnes skills list", err=True)
|
|
raise typer.Exit(1)
|
|
typer.echo(skill_file.read_text(encoding="utf-8"))
|