From de984736ca116aae552c81f91a92030e565d42ef Mon Sep 17 00:00:00 2001 From: tegwick Date: Sat, 16 May 2026 23:21:37 +0200 Subject: [PATCH] feat(cli): add `bridge conventions` and link from actor errors Surfaces the actor naming rules (adm-/agt-/atm- prefixes, legacy class aliases) so users hitting a ConfigError have an in-CLI way to read the spec without grepping the wiki. Co-Authored-By: Claude Opus 4.7 --- src/bridge/cli.py | 32 ++++++++++++++++++++++++++++++++ src/bridge/config.py | 6 ++++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/bridge/cli.py b/src/bridge/cli.py index cc1ad11..6807b33 100644 --- a/src/bridge/cli.py +++ b/src/bridge/cli.py @@ -633,3 +633,35 @@ def catalog_show( if b.target in cat.targets: t = cat.targets[b.target] typer.echo(f"Target: {t.description or t.id} ({t.kind})") + + +_CONVENTIONS_TEXT = """\ +Actor Naming Conventions (from AccessManagementDirective.md ยง2) + +Every actor declared under `actors:` in ~/.config/bridge/tunnels.yaml must have +a `class` field, and the actor name must start with the class-specific prefix: + + class prefix purpose + ----- ------ ------------------------------------------------------------ + adm adm- Human operator (interactive shell when needed) + agt agt- LLM-powered autonomous agent (Claude Code, etc.) + atm atm- Deterministic script / cron job / pipeline + +Legacy class aliases (deprecated, still accepted with a warning): + human -> adm + automation -> atm + +Examples: + adm-bernd: { class: adm, description: Bernd Worsch } + agt-claude-coulombcore: { class: agt, description: Claude Code on CoulombCore } + atm-backup-daily: { class: atm, description: Nightly DB backup } + +Full specification: + /wiki/AccessManagementDirective.md +""" + + +@app.command() +def conventions(): + """Show the actor naming conventions enforced by tunnels.yaml.""" + typer.echo(_CONVENTIONS_TEXT) diff --git a/src/bridge/config.py b/src/bridge/config.py index 2a749a7..9283bf1 100644 --- a/src/bridge/config.py +++ b/src/bridge/config.py @@ -137,7 +137,8 @@ def _parse_actor_type(name: str, raw_class: str) -> ActorType: except ValueError: raise ConfigError( f"Actor '{name}' has unknown class '{raw_class}'; " - f"must be one of: adm, agt, atm (or legacy: human, automation)" + f"must be one of: adm, agt, atm (or legacy: human, automation). " + f"Run `bridge conventions` for the full naming rules." ) @@ -153,7 +154,8 @@ def _parse_actors(raw: dict) -> Dict[str, ActorInfo]: if not name.startswith(required_prefix): raise ConfigError( f"Actor '{name}' has type '{actor_type.value}' but name must start " - f"with '{required_prefix}' (got '{name}')" + f"with '{required_prefix}' (got '{name}'). " + f"Run `bridge conventions` for the full naming rules." ) actors[name] = ActorInfo( name=name,