From a852a9df18ddb67ff85735db71c1837b33ef73c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Guti=C3=A9rrez?= <35082514+alezmad@users.noreply.github.com> Date: Mon, 4 May 2026 16:36:23 +0100 Subject: [PATCH] =?UTF-8?q?feat(cli):=201.31.5=20=E2=80=94=20JSON=20peer?= =?UTF-8?q?=20list=20lifts=20role=20to=20top=20level=20+=20skill=20renders?= =?UTF-8?q?=20it?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After 1.31.4 the human renderer surfaced role and groups, but launched- session LLMs still dropped them when they called peer list --json and built their own tables. - Top-level role field. The broker returns role nested under profile.role; the CLI now lifts it to a top-level role field at parse time so it is the second-most-visible JSON field after displayName. profile.role is preserved. - Updated claudemesh skill SKILL.md peer-list section with the full JSON shape (memberPubkey, sessionId, role, profile, isSelf, isThisSession) plus explicit guidance to render role + groups in any peer table inside a launched session. Co-Authored-By: Claude Opus 4.7 (1M context) --- apps/cli/CHANGELOG.md | 20 ++++++++++++++++++++ apps/cli/package.json | 2 +- apps/cli/skills/claudemesh/SKILL.md | 20 +++++++++++++++++--- apps/cli/src/commands/peers.ts | 14 +++++++++++++- 4 files changed, 51 insertions(+), 5 deletions(-) diff --git a/apps/cli/CHANGELOG.md b/apps/cli/CHANGELOG.md index 5eef0e9..795b026 100644 --- a/apps/cli/CHANGELOG.md +++ b/apps/cli/CHANGELOG.md @@ -1,5 +1,25 @@ # Changelog +## 1.31.5 (2026-05-04) — JSON peer list lifts profile.role to top-level + skill guides LLMs to render it + +Two follow-ups after 1.31.4 made the human renderer show role/groups +but a launched-session LLM still dropped them when it called +`peer list --json` and built its own table. + +- **Top-level `role` field on every peer record.** The broker has + always returned role nested under `profile.role`, but downstream + consumers (LLMs in launched sessions, jq pipelines, dashboards) kept + missing it. The CLI now lifts `profile.role` to a top-level `role` + field at parse time, so it's the second thing visible in JSON after + `displayName`. The original `profile.role` is preserved for + backward compatibility. +- **Updated SKILL.md peer-list section** with the full JSON shape + (including `memberPubkey`, `sessionId`, `role`, `profile`, `isSelf`, + `isThisSession`) and explicit guidance: when listing peers inside a + launched session, prefer the human renderer; if you do need JSON, + always include `role` and `groups` columns. The previous version of + the skill documented six fields and skipped role + identity entirely. + ## 1.31.4 (2026-05-04) — peer list shows roles and groups `claudemesh peer list` now surfaces each peer's profile-level role diff --git a/apps/cli/package.json b/apps/cli/package.json index b4d5ef1..d51f484 100644 --- a/apps/cli/package.json +++ b/apps/cli/package.json @@ -1,6 +1,6 @@ { "name": "claudemesh-cli", - "version": "1.31.4", + "version": "1.31.5", "description": "Peer mesh for Claude Code sessions — CLI + MCP server.", "keywords": [ "claude-code", diff --git a/apps/cli/skills/claudemesh/SKILL.md b/apps/cli/skills/claudemesh/SKILL.md index 4a0ee7d..a0c0b3c 100644 --- a/apps/cli/skills/claudemesh/SKILL.md +++ b/apps/cli/skills/claudemesh/SKILL.md @@ -328,22 +328,36 @@ claudemesh peer bans # list banned members claudemesh peer verify [peer] # 6×5-digit safety numbers ``` -JSON shape (per peer): +JSON shape (per peer) — **render `role` and `groups` whenever you build a table for the user**, they're the highest-signal fields after `displayName`: ```json { "displayName": "Mou", - "pubkey": "abc123...", + "pubkey": "abc123...", // session pubkey (rotates per claudemesh launch) + "memberPubkey": "def456...", // stable identity (same across all sibling sessions) + "sessionId": "uuid", "status": "idle | working | dnd", "summary": "string or null", + "role": "lead | reviewer | bot | ...", // 1.31.5+: top-level alias of profile.role "groups": [{ "name": "reviewers", "role": "lead" }], - "peerType": "claude | telegram | ...", + "profile": { + "role": "lead", + "title": "string or null", + "bio": "string or null", + "avatar": "emoji or null", + "capabilities": ["..."] + }, + "peerType": "claude | telegram | ai | human | connector | ...", "channel": "claude-code | api | ...", "model": "claude-opus-4-7 | ...", "cwd": "/path/to/working/dir or null", + "isSelf": true, // peer is one of the caller's own sessions + "isThisSession": false, // peer is the exact session running the cli "stats": { "messagesIn": 0, "messagesOut": 0, "toolCalls": 0, "errors": 0, "uptime": 1200 } } ``` +**When asked to "list peers" inside a launched session, prefer the human renderer (`claudemesh peer list`, no `--json`) — it already prints role + groups inline next to the name with an explicit `(none)` footer when both are absent. If you do need JSON for parsing, always include `role` and `groups` columns in any rendered table; the user's primary question is usually "who's in what role" and dropping those fields hides the answer.** + ### `message` — send and inspect messages ```bash diff --git a/apps/cli/src/commands/peers.ts b/apps/cli/src/commands/peers.ts index 41ad6dd..142df38 100644 --- a/apps/cli/src/commands/peers.ts +++ b/apps/cli/src/commands/peers.ts @@ -33,6 +33,10 @@ interface PeerRecord { status?: string; summary?: string; groups: Array<{ name: string; role?: string }>; + /** Top-level convenience alias for `profile.role`. Lifted by the + * CLI so JSON consumers see role at the shape's top level instead + * of nested under profile. Same value either way. */ + role?: string; peerType?: string; channel?: string; model?: string; @@ -110,6 +114,13 @@ async function listPeersForMesh(slug: string): Promise { * tell sender's own sessions from real peers. The broker has always * surfaced a sender's siblings as separate rows because they're separate * presence rows; the cli just hadn't been making that visible. + * + * Also lifts `profile.role` to a top-level `role` field. The broker has + * always returned role nested under `profile.role`, but downstream JSON + * consumers (LLMs in launched sessions, jq pipelines, dashboards) kept + * missing it because nothing pointed at the nesting. A dedicated + * top-level alias makes the intent unmissable without breaking the + * `profile` object's shape for callers that already drill into it. */ function annotateSelf( peer: PeerRecord, @@ -126,7 +137,8 @@ function annotateSelf( selfSessionPubkey && peer.pubkey === selfSessionPubkey ); - return { ...peer, isSelf, isThisSession }; + const role = peer.profile?.role?.trim() || undefined; + return { ...peer, ...(role ? { role } : {}), isSelf, isThisSession }; } export async function runPeers(flags: PeersFlags): Promise {