feat(w1): agent terminal renderer — JSON, whoami, remote gateway - #1101
Conversation
New tool: pmoves/tools/agent_terminal_theme.py — reads flat 1.0.0 agent_signatures.yaml + node-agent-specialization.yaml, renders ANSI 24-bit themed output (banners, status bars, session headers, agent roster). Cross-platform (Windows UTF-8 reconfigure). Functions: load_agent(), render_banner(), render_status_bar(), session_header(), colorize(), render_roster(). Integration-ready for sign_trail.py and pr_hedge_trim.py. Claims W1 (terminal renderer + Gate 3) in AGNOTE4482PHI.t1.md and AGNOTE4482_ROADMAP_W1-W5.md Agent Claim Register. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…eway Extend the W1 agent_terminal_theme.py skeleton with production features: - --json: machine-readable output for BoTZ CLI and P7 pterm integration - --whoami: resolve current agent identity (PMOVES_AGENT_ID env → hostname heuristic → BoTZ Gateway /v1/agent/whoami fallback) - --remote: fetch theme from BoTZ Gateway (:8054) with graceful fallback to local YAML when gateway is unreachable Add comprehensive test suite (15 tests): - Banner rendering, NO_COLOR fallback, unknown agent error handling - JSON output for agent, session, roster, demo modes - Whoami resolution from env var and hostname heuristic - Remote gateway fallback behavior - Windows UTF-8 subprocess encoding fix Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
📝 WalkthroughWalkthroughThe PR extends the agent terminal theme tool with new CLI capabilities ( Changes
Sequence DiagramsequenceDiagram
actor User
participant CLI as agent_terminal_theme.py
participant Gateway as BoTZ Gateway
participant LocalFS as Local YAML
User->>CLI: --whoami --json
alt PMOVES_AGENT_ID env set
CLI->>CLI: resolve from env var
else
CLI->>CLI: try hostname heuristic
alt heuristic succeeds
CLI->>CLI: use heuristic result
else
CLI->>Gateway: /v1/agent/whoami
Gateway-->>CLI: agent_id
end
end
CLI-->>User: JSON with agent_id
User->>CLI: --agent AGENT_ID --remote
CLI->>Gateway: /v1/agent/theme/AGENT_ID
alt gateway reachable
Gateway-->>CLI: theme data
CLI-->>User: render theme
else gateway unreachable
CLI->>LocalFS: load from YAML
LocalFS-->>CLI: theme data
CLI-->>User: render theme + warning
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~30 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 59b7e25bdb
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| result = asdict(agent) | ||
| if args.session: | ||
| node = load_node(args.node or args.agent) | ||
| if node: | ||
| result["node"] = asdict(node) | ||
| print(json_mod.dumps(result, indent=2)) |
There was a problem hiding this comment.
Include status text in JSON status mode output
When --status is combined with --json, the code returns only asdict(agent) and never serializes the provided status message, so machine consumers cannot recover the status payload they requested. This breaks the documented “all modes support --json” contract specifically for status mode and can cause downstream terminal/CLI integrations to drop live status content.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@pmoves/docs/AGENTS/AGNOTE4482PHI.t1.md`:
- Line 99: Remove the duplicate CLAIM entry that repeats "`2026-03-22T10:00:00Z`
CLAIM `4090-CLAUDE` scope: W1 terminal renderer — agent_terminal_theme.py (reads
flat 1.0.0 signatures + node specialization, renders ANSI-themed banners/status
bars). Gate 3 cross-machine TTS routing verification (p7.mesh.remote-tts +
va.p7.remote-speak). Branch: feat/w1-agent-terminal-theme. Stale branch cleanup
(7 merged locals).`" — locate this exact claim string in AGNOTE4482PHI.t1.md and
delete the second occurrence so only one identical claim with that
timestamp/agent/scope/branch remains.
In `@pmoves/tools/agent_terminal_theme.py`:
- Line 410: The print call currently uses an unnecessary f-string without
placeholders (print(f"WARNING: Gateway unreachable, falling back to local YAML",
file=sys.stderr)); remove the f prefix so it is a normal string literal
(print("WARNING: Gateway unreachable, falling back to local YAML",
file=sys.stderr)) to resolve Ruff F541; locate the print statement in
agent_terminal_theme.py (the WARNING: Gateway unreachable line) and update it
accordingly.
- Around line 246-256: The gateway fallback currently uses _BOTZ_GATEWAY_URL
without validating its scheme and catches all exceptions; update the code to
first parse and validate _BOTZ_GATEWAY_URL (using urllib.parse) to ensure its
scheme is https (reject http or other schemes), then call Request/urlopen and
narrow the except block to only expected errors (e.g., urllib.error.URLError,
urllib.error.HTTPError, socket.timeout, and json.JSONDecodeError or ValueError
from json_mod.loads) so programming errors aren't swallowed; keep returning
"unknown" on those specific failures but allow other exceptions to surface.
- Around line 199-222: The _fetch_remote_theme function currently constructs a
Request from _BOTZ_GATEWAY_URL and calls urlopen, which allows arbitrary
schemes; validate the parsed URL scheme before calling urlopen (use
urllib.parse.urlparse on f"{_BOTZ_GATEWAY_URL}/v1/agent/theme/{agent_id}" and
ensure scheme is either "http" or "https"); if the scheme is invalid, return
None immediately; keep the rest of the existing behavior (headers, timeout, JSON
parsing) intact and raise/handle the same exceptions as before in the urlopen
block.
- Around line 352-366: The source field is incorrectly set to "heuristic" when
the gateway path is used; change _resolve_whoami to return both the resolved
agent_id and the actual source (e.g., return (agent_id,
"env"|"heuristic"|"gateway")) or add a small helper that returns the source
alongside the id, then update the whoami handling block (the call to
_resolve_whoami, and the subsequent use in the args.whoami branch that builds
result) to unpack and use that source value instead of inferring from
PMOVES_AGENT_ID; keep existing load_agent and load_node calls unchanged and
ensure the printed JSON (json_mod.dumps) includes the provided source string.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 62c7a10b-fb1e-4f34-b9d2-6e24ef1bba5d
📒 Files selected for processing (3)
pmoves/docs/AGENTS/AGNOTE4482PHI.t1.mdpmoves/tests/test_agent_terminal_theme.pypmoves/tools/agent_terminal_theme.py
|
|
||
| - `2026-03-25T01:30:00Z` RELEASE `Z890-CLAUDE` scope: Full infrastructure session. 10 PRs merged (#1073-1083). Docker disk move C:→D: (4.4GB→195GB free). GHCR push 403 root-caused and fixed (permission-packages:write, PR #1083). Publisher-discord published to GHCR. Firefly III activated (port 8075). CI runner stabilized (PAT auth, persistent). 44 Dockerfiles migrated to DHI base images (PR #1084). P7 SKILL.md registration for services launcher + remote access (PR #1085). GitHub App TAC tree + runner 3-tier auth (PR #1080). Jetson Orin TAC + hardware profile (PR #1080). Hardened branch reconciliation (44-submodule gap analysis, PR #1080). W6 Life+Persona+Matrix roadmap written on AGNOTE4482. Pinokio 40GB crash-loop log identified (procs.js V8 RangeError) and truncated. 7 credential types audited. 3 memories updated. Handoff: #1084 (DHI) + #1085 (P7 SKILL.md) ready for review. GHCR build run dispatched. 5090 trimming #1082 in worktree. | ||
|
|
||
| - `2026-03-22T10:00:00Z` CLAIM `4090-CLAUDE` scope: W1 terminal renderer — agent_terminal_theme.py (reads flat 1.0.0 signatures + node specialization, renders ANSI-themed banners/status bars). Gate 3 cross-machine TTS routing verification (p7.mesh.remote-tts + va.p7.remote-speak). Branch: feat/w1-agent-terminal-theme. Stale branch cleanup (7 merged locals). |
There was a problem hiding this comment.
Duplicate CLAIM entry — remove this line.
Line 99 is an exact duplicate of the existing claim at line 89. Both have identical timestamp, agent, scope, and branch. This appears to be an accidental duplication that should be removed to maintain a clean claim register.
✏️ Proposed fix
-- `2026-03-22T10:00:00Z` CLAIM `4090-CLAUDE` scope: W1 terminal renderer — agent_terminal_theme.py (reads flat 1.0.0 signatures + node specialization, renders ANSI-themed banners/status bars). Gate 3 cross-machine TTS routing verification (p7.mesh.remote-tts + va.p7.remote-speak). Branch: feat/w1-agent-terminal-theme. Stale branch cleanup (7 merged locals).
-📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| - `2026-03-22T10:00:00Z` CLAIM `4090-CLAUDE` scope: W1 terminal renderer — agent_terminal_theme.py (reads flat 1.0.0 signatures + node specialization, renders ANSI-themed banners/status bars). Gate 3 cross-machine TTS routing verification (p7.mesh.remote-tts + va.p7.remote-speak). Branch: feat/w1-agent-terminal-theme. Stale branch cleanup (7 merged locals). |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pmoves/docs/AGENTS/AGNOTE4482PHI.t1.md` at line 99, Remove the duplicate
CLAIM entry that repeats "`2026-03-22T10:00:00Z` CLAIM `4090-CLAUDE` scope: W1
terminal renderer — agent_terminal_theme.py (reads flat 1.0.0 signatures + node
specialization, renders ANSI-themed banners/status bars). Gate 3 cross-machine
TTS routing verification (p7.mesh.remote-tts + va.p7.remote-speak). Branch:
feat/w1-agent-terminal-theme. Stale branch cleanup (7 merged locals).`" — locate
this exact claim string in AGNOTE4482PHI.t1.md and delete the second occurrence
so only one identical claim with that timestamp/agent/scope/branch remains.
| def _fetch_remote_theme(agent_id: str) -> Optional[AgentTheme]: | ||
| """Fetch agent theme from BoTZ Gateway API. Returns None on failure.""" | ||
| try: | ||
| from urllib.request import urlopen, Request | ||
| from urllib.error import URLError | ||
| except ImportError: | ||
| return None | ||
| try: | ||
| req = Request(f"{_BOTZ_GATEWAY_URL}/v1/agent/theme/{agent_id}") | ||
| req.add_header("Accept", "application/json") | ||
| with urlopen(req, timeout=3) as resp: | ||
| data = json_mod.loads(resp.read()) | ||
| return AgentTheme( | ||
| agent_id=data.get("agent_id", agent_id), | ||
| display_name=data.get("display_name", agent_id), | ||
| glyph=data.get("glyph", "?"), | ||
| color=data.get("color", "#FFFFFF"), | ||
| accent=data.get("accent", "#CCCCCC"), | ||
| voice=data.get("voice", "analytical"), | ||
| resonance=data.get("resonance", []), | ||
| description=data.get("description", ""), | ||
| ) | ||
| except (URLError, OSError, json_mod.JSONDecodeError, KeyError): | ||
| return None |
There was a problem hiding this comment.
Validate URL scheme to prevent SSRF via file:// or custom schemes.
The static analysis tool (Ruff S310) correctly flags that urlopen accepts arbitrary schemes. Since _BOTZ_GATEWAY_URL is sourced from an environment variable, a malicious or misconfigured value like file:///etc/passwd could be exploited.
🛡️ Proposed fix to validate URL scheme
def _fetch_remote_theme(agent_id: str) -> Optional[AgentTheme]:
"""Fetch agent theme from BoTZ Gateway API. Returns None on failure."""
try:
from urllib.request import urlopen, Request
from urllib.error import URLError
+ from urllib.parse import urlparse
except ImportError:
return None
+ url = f"{_BOTZ_GATEWAY_URL}/v1/agent/theme/{agent_id}"
+ if urlparse(url).scheme not in ("http", "https"):
+ return None
try:
- req = Request(f"{_BOTZ_GATEWAY_URL}/v1/agent/theme/{agent_id}")
+ req = Request(url)
req.add_header("Accept", "application/json")
with urlopen(req, timeout=3) as resp:
data = json_mod.loads(resp.read())🧰 Tools
🪛 Ruff (0.15.6)
[error] 207-207: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.
(S310)
[error] 209-209: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.
(S310)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pmoves/tools/agent_terminal_theme.py` around lines 199 - 222, The
_fetch_remote_theme function currently constructs a Request from
_BOTZ_GATEWAY_URL and calls urlopen, which allows arbitrary schemes; validate
the parsed URL scheme before calling urlopen (use urllib.parse.urlparse on
f"{_BOTZ_GATEWAY_URL}/v1/agent/theme/{agent_id}" and ensure scheme is either
"http" or "https"); if the scheme is invalid, return None immediately; keep the
rest of the existing behavior (headers, timeout, JSON parsing) intact and
raise/handle the same exceptions as before in the urlopen block.
| # 3. Gateway fallback | ||
| try: | ||
| from urllib.request import urlopen, Request | ||
| from urllib.error import URLError | ||
| req = Request(f"{_BOTZ_GATEWAY_URL}/v1/agent/whoami") | ||
| req.add_header("Accept", "application/json") | ||
| with urlopen(req, timeout=3) as resp: | ||
| data = json_mod.loads(resp.read()) | ||
| return data.get("agent_id", "unknown") | ||
| except Exception: | ||
| return "unknown" |
There was a problem hiding this comment.
Validate URL scheme and narrow exception handling in gateway fallback.
Same SSRF concern applies here. Additionally, the blind except Exception (Ruff BLE001) will silently swallow programming errors. Consider narrowing to expected exceptions.
🛡️ Proposed fix
# 3. Gateway fallback
try:
from urllib.request import urlopen, Request
from urllib.error import URLError
+ from urllib.parse import urlparse
+ url = f"{_BOTZ_GATEWAY_URL}/v1/agent/whoami"
+ if urlparse(url).scheme not in ("http", "https"):
+ return "unknown"
- req = Request(f"{_BOTZ_GATEWAY_URL}/v1/agent/whoami")
+ req = Request(url)
req.add_header("Accept", "application/json")
with urlopen(req, timeout=3) as resp:
data = json_mod.loads(resp.read())
return data.get("agent_id", "unknown")
- except Exception:
+ except (URLError, OSError, json_mod.JSONDecodeError, KeyError, ValueError):
return "unknown"🧰 Tools
🪛 Ruff (0.15.6)
[error] 250-250: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.
(S310)
[error] 252-252: Audit URL open for permitted schemes. Allowing use of file: or custom schemes is often unexpected.
(S310)
[warning] 255-255: Do not catch blind exception: Exception
(BLE001)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pmoves/tools/agent_terminal_theme.py` around lines 246 - 256, The gateway
fallback currently uses _BOTZ_GATEWAY_URL without validating its scheme and
catches all exceptions; update the code to first parse and validate
_BOTZ_GATEWAY_URL (using urllib.parse) to ensure its scheme is https (reject
http or other schemes), then call Request/urlopen and narrow the except block to
only expected errors (e.g., urllib.error.URLError, urllib.error.HTTPError,
socket.timeout, and json.JSONDecodeError or ValueError from json_mod.loads) so
programming errors aren't swallowed; keep returning "unknown" on those specific
failures but allow other exceptions to surface.
| # --- whoami: resolve identity and display --- | ||
| if args.whoami: | ||
| agent_id = _resolve_whoami() | ||
| agent = load_agent(agent_id) | ||
| node = load_node(agent_id) | ||
| if args.json_out: | ||
| result = {"agent_id": agent_id, "source": "env" if os.environ.get("PMOVES_AGENT_ID") else "heuristic"} | ||
| if agent: | ||
| result["theme"] = {"glyph": agent.glyph, "color": agent.color, "accent": agent.accent, "voice": agent.voice} | ||
| result["display_name"] = agent.display_name | ||
| result["specialization"] = agent.specialization | ||
| result["resonance"] = agent.resonance | ||
| if node: | ||
| result["node"] = {"hardware": node.hardware, "specialization": node.specialization} | ||
| print(json_mod.dumps(result, indent=2)) |
There was a problem hiding this comment.
Source detection is inaccurate when gateway fallback is used.
The source field logic at line 358 only distinguishes between "env" and "heuristic", but _resolve_whoami() has three resolution paths: env → hostname heuristic → gateway. If the hostname doesn't match any pattern and the gateway returns an ID, the source will incorrectly report "heuristic" instead of "gateway".
Consider having _resolve_whoami() return a tuple (agent_id, source) or refactor to track the actual source.
♻️ Proposed approach
-def _resolve_whoami() -> str:
+def _resolve_whoami() -> tuple[str, str]:
- """Resolve current agent identity from environment, hostname, or gateway."""
+ """Resolve current agent identity. Returns (agent_id, source)."""
# 1. Explicit env var
agent_id = os.environ.get("PMOVES_AGENT_ID")
if agent_id:
- return agent_id
+ return agent_id, "env"
# 2. Hostname heuristic
...
for pattern, aid in _HOST_MAP.items():
if pattern in hostname:
- return aid
+ return aid, "heuristic"
# 3. Gateway fallback
...
- return data.get("agent_id", "unknown")
+ return data.get("agent_id", "unknown"), "gateway"
except ...:
- return "unknown"
+ return "unknown", "unknown"Then update the caller:
- agent_id = _resolve_whoami()
+ agent_id, source = _resolve_whoami()
...
- result = {"agent_id": agent_id, "source": "env" if os.environ.get("PMOVES_AGENT_ID") else "heuristic"}
+ result = {"agent_id": agent_id, "source": source}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pmoves/tools/agent_terminal_theme.py` around lines 352 - 366, The source
field is incorrectly set to "heuristic" when the gateway path is used; change
_resolve_whoami to return both the resolved agent_id and the actual source
(e.g., return (agent_id, "env"|"heuristic"|"gateway")) or add a small helper
that returns the source alongside the id, then update the whoami handling block
(the call to _resolve_whoami, and the subsequent use in the args.whoami branch
that builds result) to unpack and use that source value instead of inferring
from PMOVES_AGENT_ID; keep existing load_agent and load_node calls unchanged and
ensure the printed JSON (json_mod.dumps) includes the provided source string.
| if agent: | ||
| print(f"{_DIM}(remote: {_BOTZ_GATEWAY_URL}){_RESET}", file=sys.stderr) | ||
| else: | ||
| print(f"WARNING: Gateway unreachable, falling back to local YAML", file=sys.stderr) |
There was a problem hiding this comment.
Remove unnecessary f-string prefix.
This f-string has no placeholders. Ruff F541 correctly flags this as extraneous.
✏️ Proposed fix
- print(f"WARNING: Gateway unreachable, falling back to local YAML", file=sys.stderr)
+ print("WARNING: Gateway unreachable, falling back to local YAML", file=sys.stderr)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| print(f"WARNING: Gateway unreachable, falling back to local YAML", file=sys.stderr) | |
| print("WARNING: Gateway unreachable, falling back to local YAML", file=sys.stderr) |
🧰 Tools
🪛 Ruff (0.15.6)
[error] 410-410: f-string without any placeholders
Remove extraneous f prefix
(F541)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@pmoves/tools/agent_terminal_theme.py` at line 410, The print call currently
uses an unnecessary f-string without placeholders (print(f"WARNING: Gateway
unreachable, falling back to local YAML", file=sys.stderr)); remove the f prefix
so it is a normal string literal (print("WARNING: Gateway unreachable, falling
back to local YAML", file=sys.stderr)) to resolve Ruff F541; locate the print
statement in agent_terminal_theme.py (the WARNING: Gateway unreachable line) and
update it accordingly.
Summary
agent_terminal_theme.py) with production features--jsonoutput mode for machine-readable BoTZ CLI / P7 pterm integration--whoamiidentity resolution:PMOVES_AGENT_IDenv → hostname heuristic → BoTZ Gateway fallback--remoteflag to fetch theme from BoTZ Gateway (:8054) with graceful local YAML fallbackW1 Scope (AGNOTE4482)
This PR delivers W1 deliverable #1: Agent-Themed Terminal Renderer — the foundation for BoTZ CLI theme bridge and P7 terminal integration.
Render modes
--banner--status "text"--session--roster--demo--whoamiAll modes support
--jsonfor machine-readable output.Test plan
python -m pytest pmoves/tests/test_agent_terminal_theme.py -v— 15/15 passingNO_COLOR=1strips all ANSI codes--remotegracefully falls back when gateway unreachablePMOVES_AGENT_ID=4090-claude --whoami --jsonreturns correct identity🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
--whoamiflag to display the current agent identity with fallback resolution--jsonflag for structured JSON-formatted output--remoteflag to fetch agent theme data from a remote endpoint, with automatic local fallback on connection failuresTests
Documentation